diff --git a/packages/bump/index.d.ts b/packages/bump/_old_index.d.ts similarity index 100% rename from packages/bump/index.d.ts rename to packages/bump/_old_index.d.ts diff --git a/packages/bump/package.json b/packages/bump/package.json index 43b6b56c6d..46b1678d3d 100644 --- a/packages/bump/package.json +++ b/packages/bump/package.json @@ -22,17 +22,18 @@ ], "main": "./dist/nivo-bump.cjs.js", "module": "./dist/nivo-bump.es.js", - "typings": "./index.d.ts", + "typings": "./dist/types/index.d.ts", "files": [ "README.md", "LICENSE.md", - "index.d.ts", - "dist/" + "dist/", + "!dist/tsconfig.tsbuildinfo" ], "dependencies": { "@nivo/axes": "0.75.0", "@nivo/colors": "0.75.0", "@nivo/legends": "0.75.0", + "@nivo/scales": "0.75.0", "@nivo/tooltip": "0.75.0", "@react-spring/web": "9.3.1", "d3-shape": "^1.3.5" @@ -42,7 +43,6 @@ }, "peerDependencies": { "@nivo/core": "0.75.0", - "prop-types": ">= 15.5.10 < 16.0.0", "react": ">= 16.14.0 < 18.0.0" }, "publishConfig": { diff --git a/packages/bump/src/area-bump/Area.js b/packages/bump/src/area-bump/Area.tsx similarity index 52% rename from packages/bump/src/area-bump/Area.js rename to packages/bump/src/area-bump/Area.tsx index 5f29d5e362..b9bbc3c10b 100644 --- a/packages/bump/src/area-bump/Area.js +++ b/packages/bump/src/area-bump/Area.tsx @@ -1,10 +1,27 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' import { useSpring, animated } from '@react-spring/web' -import { useAnimatedPath, useMotionConfig, blendModePropType } from '@nivo/core' -import { useSerieHandlers } from './hooks' +import { useAnimatedPath, useMotionConfig } from '@nivo/core' +import { useAreaBumpSerieHandlers } from './hooks' +import { + AreaBumpAreaGenerator, + AreaBumpCommonProps, + AreaBumpComputedSerie, + AreaBumpDatum, +} from './types' -const Area = ({ +interface AreaProps { + serie: AreaBumpComputedSerie + areaGenerator: AreaBumpAreaGenerator + blendMode: AreaBumpCommonProps['blendMode'] + isInteractive: AreaBumpCommonProps['isInteractive'] + onMouseEnter?: AreaBumpCommonProps['onMouseEnter'] + onMouseMove?: AreaBumpCommonProps['onMouseMove'] + onMouseLeave?: AreaBumpCommonProps['onMouseLeave'] + onClick?: AreaBumpCommonProps['onClick'] + setCurrentSerie: any + tooltip: AreaBumpCommonProps['tooltip'] +} + +export const Area = ({ serie, areaGenerator, blendMode, @@ -15,8 +32,8 @@ const Area = ({ onClick, setCurrentSerie, tooltip, -}) => { - const handlers = useSerieHandlers({ +}: AreaProps) => { + const handlers = useAreaBumpSerieHandlers({ serie, isInteractive, onMouseEnter, @@ -29,8 +46,13 @@ const Area = ({ const { animate, config: springConfig } = useMotionConfig() - const animatedPath = useAnimatedPath(areaGenerator(serie.areaPoints)) - const animatedProps = useSpring({ + const animatedPath = useAnimatedPath(areaGenerator(serie.areaPoints)!) + const animatedProps = useSpring<{ + color: string + fillOpacity: number + stroke: string + strokeOpacity: number + }>({ color: serie.color, fillOpacity: serie.style.fillOpacity, stroke: serie.style.borderColor, @@ -55,29 +77,3 @@ const Area = ({ /> ) } - -Area.propTypes = { - serie: PropTypes.shape({ - id: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - fill: PropTypes.string, - areaPoints: PropTypes.array.isRequired, - style: PropTypes.shape({ - fillOpacity: PropTypes.number.isRequired, - borderWidth: PropTypes.number.isRequired, - borderColor: PropTypes.string.isRequired, - borderOpacity: PropTypes.number.isRequired, - }).isRequired, - }).isRequired, - areaGenerator: PropTypes.func.isRequired, - blendMode: blendModePropType.isRequired, - isInteractive: PropTypes.bool.isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - setCurrentSerie: PropTypes.func.isRequired, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, -} - -export default memo(Area) diff --git a/packages/bump/src/area-bump/AreaBump.js b/packages/bump/src/area-bump/AreaBump.js deleted file mode 100644 index a2e1f8a598..0000000000 --- a/packages/bump/src/area-bump/AreaBump.js +++ /dev/null @@ -1,189 +0,0 @@ -import { memo, useState, Fragment, useMemo } from 'react' -import { bindDefs, withContainer, useDimensions, SvgWrapper } from '@nivo/core' -import { Grid, Axes } from '@nivo/axes' -import { AreaBumpPropTypes, AreaBumpDefaultProps } from './props' -import { useAreaBump } from './hooks' -import Area from './Area' -import AreasLabels from './AreasLabels' - -const AreaBump = props => { - const { - data, - align, - - width, - height, - margin: partialMargin, - - layers, - - interpolation, - spacing, - xPadding, - - colors, - blendMode, - fillOpacity, - activeFillOpacity, - inactiveFillOpacity, - defs, - fill, - borderWidth, - activeBorderWidth, - inactiveBorderWidth, - borderColor, - borderOpacity, - activeBorderOpacity, - inactiveBorderOpacity, - - startLabel, - startLabelPadding, - startLabelTextColor, - endLabel, - endLabelPadding, - endLabelTextColor, - - enableGridX, - axisTop, - axisBottom, - - isInteractive, - onMouseEnter, - onMouseMove, - onMouseLeave, - onClick, - tooltip, - role, - } = props - - const [currentSerie, setCurrentSerie] = useState(null) - - const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( - width, - height, - partialMargin - ) - - const { series, xScale, areaGenerator } = useAreaBump({ - data, - width: innerWidth, - height: innerHeight, - align, - spacing, - xPadding, - interpolation, - colors, - fillOpacity, - activeFillOpacity, - inactiveFillOpacity, - borderWidth, - activeBorderWidth, - inactiveBorderWidth, - borderColor, - borderOpacity, - activeBorderOpacity, - inactiveBorderOpacity, - isInteractive, - current: currentSerie, - }) - - const boundDefs = useMemo( - () => bindDefs(defs, series, fill, { targetKey: 'fill' }), - [defs, series, fill] - ) - - const layerById = { - grid: enableGridX && ( - - ), - axes: ( - - ), - labels: [], - areas: ( - - {series.map(serie => ( - - ))} - - ), - } - - if (startLabel !== false) { - layerById.labels.push( - - ) - } - if (endLabel !== false) { - layerById.labels.push( - - ) - } - - return ( - - {layers.map((layer, i) => { - if (typeof layer === 'function') { - return ( - - {layer({ - ...props, - innerWidth, - innerHeight, - outerWidth, - outerHeight, - series, - xScale, - areaGenerator, - })} - - ) - } - - return layerById[layer] - })} - - ) -} - -AreaBump.propTypes = AreaBumpPropTypes -AreaBump.defaultProps = AreaBumpDefaultProps - -export default memo(withContainer(AreaBump)) diff --git a/packages/bump/src/area-bump/AreaBump.tsx b/packages/bump/src/area-bump/AreaBump.tsx new file mode 100644 index 0000000000..db30a80f7f --- /dev/null +++ b/packages/bump/src/area-bump/AreaBump.tsx @@ -0,0 +1,234 @@ +import { useState, Fragment, useMemo, ReactNode, createElement } from 'react' +import { + // @ts-ignore + bindDefs, + useDimensions, + SvgWrapper, + Container, +} from '@nivo/core' +import { Grid, Axes } from '@nivo/axes' +import { useAreaBump } from './hooks' +import { Area } from './Area' +import { AreasLabels } from './AreasLabels' +import { + AreaBumpSvgProps, + AreaBumpDatum, + DefaultAreaBumpDatum, + AreaBumpLayerId, + AreaBumpCustomLayerProps, +} from './types' +import { areaBumpSvgDefaultProps } from './defaults' + +type InnerAreaBumpProps = Omit< + AreaBumpSvgProps, + 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' +> + +const InnerAreaBump = ({ + data, + align = areaBumpSvgDefaultProps.align, + + width, + height, + margin: partialMargin, + + layers = areaBumpSvgDefaultProps.layers, + + interpolation = areaBumpSvgDefaultProps.interpolation, + spacing = areaBumpSvgDefaultProps.spacing, + xPadding = areaBumpSvgDefaultProps.xPadding, + + colors = areaBumpSvgDefaultProps.colors, + blendMode = areaBumpSvgDefaultProps.blendMode, + fillOpacity = areaBumpSvgDefaultProps.fillOpacity, + activeFillOpacity = areaBumpSvgDefaultProps.activeFillOpacity, + inactiveFillOpacity = areaBumpSvgDefaultProps.inactiveFillOpacity, + defs = areaBumpSvgDefaultProps.defs, + fill = areaBumpSvgDefaultProps.fill, + borderWidth = areaBumpSvgDefaultProps.borderWidth, + activeBorderWidth = areaBumpSvgDefaultProps.activeBorderWidth, + inactiveBorderWidth = areaBumpSvgDefaultProps.inactiveBorderWidth, + borderColor = areaBumpSvgDefaultProps.borderColor, + borderOpacity = areaBumpSvgDefaultProps.borderOpacity, + activeBorderOpacity = areaBumpSvgDefaultProps.activeBorderOpacity, + inactiveBorderOpacity = areaBumpSvgDefaultProps.inactiveBorderOpacity, + + startLabel = areaBumpSvgDefaultProps.startLabel, + startLabelPadding = areaBumpSvgDefaultProps.startLabelPadding, + startLabelTextColor = areaBumpSvgDefaultProps.startLabelTextColor, + endLabel = areaBumpSvgDefaultProps.endLabel, + endLabelPadding = areaBumpSvgDefaultProps.endLabelPadding, + endLabelTextColor = areaBumpSvgDefaultProps.endLabelTextColor, + + enableGridX = areaBumpSvgDefaultProps.enableGridX, + axisTop = areaBumpSvgDefaultProps.axisTop, + axisBottom = areaBumpSvgDefaultProps.axisBottom, + + isInteractive = areaBumpSvgDefaultProps.isInteractive, + onMouseEnter, + onMouseMove, + onMouseLeave, + onClick, + tooltip = areaBumpSvgDefaultProps.tooltip, + role = areaBumpSvgDefaultProps.role, +}: InnerAreaBumpProps) => { + const [currentSerie, setCurrentSerie] = useState(null) + + const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( + width, + height, + partialMargin + ) + + const { series, xScale, heightScale, areaGenerator } = useAreaBump({ + data, + width: innerWidth, + height: innerHeight, + align, + spacing, + xPadding, + interpolation, + colors, + fillOpacity, + activeFillOpacity, + inactiveFillOpacity, + borderWidth, + activeBorderWidth, + inactiveBorderWidth, + borderColor, + borderOpacity, + activeBorderOpacity, + inactiveBorderOpacity, + isInteractive, + current: currentSerie, + }) + + const boundDefs = useMemo( + () => bindDefs(defs, series, fill, { targetKey: 'fill' }), + [defs, series, fill] + ) + + const layerById: Record = { + grid: null, + axes: null, + labels: null, + areas: null, + } + + if (layers.includes('grid') && enableGridX) { + layerById.grid = + } + + if (layers.includes('axes')) { + layerById.axes = ( + + ) + } + + if (layers.includes('areas')) { + layerById.areas = ( + + {series.map(serie => ( + + key={serie.id} + areaGenerator={areaGenerator} + serie={serie} + blendMode={blendMode} + isInteractive={isInteractive} + setCurrentSerie={setCurrentSerie} + onMouseEnter={onMouseEnter} + onMouseMove={onMouseMove} + onMouseLeave={onMouseLeave} + onClick={onClick} + tooltip={tooltip} + /> + ))} + + ) + } + + if (layers.includes('labels')) { + layerById.labels = ( + + {startLabel !== false && ( + + label={startLabel} + series={series} + position="start" + padding={startLabelPadding} + color={startLabelTextColor} + /> + )} + {endLabel !== false && ( + + label={endLabel} + series={series} + position="end" + padding={endLabelPadding} + color={endLabelTextColor} + /> + )} + + ) + } + + const customLayerProps: AreaBumpCustomLayerProps = useMemo( + () => ({ + innerWidth, + innerHeight, + outerWidth, + outerHeight, + series, + xScale, + areaGenerator, + }), + [innerWidth, innerHeight, outerWidth, outerHeight, series, xScale, areaGenerator] + ) + + return ( + + {layers.map((layer, i) => { + if (typeof layer === 'function') { + return {createElement(layer, customLayerProps)} + } + + return layerById?.[layer] ?? null + })} + + ) +} + +export const AreaBump = ({ + isInteractive = areaBumpSvgDefaultProps.isInteractive, + animate = areaBumpSvgDefaultProps.animate, + motionConfig = areaBumpSvgDefaultProps.motionConfig, + theme, + renderWrapper, + ...otherProps +}: AreaBumpSvgProps) => ( + + isInteractive={isInteractive} {...otherProps} /> + +) diff --git a/packages/bump/src/area-bump/AreaTooltip.js b/packages/bump/src/area-bump/AreaTooltip.js deleted file mode 100644 index c3519a5e72..0000000000 --- a/packages/bump/src/area-bump/AreaTooltip.js +++ /dev/null @@ -1,16 +0,0 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { BasicTooltip } from '@nivo/tooltip' - -const AreaTooltip = ({ serie }) => { - return -} - -AreaTooltip.propTypes = { - serie: PropTypes.shape({ - id: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - }), -} - -export default memo(AreaTooltip) diff --git a/packages/bump/src/area-bump/AreaTooltip.tsx b/packages/bump/src/area-bump/AreaTooltip.tsx new file mode 100644 index 0000000000..f17c8b1489 --- /dev/null +++ b/packages/bump/src/area-bump/AreaTooltip.tsx @@ -0,0 +1,10 @@ +import { BasicTooltip } from '@nivo/tooltip' +import { AreaBumpDatum, AreaBumpComputedSerie } from './types' + +interface AreaTooltipProps { + serie: AreaBumpComputedSerie +} + +export const AreaTooltip = ({ serie }: AreaTooltipProps) => { + return +} diff --git a/packages/bump/src/area-bump/AreasLabels.js b/packages/bump/src/area-bump/AreasLabels.js deleted file mode 100644 index 3756d113dc..0000000000 --- a/packages/bump/src/area-bump/AreasLabels.js +++ /dev/null @@ -1,69 +0,0 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { useSprings, animated } from '@react-spring/web' -import { useTheme, useMotionConfig } from '@nivo/core' -import { inheritedColorPropType } from '@nivo/colors' -import { useSeriesLabels } from './hooks' - -const AreasLabels = ({ series, position, padding, color }) => { - const theme = useTheme() - const { animate, config: springConfig } = useMotionConfig() - - const labels = useSeriesLabels({ - series, - position, - padding, - color, - }) - - const springs = useSprings( - labels.length, - labels.map(label => ({ - x: label.x, - y: label.y, - opacity: label.opacity, - config: springConfig, - immediate: !animate, - })) - ) - - return springs.map((animatedProps, index) => { - const label = labels[index] - - return ( - - {label.id} - - ) - }) -} - -AreasLabels.propTypes = { - series: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - data: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - }) - ).isRequired, - }) - ).isRequired, - position: PropTypes.oneOf(['start', 'end']).isRequired, - padding: PropTypes.number.isRequired, - color: inheritedColorPropType.isRequired, -} - -export default memo(AreasLabels) diff --git a/packages/bump/src/area-bump/AreasLabels.tsx b/packages/bump/src/area-bump/AreasLabels.tsx new file mode 100644 index 0000000000..be6e7a7490 --- /dev/null +++ b/packages/bump/src/area-bump/AreasLabels.tsx @@ -0,0 +1,72 @@ +import { useSprings, animated } from '@react-spring/web' +import { useTheme, useMotionConfig } from '@nivo/core' +import { InheritedColorConfig } from '@nivo/colors' +import { AreaBumpComputedSerie, AreaBumpDatum, AreaBumpLabel } from './types' +import { useAreaBumpSeriesLabels } from './hooks' + +interface AreaLabelsProps { + label: Exclude, false> + series: AreaBumpComputedSerie[] + position: 'start' | 'end' + padding: number + color: InheritedColorConfig> +} + +export const AreasLabels = ({ + label, + series, + position, + padding, + color, +}: AreaLabelsProps) => { + const theme = useTheme() + const { animate, config: springConfig } = useMotionConfig() + + const labels = useAreaBumpSeriesLabels({ + label, + series, + position, + padding, + color, + }) + + const springs = useSprings<{ + x: number + y: number + opacity: number + }>( + labels.length, + labels.map(label => ({ + x: label.x, + y: label.y, + opacity: label.opacity, + config: springConfig, + immediate: !animate, + })) + ) + + return ( + <> + {springs.map((animatedProps, index) => { + const label = labels[index] + + return ( + + {label.label} + + ) + })} + + ) +} diff --git a/packages/bump/src/area-bump/ResponsiveAreaBump.js b/packages/bump/src/area-bump/ResponsiveAreaBump.js deleted file mode 100644 index f2efb3b181..0000000000 --- a/packages/bump/src/area-bump/ResponsiveAreaBump.js +++ /dev/null @@ -1,10 +0,0 @@ -import { ResponsiveWrapper } from '@nivo/core' -import AreaBump from './AreaBump' - -const ResponsiveAreaBump = props => ( - - {({ width, height }) => } - -) - -export default ResponsiveAreaBump diff --git a/packages/bump/src/area-bump/ResponsiveAreaBump.tsx b/packages/bump/src/area-bump/ResponsiveAreaBump.tsx new file mode 100644 index 0000000000..7d84355631 --- /dev/null +++ b/packages/bump/src/area-bump/ResponsiveAreaBump.tsx @@ -0,0 +1,11 @@ +import { ResponsiveWrapper } from '@nivo/core' +import { AreaBumpDatum, AreaBumpSvgProps, DefaultAreaBumpDatum } from './types' +import { AreaBump } from './AreaBump' + +export const ResponsiveAreaBump = ( + props: Omit, 'width' | 'height'> +) => ( + + {({ width, height }) => width={width} height={height} {...props} />} + +) diff --git a/packages/bump/src/area-bump/compute.js b/packages/bump/src/area-bump/compute.js deleted file mode 100644 index fd6a8e4308..0000000000 --- a/packages/bump/src/area-bump/compute.js +++ /dev/null @@ -1,106 +0,0 @@ -import { scalePoint, scaleLinear } from 'd3-scale' - -export const computeSeries = ({ data, width, height, align, spacing, xPadding }) => { - const slices = new Map() - - let maxSum = null - let maxValues = null - - data.forEach(serie => { - serie.data.forEach(datum => { - if (!slices.has(datum.x)) { - slices.set(datum.x, { - id: datum.x, - total: 0, - values: new Map(), - }) - } - - const slice = slices.get(datum.x) - - const total = slice.total + datum.y - slice.total = total - - slice.values.set(serie.id, { - serieId: serie.id, - value: datum.y, - }) - - if (total === null || total > maxSum) { - maxSum = total - maxValues = slice.values.size - } - }) - }) - - const xScale = scalePoint().domain(Array.from(slices.keys())).range([0, width]) - - const heightScale = scaleLinear() - .domain([0, maxSum]) - .range([0, height - maxValues * spacing]) - - slices.forEach((slice, x) => { - slice.x = xScale(x) - const sliceHeight = heightScale(slice.total) + slice.values.size * spacing - - let offset = 0 - if (align === 'middle') { - offset = (height - sliceHeight) / 2 - } else if (align === 'end') { - offset = height - sliceHeight - } - - Array.from(slice.values.values()) - .sort((a, b) => b.value - a.value) - .forEach((value, position, all) => { - const previousValues = all.filter((i, pos) => pos < position) - const beforeValue = previousValues.reduce((t, v) => t + v.value, 0) - - const sliceValue = slice.values.get(value.serieId) - sliceValue.position = position - sliceValue.height = heightScale(value.value) - sliceValue.beforeHeight = - heightScale(beforeValue) + offset + spacing * (previousValues.length + 0.5) - }) - }) - - const areaPointPadding = xScale.step() * Math.min(xPadding * 0.5, 0.5) - - const series = data.map(serie => { - const serieCopy = { ...serie } - serieCopy.points = [] - serieCopy.areaPoints = [] - serie.data.forEach((datum, i) => { - const slice = slices.get(datum.x) - const position = slice.values.get(serie.id) - - const x = slice.x - const { beforeHeight, height } = position - const y = beforeHeight + height / 2 - const y0 = beforeHeight - const y1 = beforeHeight + height - - serieCopy.points.push({ - x, - y, - height, - data: { ...datum }, - }) - if (i > 0) { - serieCopy.areaPoints.push({ x: x - areaPointPadding, y0, y1 }) - } - serieCopy.areaPoints.push({ x, y0, y1 }) - if (i < serie.data.length - 1) { - serieCopy.areaPoints.push({ x: x + areaPointPadding, y0, y1 }) - } - }) - - return serieCopy - }) - - return { - xScale, - heightScale, - series, - } -} diff --git a/packages/bump/src/area-bump/compute.ts b/packages/bump/src/area-bump/compute.ts new file mode 100644 index 0000000000..4b7855a4e0 --- /dev/null +++ b/packages/bump/src/area-bump/compute.ts @@ -0,0 +1,161 @@ +import { scalePoint, scaleLinear } from 'd3-scale' +import { castPointScale, castLinearScale, ScalePoint, ScaleLinear } from '@nivo/scales' +import { + AreaBumpCommonProps, + AreaBumpComputedSerie, + AreaBumpDataProps, + AreaBumpDatum, +} from './types' + +export const computeSeries = ({ + data, + width, + height, + align, + spacing, + xPadding, +}: { + data: AreaBumpDataProps['data'] + width: number + height: number + align: AreaBumpCommonProps['align'] + spacing: AreaBumpCommonProps['spacing'] + xPadding: AreaBumpCommonProps['xPadding'] +}): { + series: Omit, 'color' | 'style'>[] + xScale: ScalePoint + heightScale: ScaleLinear +} => { + const slices = new Map< + D['x'], + { + id: D['x'] + total: number + x: number + values: Map< + string, + { + serieId: string + value: number + position: number + height: number + beforeHeight: number + } + > + } + >() + + let maxSum: number + let maxValues: number + + data.forEach(serie => { + serie.data.forEach(datum => { + if (!slices.has(datum.x)) { + slices.set(datum.x, { + id: datum.x, + total: 0, + values: new Map(), + x: 0, + }) + } + + const slice = slices.get(datum.x)! + + const total = slice.total + datum.y + slice.total = total + + slice.values.set(serie.id, { + serieId: serie.id, + value: datum.y, + position: 0, + height: 0, + beforeHeight: 0, + }) + + if (maxSum === undefined || total > maxSum) { + maxSum = total + } + if (maxValues === undefined || slice.values.size > maxValues) { + maxValues = slice.values.size + } + }) + }) + + const xScale = castPointScale( + scalePoint().domain(Array.from(slices.keys())).range([0, width]) + ) + + const heightScale = castLinearScale( + scaleLinear() + .domain([0, maxSum!]) + .range([0, height - maxValues! * spacing]) + ) + + slices.forEach((slice, x) => { + slice.x = xScale(x)! + const sliceHeight = heightScale(slice.total) + slice.values.size * spacing + + let offset = 0 + if (align === 'middle') { + offset = (height - sliceHeight) / 2 + } else if (align === 'end') { + offset = height - sliceHeight + } + + Array.from(slice.values.values()) + .sort((a, b) => b.value - a.value) + .forEach((value, position, all) => { + const previousValues = all.filter((_i, pos) => pos < position) + const beforeValue = previousValues.reduce((t, v) => t + v.value, 0) + + const sliceValue = slice.values.get(value.serieId)! + sliceValue.position = position + sliceValue.height = heightScale(value.value) + sliceValue.beforeHeight = + heightScale(beforeValue) + offset + spacing * (previousValues.length + 0.5) + }) + }) + + const areaPointPadding = xScale.step() * Math.min(xPadding * 0.5, 0.5) + + const series = data.map(serie => { + const computedSerie: Omit, 'color' | 'style'> = { + ...serie, + points: [], + areaPoints: [], + } + + serie.data.forEach((datum, i) => { + const slice = slices.get(datum.x)! + const position = slice.values.get(serie.id)! + + const x = slice.x + const { beforeHeight, height } = position + const y = beforeHeight + height / 2 + const y0 = beforeHeight + const y1 = beforeHeight + height + + computedSerie.points.push({ + x, + y, + height, + data: { ...datum }, + }) + if (i > 0) { + computedSerie.areaPoints.push({ x: x - areaPointPadding, y0, y1 }) + } + computedSerie.areaPoints.push({ x, y0, y1 }) + if (i < serie.data.length - 1) { + computedSerie.areaPoints.push({ x: x + areaPointPadding, y0, y1 }) + } + }) + + return computedSerie + }) + + return { + series, + xScale, + heightScale, + } +} diff --git a/packages/bump/src/area-bump/defaults.ts b/packages/bump/src/area-bump/defaults.ts new file mode 100644 index 0000000000..939004d8bb --- /dev/null +++ b/packages/bump/src/area-bump/defaults.ts @@ -0,0 +1,64 @@ +import { ModernMotionProps, SvgDefsAndFill } from '@nivo/core' +import { AreaBumpCommonProps, AreaBumpComputedSerie } from './types' +import { AreaTooltip } from './AreaTooltip' + +const commonDefaultProps: Omit< + AreaBumpCommonProps, + | 'onMouseEnter' + | 'onMouseMove' + | 'onMouseLeave' + | 'onClick' + | 'margin' + | 'theme' + | 'renderWrapper' +> = { + align: 'middle', + + layers: ['grid', 'axes', 'labels', 'areas'], + + interpolation: 'smooth', + spacing: 0, + xPadding: 0.6, + + colors: { scheme: 'nivo' }, + blendMode: 'normal', + fillOpacity: 0.8, + activeFillOpacity: 1, + inactiveFillOpacity: 0.15, + borderWidth: 1, + activeBorderWidth: 1, + inactiveBorderWidth: 0, + borderColor: { from: 'color', modifiers: [['darker', 0.4]] }, + borderOpacity: 1, + activeBorderOpacity: 1, + inactiveBorderOpacity: 0, + + startLabel: false, + startLabelPadding: 12, + startLabelTextColor: { from: 'color', modifiers: [['darker', 1]] }, + endLabel: 'id', + endLabelPadding: 12, + endLabelTextColor: { from: 'color', modifiers: [['darker', 1]] }, + + enableGridX: true, + axisTop: {}, + axisBottom: {}, + + isInteractive: true, + defaultActiveSerieIds: [], + tooltip: AreaTooltip, + + role: 'img', +} + +export const areaBumpSvgDefaultProps: typeof commonDefaultProps & + SvgDefsAndFill> & { + animate: boolean + motionConfig: ModernMotionProps['motionConfig'] + } = { + ...commonDefaultProps, + defs: [], + fill: [], + animate: true, + motionConfig: 'gentle', +} diff --git a/packages/bump/src/area-bump/hooks.js b/packages/bump/src/area-bump/hooks.js deleted file mode 100644 index 889fc2fabc..0000000000 --- a/packages/bump/src/area-bump/hooks.js +++ /dev/null @@ -1,259 +0,0 @@ -import { createElement, useMemo, useCallback } from 'react' -import { area as d3Area, curveBasis, curveLinear } from 'd3-shape' -import { useTheme } from '@nivo/core' -import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors' -import { useTooltip } from '@nivo/tooltip' -import { computeSeries } from './compute' - -export const useAreaBumpSeries = ({ data, width, height, align, spacing, xPadding }) => - useMemo( - () => computeSeries({ data, width, height, align, spacing, xPadding }), - [data, width, height, align, spacing, xPadding] - ) - -export const useAreaGenerator = interpolation => - useMemo( - () => - d3Area() - .x(d => d.x) - .y0(d => d.y0) - .y1(d => d.y1) - .curve(interpolation === 'smooth' ? curveBasis : curveLinear), - [interpolation] - ) - -export const useSerieDerivedProp = instruction => - useMemo(() => { - if (typeof instruction === 'function') return instruction - return () => instruction - }, [instruction]) - -export const useSerieStyle = ({ - fillOpacity, - activeFillOpacity, - inactiveFillOpacity, - borderWidth, - activeBorderWidth, - inactiveBorderWidth, - borderColor, - borderOpacity, - activeBorderOpacity, - inactiveBorderOpacity, - isInteractive, - current, -}) => { - const getFillOpacity = useSerieDerivedProp(fillOpacity) - const getActiveFillOpacity = useSerieDerivedProp(activeFillOpacity) - const getInactiveFillOpacity = useSerieDerivedProp(inactiveFillOpacity) - - const getBorderWidth = useSerieDerivedProp(borderWidth) - const getActiveBorderWidth = useSerieDerivedProp(activeBorderWidth) - const getInactiveBorderWidth = useSerieDerivedProp(inactiveBorderWidth) - - const theme = useTheme() - const getBorderColor = useInheritedColor(borderColor, theme) - - const getBorderOpacity = useSerieDerivedProp(borderOpacity) - const getActiveBorderOpacity = useSerieDerivedProp(activeBorderOpacity) - const getInactiveBorderOpacity = useSerieDerivedProp(inactiveBorderOpacity) - - const getNormalStyle = useMemo( - () => serie => ({ - fillOpacity: getFillOpacity(serie), - borderWidth: getBorderWidth(serie), - borderColor: getBorderColor(serie), - borderOpacity: getBorderOpacity(serie), - }), - [getFillOpacity, getBorderWidth, getBorderColor, getBorderOpacity] - ) - const getActiveStyle = useMemo( - () => serie => ({ - fillOpacity: getActiveFillOpacity(serie), - borderWidth: getActiveBorderWidth(serie), - borderColor: getBorderColor(serie), - borderOpacity: getActiveBorderOpacity(serie), - }), - [getActiveFillOpacity, getActiveBorderWidth, getBorderColor, getActiveBorderOpacity] - ) - const getInactiveStyle = useMemo( - () => serie => ({ - fillOpacity: getInactiveFillOpacity(serie), - borderWidth: getInactiveBorderWidth(serie), - borderColor: getBorderColor(serie), - borderOpacity: getInactiveBorderOpacity(serie), - }), - [getInactiveFillOpacity, getInactiveBorderWidth, getBorderColor, getInactiveBorderOpacity] - ) - - return useMemo(() => { - if (!isInteractive) return getNormalStyle - - return serie => { - if (current === null) return getNormalStyle(serie) - if (serie.id === current) return getActiveStyle(serie) - return getInactiveStyle(serie) - } - }, [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, current]) -} - -export const useAreaBump = ({ - data, - width, - height, - align, - spacing, - xPadding, - interpolation, - colors, - fillOpacity, - activeFillOpacity, - inactiveFillOpacity, - borderWidth, - activeBorderWidth, - inactiveBorderWidth, - borderColor, - borderOpacity, - activeBorderOpacity, - inactiveBorderOpacity, - isInteractive, - current, -}) => { - const { - series: rawSeries, - xScale, - heightScale, - } = useAreaBumpSeries({ - data, - width, - height, - align, - spacing, - xPadding, - }) - - const areaGenerator = useAreaGenerator(interpolation) - - const getColor = useOrdinalColorScale(colors, 'id') - const getSerieStyle = useSerieStyle({ - fillOpacity, - activeFillOpacity, - inactiveFillOpacity, - borderWidth, - activeBorderWidth, - inactiveBorderWidth, - borderColor, - borderOpacity, - activeBorderOpacity, - inactiveBorderOpacity, - isInteractive, - current, - }) - - const series = useMemo( - () => - rawSeries.map(serie => { - const nextSerie = { ...serie } - nextSerie.color = getColor(nextSerie) - nextSerie.style = getSerieStyle(nextSerie) - return nextSerie - }), - [rawSeries, getColor, getSerieStyle] - ) - - return { - series, - xScale, - heightScale, - areaGenerator, - } -} - -export const useSerieHandlers = ({ - serie, - isInteractive, - onMouseEnter, - onMouseMove, - onMouseLeave, - onClick, - setCurrent, - tooltip, -}) => { - const { showTooltipFromEvent, hideTooltip } = useTooltip() - - const handleMouseEnter = useCallback( - event => { - showTooltipFromEvent(createElement(tooltip, { serie }), event) - setCurrent(serie.id) - onMouseEnter && onMouseEnter(serie, event) - }, - [serie, onMouseEnter, showTooltipFromEvent, setCurrent] - ) - - const handleMouseMove = useCallback( - event => { - showTooltipFromEvent(createElement(tooltip, { serie }), event) - onMouseMove && onMouseMove(serie, event) - }, - [serie, onMouseMove, showTooltipFromEvent] - ) - - const handleMouseLeave = useCallback( - event => { - hideTooltip() - setCurrent(null) - onMouseLeave && onMouseLeave(serie, event) - }, - [serie, onMouseLeave, hideTooltip, setCurrent] - ) - - const handleClick = useCallback( - event => { - onClick && onClick(serie, event) - }, - [serie, onClick] - ) - - const handlers = useMemo( - () => ({ - onMouseEnter: isInteractive ? handleMouseEnter : undefined, - onMouseMove: isInteractive ? handleMouseMove : undefined, - onMouseLeave: isInteractive ? handleMouseLeave : undefined, - onClick: isInteractive ? handleClick : undefined, - }), - [isInteractive, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] - ) - - return handlers -} - -export const useSeriesLabels = ({ series, position, padding, color }) => { - const theme = useTheme() - const getColor = useInheritedColor(color, theme) - - return useMemo(() => { - let textAnchor - let signedPadding - if (position === 'start') { - textAnchor = 'end' - signedPadding = padding * -1 - } else { - textAnchor = 'start' - signedPadding = padding - } - - return series.map(serie => { - const point = - position === 'start' ? serie.points[0] : serie.points[serie.points.length - 1] - - return { - id: serie.id, - x: point.x + signedPadding, - y: point.y, - color: getColor(serie), - opacity: serie.style.fillOpacity, - serie, - textAnchor, - } - }) - }, [series, position, padding, getColor]) -} diff --git a/packages/bump/src/area-bump/hooks.ts b/packages/bump/src/area-bump/hooks.ts new file mode 100644 index 0000000000..ce93cd9ad3 --- /dev/null +++ b/packages/bump/src/area-bump/hooks.ts @@ -0,0 +1,347 @@ +import { createElement, useMemo, useCallback } from 'react' +import { area as d3Area, curveBasis, curveLinear } from 'd3-shape' +import { useTheme, usePropertyAccessor } from '@nivo/core' +import { useOrdinalColorScale, useInheritedColor, InheritedColorConfig } from '@nivo/colors' +import { useTooltip } from '@nivo/tooltip' +import { computeSeries } from './compute' +import { + AreaBumpAreaPoint, + AreaBumpCommonProps, + AreaBumpComputedSerie, + AreaBumpDataProps, + AreaBumpDatum, + AreaBumpInterpolation, + AreaBumpLabel, + AreaBumpLabelData, +} from './types' + +const useAreaBumpSeries = ({ + data, + width, + height, + align, + spacing, + xPadding, +}: { + data: AreaBumpDataProps['data'] + width: number + height: number + align: AreaBumpCommonProps['align'] + spacing: AreaBumpCommonProps['spacing'] + xPadding: AreaBumpCommonProps['xPadding'] +}) => + useMemo( + () => computeSeries({ data, width, height, align, spacing, xPadding }), + [data, width, height, align, spacing, xPadding] + ) + +const useAreaGenerator = (interpolation: AreaBumpInterpolation) => + useMemo( + () => + d3Area() + .x(d => d.x) + .y0(d => d.y0) + .y1(d => d.y1) + .curve(interpolation === 'smooth' ? curveBasis : curveLinear), + [interpolation] + ) + +const useSerieDerivedProp = ( + instruction: ((target: Target) => Output) | Output +): ((target: Target) => Output) => + useMemo(() => { + if (typeof instruction === 'function') return instruction + return () => instruction + }, [instruction]) + +const useSerieStyle = ({ + fillOpacity, + activeFillOpacity, + inactiveFillOpacity, + borderWidth, + activeBorderWidth, + inactiveBorderWidth, + borderColor, + borderOpacity, + activeBorderOpacity, + inactiveBorderOpacity, + isInteractive, + current, +}: { + fillOpacity: AreaBumpCommonProps['fillOpacity'] + activeFillOpacity: AreaBumpCommonProps['activeFillOpacity'] + inactiveFillOpacity: AreaBumpCommonProps['inactiveFillOpacity'] + borderWidth: AreaBumpCommonProps['borderWidth'] + activeBorderWidth: AreaBumpCommonProps['activeBorderWidth'] + inactiveBorderWidth: AreaBumpCommonProps['inactiveBorderWidth'] + borderColor: AreaBumpCommonProps['borderColor'] + borderOpacity: AreaBumpCommonProps['borderOpacity'] + activeBorderOpacity: AreaBumpCommonProps['activeBorderOpacity'] + inactiveBorderOpacity: AreaBumpCommonProps['inactiveBorderOpacity'] + isInteractive: AreaBumpCommonProps['isInteractive'] + current: any +}) => { + type Serie = Omit, 'style'> + + const getFillOpacity = useSerieDerivedProp(fillOpacity) + const getActiveFillOpacity = useSerieDerivedProp(activeFillOpacity) + const getInactiveFillOpacity = useSerieDerivedProp(inactiveFillOpacity) + + const getBorderWidth = useSerieDerivedProp(borderWidth) + const getActiveBorderWidth = useSerieDerivedProp(activeBorderWidth) + const getInactiveBorderWidth = useSerieDerivedProp(inactiveBorderWidth) + + const theme = useTheme() + const getBorderColor = useInheritedColor(borderColor, theme) + + const getBorderOpacity = useSerieDerivedProp(borderOpacity) + const getActiveBorderOpacity = useSerieDerivedProp(activeBorderOpacity) + const getInactiveBorderOpacity = useSerieDerivedProp(inactiveBorderOpacity) + + const getNormalStyle = useCallback( + (serie: Serie) => ({ + fillOpacity: getFillOpacity(serie), + borderWidth: getBorderWidth(serie), + borderColor: getBorderColor(serie), + borderOpacity: getBorderOpacity(serie), + }), + [getFillOpacity, getBorderWidth, getBorderColor, getBorderOpacity] + ) + const getActiveStyle = useCallback( + (serie: Serie) => ({ + fillOpacity: getActiveFillOpacity(serie), + borderWidth: getActiveBorderWidth(serie), + borderColor: getBorderColor(serie), + borderOpacity: getActiveBorderOpacity(serie), + }), + [getActiveFillOpacity, getActiveBorderWidth, getBorderColor, getActiveBorderOpacity] + ) + const getInactiveStyle = useCallback( + (serie: Serie) => ({ + fillOpacity: getInactiveFillOpacity(serie), + borderWidth: getInactiveBorderWidth(serie), + borderColor: getBorderColor(serie), + borderOpacity: getInactiveBorderOpacity(serie), + }), + [getInactiveFillOpacity, getInactiveBorderWidth, getBorderColor, getInactiveBorderOpacity] + ) + + return useCallback( + (serie: Serie) => { + if (!isInteractive || current === null) return getNormalStyle(serie) + if (serie.id === current) return getActiveStyle(serie) + return getInactiveStyle(serie) + }, + [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, current] + ) +} + +export const useAreaBump = ({ + data, + width, + height, + align, + spacing, + xPadding, + interpolation, + colors, + fillOpacity, + activeFillOpacity, + inactiveFillOpacity, + borderWidth, + activeBorderWidth, + inactiveBorderWidth, + borderColor, + borderOpacity, + activeBorderOpacity, + inactiveBorderOpacity, + isInteractive, + current, +}: { + data: AreaBumpDataProps['data'] + width: number + height: number + align: AreaBumpCommonProps['align'] + spacing: AreaBumpCommonProps['spacing'] + xPadding: AreaBumpCommonProps['xPadding'] + interpolation: AreaBumpCommonProps['interpolation'] + colors: AreaBumpCommonProps['colors'] + fillOpacity: AreaBumpCommonProps['fillOpacity'] + activeFillOpacity: AreaBumpCommonProps['activeFillOpacity'] + inactiveFillOpacity: AreaBumpCommonProps['inactiveFillOpacity'] + borderWidth: AreaBumpCommonProps['borderWidth'] + activeBorderWidth: AreaBumpCommonProps['activeBorderWidth'] + inactiveBorderWidth: AreaBumpCommonProps['inactiveBorderWidth'] + borderColor: AreaBumpCommonProps['borderColor'] + borderOpacity: AreaBumpCommonProps['borderOpacity'] + activeBorderOpacity: AreaBumpCommonProps['activeBorderOpacity'] + inactiveBorderOpacity: AreaBumpCommonProps['inactiveBorderOpacity'] + isInteractive: AreaBumpCommonProps['isInteractive'] + current: any +}) => { + const { + series: rawSeries, + xScale, + heightScale, + } = useAreaBumpSeries({ + data, + width, + height, + align, + spacing, + xPadding, + }) + + const areaGenerator = useAreaGenerator(interpolation) + + const getColor = useOrdinalColorScale(colors, 'id') + const getSerieStyle = useSerieStyle({ + fillOpacity, + activeFillOpacity, + inactiveFillOpacity, + borderWidth, + activeBorderWidth, + inactiveBorderWidth, + borderColor, + borderOpacity, + activeBorderOpacity, + inactiveBorderOpacity, + isInteractive, + current, + }) + + const series: AreaBumpComputedSerie[] = useMemo( + () => + rawSeries.map(serie => { + const serieWithColor = { + ...serie, + color: getColor(serie), + } + + return { + ...serieWithColor, + style: getSerieStyle(serieWithColor), + } + }), + [rawSeries, getColor, getSerieStyle] + ) + + return { + series, + xScale, + heightScale, + areaGenerator, + } +} + +export const useAreaBumpSerieHandlers = ({ + serie, + isInteractive, + onMouseEnter, + onMouseMove, + onMouseLeave, + onClick, + setCurrent, + tooltip, +}: { + serie: AreaBumpComputedSerie + isInteractive: AreaBumpCommonProps['isInteractive'] + onMouseEnter?: AreaBumpCommonProps['onMouseEnter'] + onMouseMove?: AreaBumpCommonProps['onMouseMove'] + onMouseLeave?: AreaBumpCommonProps['onMouseLeave'] + onClick?: AreaBumpCommonProps['onClick'] + setCurrent: any + tooltip: AreaBumpCommonProps['tooltip'] +}) => { + const { showTooltipFromEvent, hideTooltip } = useTooltip() + + const handleMouseEnter = useCallback( + event => { + showTooltipFromEvent(createElement(tooltip, { serie }), event) + setCurrent(serie.id) + onMouseEnter && onMouseEnter(serie, event) + }, + [serie, onMouseEnter, showTooltipFromEvent, setCurrent] + ) + + const handleMouseMove = useCallback( + event => { + showTooltipFromEvent(createElement(tooltip, { serie }), event) + onMouseMove && onMouseMove(serie, event) + }, + [serie, onMouseMove, showTooltipFromEvent] + ) + + const handleMouseLeave = useCallback( + event => { + hideTooltip() + setCurrent(null) + onMouseLeave && onMouseLeave(serie, event) + }, + [serie, onMouseLeave, hideTooltip, setCurrent] + ) + + const handleClick = useCallback( + event => { + onClick && onClick(serie, event) + }, + [serie, onClick] + ) + + return useMemo( + () => ({ + onMouseEnter: isInteractive ? handleMouseEnter : undefined, + onMouseMove: isInteractive ? handleMouseMove : undefined, + onMouseLeave: isInteractive ? handleMouseLeave : undefined, + onClick: isInteractive ? handleClick : undefined, + }), + [isInteractive, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] + ) +} + +export const useAreaBumpSeriesLabels = ({ + label, + series, + position, + padding, + color, +}: { + label: Exclude, false> + series: AreaBumpComputedSerie[] + position: 'start' | 'end' + padding: number + color: InheritedColorConfig> +}): AreaBumpLabelData[] => { + const theme = useTheme() + const getColor = useInheritedColor(color, theme) + + const getLabel = usePropertyAccessor(label) + + return useMemo(() => { + let textAnchor: 'start' | 'end' + let signedPadding: number + if (position === 'start') { + textAnchor = 'end' + signedPadding = padding * -1 + } else { + textAnchor = 'start' + signedPadding = padding + } + + return series.map(serie => { + const point = + position === 'start' ? serie.points[0] : serie.points[serie.points.length - 1] + + return { + id: serie.id, + label: getLabel(serie), + x: point.x + signedPadding, + y: point.y, + color: getColor(serie), + opacity: serie.style.fillOpacity, + serie, + textAnchor, + } + }) + }, [getLabel, series, position, padding, getColor]) +} diff --git a/packages/bump/src/area-bump/index.js b/packages/bump/src/area-bump/index.js deleted file mode 100644 index a9fdd8c38b..0000000000 --- a/packages/bump/src/area-bump/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as AreaBump } from './AreaBump' -export { default as ResponsiveAreaBump } from './ResponsiveAreaBump' -export * from './props' diff --git a/packages/bump/src/area-bump/index.ts b/packages/bump/src/area-bump/index.ts new file mode 100644 index 0000000000..39f36ec26a --- /dev/null +++ b/packages/bump/src/area-bump/index.ts @@ -0,0 +1,5 @@ +export * from './AreaBump' +export * from './defaults' +export * from './hooks' +export * from './ResponsiveAreaBump' +export * from './types' diff --git a/packages/bump/src/area-bump/props.js b/packages/bump/src/area-bump/props.js deleted file mode 100644 index 6c43fc4f9b..0000000000 --- a/packages/bump/src/area-bump/props.js +++ /dev/null @@ -1,126 +0,0 @@ -import PropTypes from 'prop-types' -import { motionPropTypes, blendModePropType } from '@nivo/core' -import { ordinalColorsPropType, inheritedColorPropType } from '@nivo/colors' -import { axisPropType } from '@nivo/axes' -import AreaTooltip from './AreaTooltip' - -const commonPropTypes = { - data: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - data: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - y: PropTypes.number.isRequired, - }) - ).isRequired, - }) - ).isRequired, - - align: PropTypes.oneOf(['start', 'middle', 'end']).isRequired, - - layers: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.oneOf(['grid', 'axes', 'labels', 'areas']), PropTypes.func]) - ).isRequired, - - interpolation: PropTypes.oneOf(['linear', 'smooth']).isRequired, - spacing: PropTypes.number.isRequired, - xPadding: PropTypes.number.isRequired, - - colors: ordinalColorsPropType.isRequired, - blendMode: blendModePropType.isRequired, - fillOpacity: PropTypes.number.isRequired, - activeFillOpacity: PropTypes.number.isRequired, - inactiveFillOpacity: PropTypes.number.isRequired, - defs: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - }) - ).isRequired, - fill: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string, - match: PropTypes.oneOfType([PropTypes.oneOf(['*']), PropTypes.object, PropTypes.func]) - .isRequired, - }) - ).isRequired, - borderWidth: PropTypes.number.isRequired, - activeBorderWidth: PropTypes.number.isRequired, - inactiveBorderWidth: PropTypes.number.isRequired, - borderColor: inheritedColorPropType.isRequired, - borderOpacity: PropTypes.number.isRequired, - activeBorderOpacity: PropTypes.number.isRequired, - inactiveBorderOpacity: PropTypes.number.isRequired, - - startLabel: PropTypes.oneOfType([PropTypes.oneOf([false]), PropTypes.string, PropTypes.func]) - .isRequired, - startLabelPadding: PropTypes.number.isRequired, - startLabelTextColor: inheritedColorPropType.isRequired, - endLabel: PropTypes.oneOfType([PropTypes.oneOf([false]), PropTypes.string, PropTypes.func]) - .isRequired, - endLabelPadding: PropTypes.number.isRequired, - endLabelTextColor: inheritedColorPropType.isRequired, - - enableGridX: PropTypes.bool.isRequired, - axisTop: axisPropType, - axisBottom: axisPropType, - - isInteractive: PropTypes.bool.isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, -} - -export const AreaBumpPropTypes = { - ...commonPropTypes, - ...motionPropTypes, - role: PropTypes.string.isRequired, -} - -const commonDefaultProps = { - align: 'middle', - - layers: ['grid', 'axes', 'labels', 'areas'], - - interpolation: 'smooth', - spacing: 0, - xPadding: 0.6, - - colors: { scheme: 'nivo' }, - blendMode: 'normal', - fillOpacity: 0.8, - activeFillOpacity: 1, - inactiveFillOpacity: 0.15, - defs: [], - fill: [], - borderWidth: 1, - activeBorderWidth: 1, - inactiveBorderWidth: 0, - borderColor: { from: 'color', modifiers: [['darker', 0.4]] }, - borderOpacity: 1, - activeBorderOpacity: 1, - inactiveBorderOpacity: 0, - - startLabel: false, - startLabelPadding: 12, - startLabelTextColor: { from: 'color', modifiers: [['darker', 1]] }, - endLabel: 'id', - endLabelPadding: 12, - endLabelTextColor: { from: 'color', modifiers: [['darker', 1]] }, - - enableGridX: true, - axisTop: {}, - axisBottom: {}, - - isInteractive: true, - tooltip: AreaTooltip, -} - -export const AreaBumpDefaultProps = { - ...commonDefaultProps, - animate: true, - motionConfig: 'gentle', - role: 'img', -} diff --git a/packages/bump/src/area-bump/types.ts b/packages/bump/src/area-bump/types.ts new file mode 100644 index 0000000000..7a6d8b5683 --- /dev/null +++ b/packages/bump/src/area-bump/types.ts @@ -0,0 +1,151 @@ +import { FunctionComponent, MouseEvent } from 'react' +import { + PropertyAccessor, + Box, + Theme, + Dimensions, + ModernMotionProps, + CssMixBlendMode, + SvgDefsAndFill, +} from '@nivo/core' +import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' +import { AxisProps } from '@nivo/axes' +import { Area } from 'd3-shape' +import { ScalePoint } from '@nivo/scales' + +export interface AreaBumpDatum { + x: number | string + y: number +} + +export interface DefaultAreaBumpDatum { + x: string + y: number +} + +export interface AreaBumpSerie { + id: string + data: D[] +} + +export interface AreaBumpPoint { + x: number + y: number + height: number + data: D +} + +export interface AreaBumpAreaPoint { + x: number + y0: number + y1: number +} + +export type AreaBumpAreaGenerator = Area + +export interface AreaBumpComputedSerie extends AreaBumpSerie { + points: AreaBumpPoint[] + areaPoints: AreaBumpAreaPoint[] + color: string + fill?: string + style: { + fillOpacity: number + borderWidth: number + borderColor: string + borderOpacity: number + } +} + +export type AreaBumpAlign = 'start' | 'middle' | 'end' + +export type AreaBumpDataProps = { + data: AreaBumpSerie[] +} + +export type AreaBumpInterpolation = 'smooth' | 'linear' + +export type AreaBumpLabel = + | PropertyAccessor, string> + | false +export interface AreaBumpLabelData { + serie: AreaBumpComputedSerie + id: AreaBumpSerie['id'] + label: string + x: number + y: number + color: string + opacity: number + textAnchor: 'start' | 'end' +} + +export type AreaBumpMouseHandler = ( + serie: AreaBumpComputedSerie, + event: MouseEvent +) => void + +export type AreaBumpLayerId = 'grid' | 'axes' | 'labels' | 'areas' +export interface AreaBumpCustomLayerProps { + innerWidth: number + innerHeight: number + outerWidth: number + outerHeight: number + series: AreaBumpComputedSerie[] + xScale: ScalePoint + areaGenerator: AreaBumpAreaGenerator +} +export type AreaBumpCustomLayer = FunctionComponent< + AreaBumpCustomLayerProps +> + +export type AreaBumpCommonProps = { + margin: Box + + align: AreaBumpAlign + interpolation: AreaBumpInterpolation + spacing: number + xPadding: number + + theme: Theme + colors: OrdinalColorScaleConfig, 'color' | 'style'>> + blendMode: CssMixBlendMode + fillOpacity: number + activeFillOpacity: number + inactiveFillOpacity: number + borderWidth: number + activeBorderWidth: number + inactiveBorderWidth: number + borderColor: InheritedColorConfig, 'style'>> + borderOpacity: number + activeBorderOpacity: number + inactiveBorderOpacity: number + + startLabel: AreaBumpLabel + startLabelPadding: number + startLabelTextColor: InheritedColorConfig> + endLabel: AreaBumpLabel + endLabelPadding: number + endLabelTextColor: InheritedColorConfig> + + enableGridX: boolean + axisBottom: AxisProps | null + axisTop: AxisProps | null + + isInteractive: boolean + defaultActiveSerieIds: string[] + onMouseEnter: AreaBumpMouseHandler + onMouseMove: AreaBumpMouseHandler + onMouseLeave: AreaBumpMouseHandler + onClick: AreaBumpMouseHandler + tooltip: FunctionComponent<{ serie: AreaBumpComputedSerie }> + role: string + + layers: (AreaBumpLayerId | AreaBumpCustomLayer)[] + + renderWrapper: boolean +} + +export type AreaBumpSvgProps = Partial> & + AreaBumpDataProps & + SvgDefsAndFill> & + Dimensions & + ModernMotionProps diff --git a/packages/bump/src/bump/Bump.js b/packages/bump/src/bump/Bump.js deleted file mode 100644 index c33574f6ea..0000000000 --- a/packages/bump/src/bump/Bump.js +++ /dev/null @@ -1,207 +0,0 @@ -import { createElement, memo, useMemo, useState, Fragment } from 'react' -import { withContainer, useDimensions, SvgWrapper } from '@nivo/core' -import { Grid, Axes } from '@nivo/axes' -import { useBump } from './hooks' -import { BumpPropTypes, BumpDefaultProps } from './props' -import Line from './Line' -import LinesLabels from './LinesLabels' -import Points from './Points' - -const Bump = props => { - const { - data, - - width, - height, - margin: partialMargin, - - layers, - - interpolation, - xPadding, - xOuterPadding, - yOuterPadding, - - colors, - lineWidth, - activeLineWidth, - inactiveLineWidth, - opacity, - activeOpacity, - inactiveOpacity, - - startLabel, - startLabelPadding, - startLabelTextColor, - endLabel, - endLabelPadding, - endLabelTextColor, - - pointComponent, - pointSize, - activePointSize, - inactivePointSize, - pointColor, - pointBorderWidth, - activePointBorderWidth, - inactivePointBorderWidth, - pointBorderColor, - - axisTop, - axisRight, - axisBottom, - axisLeft, - enableGridX, - enableGridY, - - isInteractive, - onMouseEnter, - onMouseMove, - onMouseLeave, - onClick, - tooltip, - role, - } = props - - const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( - width, - height, - partialMargin - ) - - const [currentSerie, setCurrentSerie] = useState(null) - - const { series, points, xScale, yScale, lineGenerator } = useBump({ - width: innerWidth, - height: innerHeight, - data, - interpolation, - xPadding, - xOuterPadding, - yOuterPadding, - lineWidth, - activeLineWidth, - inactiveLineWidth, - colors, - opacity, - activeOpacity, - inactiveOpacity, - pointSize, - activePointSize, - inactivePointSize, - pointColor, - pointBorderWidth, - activePointBorderWidth, - inactivePointBorderWidth, - pointBorderColor, - startLabel, - endLabel, - isInteractive, - currentSerie, - }) - - const layerById = { - grid: ( - - ), - axes: ( - - ), - labels: [], - lines: ( - - {series.map(serie => ( - - ))} - - ), - points: , - } - - if (startLabel !== false) { - layerById.labels.push( - - ) - } - if (endLabel !== false) { - layerById.labels.push( - - ) - } - - const bumpLayerContext = useMemo( - () => ({ - currentSerie, - innerHeight, - innerWidth, - lineGenerator, - points, - series, - setCurrentSerie, - xScale, - yScale, - }), - [currentSerie, innerHeight, innerWidth, lineGenerator, points, series, xScale, yScale] - ) - - return ( - - {layers.map((layer, i) => { - if (typeof layer === 'function') { - return {createElement(layer, bumpLayerContext)} - } - - return layerById[layer] - })} - - ) -} - -Bump.propTypes = BumpPropTypes -Bump.defaultProps = BumpDefaultProps - -export default memo(withContainer(Bump)) diff --git a/packages/bump/src/bump/Bump.tsx b/packages/bump/src/bump/Bump.tsx new file mode 100644 index 0000000000..a12d0faef5 --- /dev/null +++ b/packages/bump/src/bump/Bump.tsx @@ -0,0 +1,251 @@ +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 { useBump } from './hooks' +import { bumpSvgDefaultProps } from './defaults' +import { Line } from './Line' +import { LinesLabels } from './LinesLabels' +import { Points } from './Points' + +type InnerBumpProps = Omit< + BumpSvgProps, + 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' +> + +const InnerBump = ({ + data, + + width, + height, + margin: partialMargin, + + layers = bumpSvgDefaultProps.layers, + + interpolation = bumpSvgDefaultProps.interpolation, + xPadding = bumpSvgDefaultProps.xPadding, + xOuterPadding = bumpSvgDefaultProps.xOuterPadding, + yOuterPadding = bumpSvgDefaultProps.yOuterPadding, + + colors = bumpSvgDefaultProps.colors, + lineWidth = bumpSvgDefaultProps.lineWidth, + activeLineWidth = bumpSvgDefaultProps.activeLineWidth, + inactiveLineWidth = bumpSvgDefaultProps.inactiveLineWidth, + opacity = bumpSvgDefaultProps.opacity, + activeOpacity = bumpSvgDefaultProps.activeOpacity, + inactiveOpacity = bumpSvgDefaultProps.inactiveOpacity, + + startLabel = bumpSvgDefaultProps.startLabel, + startLabelPadding = bumpSvgDefaultProps.startLabelPadding, + startLabelTextColor = bumpSvgDefaultProps.startLabelTextColor, + endLabel = bumpSvgDefaultProps.endLabel, + endLabelPadding = bumpSvgDefaultProps.endLabelPadding, + endLabelTextColor = bumpSvgDefaultProps.endLabelTextColor, + + pointComponent = bumpSvgDefaultProps.pointComponent, + pointSize = bumpSvgDefaultProps.pointSize, + activePointSize = bumpSvgDefaultProps.activePointSize, + inactivePointSize = bumpSvgDefaultProps.inactivePointSize, + pointColor = bumpSvgDefaultProps.pointColor, + pointBorderWidth = bumpSvgDefaultProps.pointBorderWidth, + activePointBorderWidth = bumpSvgDefaultProps.activePointBorderWidth, + inactivePointBorderWidth = bumpSvgDefaultProps.inactivePointBorderWidth, + pointBorderColor = bumpSvgDefaultProps.pointBorderColor, + + enableGridX = bumpSvgDefaultProps.enableGridX, + enableGridY = bumpSvgDefaultProps.enableGridY, + axisTop = bumpSvgDefaultProps.axisTop, + axisRight, + axisBottom = bumpSvgDefaultProps.axisBottom, + axisLeft = bumpSvgDefaultProps.axisLeft, + + isInteractive = bumpSvgDefaultProps.isInteractive, + defaultActiveSerieIds = bumpSvgDefaultProps.defaultActiveSerieIds, + onMouseEnter, + onMouseMove, + onMouseLeave, + onClick, + tooltip = bumpSvgDefaultProps.tooltip, + role = bumpSvgDefaultProps.role, +}: InnerBumpProps) => { + const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( + width, + height, + partialMargin + ) + + const { series, points, xScale, yScale, lineGenerator, activeSerieIds, setActiveSerieIds } = + useBump({ + width: innerWidth, + height: innerHeight, + data, + interpolation, + xPadding, + xOuterPadding, + yOuterPadding, + lineWidth, + activeLineWidth, + inactiveLineWidth, + colors, + opacity, + activeOpacity, + inactiveOpacity, + pointSize, + activePointSize, + inactivePointSize, + pointColor, + pointBorderWidth, + activePointBorderWidth, + inactivePointBorderWidth, + pointBorderColor, + isInteractive, + defaultActiveSerieIds, + }) + + const layerById: Record = { + grid: null, + axes: null, + labels: null, + lines: null, + points: null, + } + + if (layers.includes('grid')) { + layerById.grid = ( + + ) + } + + if (layers.includes('axes')) { + layerById.axes = ( + + ) + } + + if (layers.includes('lines')) { + layerById.lines = ( + + {series.map(serie => ( + + key={serie.id} + serie={serie} + setActiveSerieIds={setActiveSerieIds} + lineGenerator={lineGenerator} + yStep={yScale.step()} + isInteractive={isInteractive} + onMouseEnter={onMouseEnter} + onMouseMove={onMouseMove} + onMouseLeave={onMouseLeave} + onClick={onClick} + tooltip={tooltip} + /> + ))} + + ) + } + + if (layers.includes('points')) { + layerById.points = ( + key="points" pointComponent={pointComponent} points={points} /> + ) + } + + if (layers.includes('labels')) { + layerById.labels = ( + + {startLabel !== false && ( + + series={series} + getLabel={startLabel} + position="start" + padding={startLabelPadding} + color={startLabelTextColor} + /> + )} + {endLabel !== false && ( + + series={series} + getLabel={endLabel} + position="end" + padding={endLabelPadding} + color={endLabelTextColor} + /> + )} + + ) + } + + const customLayerProps = useMemo( + () => ({ + innerHeight, + innerWidth, + lineGenerator, + points, + series, + xScale, + yScale, + activeSerieIds, + setActiveSerieIds, + }), + [ + activeSerieIds, + setActiveSerieIds, + innerHeight, + innerWidth, + lineGenerator, + points, + series, + xScale, + yScale, + ] + ) + + return ( + + {layers.map((layer, i) => { + if (typeof layer === 'function') { + return {createElement(layer, customLayerProps)} + } + + return layerById?.[layer] ?? null + })} + + ) +} + +export const Bump = ({ + isInteractive = bumpSvgDefaultProps.isInteractive, + animate = bumpSvgDefaultProps.animate, + motionConfig = bumpSvgDefaultProps.motionConfig, + theme, + renderWrapper, + ...otherProps +}: BumpSvgProps) => ( + + isInteractive={isInteractive} {...otherProps} /> + +) diff --git a/packages/bump/src/bump/Line.js b/packages/bump/src/bump/Line.tsx similarity index 59% rename from packages/bump/src/bump/Line.js rename to packages/bump/src/bump/Line.tsx index 8b207fcbe0..2d6c94f8df 100644 --- a/packages/bump/src/bump/Line.js +++ b/packages/bump/src/bump/Line.tsx @@ -1,10 +1,23 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' import { useSpring, animated } from '@react-spring/web' +import { Line as D3Line } from 'd3-shape' import { useAnimatedPath, useMotionConfig } from '@nivo/core' -import { useSerieHandlers } from './hooks' +import { BumpCommonProps, BumpComputedSerie, BumpDatum } from './types' +import { useBumpSerieHandlers } from './hooks' -const Line = ({ +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'] + setActiveSerieIds: (serieIds: string[]) => void + tooltip: BumpCommonProps['tooltip'] +} + +export const Line = ({ serie, lineGenerator, yStep, @@ -13,26 +26,30 @@ const Line = ({ onMouseMove, onMouseLeave, onClick, - setCurrentSerie, + setActiveSerieIds, tooltip, -}) => { - const handlers = useSerieHandlers({ +}: LineProps) => { + const handlers = useBumpSerieHandlers({ serie, isInteractive, onMouseEnter, onMouseMove, onMouseLeave, onClick, - setCurrent: setCurrentSerie, + setActiveSerieIds, tooltip, }) const { animate, config: springConfig } = useMotionConfig() - const linePath = lineGenerator(serie.linePoints) + const linePath = lineGenerator(serie.linePoints)! const animatedPath = useAnimatedPath(linePath) - const animatedProps = useSpring({ + const animatedProps = useSpring<{ + color: string + opacity: number + lineWidth: number + }>({ color: serie.color, opacity: serie.style.opacity, lineWidth: serie.style.lineWidth, @@ -68,26 +85,3 @@ const Line = ({ ) } - -Line.propTypes = { - serie: PropTypes.shape({ - id: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - linePoints: PropTypes.array.isRequired, - style: PropTypes.shape({ - lineWidth: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }).isRequired, - }).isRequired, - lineGenerator: PropTypes.func.isRequired, - yStep: PropTypes.number.isRequired, - isInteractive: PropTypes.bool.isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - setCurrentSerie: PropTypes.func.isRequired, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, -} - -export default memo(Line) diff --git a/packages/bump/src/bump/LineTooltip.js b/packages/bump/src/bump/LineTooltip.js deleted file mode 100644 index c2b7fd71dd..0000000000 --- a/packages/bump/src/bump/LineTooltip.js +++ /dev/null @@ -1,16 +0,0 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { BasicTooltip } from '@nivo/tooltip' - -const LineTooltip = ({ serie }) => { - return -} - -LineTooltip.propTypes = { - serie: PropTypes.shape({ - id: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - }), -} - -export default memo(LineTooltip) diff --git a/packages/bump/src/bump/LineTooltip.tsx b/packages/bump/src/bump/LineTooltip.tsx new file mode 100644 index 0000000000..19583169b1 --- /dev/null +++ b/packages/bump/src/bump/LineTooltip.tsx @@ -0,0 +1,10 @@ +import { BasicTooltip } from '@nivo/tooltip' +import { BumpComputedSerie, BumpDatum } from './types' + +interface LineTooltipProps { + serie: BumpComputedSerie +} + +export const LineTooltip = ({ serie }: LineTooltipProps) => { + return +} diff --git a/packages/bump/src/bump/LinesLabels.js b/packages/bump/src/bump/LinesLabels.js deleted file mode 100644 index 6c07556216..0000000000 --- a/packages/bump/src/bump/LinesLabels.js +++ /dev/null @@ -1,72 +0,0 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { useSprings, animated } from '@react-spring/web' -import { useTheme, useMotionConfig } from '@nivo/core' -import { inheritedColorPropType } from '@nivo/colors' -import { useSeriesLabels } from './hooks' - -const LinesLabels = ({ series, getLabel, position, padding, color }) => { - const theme = useTheme() - const { animate, config: springConfig } = useMotionConfig() - - const labels = useSeriesLabels({ - series, - getLabel, - position, - padding, - color, - }) - - const springs = useSprings( - labels.length, - labels.map(label => ({ - x: label.x, - y: label.y, - opacity: label.opacity, - config: springConfig, - immediate: !animate, - })) - ) - - return springs.map((animatedProps, index) => { - const label = labels[index] - - return ( - - {label.label} - - ) - }) -} - -LinesLabels.propTypes = { - series: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - data: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - }) - ).isRequired, - }) - ).isRequired, - getLabel: PropTypes.oneOfType([PropTypes.oneOf([false]), PropTypes.string, PropTypes.func]) - .isRequired, - position: PropTypes.oneOf(['start', 'end']).isRequired, - padding: PropTypes.number.isRequired, - color: inheritedColorPropType.isRequired, -} - -export default memo(LinesLabels) diff --git a/packages/bump/src/bump/LinesLabels.tsx b/packages/bump/src/bump/LinesLabels.tsx new file mode 100644 index 0000000000..6a88896912 --- /dev/null +++ b/packages/bump/src/bump/LinesLabels.tsx @@ -0,0 +1,72 @@ +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 { useBumpSeriesLabels } from './hooks' + +interface LineLabelsProps { + series: BumpComputedSerie[] + getLabel: BumpLabel + position: 'start' | 'end' + padding: number + color: InheritedColorConfig> +} + +export const LinesLabels = ({ + series, + getLabel, + position, + padding, + color, +}: LineLabelsProps) => { + const theme = useTheme() + const { animate, config: springConfig } = useMotionConfig() + + const labels = useBumpSeriesLabels({ + series, + getLabel, + position, + padding, + color, + }) + + const springs = useSprings<{ + x: number + y: number + opacity: number + }>( + labels.length, + labels.map(label => ({ + x: label.x, + y: label.y, + opacity: label.opacity, + config: springConfig, + immediate: !animate, + })) + ) + + return ( + <> + {springs.map((animatedProps, index) => { + const label = labels[index] + + return ( + + {label.label} + + ) + })} + + ) +} diff --git a/packages/bump/src/bump/Point.js b/packages/bump/src/bump/Point.js deleted file mode 100644 index f090bf036c..0000000000 --- a/packages/bump/src/bump/Point.js +++ /dev/null @@ -1,46 +0,0 @@ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { useSpring, animated, to } from '@react-spring/web' -import { useMotionConfig } from '@nivo/core' - -const pointStyle = { pointerEvents: 'none' } - -const Point = ({ x, y, size, color, borderColor, borderWidth }) => { - const { animate, config: springConfig } = useMotionConfig() - - const animatedProps = useSpring({ - x, - y, - radius: size / 2, - color, - borderWidth, - config: springConfig, - immediate: !animate, - }) - - return ( - Math.max(v, 0))} - fill={animatedProps.color} - strokeWidth={animatedProps.borderWidth} - stroke={borderColor} - style={pointStyle} - /> - ) -} - -Point.propTypes = { - data: PropTypes.object.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - isActive: PropTypes.bool.isRequired, - isInactive: PropTypes.bool.isRequired, - size: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, - borderColor: PropTypes.string.isRequired, - borderWidth: PropTypes.number.isRequired, -} - -export default memo(Point) diff --git a/packages/bump/src/bump/Point.tsx b/packages/bump/src/bump/Point.tsx new file mode 100644 index 0000000000..979d579e32 --- /dev/null +++ b/packages/bump/src/bump/Point.tsx @@ -0,0 +1,42 @@ +import { SVGAttributes } from 'react' +import { useSpring, animated, to } from '@react-spring/web' +import { useMotionConfig } from '@nivo/core' +import { BumpDatum, BumpPoint } from './types' + +const pointStyle: SVGAttributes['style'] = { pointerEvents: 'none' } + +interface PointProps { + point: BumpPoint +} + +export const Point = ({ point }: PointProps) => { + const { animate, config: springConfig } = useMotionConfig() + + const animatedProps = useSpring<{ + x: number + y: number + radius: number + color: string + borderWidth: number + }>({ + x: point.x, + y: point.y, + radius: point.style.size / 2, + color: point.color, + borderWidth: point.style.borderWidth, + config: springConfig, + immediate: !animate, + }) + + return ( + Math.max(v, 0))} + fill={animatedProps.color} + strokeWidth={animatedProps.borderWidth} + stroke={point.borderColor} + style={pointStyle} + /> + ) +} diff --git a/packages/bump/src/bump/Points.js b/packages/bump/src/bump/Points.js deleted file mode 100644 index 4aa6414062..0000000000 --- a/packages/bump/src/bump/Points.js +++ /dev/null @@ -1,41 +0,0 @@ -import { createElement, memo } from 'react' -import PropTypes from 'prop-types' - -const Points = ({ pointComponent, points }) => { - return points.map(point => { - return createElement(pointComponent, { - key: point.id, - data: point.data, - x: point.x, - y: point.y, - isActive: point.isActive, - isInactive: point.isInactive, - size: point.style.size, - color: point.color, - borderColor: point.borderColor, - borderWidth: point.style.borderWidth, - }) - }) -} - -Points.propTypes = { - pointComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - points: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - data: PropTypes.object.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - isActive: PropTypes.bool.isRequired, - isInactive: PropTypes.bool.isRequired, - color: PropTypes.string.isRequired, - borderColor: PropTypes.string.isRequired, - style: PropTypes.shape({ - size: PropTypes.number.isRequired, - borderWidth: PropTypes.number.isRequired, - }).isRequired, - }) - ).isRequired, -} - -export default memo(Points) diff --git a/packages/bump/src/bump/Points.tsx b/packages/bump/src/bump/Points.tsx new file mode 100644 index 0000000000..8e72c3d10f --- /dev/null +++ b/packages/bump/src/bump/Points.tsx @@ -0,0 +1,20 @@ +import { createElement } from 'react' +import { BumpDatum, BumpPoint, BumpPointComponent } from './types' + +interface PointsProps { + points: BumpPoint[] + pointComponent: BumpPointComponent +} + +export const Points = ({ points, pointComponent }: PointsProps) => { + return ( + <> + {points.map(point => + createElement(pointComponent, { + key: point.id, + point, + }) + )} + + ) +} diff --git a/packages/bump/src/bump/ResponsiveBump.js b/packages/bump/src/bump/ResponsiveBump.js deleted file mode 100644 index 839ae18424..0000000000 --- a/packages/bump/src/bump/ResponsiveBump.js +++ /dev/null @@ -1,10 +0,0 @@ -import { ResponsiveWrapper } from '@nivo/core' -import Bump from './Bump' - -const ResponsiveBump = props => ( - - {({ width, height }) => } - -) - -export default ResponsiveBump diff --git a/packages/bump/src/bump/ResponsiveBump.tsx b/packages/bump/src/bump/ResponsiveBump.tsx new file mode 100644 index 0000000000..c52abce4b4 --- /dev/null +++ b/packages/bump/src/bump/ResponsiveBump.tsx @@ -0,0 +1,11 @@ +import { ResponsiveWrapper } from '@nivo/core' +import { BumpDatum, BumpSvgProps, DefaultBumpDatum } from './types' +import { Bump } from './Bump' + +export const ResponsiveBump = ( + props: Omit, 'width' | 'height'> +) => ( + + {({ width, height }) => width={width} height={height} {...props} />} + +) diff --git a/packages/bump/src/bump/compute.js b/packages/bump/src/bump/compute.ts similarity index 52% rename from packages/bump/src/bump/compute.js rename to packages/bump/src/bump/compute.ts index 307d22bf4a..17a4656b7b 100644 --- a/packages/bump/src/bump/compute.js +++ b/packages/bump/src/bump/compute.ts @@ -1,29 +1,48 @@ import { scalePoint } from 'd3-scale' +import { castPointScale } from '@nivo/scales' +import { BumpDataProps, BumpDatum, BumpComputedSerie, BumpSeriePoint } from './types' + +export const computeSeries = ({ + width, + height, + data, + xPadding, + xOuterPadding, + yOuterPadding, +}: { + width: number + height: number + data: BumpDataProps['data'] + xPadding: number + xOuterPadding: number + yOuterPadding: number +}) => { + const xValuesSet = new Set() + const yValuesSet = new Set() -export const computeSeries = ({ width, height, data, xPadding, xOuterPadding, yOuterPadding }) => { - let xValues = new Set() - let yValues = new Set() data.forEach(serie => { serie.data.forEach(datum => { - if (!xValues.has(datum.x)) { - xValues.add(datum.x) - } - if (!yValues.has(datum.y) && datum.y !== null) { - yValues.add(datum.y) + xValuesSet.add(datum.x) + if (datum.y !== null) { + yValuesSet.add(datum.y) } }) }) - xValues = Array.from(xValues) - yValues = Array.from(yValues).sort((a, b) => a - b) - const xScale = scalePoint().domain(xValues).range([0, width]).padding(xOuterPadding) + const xValues: D['x'][] = Array.from(xValuesSet) + const xScale = castPointScale( + scalePoint().domain(xValues).range([0, width]).padding(xOuterPadding) + ) - const yScale = scalePoint().domain(yValues).range([0, height]).padding(yOuterPadding) + const yValues: number[] = Array.from(yValuesSet).sort((a, b) => a - b) + const yScale = castPointScale( + scalePoint().domain(yValues).range([0, height]).padding(yOuterPadding) + ) const linePointPadding = xScale.step() * Math.min(xPadding * 0.5, 0.5) - const series = data.map(rawSerie => { - const serie = { + const series: Omit, 'color' | 'style'>[] = data.map(rawSerie => { + const serie: Omit, 'color' | 'style'> = { ...rawSerie, points: [], linePoints: [], @@ -32,21 +51,23 @@ export const computeSeries = ({ width, height, data, xPadding, xOuterPadding, yO rawSerie.data.forEach((datum, i) => { let x = null let y = null - if (datum.y !== null && datum.y !== undefined) { - x = xScale(datum.x) - y = yScale(datum.y) + + if (datum.y !== null) { + x = xScale(datum.x)! + y = yScale(datum.y)! } - const point = { + + const point: BumpSeriePoint = { id: `${rawSerie.id}.${i}`, serie: rawSerie, data: datum, - x, + x: x as number, y, } serie.points.push(point) // only add pre transition point if the datum is not empty - if (x !== null) { + if (point.x !== null) { if (i === 0) { serie.linePoints.push([0, point.y]) } else { diff --git a/packages/bump/src/bump/defaults.ts b/packages/bump/src/bump/defaults.ts new file mode 100644 index 0000000000..bcf95105f0 --- /dev/null +++ b/packages/bump/src/bump/defaults.ts @@ -0,0 +1,70 @@ +import { ModernMotionProps } from '@nivo/core' +import { LineTooltip } from './LineTooltip' +import { Point } from './Point' +import { BumpCommonProps, BumpSvgProps } from './types' + +const commonDefaultProps: Omit< + BumpCommonProps, + | 'onMouseEnter' + | 'onMouseMove' + | 'onMouseLeave' + | 'onClick' + | 'margin' + | 'theme' + | 'axisRight' + | 'renderWrapper' +> = { + layers: ['grid', 'axes', 'labels', 'lines', 'points'], + + interpolation: 'smooth', + xPadding: 0.6, + xOuterPadding: 0.5, + yOuterPadding: 0.5, + + colors: { scheme: 'nivo' }, + lineWidth: 2, + activeLineWidth: 4, + inactiveLineWidth: 1, + opacity: 1, + activeOpacity: 1, + inactiveOpacity: 0.3, + + startLabel: false, + startLabelPadding: 16, + startLabelTextColor: { from: 'color' }, + endLabel: 'id', + endLabelPadding: 16, + endLabelTextColor: { from: 'color' }, + + pointSize: 6, + activePointSize: 8, + inactivePointSize: 4, + pointColor: { from: 'serie.color' }, + pointBorderWidth: 0, + activePointBorderWidth: 0, + inactivePointBorderWidth: 0, + pointBorderColor: { from: 'serie.color', modifiers: [['darker', 1.4]] }, + + enableGridX: true, + enableGridY: true, + axisTop: {}, + axisBottom: {}, + axisLeft: {}, + + isInteractive: true, + defaultActiveSerieIds: [], + tooltip: LineTooltip, + + role: 'img', +} + +export const bumpSvgDefaultProps: typeof commonDefaultProps & { + pointComponent: BumpSvgProps['pointComponent'] + animate: boolean + motionConfig: ModernMotionProps['motionConfig'] +} = { + ...commonDefaultProps, + pointComponent: Point, + animate: true, + motionConfig: 'gentle', +} diff --git a/packages/bump/src/bump/hooks.js b/packages/bump/src/bump/hooks.js deleted file mode 100644 index cdfa5e19d4..0000000000 --- a/packages/bump/src/bump/hooks.js +++ /dev/null @@ -1,341 +0,0 @@ -import { createElement, useMemo, useCallback } from 'react' -import { line as d3Line, curveBasis, curveLinear } from 'd3-shape' -import { useTheme } from '@nivo/core' -import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors' -import { useTooltip } from '@nivo/tooltip' -import { computeSeries } from './compute' - -export const useLineGenerator = interpolation => - useMemo( - () => - d3Line() - .curve(interpolation === 'smooth' ? curveBasis : curveLinear) - .defined(d => d[0] !== null && d[1] !== null), - - [interpolation] - ) - -export const useSerieDerivedProp = instruction => - useMemo(() => { - if (typeof instruction === 'function') return instruction - return () => instruction - }, [instruction]) - -export const useSerieStyle = ({ - lineWidth, - activeLineWidth, - inactiveLineWidth, - opacity, - activeOpacity, - inactiveOpacity, - isInteractive, - currentSerie, -}) => { - const getLineWidth = useSerieDerivedProp(lineWidth) - const getActiveLineWidth = useSerieDerivedProp(activeLineWidth) - const getInactiveLineWidth = useSerieDerivedProp(inactiveLineWidth) - - const getOpacity = useSerieDerivedProp(opacity) - const getActiveOpacity = useSerieDerivedProp(activeOpacity) - const getInactiveOpacity = useSerieDerivedProp(inactiveOpacity) - - const getNormalStyle = useMemo( - () => serie => ({ - lineWidth: getLineWidth(serie), - opacity: getOpacity(serie), - }), - [getLineWidth, getOpacity] - ) - const getActiveStyle = useMemo( - () => serie => ({ - lineWidth: getActiveLineWidth(serie), - opacity: getActiveOpacity(serie), - }), - [getActiveLineWidth, getActiveOpacity] - ) - const getInactiveStyle = useMemo( - () => serie => ({ - lineWidth: getInactiveLineWidth(serie), - opacity: getInactiveOpacity(serie), - }), - [getInactiveLineWidth, getInactiveOpacity] - ) - - return useMemo(() => { - if (!isInteractive) return getNormalStyle - - return serie => { - if (currentSerie === null) return getNormalStyle(serie) - if (serie.id === currentSerie) return getActiveStyle(serie) - return getInactiveStyle(serie) - } - }, [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, currentSerie]) -} - -export const usePointStyle = ({ - pointSize, - activePointSize, - inactivePointSize, - pointBorderWidth, - activePointBorderWidth, - inactivePointBorderWidth, - isInteractive, - currentSerie, -}) => { - const getSize = useSerieDerivedProp(pointSize) - const getActiveSize = useSerieDerivedProp(activePointSize) - const getInactiveSize = useSerieDerivedProp(inactivePointSize) - - const getBorderWidth = useSerieDerivedProp(pointBorderWidth) - const getActiveBorderWidth = useSerieDerivedProp(activePointBorderWidth) - const getInactiveBorderWidth = useSerieDerivedProp(inactivePointBorderWidth) - - const getNormalStyle = useMemo( - () => point => ({ - size: getSize(point), - borderWidth: getBorderWidth(point), - }), - [getSize, getBorderWidth] - ) - const getActiveStyle = useMemo( - () => point => ({ - size: getActiveSize(point), - borderWidth: getActiveBorderWidth(point), - }), - [getActiveSize, getActiveBorderWidth] - ) - const getInactiveStyle = useMemo( - () => point => ({ - size: getInactiveSize(point), - borderWidth: getInactiveBorderWidth(point), - }), - [getInactiveSize, getInactiveBorderWidth] - ) - - return useMemo(() => { - if (!isInteractive) return getNormalStyle - - return point => { - if (currentSerie === null) return getNormalStyle(point) - if (point.serieId === currentSerie) return getActiveStyle(point) - return getInactiveStyle(point) - } - }, [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, currentSerie]) -} - -export const useBump = ({ - width, - height, - data, - interpolation, - xPadding, - xOuterPadding, - yOuterPadding, - lineWidth, - activeLineWidth, - inactiveLineWidth, - colors, - opacity, - activeOpacity, - inactiveOpacity, - pointSize, - activePointSize, - inactivePointSize, - pointColor, - pointBorderWidth, - activePointBorderWidth, - inactivePointBorderWidth, - pointBorderColor, - isInteractive, - currentSerie, -}) => { - const { - series: rawSeries, - xScale, - yScale, - } = useMemo( - () => - computeSeries({ - width, - height, - data, - xPadding, - xOuterPadding, - yOuterPadding, - }), - [width, height, data, xPadding, xOuterPadding, yOuterPadding] - ) - - const lineGenerator = useLineGenerator(interpolation) - - const getColor = useOrdinalColorScale(colors, 'id') - const getSerieStyle = useSerieStyle({ - lineWidth, - activeLineWidth, - inactiveLineWidth, - opacity, - activeOpacity, - inactiveOpacity, - isInteractive, - currentSerie, - }) - - const series = useMemo( - () => - rawSeries.map(serie => { - const nextSerie = { ...serie } - nextSerie.color = getColor(nextSerie) - nextSerie.style = getSerieStyle(nextSerie) - return nextSerie - }), - [rawSeries, getColor, getSerieStyle] - ) - - const theme = useTheme() - const getPointColor = useInheritedColor(pointColor, theme) - const getPointBorderColor = useInheritedColor(pointBorderColor, theme) - const getPointStyle = usePointStyle({ - pointSize, - activePointSize, - inactivePointSize, - pointBorderWidth, - activePointBorderWidth, - inactivePointBorderWidth, - isInteractive, - currentSerie, - }) - const points = useMemo(() => { - const pts = [] - series.forEach(serie => { - serie.points.forEach(rawPoint => { - const point = { - ...rawPoint, - serie, - serieId: serie.id, - isActive: currentSerie === serie.id, - isInactive: currentSerie !== null && currentSerie !== serie.id, - } - point.color = getPointColor(point) - point.borderColor = getPointBorderColor(point) - point.style = getPointStyle({ ...point, serie }) - pts.push(point) - }) - }) - - return pts - }, [series, getPointColor, getPointBorderColor, getPointStyle, currentSerie]) - - return { - xScale, - yScale, - series, - points, - lineGenerator, - } -} - -export const useSerieHandlers = ({ - serie, - isInteractive, - onMouseEnter, - onMouseMove, - onMouseLeave, - onClick, - setCurrent, - tooltip, -}) => { - const { showTooltipFromEvent, hideTooltip } = useTooltip() - - const handleMouseEnter = useCallback( - event => { - showTooltipFromEvent(createElement(tooltip, { serie }), event) - setCurrent(serie.id) - onMouseEnter && onMouseEnter(serie, event) - }, - [serie, onMouseEnter, showTooltipFromEvent, setCurrent] - ) - - const handleMouseMove = useCallback( - event => { - showTooltipFromEvent(createElement(tooltip, { serie }), event) - onMouseMove && onMouseMove(serie, event) - }, - [serie, onMouseMove, showTooltipFromEvent] - ) - - const handleMouseLeave = useCallback( - event => { - hideTooltip() - setCurrent(null) - onMouseLeave && onMouseLeave(serie, event) - }, - [serie, onMouseLeave, hideTooltip, setCurrent] - ) - - const handleClick = useCallback( - event => { - onClick && onClick(serie, event) - }, - [serie, onClick] - ) - - const handlers = useMemo( - () => ({ - onMouseEnter: isInteractive ? handleMouseEnter : undefined, - onMouseMove: isInteractive ? handleMouseMove : undefined, - onMouseLeave: isInteractive ? handleMouseLeave : undefined, - onClick: isInteractive ? handleClick : undefined, - }), - [isInteractive, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] - ) - - return handlers -} - -export const useSeriesLabels = ({ series, position, padding, color, getLabel }) => { - const theme = useTheme() - const getColor = useInheritedColor(color, theme) - - return useMemo(() => { - let textAnchor - let signedPadding - if (position === 'start') { - textAnchor = 'end' - signedPadding = padding * -1 - } else { - textAnchor = 'start' - signedPadding = padding - } - - const labels = [] - series.forEach(serie => { - let label = serie.id - if (typeof getLabel === 'function') { - label = getLabel(serie) - } - - const point = - position === 'start' - ? serie.linePoints[0] - : serie.linePoints[serie.linePoints.length - 1] - - // exclude labels for series having missing data at the beginning/end - if (point[0] === null || point[1] === null) { - return - } - - labels.push({ - id: serie.id, - label, - x: point[0] + signedPadding, - y: point[1], - color: getColor(serie), - opacity: serie.style.opacity, - serie, - textAnchor, - }) - }) - - return labels - }, [series, position, padding, getColor]) -} diff --git a/packages/bump/src/bump/hooks.ts b/packages/bump/src/bump/hooks.ts new file mode 100644 index 0000000000..018e91c549 --- /dev/null +++ b/packages/bump/src/bump/hooks.ts @@ -0,0 +1,425 @@ +import { createElement, useMemo, useCallback, useState } from 'react' +import { line as d3Line, curveBasis, curveLinear } from 'd3-shape' +import { useTheme } from '@nivo/core' +import { useOrdinalColorScale, useInheritedColor, InheritedColorConfig } from '@nivo/colors' +import { useTooltip } from '@nivo/tooltip' +import { + BumpInterpolation, + BumpCommonProps, + BumpDatum, + DefaultBumpDatum, + BumpDataProps, + BumpComputedSerie, + BumpPoint, + BumpLabel, + BumpLabelData, +} from './types' +import { computeSeries } from './compute' + +const useLineGenerator = (interpolation: BumpInterpolation) => + useMemo( + () => + d3Line<[number, number | null]>() + .curve(interpolation === 'smooth' ? curveBasis : curveLinear) + .defined(d => d[0] !== null && d[1] !== null), + + [interpolation] + ) + +const useSerieDerivedProp = ( + instruction: ((target: Target) => Output) | Output +): ((target: Target) => Output) => + useMemo(() => { + if (typeof instruction === 'function') return instruction + return () => instruction + }, [instruction]) + +const useSerieStyle = ({ + lineWidth, + activeLineWidth, + inactiveLineWidth, + opacity, + activeOpacity, + inactiveOpacity, + isInteractive, + activeSerieIds, +}: { + 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'> + + const getLineWidth = useSerieDerivedProp(lineWidth) + const getActiveLineWidth = useSerieDerivedProp(activeLineWidth) + const getInactiveLineWidth = useSerieDerivedProp(inactiveLineWidth) + + const getOpacity = useSerieDerivedProp(opacity) + const getActiveOpacity = useSerieDerivedProp(activeOpacity) + const getInactiveOpacity = useSerieDerivedProp(inactiveOpacity) + + const getNormalStyle = useCallback( + (serie: Serie) => ({ + lineWidth: getLineWidth(serie), + opacity: getOpacity(serie), + }), + [getLineWidth, getOpacity] + ) + const getActiveStyle = useCallback( + (serie: Serie) => ({ + lineWidth: getActiveLineWidth(serie), + opacity: getActiveOpacity(serie), + }), + [getActiveLineWidth, getActiveOpacity] + ) + const getInactiveStyle = useCallback( + (serie: Serie) => ({ + lineWidth: getInactiveLineWidth(serie), + opacity: getInactiveOpacity(serie), + }), + [getInactiveLineWidth, getInactiveOpacity] + ) + + return useCallback( + (serie: Serie) => { + if (!isInteractive || activeSerieIds.length === 0) return getNormalStyle(serie) + if (activeSerieIds.includes(serie.id)) return getActiveStyle(serie) + return getInactiveStyle(serie) + }, + [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, activeSerieIds] + ) +} + +const usePointStyle = ({ + pointSize, + activePointSize, + inactivePointSize, + pointBorderWidth, + activePointBorderWidth, + inactivePointBorderWidth, + isInteractive, + activeSerieIds, +}: { + 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'> + + const getSize = useSerieDerivedProp(pointSize) + const getActiveSize = useSerieDerivedProp(activePointSize) + const getInactiveSize = useSerieDerivedProp(inactivePointSize) + + const getBorderWidth = useSerieDerivedProp(pointBorderWidth) + const getActiveBorderWidth = useSerieDerivedProp(activePointBorderWidth) + const getInactiveBorderWidth = useSerieDerivedProp(inactivePointBorderWidth) + + const getNormalStyle = useCallback( + (point: Point) => ({ + size: getSize(point), + borderWidth: getBorderWidth(point), + }), + [getSize, getBorderWidth] + ) + const getActiveStyle = useCallback( + (point: Point) => ({ + size: getActiveSize(point), + borderWidth: getActiveBorderWidth(point), + }), + [getActiveSize, getActiveBorderWidth] + ) + const getInactiveStyle = useCallback( + (point: Point) => ({ + size: getInactiveSize(point), + borderWidth: getInactiveBorderWidth(point), + }), + [getInactiveSize, getInactiveBorderWidth] + ) + + return useCallback( + (point: Point) => { + if (!isInteractive || activeSerieIds.length === 0) return getNormalStyle(point) + if (activeSerieIds.includes(point.serie.id)) return getActiveStyle(point) + return getInactiveStyle(point) + }, + [getNormalStyle, getActiveStyle, getInactiveStyle, isInteractive, activeSerieIds] + ) +} + +export const useBump = ({ + width, + height, + data, + interpolation, + xPadding, + xOuterPadding, + yOuterPadding, + lineWidth, + activeLineWidth, + inactiveLineWidth, + colors, + opacity, + activeOpacity, + inactiveOpacity, + pointSize, + activePointSize, + inactivePointSize, + pointColor, + pointBorderWidth, + activePointBorderWidth, + inactivePointBorderWidth, + pointBorderColor, + isInteractive, + defaultActiveSerieIds, +}: { + 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'] +}) => { + const [activeSerieIds, setActiveSerieIds] = useState(defaultActiveSerieIds) + + const { + series: rawSeries, + xScale, + yScale, + } = useMemo( + () => + computeSeries({ + width, + height, + data, + xPadding, + xOuterPadding, + yOuterPadding, + }), + [width, height, data, xPadding, xOuterPadding, yOuterPadding] + ) + + const lineGenerator = useLineGenerator(interpolation) + + const getColor = useOrdinalColorScale, 'color' | 'style'>>( + colors, + 'id' + ) + const getSerieStyle = useSerieStyle({ + lineWidth, + activeLineWidth, + inactiveLineWidth, + opacity, + activeOpacity, + inactiveOpacity, + isInteractive, + activeSerieIds, + }) + + const series: BumpComputedSerie[] = useMemo( + () => + rawSeries.map(serie => ({ + ...serie, + color: getColor(serie), + style: getSerieStyle(serie), + })), + [rawSeries, getColor, getSerieStyle] + ) + + const theme = useTheme() + const getPointColor = useInheritedColor(pointColor, theme) + const getPointBorderColor = useInheritedColor(pointBorderColor, theme) + const getPointStyle = usePointStyle({ + pointSize, + activePointSize, + inactivePointSize, + pointBorderWidth, + activePointBorderWidth, + inactivePointBorderWidth, + isInteractive, + activeSerieIds, + }) + const points: BumpPoint[] = useMemo(() => { + const pts: BumpPoint[] = [] + series.forEach(serie => { + serie.points.forEach(rawPoint => { + // @ts-ignore + const point: BumpPoint = { + ...rawPoint, + serie, + isActive: activeSerieIds.includes(serie.id), + isInactive: activeSerieIds.length > 0 && !activeSerieIds.includes(serie.id), + } + point.color = getPointColor(point) + point.borderColor = getPointBorderColor(point) + point.style = getPointStyle(point) + + pts.push(point) + }) + }) + + return pts + }, [series, getPointColor, getPointBorderColor, getPointStyle, activeSerieIds]) + + return { + xScale, + yScale, + series, + points, + lineGenerator, + activeSerieIds, + setActiveSerieIds, + } +} + +export const useBumpSerieHandlers = ({ + serie, + isInteractive, + onMouseEnter, + onMouseMove, + onMouseLeave, + onClick, + setActiveSerieIds, + tooltip, +}: { + serie: BumpComputedSerie + isInteractive: BumpCommonProps['isInteractive'] + onMouseEnter?: BumpCommonProps['onMouseEnter'] + onMouseMove?: BumpCommonProps['onMouseMove'] + onMouseLeave?: BumpCommonProps['onMouseLeave'] + onClick?: BumpCommonProps['onClick'] + setActiveSerieIds: (serieIds: string[]) => void + tooltip: BumpCommonProps['tooltip'] +}) => { + const { showTooltipFromEvent, hideTooltip } = useTooltip() + + const handleMouseEnter = useCallback( + event => { + showTooltipFromEvent(createElement(tooltip, { serie }), event) + setActiveSerieIds([serie.id]) + onMouseEnter && onMouseEnter(serie, event) + }, + [serie, onMouseEnter, showTooltipFromEvent, setActiveSerieIds] + ) + + const handleMouseMove = useCallback( + event => { + showTooltipFromEvent(createElement(tooltip, { serie }), event) + onMouseMove && onMouseMove(serie, event) + }, + [serie, onMouseMove, showTooltipFromEvent] + ) + + const handleMouseLeave = useCallback( + event => { + hideTooltip() + setActiveSerieIds([]) + onMouseLeave && onMouseLeave(serie, event) + }, + [serie, onMouseLeave, hideTooltip, setActiveSerieIds] + ) + + const handleClick = useCallback( + event => { + onClick && onClick(serie, event) + }, + [serie, onClick] + ) + + return useMemo( + () => ({ + onMouseEnter: isInteractive ? handleMouseEnter : undefined, + onMouseMove: isInteractive ? handleMouseMove : undefined, + onMouseLeave: isInteractive ? handleMouseLeave : undefined, + onClick: isInteractive ? handleClick : undefined, + }), + [isInteractive, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] + ) +} + +export const useBumpSeriesLabels = ({ + series, + position, + padding, + color, + getLabel, +}: { + series: BumpComputedSerie[] + position: 'start' | 'end' + padding: number + color: InheritedColorConfig> + getLabel: BumpLabel +}) => { + const theme = useTheme() + const getColor = useInheritedColor(color, theme) + + return useMemo(() => { + let textAnchor: 'start' | 'end' + let signedPadding: number + if (position === 'start') { + textAnchor = 'end' + signedPadding = padding * -1 + } else { + textAnchor = 'start' + signedPadding = padding + } + + const labels: BumpLabelData[] = [] + series.forEach(serie => { + let label = serie.id + if (typeof getLabel === 'function') { + label = getLabel(serie) + } + + const point = + position === 'start' + ? serie.linePoints[0] + : serie.linePoints[serie.linePoints.length - 1] + + // exclude labels for series having missing data at the beginning/end + if (point[0] === null || point[1] === null) { + return + } + + labels.push({ + id: serie.id, + label, + x: point[0] + signedPadding, + y: point[1], + color: getColor(serie), + opacity: serie.style.opacity, + serie, + textAnchor, + }) + }) + + return labels + }, [series, position, padding, getColor]) +} diff --git a/packages/bump/src/bump/index.js b/packages/bump/src/bump/index.js deleted file mode 100644 index 71abc3836c..0000000000 --- a/packages/bump/src/bump/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as Bump } from './Bump' -export { default as ResponsiveBump } from './ResponsiveBump' -export * from './props' diff --git a/packages/bump/src/bump/index.ts b/packages/bump/src/bump/index.ts new file mode 100644 index 0000000000..28f39cc2d2 --- /dev/null +++ b/packages/bump/src/bump/index.ts @@ -0,0 +1,5 @@ +export * from './Bump' +export * from './defaults' +export * from './hooks' +export * from './ResponsiveBump' +export * from './types' diff --git a/packages/bump/src/bump/props.js b/packages/bump/src/bump/props.js deleted file mode 100644 index b165841db7..0000000000 --- a/packages/bump/src/bump/props.js +++ /dev/null @@ -1,129 +0,0 @@ -import PropTypes from 'prop-types' -import { motionPropTypes } from '@nivo/core' -import { ordinalColorsPropType, inheritedColorPropType } from '@nivo/colors' -import { axisPropType } from '@nivo/axes' -import LineTooltip from './LineTooltip' -import Point from './Point' - -const commonPropTypes = { - data: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - data: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - }) - ).isRequired, - }) - ).isRequired, - - layers: PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.oneOf(['grid', 'axes', 'labels', 'lines', 'points']), - PropTypes.func, - ]) - ).isRequired, - - interpolation: PropTypes.oneOf(['linear', 'smooth']).isRequired, - xPadding: PropTypes.number.isRequired, - xOuterPadding: PropTypes.number.isRequired, - yOuterPadding: PropTypes.number.isRequired, - - colors: ordinalColorsPropType.isRequired, - lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - activeLineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - inactiveLineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - activeOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - inactiveOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - - startLabel: PropTypes.oneOfType([PropTypes.oneOf([false]), PropTypes.string, PropTypes.func]) - .isRequired, - startLabelPadding: PropTypes.number.isRequired, - startLabelTextColor: inheritedColorPropType.isRequired, - endLabel: PropTypes.oneOfType([PropTypes.oneOf([false]), PropTypes.string, PropTypes.func]) - .isRequired, - endLabelPadding: PropTypes.number.isRequired, - endLabelTextColor: inheritedColorPropType.isRequired, - - pointComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - pointSize: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - activePointSize: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - inactivePointSize: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - pointColor: inheritedColorPropType.isRequired, - pointBorderWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - activePointBorderWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - inactivePointBorderWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, - pointBorderColor: inheritedColorPropType.isRequired, - - enableGridX: PropTypes.bool.isRequired, - enableGridY: PropTypes.bool.isRequired, - axisTop: axisPropType, - axisRight: axisPropType, - axisBottom: axisPropType, - axisLeft: axisPropType, - - isInteractive: PropTypes.bool.isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, -} - -export const BumpPropTypes = { - ...commonPropTypes, - ...motionPropTypes, - role: PropTypes.string.isRequired, -} - -const commonDefaultProps = { - layers: ['grid', 'axes', 'labels', 'lines', 'points'], - - interpolation: 'smooth', - xPadding: 0.6, - xOuterPadding: 0.5, - yOuterPadding: 0.5, - - colors: { scheme: 'nivo' }, - lineWidth: 2, - activeLineWidth: 4, - inactiveLineWidth: 1, - opacity: 1, - activeOpacity: 1, - inactiveOpacity: 0.3, - - startLabel: false, - startLabelPadding: 16, - startLabelTextColor: { from: 'color' }, - endLabel: 'id', - endLabelPadding: 16, - endLabelTextColor: { from: 'color' }, - - pointSize: 6, - activePointSize: 8, - inactivePointSize: 4, - pointColor: { from: 'serie.color' }, - pointBorderWidth: 0, - activePointBorderWidth: 0, - inactivePointBorderWidth: 0, - pointBorderColor: { from: 'serie.color', modifiers: [['darker', 1.4]] }, - - enableGridX: true, - enableGridY: true, - axisTop: {}, - axisBottom: {}, - axisLeft: {}, - - isInteractive: true, - tooltip: LineTooltip, -} - -export const BumpDefaultProps = { - ...commonDefaultProps, - pointComponent: Point, - animate: true, - motionConfig: 'gentle', - role: 'img', -} diff --git a/packages/bump/src/bump/types.ts b/packages/bump/src/bump/types.ts new file mode 100644 index 0000000000..c174813235 --- /dev/null +++ b/packages/bump/src/bump/types.ts @@ -0,0 +1,138 @@ +import { FunctionComponent, MouseEvent } from 'react' +import { Theme, Box, Dimensions, ModernMotionProps, PropertyAccessor } from '@nivo/core' +import { OrdinalColorScaleConfig, InheritedColorConfig } from '@nivo/colors' +import { AxisProps } from '@nivo/axes' + +export interface BumpDatum { + x: number | string + y: number | null +} + +export interface DefaultBumpDatum { + x: string + y: number +} + +export interface BumpSerie { + id: string + data: D[] +} + +export interface BumpSeriePoint { + id: string + serie: BumpSerie + data: D + x: number + y: number | null +} + +export interface BumpPoint extends BumpSeriePoint { + isActive: boolean + isInactive: boolean + color: string + borderColor: string + style: { + size: number + borderWidth: number + } +} +export type BumpPointComponent = FunctionComponent<{ + point: BumpPoint +}> + +export interface BumpComputedSerie extends BumpSerie { + color: string + style: { + lineWidth: number + opacity: number + } + points: BumpSeriePoint[] + linePoints: [number, number | null][] +} + +export type BumpDataProps = { + data: BumpSerie[] +} + +export type BumpInterpolation = 'smooth' | 'linear' + +export type BumpLabel = PropertyAccessor | false +export interface BumpLabelData { + serie: BumpComputedSerie + id: BumpSerie['id'] + label: string + x: number + y: number + color: string + opacity: number + textAnchor: 'start' | 'end' +} + +export type BumpMouseHandler = ( + serie: BumpComputedSerie, + event: MouseEvent +) => void + +export type BumpLayerId = 'grid' | 'axes' | 'labels' | 'lines' | 'points' +export interface BumpCustomLayerProps {} +export type BumpCustomLayer = FunctionComponent + +export type BumpCommonProps = { + margin: Box + + interpolation: BumpInterpolation + xPadding: number + xOuterPadding: number + yOuterPadding: number + + theme: Theme + colors: OrdinalColorScaleConfig, 'color' | 'style'>> + lineWidth: number + activeLineWidth: number + inactiveLineWidth: number + opacity: number + activeOpacity: number + inactiveOpacity: number + + startLabel: BumpLabel + startLabelPadding: number + startLabelTextColor: InheritedColorConfig> + endLabel: BumpLabel + endLabelPadding: number + endLabelTextColor: InheritedColorConfig> + + pointSize: number + activePointSize: number + inactivePointSize: number + pointColor: InheritedColorConfig + pointBorderWidth: number + activePointBorderWidth: number + inactivePointBorderWidth: number + pointBorderColor: InheritedColorConfig + + enableGridX: boolean + enableGridY: boolean + axisBottom: AxisProps | null + axisLeft: AxisProps | null + axisRight: AxisProps | null + axisTop: AxisProps | null + + isInteractive: boolean + defaultActiveSerieIds: string[] + onMouseEnter: BumpMouseHandler + onMouseMove: BumpMouseHandler + onMouseLeave: BumpMouseHandler + onClick: BumpMouseHandler + tooltip: FunctionComponent<{ serie: BumpComputedSerie }> + role: string + + layers: (BumpLayerId | BumpCustomLayer)[] + + renderWrapper: boolean +} + +export type BumpSvgProps = Partial> & + BumpDataProps & { + pointComponent: BumpPointComponent + } & Dimensions & + ModernMotionProps diff --git a/packages/bump/src/index.js b/packages/bump/src/index.ts similarity index 100% rename from packages/bump/src/index.js rename to packages/bump/src/index.ts diff --git a/packages/bump/tsconfig.json b/packages/bump/tsconfig.json new file mode 100644 index 0000000000..39c997d5e7 --- /dev/null +++ b/packages/bump/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.types.json", + "compilerOptions": { + "outDir": "./dist/types", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/packages/scales/src/pointScale.ts b/packages/scales/src/pointScale.ts index 05f689a32f..1f8e40a52f 100644 --- a/packages/scales/src/pointScale.ts +++ b/packages/scales/src/pointScale.ts @@ -1,4 +1,4 @@ -import { scalePoint } from 'd3-scale' +import { scalePoint, ScalePoint as D3ScalePoint } from 'd3-scale' import { ComputedSerieAxis, ScalePoint, ScalePointSpec, StringValue } from './types' export const createPointScale = ( @@ -13,3 +13,10 @@ export const createPointScale = ( return typedScale } + +export const castPointScale = (scale: D3ScalePoint) => { + const typedScale = scale as ScalePoint + typedScale.type = 'point' + + return typedScale +} diff --git a/tsconfig.monorepo.json b/tsconfig.monorepo.json index cbde2854f8..ace0712905 100644 --- a/tsconfig.monorepo.json +++ b/tsconfig.monorepo.json @@ -15,13 +15,14 @@ { "path": "./packages/polar-axes" }, { "path": "./packages/voronoi" }, - // Utility package + // Utility packages { "path": "./packages/generators" }, { "path": "./packages/recompose" }, // Charts now { "path": "./packages/bar" }, { "path": "./packages/bullet" }, + { "path": "./packages/bump" }, { "path": "./packages/calendar" }, { "path": "./packages/circle-packing" }, { "path": "./packages/funnel" }, @@ -35,7 +36,7 @@ { "path": "./packages/stream" }, { "path": "./packages/swarmplot" }, - // static rendering and express middleware + // Static rendering and express middleware { "path": "./packages/static" }, { "path": "./packages/express" } ] diff --git a/website/src/data/components/area-bump/props.ts b/website/src/data/components/area-bump/props.ts index c9d3533337..ea8080ceec 100644 --- a/website/src/data/components/area-bump/props.ts +++ b/website/src/data/components/area-bump/props.ts @@ -1,5 +1,4 @@ -// @ts-ignore -import { AreaBumpDefaultProps as defaults } from '@nivo/bump' +import { areaBumpSvgDefaultProps as defaults } from '@nivo/bump' import { themeProperty, defsProperties, diff --git a/website/src/data/components/bump/props.ts b/website/src/data/components/bump/props.ts index 1050f39418..28f7a29363 100644 --- a/website/src/data/components/bump/props.ts +++ b/website/src/data/components/bump/props.ts @@ -1,5 +1,4 @@ -// @ts-ignore -import { BumpDefaultProps as defaults } from '@nivo/bump' +import { bumpSvgDefaultProps as defaults } from '@nivo/bump' import { themeProperty, motionProperties, groupProperties } from '../../../lib/componentProperties' import { chartDimensions, diff --git a/website/src/lib/settings.ts b/website/src/lib/settings.ts index a9a8a8b1e0..fc5d81db73 100644 --- a/website/src/lib/settings.ts +++ b/website/src/lib/settings.ts @@ -2,7 +2,7 @@ import omit from 'lodash/omit' import upperFirst from 'lodash/upperFirst' export const settingsMapper = - (mapping: any, { exclude = [] } = {}) => + (mapping: any, { exclude = [] }: { exclude?: string[] } = {}) => (settings: any, options: any = {}) => { const overrides: any = {} diff --git a/website/src/pages/area-bump/index.tsx b/website/src/pages/area-bump/index.tsx index 187f944964..22521aa678 100644 --- a/website/src/pages/area-bump/index.tsx +++ b/website/src/pages/area-bump/index.tsx @@ -3,7 +3,7 @@ import range from 'lodash/range' import random from 'lodash/random' import { useStaticQuery, graphql } from 'gatsby' import { patternDotsDef, patternLinesDef } from '@nivo/core' -import { ResponsiveAreaBump, AreaBumpDefaultProps } from '@nivo/bump' +import { ResponsiveAreaBump, areaBumpSvgDefaultProps as defaults } from '@nivo/bump' import { ComponentTemplate } from '../../components/components/ComponentTemplate' import meta from '../../data/components/area-bump/meta.yml' import { groups } from '../../data/components/area-bump/props' @@ -30,16 +30,16 @@ const initialProperties = { left: 100, }, - align: AreaBumpDefaultProps.align, - interpolation: AreaBumpDefaultProps.interpolation, + align: defaults.align, + interpolation: defaults.interpolation, spacing: 8, - xPadding: AreaBumpDefaultProps.xPadding, + xPadding: defaults.xPadding, colors: { scheme: 'nivo' }, blendMode: 'multiply', - fillOpacity: AreaBumpDefaultProps.fillOpacity, - activeFillOpacity: AreaBumpDefaultProps.activeFillOpacity, - inactiveFillOpacity: AreaBumpDefaultProps.inactiveFillOpacity, + fillOpacity: defaults.fillOpacity, + activeFillOpacity: defaults.activeFillOpacity, + inactiveFillOpacity: defaults.inactiveFillOpacity, defs: [ patternDotsDef('dots', { background: 'inherit', @@ -60,22 +60,22 @@ const initialProperties = { { match: { id: 'CoffeeScript' }, id: 'dots' }, { match: { id: 'TypeScript' }, id: 'lines' }, ], - borderWidth: AreaBumpDefaultProps.borderWidth, - activeBorderWidth: AreaBumpDefaultProps.activeBorderWidth, - inactiveBorderWidth: AreaBumpDefaultProps.inactiveBorderWidth, - borderColor: AreaBumpDefaultProps.borderColor, - borderOpacity: AreaBumpDefaultProps.borderOpacity, - activeBorderOpacity: AreaBumpDefaultProps.activeBorderOpacity, - inactiveBorderOpacity: AreaBumpDefaultProps.inactiveBorderOpacity, + borderWidth: defaults.borderWidth, + activeBorderWidth: defaults.activeBorderWidth, + inactiveBorderWidth: defaults.inactiveBorderWidth, + borderColor: defaults.borderColor, + borderOpacity: defaults.borderOpacity, + activeBorderOpacity: defaults.activeBorderOpacity, + inactiveBorderOpacity: defaults.inactiveBorderOpacity, startLabel: 'id', - startLabelPadding: AreaBumpDefaultProps.startLabelPadding, - startLabelTextColor: AreaBumpDefaultProps.startLabelTextColor, + startLabelPadding: defaults.startLabelPadding, + startLabelTextColor: defaults.startLabelTextColor, endLabel: 'id', - endLabelPadding: AreaBumpDefaultProps.endLabelPadding, - endLabelTextColor: AreaBumpDefaultProps.endLabelTextColor, + endLabelPadding: defaults.endLabelPadding, + endLabelTextColor: defaults.endLabelTextColor, - enableGridX: AreaBumpDefaultProps.enableGridX, + enableGridX: defaults.enableGridX, axisTop: { enable: true, tickSize: 5, @@ -97,8 +97,8 @@ const initialProperties = { isInteractive: true, - animate: AreaBumpDefaultProps.animate, - motionConfig: AreaBumpDefaultProps.motionConfig, + animate: defaults.animate, + motionConfig: defaults.motionConfig, } const AreaBump = () => { @@ -124,20 +124,15 @@ const AreaBump = () => { flavors={meta.flavors} currentFlavor="svg" properties={groups} - defaultProperties={AreaBumpDefaultProps} + defaultProperties={defaults} initialProperties={initialProperties} propertiesMapper={mapper} generateData={generateData} image={image} > {(properties, data, theme, logAction) => { - console.log({ - properties, - data, - }) - return ( - data={data} {...properties} theme={theme} diff --git a/website/src/pages/bump/index.js b/website/src/pages/bump/index.tsx similarity index 79% rename from website/src/pages/bump/index.js rename to website/src/pages/bump/index.tsx index 98e661065a..c53ddedbbd 100644 --- a/website/src/pages/bump/index.js +++ b/website/src/pages/bump/index.tsx @@ -1,18 +1,24 @@ import React from 'react' import range from 'lodash/range' import shuffle from 'lodash/shuffle' -import { ResponsiveBump, BumpDefaultProps } from '@nivo/bump' +import { graphql, useStaticQuery } from 'gatsby' +import { ResponsiveBump, bumpSvgDefaultProps as defaults } from '@nivo/bump' import { ComponentTemplate } from '../../components/components/ComponentTemplate' import meta from '../../data/components/bump/meta.yml' import { groups } from '../../data/components/bump/props' import mapper from '../../data/components/bump/mapper' -import { graphql, useStaticQuery } from 'gatsby' const generateData = () => { const years = range(2000, 2005) const ranks = range(1, 13) - const series = ranks.map(rank => { + const series: { + id: string + data: { + x: number + y: number + }[] + }[] = ranks.map(rank => { return { id: `Serie ${rank}`, data: [], @@ -39,25 +45,25 @@ const initialProperties = { left: 60, }, - interpolation: BumpDefaultProps.interpolation, - xPadding: BumpDefaultProps.xPadding, - xOuterPadding: BumpDefaultProps.xOuterPadding, - yOuterPadding: BumpDefaultProps.yOuterPadding, + interpolation: defaults.interpolation, + xPadding: defaults.xPadding, + xOuterPadding: defaults.xOuterPadding, + yOuterPadding: defaults.yOuterPadding, colors: { scheme: 'spectral' }, lineWidth: 3, activeLineWidth: 6, inactiveLineWidth: 3, - opacity: BumpDefaultProps.opacity, - activeOpacity: BumpDefaultProps.activeOpacity, + opacity: defaults.opacity, + activeOpacity: defaults.activeOpacity, inactiveOpacity: 0.15, startLabel: false, - startLabelPadding: BumpDefaultProps.startLabelPadding, - startLabelTextColor: BumpDefaultProps.startLabelTextColor, + startLabelPadding: defaults.startLabelPadding, + startLabelTextColor: defaults.startLabelTextColor, endLabel: 'id', - endLabelPadding: BumpDefaultProps.endLabelPadding, - endLabelTextColor: BumpDefaultProps.endLabelTextColor, + endLabelPadding: defaults.endLabelPadding, + endLabelTextColor: defaults.endLabelTextColor, pointSize: 10, activePointSize: 16, @@ -84,7 +90,7 @@ const initialProperties = { tickSize: 5, tickPadding: 5, tickRotation: 0, - format: v => `#${v}`, + format: (value: number) => `#${value}`, legend: 'ranking', legendPosition: 'middle', legendOffset: 40, @@ -103,7 +109,7 @@ const initialProperties = { tickSize: 5, tickPadding: 5, tickRotation: 0, - format: v => `#${v}`, + format: (value: number) => `#${value}`, legend: 'ranking', legendPosition: 'middle', legendOffset: -40, @@ -111,8 +117,8 @@ const initialProperties = { isInteractive: true, - animate: BumpDefaultProps.animate, - motionConfig: BumpDefaultProps.motionConfig, + animate: defaults.animate, + motionConfig: defaults.motionConfig, } const Bump = () => { @@ -138,7 +144,7 @@ const Bump = () => { flavors={meta.flavors} currentFlavor="svg" properties={groups} - defaultProperties={BumpDefaultProps} + defaultProperties={defaults} initialProperties={initialProperties} propertiesMapper={mapper} generateData={generateData} @@ -146,7 +152,7 @@ const Bump = () => { > {(properties, data, theme, logAction) => { return ( - data={data} {...properties} theme={theme}