From ba2fca2ef63f2527505013cb4982a2fe5f5e4185 Mon Sep 17 00:00:00 2001 From: plouc Date: Wed, 29 Dec 2021 10:42:28 +0900 Subject: [PATCH] feat(bump): allow extra props for Bump data series --- packages/bump/src/bump/Bump.tsx | 39 +++-- packages/bump/src/bump/Line.tsx | 28 ++-- packages/bump/src/bump/LineTooltip.tsx | 14 +- packages/bump/src/bump/LinesLabels.tsx | 16 +- packages/bump/src/bump/Point.tsx | 14 +- packages/bump/src/bump/Points.tsx | 33 ++-- packages/bump/src/bump/ResponsiveBump.tsx | 13 +- packages/bump/src/bump/compute.ts | 107 ++++++------ packages/bump/src/bump/defaults.ts | 4 +- packages/bump/src/bump/hooks.ts | 157 +++++++++--------- packages/bump/src/bump/types.ts | 108 +++++++----- .../{bump.stories.js => bump.stories.tsx} | 58 ++++--- packages/bump/tests/Bump.test.tsx | 39 ++++- website/src/pages/bump/index.tsx | 4 +- 14 files changed, 368 insertions(+), 266 deletions(-) rename packages/bump/stories/{bump.stories.js => bump.stories.tsx} (76%) diff --git a/packages/bump/src/bump/Bump.tsx b/packages/bump/src/bump/Bump.tsx index a12d0faef5..cbf82dcd25 100644 --- a/packages/bump/src/bump/Bump.tsx +++ b/packages/bump/src/bump/Bump.tsx @@ -1,19 +1,25 @@ import { createElement, useMemo, Fragment, ReactNode } from 'react' import { Container, useDimensions, SvgWrapper } from '@nivo/core' import { Grid, Axes } from '@nivo/axes' -import { BumpDatum, BumpLayerId, BumpSvgProps, DefaultBumpDatum } from './types' +import { + BumpDatum, + BumpLayerId, + BumpSerieExtraProps, + BumpSvgProps, + DefaultBumpDatum, +} from './types' import { useBump } from './hooks' import { bumpSvgDefaultProps } from './defaults' import { Line } from './Line' import { LinesLabels } from './LinesLabels' import { Points } from './Points' -type InnerBumpProps = Omit< - BumpSvgProps, +type InnerBumpProps = Omit< + BumpSvgProps, 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' > -const InnerBump = ({ +const InnerBump = ({ data, width, @@ -67,7 +73,7 @@ const InnerBump = ({ onClick, tooltip = bumpSvgDefaultProps.tooltip, role = bumpSvgDefaultProps.role, -}: InnerBumpProps) => { +}: InnerBumpProps) => { const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( width, height, @@ -75,7 +81,7 @@ const InnerBump = ({ ) const { series, points, xScale, yScale, lineGenerator, activeSerieIds, setActiveSerieIds } = - useBump({ + useBump({ width: innerWidth, height: innerHeight, data, @@ -142,7 +148,7 @@ const InnerBump = ({ layerById.lines = ( {series.map(serie => ( - + key={serie.id} serie={serie} setActiveSerieIds={setActiveSerieIds} @@ -162,7 +168,11 @@ const InnerBump = ({ if (layers.includes('points')) { layerById.points = ( - key="points" pointComponent={pointComponent} points={points} /> + + key="points" + pointComponent={pointComponent} + points={points} + /> ) } @@ -170,7 +180,7 @@ const InnerBump = ({ layerById.labels = ( {startLabel !== false && ( - + series={series} getLabel={startLabel} position="start" @@ -179,7 +189,7 @@ const InnerBump = ({ /> )} {endLabel !== false && ( - + series={series} getLabel={endLabel} position="end" @@ -229,14 +239,17 @@ const InnerBump = ({ ) } -export const Bump = ({ +export const Bump = < + Datum extends BumpDatum = DefaultBumpDatum, + ExtraProps extends BumpSerieExtraProps = {} +>({ isInteractive = bumpSvgDefaultProps.isInteractive, animate = bumpSvgDefaultProps.animate, motionConfig = bumpSvgDefaultProps.motionConfig, theme, renderWrapper, ...otherProps -}: BumpSvgProps) => ( +}: BumpSvgProps) => ( ({ theme, }} > - isInteractive={isInteractive} {...otherProps} /> + isInteractive={isInteractive} {...otherProps} /> ) diff --git a/packages/bump/src/bump/Line.tsx b/packages/bump/src/bump/Line.tsx index 3167d2359b..99a76434b5 100644 --- a/packages/bump/src/bump/Line.tsx +++ b/packages/bump/src/bump/Line.tsx @@ -1,23 +1,23 @@ import { useSpring, animated } from '@react-spring/web' import { Line as D3Line } from 'd3-shape' import { useAnimatedPath, useMotionConfig } from '@nivo/core' -import { BumpCommonProps, BumpComputedSerie, BumpDatum } from './types' +import { BumpCommonProps, BumpComputedSerie, BumpDatum, BumpSerieExtraProps } from './types' import { useBumpSerieHandlers } from './hooks' -interface LineProps { - serie: BumpComputedSerie +interface LineProps { + serie: BumpComputedSerie lineGenerator: D3Line<[number, number | null]> yStep: number - isInteractive: BumpCommonProps['isInteractive'] - onMouseEnter?: BumpCommonProps['onMouseEnter'] - onMouseMove?: BumpCommonProps['onMouseMove'] - onMouseLeave?: BumpCommonProps['onMouseLeave'] - onClick?: BumpCommonProps['onClick'] + isInteractive: BumpCommonProps['isInteractive'] + onMouseEnter?: BumpCommonProps['onMouseEnter'] + onMouseMove?: BumpCommonProps['onMouseMove'] + onMouseLeave?: BumpCommonProps['onMouseLeave'] + onClick?: BumpCommonProps['onClick'] setActiveSerieIds: (serieIds: string[]) => void - tooltip: BumpCommonProps['tooltip'] + tooltip: BumpCommonProps['tooltip'] } -export const Line = ({ +export const Line = ({ serie, lineGenerator, yStep, @@ -28,8 +28,8 @@ export const Line = ({ onClick, setActiveSerieIds, tooltip, -}: LineProps) => { - const handlers = useBumpSerieHandlers({ +}: LineProps) => { + const handlers = useBumpSerieHandlers({ serie, isInteractive, onMouseEnter, @@ -51,8 +51,8 @@ export const Line = ({ lineWidth: number }>({ color: serie.color, - opacity: serie.style.opacity, - lineWidth: serie.style.lineWidth, + opacity: serie.opacity, + lineWidth: serie.lineWidth, config: springConfig, immediate: !animate, }) diff --git a/packages/bump/src/bump/LineTooltip.tsx b/packages/bump/src/bump/LineTooltip.tsx index 49f12f69e7..888c0e6fef 100644 --- a/packages/bump/src/bump/LineTooltip.tsx +++ b/packages/bump/src/bump/LineTooltip.tsx @@ -1,14 +1,16 @@ import { BasicTooltip } from '@nivo/tooltip' -import { BumpComputedSerie, BumpDatum } from './types' +import { BumpComputedSerie, BumpDatum, BumpSerieExtraProps } from './types' -interface LineTooltipProps { - serie: BumpComputedSerie +interface LineTooltipProps { + serie: BumpComputedSerie } -export const LineTooltip = ({ serie }: LineTooltipProps) => ( +export const LineTooltip = ({ + serie, +}: LineTooltipProps) => ( diff --git a/packages/bump/src/bump/LinesLabels.tsx b/packages/bump/src/bump/LinesLabels.tsx index 7e644e0a9a..ad78c4f019 100644 --- a/packages/bump/src/bump/LinesLabels.tsx +++ b/packages/bump/src/bump/LinesLabels.tsx @@ -1,28 +1,28 @@ import { useSprings, animated } from '@react-spring/web' import { useTheme, useMotionConfig } from '@nivo/core' import { InheritedColorConfig } from '@nivo/colors' -import { BumpComputedSerie, BumpDatum, BumpLabel } from './types' +import { BumpComputedSerie, BumpDatum, BumpLabel, BumpSerieExtraProps } from './types' import { useBumpSeriesLabels } from './hooks' -interface LineLabelsProps { - series: BumpComputedSerie[] - getLabel: BumpLabel +interface LineLabelsProps { + series: BumpComputedSerie[] + getLabel: BumpLabel position: 'start' | 'end' padding: number - color: InheritedColorConfig> + color: InheritedColorConfig> } -export const LinesLabels = ({ +export const LinesLabels = ({ series, getLabel, position, padding, color, -}: LineLabelsProps) => { +}: LineLabelsProps) => { const theme = useTheme() const { animate, config: springConfig } = useMotionConfig() - const labels = useBumpSeriesLabels({ + const labels = useBumpSeriesLabels({ series, getLabel, position, diff --git a/packages/bump/src/bump/Point.tsx b/packages/bump/src/bump/Point.tsx index d8bc7f30d7..080b6cfb1f 100644 --- a/packages/bump/src/bump/Point.tsx +++ b/packages/bump/src/bump/Point.tsx @@ -1,15 +1,17 @@ import { SVGAttributes } from 'react' import { useSpring, animated, to } from '@react-spring/web' import { useMotionConfig } from '@nivo/core' -import { BumpDatum, BumpPoint } from './types' +import { BumpDatum, BumpPoint, BumpSerieExtraProps } from './types' const pointStyle: SVGAttributes['style'] = { pointerEvents: 'none' } -interface PointProps { - point: BumpPoint +interface PointProps { + point: BumpPoint } -export const Point = ({ point }: PointProps) => { +export const Point = ({ + point, +}: PointProps) => { const { animate, config: springConfig } = useMotionConfig() const animatedProps = useSpring<{ @@ -21,9 +23,9 @@ export const Point = ({ point }: PointProps) => { }>({ x: point.x, y: point.y, - radius: point.style.size / 2, + radius: point.size / 2, color: point.color, - borderWidth: point.style.borderWidth, + borderWidth: point.borderWidth, config: springConfig, immediate: !animate, }) diff --git a/packages/bump/src/bump/Points.tsx b/packages/bump/src/bump/Points.tsx index 8e72c3d10f..dc79a938a7 100644 --- a/packages/bump/src/bump/Points.tsx +++ b/packages/bump/src/bump/Points.tsx @@ -1,20 +1,21 @@ import { createElement } from 'react' -import { BumpDatum, BumpPoint, BumpPointComponent } from './types' +import { BumpDatum, BumpPoint, BumpPointComponent, BumpSerieExtraProps } from './types' -interface PointsProps { - points: BumpPoint[] - pointComponent: BumpPointComponent +interface PointsProps { + points: BumpPoint[] + pointComponent: BumpPointComponent } -export const Points = ({ points, pointComponent }: PointsProps) => { - return ( - <> - {points.map(point => - createElement(pointComponent, { - key: point.id, - point, - }) - )} - - ) -} +export const Points = ({ + points, + pointComponent, +}: PointsProps) => ( + <> + {points.map(point => + createElement(pointComponent, { + key: point.id, + point, + }) + )} + +) diff --git a/packages/bump/src/bump/ResponsiveBump.tsx b/packages/bump/src/bump/ResponsiveBump.tsx index c52abce4b4..4bae76cf9e 100644 --- a/packages/bump/src/bump/ResponsiveBump.tsx +++ b/packages/bump/src/bump/ResponsiveBump.tsx @@ -1,11 +1,16 @@ import { ResponsiveWrapper } from '@nivo/core' -import { BumpDatum, BumpSvgProps, DefaultBumpDatum } from './types' +import { BumpDatum, BumpSerieExtraProps, BumpSvgProps, DefaultBumpDatum } from './types' import { Bump } from './Bump' -export const ResponsiveBump = ( - props: Omit, 'width' | 'height'> +export const ResponsiveBump = < + Datum extends BumpDatum = DefaultBumpDatum, + ExtraProps extends BumpSerieExtraProps = {} +>( + props: Omit, 'width' | 'height'> ) => ( - {({ width, height }) => width={width} height={height} {...props} />} + {({ width, height }) => ( + width={width} height={height} {...props} /> + )} ) diff --git a/packages/bump/src/bump/compute.ts b/packages/bump/src/bump/compute.ts index 17a4656b7b..c89dbd15ca 100644 --- a/packages/bump/src/bump/compute.ts +++ b/packages/bump/src/bump/compute.ts @@ -1,8 +1,14 @@ import { scalePoint } from 'd3-scale' import { castPointScale } from '@nivo/scales' -import { BumpDataProps, BumpDatum, BumpComputedSerie, BumpSeriePoint } from './types' +import { + BumpDataProps, + BumpDatum, + BumpComputedSerie, + BumpSeriePoint, + BumpSerieExtraProps, +} from './types' -export const computeSeries = ({ +export const computeSeries = ({ width, height, data, @@ -12,12 +18,12 @@ export const computeSeries = ({ }: { width: number height: number - data: BumpDataProps['data'] + data: BumpDataProps['data'] xPadding: number xOuterPadding: number yOuterPadding: number }) => { - const xValuesSet = new Set() + const xValuesSet = new Set() const yValuesSet = new Set() data.forEach(serie => { @@ -29,9 +35,9 @@ export const computeSeries = ({ }) }) - const xValues: D['x'][] = Array.from(xValuesSet) - const xScale = castPointScale( - scalePoint().domain(xValues).range([0, width]).padding(xOuterPadding) + const xValues: Datum['x'][] = Array.from(xValuesSet) + const xScale = castPointScale( + scalePoint().domain(xValues).range([0, width]).padding(xOuterPadding) ) const yValues: number[] = Array.from(yValuesSet).sort((a, b) => a - b) @@ -41,57 +47,62 @@ export const computeSeries = ({ const linePointPadding = xScale.step() * Math.min(xPadding * 0.5, 0.5) - const series: Omit, 'color' | 'style'>[] = data.map(rawSerie => { - const serie: Omit, 'color' | 'style'> = { - ...rawSerie, - points: [], - linePoints: [], - } + const series: Omit, 'color' | 'opacity' | 'lineWidth'>[] = + data.map(rawSerie => { + const serie: Omit< + BumpComputedSerie, + 'color' | 'opacity' | 'lineWidth' + > = { + id: rawSerie.id, + data: rawSerie, + points: [], + linePoints: [], + } - rawSerie.data.forEach((datum, i) => { - let x = null - let y = null + rawSerie.data.forEach((datum, i) => { + let x = null + let y = null - if (datum.y !== null) { - x = xScale(datum.x)! - y = yScale(datum.y)! - } + if (datum.y !== null) { + x = xScale(datum.x)! + y = yScale(datum.y)! + } - const point: BumpSeriePoint = { - id: `${rawSerie.id}.${i}`, - serie: rawSerie, - data: datum, - x: x as number, - y, - } - serie.points.push(point) + const point: BumpSeriePoint = { + id: `${rawSerie.id}.${i}`, + serie: rawSerie, + data: datum, + x: x as number, + y, + } + serie.points.push(point) - // only add pre transition point if the datum is not empty - if (point.x !== null) { - if (i === 0) { - serie.linePoints.push([0, point.y]) - } else { - serie.linePoints.push([point.x - linePointPadding, point.y]) + // only add pre transition point if the datum is not empty + if (point.x !== null) { + if (i === 0) { + serie.linePoints.push([0, point.y]) + } else { + serie.linePoints.push([point.x - linePointPadding, point.y]) + } } - } - serie.linePoints.push([point.x, point.y]) + serie.linePoints.push([point.x, point.y]) - // only add post transition point if the datum is not empty - if (x !== null) { - if (i === rawSerie.data.length - 1 && x) { - serie.linePoints.push([width, point.y]) - } else { - serie.linePoints.push([point.x + linePointPadding, point.y]) + // only add post transition point if the datum is not empty + if (x !== null) { + if (i === rawSerie.data.length - 1 && x) { + serie.linePoints.push([width, point.y]) + } else { + serie.linePoints.push([point.x + linePointPadding, point.y]) + } } - } - // remove points having null coordinates - serie.points = serie.points.filter(point => point.x !== null) - }) + // remove points having null coordinates + serie.points = serie.points.filter(point => point.x !== null) + }) - return serie - }) + return serie + }) return { series, diff --git a/packages/bump/src/bump/defaults.ts b/packages/bump/src/bump/defaults.ts index 0f144f3c6f..1a23818020 100644 --- a/packages/bump/src/bump/defaults.ts +++ b/packages/bump/src/bump/defaults.ts @@ -4,7 +4,7 @@ import { Point } from './Point' import { BumpCommonProps, BumpPointComponent } from './types' const commonDefaultProps: Omit< - BumpCommonProps, + BumpCommonProps, | 'onMouseEnter' | 'onMouseMove' | 'onMouseLeave' @@ -59,7 +59,7 @@ const commonDefaultProps: Omit< } export const bumpSvgDefaultProps: typeof commonDefaultProps & { - pointComponent: BumpPointComponent + pointComponent: BumpPointComponent animate: boolean motionConfig: ModernMotionProps['motionConfig'] } = { diff --git a/packages/bump/src/bump/hooks.ts b/packages/bump/src/bump/hooks.ts index 9f607f6224..c378e24221 100644 --- a/packages/bump/src/bump/hooks.ts +++ b/packages/bump/src/bump/hooks.ts @@ -13,6 +13,7 @@ import { BumpPoint, BumpLabel, BumpLabelData, + BumpSerieExtraProps, } from './types' import { computeSeries } from './compute' @@ -34,7 +35,7 @@ const useSerieDerivedProp = ( return () => instruction }, [instruction]) -const useSerieStyle = ({ +const useSerieStyle = ({ lineWidth, activeLineWidth, inactiveLineWidth, @@ -44,16 +45,16 @@ const useSerieStyle = ({ isInteractive, activeSerieIds, }: { - lineWidth: BumpCommonProps['lineWidth'] - activeLineWidth: BumpCommonProps['activeLineWidth'] - inactiveLineWidth: BumpCommonProps['inactiveLineWidth'] - opacity: BumpCommonProps['opacity'] - activeOpacity: BumpCommonProps['activeOpacity'] - inactiveOpacity: BumpCommonProps['inactiveOpacity'] - isInteractive: BumpCommonProps['isInteractive'] + lineWidth: BumpCommonProps['lineWidth'] + activeLineWidth: BumpCommonProps['activeLineWidth'] + inactiveLineWidth: BumpCommonProps['inactiveLineWidth'] + opacity: BumpCommonProps['opacity'] + activeOpacity: BumpCommonProps['activeOpacity'] + inactiveOpacity: BumpCommonProps['inactiveOpacity'] + isInteractive: BumpCommonProps['isInteractive'] activeSerieIds: string[] }) => { - type Serie = Omit, 'color' | 'style'> + type Serie = Omit, 'color' | 'opacity' | 'lineWidth'> const getLineWidth = useSerieDerivedProp(lineWidth) const getActiveLineWidth = useSerieDerivedProp(activeLineWidth) @@ -65,22 +66,22 @@ const useSerieStyle = ({ const getNormalStyle = useCallback( (serie: Serie) => ({ - lineWidth: getLineWidth(serie), opacity: getOpacity(serie), + lineWidth: getLineWidth(serie), }), [getLineWidth, getOpacity] ) const getActiveStyle = useCallback( (serie: Serie) => ({ - lineWidth: getActiveLineWidth(serie), opacity: getActiveOpacity(serie), + lineWidth: getActiveLineWidth(serie), }), [getActiveLineWidth, getActiveOpacity] ) const getInactiveStyle = useCallback( (serie: Serie) => ({ - lineWidth: getInactiveLineWidth(serie), opacity: getInactiveOpacity(serie), + lineWidth: getInactiveLineWidth(serie), }), [getInactiveLineWidth, getInactiveOpacity] ) @@ -95,7 +96,7 @@ const useSerieStyle = ({ ) } -const usePointStyle = ({ +const usePointStyle = ({ pointSize, activePointSize, inactivePointSize, @@ -105,16 +106,16 @@ const usePointStyle = ({ isInteractive, activeSerieIds, }: { - pointSize: BumpCommonProps['pointSize'] - activePointSize: BumpCommonProps['activePointSize'] - inactivePointSize: BumpCommonProps['inactivePointSize'] - pointBorderWidth: BumpCommonProps['pointBorderWidth'] - activePointBorderWidth: BumpCommonProps['activePointBorderWidth'] - inactivePointBorderWidth: BumpCommonProps['inactivePointBorderWidth'] - isInteractive: BumpCommonProps['isInteractive'] + pointSize: BumpCommonProps['pointSize'] + activePointSize: BumpCommonProps['activePointSize'] + inactivePointSize: BumpCommonProps['inactivePointSize'] + pointBorderWidth: BumpCommonProps['pointBorderWidth'] + activePointBorderWidth: BumpCommonProps['activePointBorderWidth'] + inactivePointBorderWidth: BumpCommonProps['inactivePointBorderWidth'] + isInteractive: BumpCommonProps['isInteractive'] activeSerieIds: string[] }) => { - type Point = Omit, 'style'> + type Point = Omit, 'size' | 'borderWidth'> const getSize = useSerieDerivedProp(pointSize) const getActiveSize = useSerieDerivedProp(activePointSize) @@ -156,7 +157,10 @@ const usePointStyle = ({ ) } -export const useBump = ({ +export const useBump = < + Datum extends BumpDatum = DefaultBumpDatum, + ExtraProps extends BumpSerieExtraProps = {} +>({ width, height, data, @@ -184,28 +188,28 @@ export const useBump = ({ }: { width: number height: number - data: BumpDataProps['data'] - interpolation: BumpCommonProps['interpolation'] - xPadding: BumpCommonProps['xPadding'] - xOuterPadding: BumpCommonProps['xOuterPadding'] - yOuterPadding: BumpCommonProps['yOuterPadding'] - lineWidth: BumpCommonProps['lineWidth'] - activeLineWidth: BumpCommonProps['activeLineWidth'] - inactiveLineWidth: BumpCommonProps['inactiveLineWidth'] - colors: BumpCommonProps['colors'] - opacity: BumpCommonProps['opacity'] - activeOpacity: BumpCommonProps['activeOpacity'] - inactiveOpacity: BumpCommonProps['inactiveOpacity'] - pointSize: BumpCommonProps['pointSize'] - activePointSize: BumpCommonProps['activePointSize'] - inactivePointSize: BumpCommonProps['inactivePointSize'] - pointColor: BumpCommonProps['pointColor'] - pointBorderWidth: BumpCommonProps['pointBorderWidth'] - activePointBorderWidth: BumpCommonProps['activePointBorderWidth'] - inactivePointBorderWidth: BumpCommonProps['inactivePointBorderWidth'] - pointBorderColor: BumpCommonProps['pointBorderColor'] - isInteractive: BumpCommonProps['isInteractive'] - defaultActiveSerieIds: BumpCommonProps['defaultActiveSerieIds'] + data: BumpDataProps['data'] + interpolation: BumpCommonProps['interpolation'] + xPadding: BumpCommonProps['xPadding'] + xOuterPadding: BumpCommonProps['xOuterPadding'] + yOuterPadding: BumpCommonProps['yOuterPadding'] + lineWidth: BumpCommonProps['lineWidth'] + activeLineWidth: BumpCommonProps['activeLineWidth'] + inactiveLineWidth: BumpCommonProps['inactiveLineWidth'] + colors: BumpCommonProps['colors'] + opacity: BumpCommonProps['opacity'] + activeOpacity: BumpCommonProps['activeOpacity'] + inactiveOpacity: BumpCommonProps['inactiveOpacity'] + pointSize: BumpCommonProps['pointSize'] + activePointSize: BumpCommonProps['activePointSize'] + inactivePointSize: BumpCommonProps['inactivePointSize'] + pointColor: BumpCommonProps['pointColor'] + pointBorderWidth: BumpCommonProps['pointBorderWidth'] + activePointBorderWidth: BumpCommonProps['activePointBorderWidth'] + inactivePointBorderWidth: BumpCommonProps['inactivePointBorderWidth'] + pointBorderColor: BumpCommonProps['pointBorderColor'] + isInteractive: BumpCommonProps['isInteractive'] + defaultActiveSerieIds: string[] }) => { const [activeSerieIds, setActiveSerieIds] = useState(defaultActiveSerieIds) @@ -215,7 +219,7 @@ export const useBump = ({ yScale, } = useMemo( () => - computeSeries({ + computeSeries({ width, height, data, @@ -228,11 +232,8 @@ export const useBump = ({ const lineGenerator = useLineGenerator(interpolation) - const getColor = useOrdinalColorScale, 'color' | 'style'>>( - colors, - 'id' - ) - const getSerieStyle = useSerieStyle({ + const getColor = useOrdinalColorScale(colors, 'id') + const getSerieStyle = useSerieStyle({ lineWidth, activeLineWidth, inactiveLineWidth, @@ -243,12 +244,12 @@ export const useBump = ({ activeSerieIds, }) - const series: BumpComputedSerie[] = useMemo( + const series: BumpComputedSerie[] = useMemo( () => rawSeries.map(serie => ({ ...serie, - color: getColor(serie), - style: getSerieStyle(serie), + color: getColor(serie.data), + ...getSerieStyle(serie), })), [rawSeries, getColor, getSerieStyle] ) @@ -256,7 +257,7 @@ export const useBump = ({ const theme = useTheme() const getPointColor = useInheritedColor(pointColor, theme) const getPointBorderColor = useInheritedColor(pointBorderColor, theme) - const getPointStyle = usePointStyle({ + const getPointStyle = usePointStyle({ pointSize, activePointSize, inactivePointSize, @@ -266,12 +267,12 @@ export const useBump = ({ isInteractive, activeSerieIds, }) - const points: BumpPoint[] = useMemo(() => { - const pts: BumpPoint[] = [] + const points: BumpPoint[] = useMemo(() => { + const pts: BumpPoint[] = [] series.forEach(serie => { serie.points.forEach(rawPoint => { // @ts-ignore - const point: BumpPoint = { + const point: BumpPoint = { ...rawPoint, serie, isActive: activeSerieIds.includes(serie.id), @@ -279,9 +280,11 @@ export const useBump = ({ } point.color = getPointColor(point) point.borderColor = getPointBorderColor(point) - point.style = getPointStyle(point) - pts.push(point) + pts.push({ + ...point, + ...getPointStyle(point), + }) }) }) @@ -299,7 +302,10 @@ export const useBump = ({ } } -export const useBumpSerieHandlers = ({ +export const useBumpSerieHandlers = < + Datum extends BumpDatum, + ExtraProps extends BumpSerieExtraProps +>({ serie, isInteractive, onMouseEnter, @@ -309,14 +315,14 @@ export const useBumpSerieHandlers = ({ setActiveSerieIds, tooltip, }: { - serie: BumpComputedSerie - isInteractive: BumpCommonProps['isInteractive'] - onMouseEnter?: BumpCommonProps['onMouseEnter'] - onMouseMove?: BumpCommonProps['onMouseMove'] - onMouseLeave?: BumpCommonProps['onMouseLeave'] - onClick?: BumpCommonProps['onClick'] + serie: BumpComputedSerie + isInteractive: BumpCommonProps['isInteractive'] + onMouseEnter?: BumpCommonProps['onMouseEnter'] + onMouseMove?: BumpCommonProps['onMouseMove'] + onMouseLeave?: BumpCommonProps['onMouseLeave'] + onClick?: BumpCommonProps['onClick'] setActiveSerieIds: (serieIds: string[]) => void - tooltip: BumpCommonProps['tooltip'] + tooltip: BumpCommonProps['tooltip'] }) => { const { showTooltipFromEvent, hideTooltip } = useTooltip() @@ -364,18 +370,21 @@ export const useBumpSerieHandlers = ({ ) } -export const useBumpSeriesLabels = ({ +export const useBumpSeriesLabels = < + Datum extends BumpDatum, + ExtraProps extends BumpSerieExtraProps +>({ series, position, padding, color, getLabel, }: { - series: BumpComputedSerie[] + series: BumpComputedSerie[] position: 'start' | 'end' padding: number - color: InheritedColorConfig> - getLabel: BumpLabel + color: InheritedColorConfig> + getLabel: BumpLabel }) => { const theme = useTheme() const getColor = useInheritedColor(color, theme) @@ -391,11 +400,11 @@ export const useBumpSeriesLabels = ({ signedPadding = padding } - const labels: BumpLabelData[] = [] + const labels: BumpLabelData[] = [] series.forEach(serie => { let label = serie.id if (typeof getLabel === 'function') { - label = getLabel(serie) + label = getLabel(serie.data) } const point = @@ -414,7 +423,7 @@ export const useBumpSeriesLabels = ({ x: point[0] + signedPadding, y: point[1], color: getColor(serie), - opacity: serie.style.opacity, + opacity: serie.opacity, serie, textAnchor, }) diff --git a/packages/bump/src/bump/types.ts b/packages/bump/src/bump/types.ts index 9d1a509cbc..273be56118 100644 --- a/packages/bump/src/bump/types.ts +++ b/packages/bump/src/bump/types.ts @@ -13,53 +13,71 @@ export interface DefaultBumpDatum { y: number } -export interface BumpSerie { +export type BumpSerieExtraProps = { + [key: string]: any +} + +export type BumpSerie< + Datum extends BumpDatum, + ExtraProps extends BumpSerieExtraProps +> = ExtraProps & { id: string - data: D[] + data: Datum[] } -export interface BumpSeriePoint { +export interface BumpSeriePoint { id: string - serie: BumpSerie - data: D + serie: BumpSerie + data: Datum x: number y: number | null } -export interface BumpPoint extends BumpSeriePoint { +export interface BumpPoint { + id: string + serie: BumpComputedSerie + data: Datum + x: number + y: number | null isActive: boolean isInactive: boolean + size: number color: string + borderWidth: number borderColor: string - style: { - size: number - borderWidth: number - } } -export type BumpPointComponent = FunctionComponent<{ - point: BumpPoint +export type BumpPointComponent< + Datum extends BumpDatum, + ExtraProps extends BumpSerieExtraProps +> = FunctionComponent<{ + point: BumpPoint }> -export interface BumpComputedSerie extends BumpSerie { - color: string - style: { - lineWidth: number - opacity: number - } - points: BumpSeriePoint[] +export interface BumpComputedSerie< + Datum extends BumpDatum, + ExtraProps extends BumpSerieExtraProps +> { + id: string + data: BumpSerie + points: BumpSeriePoint[] linePoints: [number, number | null][] + color: string + opacity: number + lineWidth: number } -export type BumpDataProps = { - data: BumpSerie[] +export type BumpDataProps = { + data: BumpSerie[] } export type BumpInterpolation = 'smooth' | 'linear' -export type BumpLabel = ((serie: BumpComputedSerie) => string) | boolean -export interface BumpLabelData { - serie: BumpComputedSerie - id: BumpSerie['id'] +export type BumpLabel = + | ((serie: BumpSerie) => string) + | boolean +export interface BumpLabelData { + serie: BumpComputedSerie + id: string label: string x: number y: number @@ -68,8 +86,8 @@ export interface BumpLabelData { textAnchor: 'start' | 'end' } -export type BumpMouseHandler = ( - serie: BumpComputedSerie, +export type BumpMouseHandler = ( + serie: BumpComputedSerie, event: MouseEvent ) => void @@ -77,7 +95,7 @@ export type BumpLayerId = 'grid' | 'axes' | 'labels' | 'lines' | 'points' export interface BumpCustomLayerProps {} export type BumpCustomLayer = FunctionComponent -export type BumpCommonProps = { +export type BumpCommonProps = { margin: Box interpolation: BumpInterpolation @@ -86,7 +104,7 @@ export type BumpCommonProps = { yOuterPadding: number theme: Theme - colors: OrdinalColorScaleConfig, 'color' | 'style'>> + colors: OrdinalColorScaleConfig> lineWidth: number activeLineWidth: number inactiveLineWidth: number @@ -94,21 +112,21 @@ export type BumpCommonProps = { activeOpacity: number inactiveOpacity: number - startLabel: BumpLabel + startLabel: BumpLabel startLabelPadding: number - startLabelTextColor: InheritedColorConfig> - endLabel: BumpLabel + startLabelTextColor: InheritedColorConfig> + endLabel: BumpLabel endLabelPadding: number - endLabelTextColor: InheritedColorConfig> + endLabelTextColor: InheritedColorConfig> pointSize: number activePointSize: number inactivePointSize: number - pointColor: InheritedColorConfig + pointColor: InheritedColorConfig, 'color' | 'borderColor'>> pointBorderWidth: number activePointBorderWidth: number inactivePointBorderWidth: number - pointBorderColor: InheritedColorConfig + pointBorderColor: InheritedColorConfig, 'borderColor'>> enableGridX: boolean enableGridY: boolean @@ -118,12 +136,12 @@ export type BumpCommonProps = { axisTop: AxisProps | null isInteractive: boolean - defaultActiveSerieIds: string[] - onMouseEnter: BumpMouseHandler - onMouseMove: BumpMouseHandler - onMouseLeave: BumpMouseHandler - onClick: BumpMouseHandler - tooltip: FunctionComponent<{ serie: BumpComputedSerie }> + defaultActiveSerieIds: BumpSerie['id'][] + onMouseEnter: BumpMouseHandler + onMouseMove: BumpMouseHandler + onMouseLeave: BumpMouseHandler + onClick: BumpMouseHandler + tooltip: FunctionComponent<{ serie: BumpComputedSerie }> role: string layers: (BumpLayerId | BumpCustomLayer)[] @@ -131,8 +149,10 @@ export type BumpCommonProps = { renderWrapper: boolean } -export type BumpSvgProps = Partial> & - BumpDataProps & { - pointComponent?: BumpPointComponent +export type BumpSvgProps = Partial< + BumpCommonProps +> & + BumpDataProps & { + pointComponent?: BumpPointComponent } & Dimensions & ModernMotionProps diff --git a/packages/bump/stories/bump.stories.js b/packages/bump/stories/bump.stories.tsx similarity index 76% rename from packages/bump/stories/bump.stories.js rename to packages/bump/stories/bump.stories.tsx index 2279efb2a0..1b24a15e1b 100644 --- a/packages/bump/stories/bump.stories.js +++ b/packages/bump/stories/bump.stories.tsx @@ -1,7 +1,14 @@ import { storiesOf } from '@storybook/react' import range from 'lodash/range' import shuffle from 'lodash/shuffle' -import { AreaBump, Bump } from '../src' +// @ts-ignore +import { AreaBump, Bump, BumpPoint, BumpSvgProps } from '../src' + +interface Datum { + x: number + y: number + extra: number +} const generateData = () => { const years = range(2000, 2005) @@ -10,7 +17,7 @@ const generateData = () => { const series = ranks.map(rank => { return { id: `Serie ${rank}`, - data: [], + data: [] as Datum[], } }) @@ -27,45 +34,43 @@ const generateData = () => { return series } -const commonProps = { +const commonProps: BumpSvgProps = { width: 900, height: 360, margin: { top: 40, right: 100, bottom: 40, left: 100 }, - titleOffsetX: -80, data: generateData(), - spacing: 80, } const stories = storiesOf('Bump', module) stories.add('default', () => ) -const CustomPoint = ({ x, y, isActive, isInactive, size, color, borderColor, borderWidth }) => { +const CustomPoint = ({ point }: { point: BumpPoint }) => { return ( - + - {isActive && ( - + {point.isActive && ( + A )} - {isInactive && ( - + {point.isInactive && ( + I )} @@ -74,7 +79,7 @@ const CustomPoint = ({ x, y, isActive, isInactive, size, color, borderColor, bor } stories.add('Custom points', () => ( - {...commonProps} pointComponent={CustomPoint} pointSize={20} @@ -89,7 +94,7 @@ stories.add('Custom points', () => ( )) stories.add('Missing data', () => ( - {...commonProps} pointSize={12} activePointSize={16} @@ -133,7 +138,7 @@ stories.add('Missing data', () => ( )) stories.add('More series than ranks', () => ( - {...commonProps} data={generateData().map(series => ({ ...series, @@ -146,9 +151,8 @@ stories.add('More series than ranks', () => ( )) stories.add('Area with fill pattern', () => ( - {...commonProps} - indexBy="id" defs={[ { id: 'dots', diff --git a/packages/bump/tests/Bump.test.tsx b/packages/bump/tests/Bump.test.tsx index 6552c49acd..e22e30ccc7 100644 --- a/packages/bump/tests/Bump.test.tsx +++ b/packages/bump/tests/Bump.test.tsx @@ -7,7 +7,7 @@ interface Datum { y: number } -const sampleData: BumpSvgProps['data'] = [ +const sampleData: BumpSvgProps['data'] = [ { id: 'A', data: [ @@ -73,7 +73,7 @@ const sampleData: BumpSvgProps['data'] = [ }, ] -const baseProps: BumpSvgProps = { +const baseProps: BumpSvgProps = { width: 800, height: 600, data: sampleData, @@ -116,6 +116,24 @@ describe('style', () => { expect(wrapper.find(`circle[data-testid='point.C.2002']`).prop('fill')).toEqual(colors[2]) expect(wrapper.find(`circle[data-testid='point.C.2003']`).prop('fill')).toEqual(colors[2]) }) + + it('colors from data', () => { + const colors = ['rgba(255, 0, 0, 1)', 'rgba(0, 255, 0, 1)', 'rgba(0, 0, 255, 1)'] + const wrapper = mount( + + {...baseProps} + data={sampleData.map((serie, i) => ({ + ...serie, + color: colors[i], + }))} + colors={serie => serie.color} + /> + ) + + expect(wrapper.find(`path[data-testid='line.A']`).prop('stroke')).toEqual(colors[0]) + expect(wrapper.find(`path[data-testid='line.B']`).prop('stroke')).toEqual(colors[1]) + expect(wrapper.find(`path[data-testid='line.C']`).prop('stroke')).toEqual(colors[2]) + }) }) describe('labels', () => { @@ -148,6 +166,23 @@ describe('labels', () => { expect(wrapper.find(`text[data-testid='label.end.C']`).text()).toEqual('Serie C') }) + it('label from data', () => { + const wrapper = mount( + + {...baseProps} + data={sampleData.map(serie => ({ + ...serie, + label: `Serie ${serie.id} label`, + }))} + endLabel={serie => serie.label} + /> + ) + + expect(wrapper.find(`text[data-testid='label.end.A']`).text()).toEqual('Serie A label') + expect(wrapper.find(`text[data-testid='label.end.B']`).text()).toEqual('Serie B label') + expect(wrapper.find(`text[data-testid='label.end.C']`).text()).toEqual('Serie C label') + }) + it('start labels', () => { const wrapper = mount( {...baseProps} startLabel />) diff --git a/website/src/pages/bump/index.tsx b/website/src/pages/bump/index.tsx index 2bdc7ff82f..74d610bbaf 100644 --- a/website/src/pages/bump/index.tsx +++ b/website/src/pages/bump/index.tsx @@ -37,7 +37,7 @@ const generateData = () => { return series } -type UnmappedProps = Omit, 'whatever'> +type UnmappedProps = Omit, 'whatever'> type Props = UnmappedProps @@ -65,7 +65,7 @@ const initialProperties: UnmappedProps = { startLabel: false, startLabelPadding: defaults.startLabelPadding, startLabelTextColor: defaults.startLabelTextColor, - endLabel: 'id', + endLabel: true, endLabelPadding: defaults.endLabelPadding, endLabelTextColor: defaults.endLabelTextColor,