diff --git a/packages/line/index.d.ts b/packages/line/__old_index.d.ts similarity index 96% rename from packages/line/index.d.ts rename to packages/line/__old_index.d.ts index 694775dd70..9b0c465890 100644 --- a/packages/line/index.d.ts +++ b/packages/line/__old_index.d.ts @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import * as React from 'react' import { Dimensions, diff --git a/packages/line/package.json b/packages/line/package.json index e184091fa2..816942bc4d 100644 --- a/packages/line/package.json +++ b/packages/line/package.json @@ -21,11 +21,12 @@ ], "main": "./dist/nivo-line.cjs.js", "module": "./dist/nivo-line.es.js", + "typings": "./dist/types/index.d.ts", "files": [ "README.md", "LICENSE.md", - "index.d.ts", - "dist/" + "dist/", + "!dist/tsconfig.tsbuildinfo" ], "dependencies": { "@nivo/annotations": "0.79.1", diff --git a/packages/line/src/Areas.js b/packages/line/src/Areas.js deleted file mode 100644 index c459aa4b14..0000000000 --- a/packages/line/src/Areas.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { useSpring, animated } from '@react-spring/web' -import { useAnimatedPath, useMotionConfig, blendModePropType } from '@nivo/core' - -const AreaPath = ({ areaBlendMode, areaOpacity, color, fill, path }) => { - const { animate, config: springConfig } = useMotionConfig() - - const animatedPath = useAnimatedPath(path) - const animatedProps = useSpring({ - color, - config: springConfig, - immediate: !animate, - }) - - return ( - - ) -} - -AreaPath.propTypes = { - areaBlendMode: blendModePropType.isRequired, - areaOpacity: PropTypes.number.isRequired, - color: PropTypes.string, - fill: PropTypes.string, - path: PropTypes.string.isRequired, -} - -const Areas = ({ areaGenerator, areaOpacity, areaBlendMode, lines }) => { - const computedLines = lines.slice(0).reverse() - - return ( - - {computedLines.map(line => ( - d.position))} - {...{ areaOpacity, areaBlendMode, ...line }} - /> - ))} - - ) -} - -Areas.propTypes = { - areaGenerator: PropTypes.func.isRequired, - areaOpacity: PropTypes.number.isRequired, - areaBlendMode: blendModePropType.isRequired, - lines: PropTypes.arrayOf(PropTypes.object).isRequired, -} - -export default memo(Areas) diff --git a/packages/line/src/Areas.tsx b/packages/line/src/Areas.tsx new file mode 100644 index 0000000000..40ffab76e9 --- /dev/null +++ b/packages/line/src/Areas.tsx @@ -0,0 +1,69 @@ +import { memo } from 'react' +import { useSpring, animated } from '@react-spring/web' +import { useAnimatedPath, useMotionConfig, CssMixBlendMode } from '@nivo/core' +import { AreaGenerator, LineDatum } from './types' + +const AreaPath = ({ + blendMode, + opacity, + color, + fill, + path, +}: { + blendMode: CssMixBlendMode + opacity: number + color: string + fill?: string + path: string +}) => { + const { animate, config: springConfig } = useMotionConfig() + + const animatedPath = useAnimatedPath(path) + const animatedProps = useSpring({ + color, + config: springConfig, + immediate: !animate, + }) + + return ( + + ) +} + +const NonMemoizedAreas = ({ + areaGenerator, + areaOpacity, + areaBlendMode, + lines, +}: { + areaGenerator: AreaGenerator + areaOpacity: number + areaBlendMode: CssMixBlendMode + lines: any[] +}) => { + const computedLines = lines.slice(0).reverse() + + return ( + + {computedLines.map(line => ( + d.position))} + opacity={areaOpacity} + blendMode={areaBlendMode} + {...line} + /> + ))} + + ) +} + +export const Areas = memo(NonMemoizedAreas) as typeof NonMemoizedAreas diff --git a/packages/line/src/Line.js b/packages/line/src/Line.tsx similarity index 52% rename from packages/line/src/Line.js rename to packages/line/src/Line.tsx index 7956479100..4299aeef26 100644 --- a/packages/line/src/Line.js +++ b/packages/line/src/Line.tsx @@ -1,17 +1,10 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { Fragment, useState } from 'react' +import { Fragment, ReactNode, useState } from 'react' import { - bindDefs, - withContainer, + Container, useDimensions, useTheme, + // @ts-ignore + bindDefs, SvgWrapper, CartesianMarkers, } from '@nivo/core' @@ -19,82 +12,88 @@ import { useInheritedColor } from '@nivo/colors' import { Axes, Grid } from '@nivo/axes' import { BoxLegendSvg } from '@nivo/legends' import { Crosshair } from '@nivo/tooltip' +import { DefaultLineDatum, LineDatum, LineLayerId, LineSvgProps } from './types' +import { svgDefaultProps } from './defaults' import { useLine } from './hooks' -import { LinePropTypes, LineDefaultProps } from './props' -import Areas from './Areas' -import Lines from './Lines' -import Slices from './Slices' -import Points from './Points' -import Mesh from './Mesh' - -const Line = props => { +import { Areas } from './Areas' +import { Lines } from './Lines' +import { Slices } from './Slices' +import { Points } from './Points' +import { Mesh } from './Mesh' + +type InnerLineProps = Omit< + LineSvgProps, + 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' +> + +const InnerLine = ( + props: InnerLineProps +) => { const { data, - xScale: xScaleSpec, + xScale: xScaleSpec = svgDefaultProps.xScale, xFormat, - yScale: yScaleSpec, + yScale: yScaleSpec = svgDefaultProps.yScale, yFormat, - layers, - curve, - areaBaselineValue, + layers = svgDefaultProps.layers, - colors, + colors = svgDefaultProps.colors, margin: partialMargin, width, height, - axisTop, - axisRight, - axisBottom, - axisLeft, - enableGridX, - enableGridY, + enableGridX = svgDefaultProps.enableGridX, gridXValues, + enableGridY = svgDefaultProps.enableGridY, gridYValues, + axisTop = svgDefaultProps.axisTop, + axisRight = svgDefaultProps.axisRight, + axisBottom = svgDefaultProps.axisBottom, + axisLeft = svgDefaultProps.axisLeft, - lineWidth, - enableArea, - areaOpacity, - areaBlendMode, + curve = svgDefaultProps.curve, + lineWidth = svgDefaultProps.lineWidth, + enableArea = svgDefaultProps.enableArea, + areaBaselineValue = svgDefaultProps.areaBaselineValue, + areaOpacity = svgDefaultProps.areaOpacity, + areaBlendMode = svgDefaultProps.areaBlendMode, - enablePoints, + enablePoints = svgDefaultProps.enablePoints, pointSymbol, - pointSize, + pointSize = svgDefaultProps.pointSize, pointColor, - pointBorderWidth, + pointBorderWidth = svgDefaultProps.pointBorderWidth, pointBorderColor, - enablePointLabel, + enablePointLabel = svgDefaultProps.enablePointLabel, pointLabel, pointLabelYOffset, - defs, - fill, - - markers, - - legends, + defs = svgDefaultProps.defs, + fill = svgDefaultProps.fill, - isInteractive, + markers = svgDefaultProps.markers, - useMesh, - debugMesh, + legends = svgDefaultProps.legends, + isInteractive = svgDefaultProps.isInteractive, + useMesh = svgDefaultProps.useMesh, + debugMesh = svgDefaultProps.debugMesh, + enableSlices = svgDefaultProps.enableSlices, + debugSlices = svgDefaultProps.debugSlices, + sliceTooltip = svgDefaultProps.sliceTooltip, + enableCrosshair = svgDefaultProps.enableCrosshair, + crosshairType = svgDefaultProps.crosshairType, onMouseEnter, onMouseMove, onMouseLeave, onClick, - - tooltip, - - enableSlices, - debugSlices, - sliceTooltip, - - enableCrosshair, - crosshairType, + tooltip = svgDefaultProps.tooltip, role, + ariaLabel, + ariaLabelledBy, + ariaDescribedBy, } = props const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( @@ -112,8 +111,12 @@ const Line = props => { xScale, yScale, slices, + currentSlice, + setCurrentSlice, points, - } = useLine({ + currentPoint, + setCurrentPoint, + } = useLine({ data, xScale: xScaleSpec, xFormat, @@ -129,15 +132,31 @@ const Line = props => { enableSlices, }) + series.forEach(serie => { + console.log(serie) + serie.data.forEach(datum => { + console.log(datum) + }) + }) + const theme = useTheme() - const getPointColor = useInheritedColor(pointColor, theme) const getPointBorderColor = useInheritedColor(pointBorderColor, theme) - const [currentPoint, setCurrentPoint] = useState(null) - const [currentSlice, setCurrentSlice] = useState(null) + const layerById: Record = { + grid: null, + markers: null, + axes: null, + areas: null, + crosshair: null, + lines: null, + slices: null, + points: null, + mesh: null, + legends: null, + } - const layerById = { - grid: ( + if (layers.includes('grid')) { + layerById.grid = ( { xValues={gridXValues} yValues={gridYValues} /> - ), - markers: ( + ) + } + + if (markers.length > 0 && layers.includes('markers')) { + layerById.markers = ( { yScale={yScale} theme={theme} /> - ), - axes: ( + ) + } + + if (layers.includes('axes')) { + layerById.axes = ( { bottom={axisBottom} left={axisLeft} /> - ), - areas: null, - lines: ( - - ), - slices: null, - points: null, - crosshair: null, - mesh: null, - legends: legends.map((legend, i) => ( - - )), + ) } - const boundDefs = bindDefs(defs, series, fill) - - if (enableArea) { + if (layers.includes('areas') && enableArea) { layerById.areas = ( - key="areas" areaGenerator={areaGenerator} areaOpacity={areaOpacity} @@ -209,14 +213,24 @@ const Line = props => { ) } - if (isInteractive && enableSlices !== false) { + if (layers.includes('lines')) { + layerById.lines = ( + + key="lines" + lines={series} + lineGenerator={lineGenerator} + lineWidth={lineWidth} + /> + ) + } + + if (isInteractive && enableSlices && layers.includes('slices')) { layerById.slices = ( { ) } - if (enablePoints) { + if (enablePoints && layers.includes('points')) { layerById.points = ( - key="points" points={points} symbol={pointSymbol} size={pointSize} - color={getPointColor} borderWidth={pointBorderWidth} borderColor={getPointBorderColor} enableLabel={enablePointLabel} @@ -241,9 +254,50 @@ const Line = props => { ) } + if (isInteractive && useMesh && !enableSlices && layers.includes('mesh')) { + layerById.mesh = ( + + key="mesh" + points={points} + width={innerWidth} + height={innerHeight} + margin={margin} + setCurrent={setCurrentPoint} + onMouseEnter={onMouseEnter} + onMouseMove={onMouseMove} + onMouseLeave={onMouseLeave} + onClick={onClick} + tooltip={tooltip} + debug={debugMesh} + /> + ) + } + + if (layers.includes('legends') && legends.length > 0) { + layerById.legends = ( + + {legends.map((legend, legendIndex) => ( + + ))} + + ) + } + + const ___layerById = {} + + const boundDefs = bindDefs(defs, series, fill) + if (isInteractive && enableCrosshair) { if (currentPoint !== null) { - layerById.crosshair = ( + ___layerById.crosshair = ( { ) } if (currentSlice !== null) { - layerById.crosshair = ( + ___layerById.crosshair = ( { } } - if (isInteractive && useMesh && enableSlices === false) { - layerById.mesh = ( - - ) - } - return ( { height={outerHeight} margin={margin} role={role} + ariaLabel={ariaLabel} + ariaLabelledBy={ariaLabelledBy} + ariaDescribedBy={ariaDescribedBy} > - {layers.map((layer, i) => { + {layers.map((layer, layerIndex) => { if (typeof layer === 'function') { return ( - + {layer({ ...props, innerWidth, @@ -320,13 +357,32 @@ const Line = props => { ) } - return layerById[layer] + return layerById?.[layer] ?? null })} ) } -Line.propTypes = LinePropTypes -Line.defaultProps = LineDefaultProps - -export default withContainer(Line) +export const Line = < + Datum extends LineDatum = DefaultLineDatum, + ExtraProps extends object = Record +>({ + isInteractive = svgDefaultProps.isInteractive, + animate = svgDefaultProps.animate, + motionConfig = svgDefaultProps.motionConfig, + theme, + renderWrapper, + ...otherProps +}: LineSvgProps) => ( + + isInteractive={isInteractive} {...otherProps} /> + +) diff --git a/packages/line/src/LineCanvas.js b/packages/line/src/LineCanvas.tsx similarity index 97% rename from packages/line/src/LineCanvas.js rename to packages/line/src/LineCanvas.tsx index d47660d152..60db3aed38 100644 --- a/packages/line/src/LineCanvas.js +++ b/packages/line/src/LineCanvas.tsx @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { createElement, useRef, useEffect, useState, useCallback, forwardRef } from 'react' import { withContainer, diff --git a/packages/line/src/Lines.js b/packages/line/src/Lines.js deleted file mode 100644 index 8e38885c0f..0000000000 --- a/packages/line/src/Lines.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { memo } from 'react' -import PropTypes from 'prop-types' -import LinesItem from './LinesItem' - -const Lines = ({ lines, lineGenerator, lineWidth }) => { - return lines - .slice(0) - .reverse() - .map(({ id, data, color }) => ( - d.position)} - lineGenerator={lineGenerator} - color={color} - thickness={lineWidth} - /> - )) -} - -Lines.propTypes = { - lines: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - color: PropTypes.string.isRequired, - data: PropTypes.arrayOf( - PropTypes.shape({ - data: PropTypes.shape({ - x: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Date), - ]), - y: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Date), - ]), - }).isRequired, - position: PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - }).isRequired, - }) - ).isRequired, - }) - ).isRequired, - lineWidth: PropTypes.number.isRequired, - lineGenerator: PropTypes.func.isRequired, -} - -export default memo(Lines) diff --git a/packages/line/src/Lines.tsx b/packages/line/src/Lines.tsx new file mode 100644 index 0000000000..2c91d3ad22 --- /dev/null +++ b/packages/line/src/Lines.tsx @@ -0,0 +1,30 @@ +import { memo } from 'react' +import { LineDatum, LineGenerator } from './types' +import { LinesItem } from './LinesItem' + +const NonMemoizedLines = ({ + lines, + lineGenerator, + lineWidth, +}: { + lines: any[] + lineGenerator: LineGenerator + lineWidth: number +}) => ( + <> + {lines + .slice(0) + .reverse() + .map(({ id, data, color }) => ( + + key={id} + points={data.map(d => d.position)} + lineGenerator={lineGenerator} + color={color} + thickness={lineWidth} + /> + ))} + +) + +export const Lines = memo(NonMemoizedLines) as typeof NonMemoizedLines diff --git a/packages/line/src/LinesItem.js b/packages/line/src/LinesItem.js deleted file mode 100644 index a85c6b6989..0000000000 --- a/packages/line/src/LinesItem.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { memo, useMemo } from 'react' -import PropTypes from 'prop-types' -import { animated } from '@react-spring/web' -import { useAnimatedPath } from '@nivo/core' - -const LinesItem = ({ lineGenerator, points, color, thickness }) => { - const path = useMemo(() => lineGenerator(points), [lineGenerator, points]) - const animatedPath = useAnimatedPath(path) - - return -} - -LinesItem.propTypes = { - points: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - }) - ), - lineGenerator: PropTypes.func.isRequired, - color: PropTypes.string.isRequired, - thickness: PropTypes.number.isRequired, -} - -export default memo(LinesItem) diff --git a/packages/line/src/LinesItem.tsx b/packages/line/src/LinesItem.tsx new file mode 100644 index 0000000000..bedc3468d1 --- /dev/null +++ b/packages/line/src/LinesItem.tsx @@ -0,0 +1,23 @@ +import { memo, useMemo } from 'react' +import { animated } from '@react-spring/web' +import { useAnimatedPath } from '@nivo/core' +import { LineDatum, LineGenerator, LinePointDatum } from './types' + +const NonMemoizedLinesItem = ({ + lineGenerator, + points, + color, + thickness, +}: { + lineGenerator: LineGenerator + points: LinePointDatum[] + color: string + thickness: number +}) => { + const path = useMemo(() => lineGenerator(points), [lineGenerator, points]) + const animatedPath = useAnimatedPath(path!) + + return +} + +export const LinesItem = memo(NonMemoizedLinesItem) as typeof NonMemoizedLinesItem diff --git a/packages/line/src/Mesh.js b/packages/line/src/Mesh.tsx similarity index 70% rename from packages/line/src/Mesh.js rename to packages/line/src/Mesh.tsx index b04b5e38a1..3139c09cb2 100644 --- a/packages/line/src/Mesh.js +++ b/packages/line/src/Mesh.tsx @@ -1,17 +1,10 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { createElement, memo, useCallback } from 'react' -import PropTypes from 'prop-types' +import { Margin } from '@nivo/core' import { useTooltip } from '@nivo/tooltip' import { Mesh as BaseMesh } from '@nivo/voronoi' +import { LinePointDatum, LineDatum, LineCommonProps } from './types' -const Mesh = ({ +const NonMemoizedMesh = ({ points, width, height, @@ -23,6 +16,18 @@ const Mesh = ({ onClick, tooltip, debug, +}: { + points: LinePointDatum[] + width: number + height: number + margin: Margin + setCurrent: (point: LinePointDatum | null) => void + onMouseEnter?: LineCommonProps['onMouseEnter'] + onMouseMove?: LineCommonProps['onMouseMove'] + onMouseLeave?: LineCommonProps['onMouseLeave'] + onClick?: LineCommonProps['onClick'] + tooltip: LineCommonProps['tooltip'] + debug: LineCommonProps['debugMesh'] }) => { const { showTooltipAt, hideTooltip } = useTooltip() @@ -82,18 +87,4 @@ const Mesh = ({ ) } -Mesh.propTypes = { - points: PropTypes.arrayOf(PropTypes.object).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - margin: PropTypes.object.isRequired, - setCurrent: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func, - onMouseMove: PropTypes.func, - onMouseLeave: PropTypes.func, - onClick: PropTypes.func, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - debug: PropTypes.bool.isRequired, -} - -export default memo(Mesh) +export const Mesh = memo(NonMemoizedMesh) as typeof NonMemoizedMesh diff --git a/packages/line/src/PointTooltip.js b/packages/line/src/PointTooltip.js deleted file mode 100644 index 6436ad6aa4..0000000000 --- a/packages/line/src/PointTooltip.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { memo } from 'react' -import PropTypes from 'prop-types' -import { BasicTooltip } from '@nivo/tooltip' - -const LinePointTooltip = ({ point }) => { - return ( - - x: {point.data.xFormatted}, y:{' '} - {point.data.yFormatted} - - } - enableChip={true} - color={point.serieColor} - /> - ) -} - -LinePointTooltip.propTypes = { - point: PropTypes.object.isRequired, -} - -export default memo(LinePointTooltip) diff --git a/packages/line/src/PointTooltip.tsx b/packages/line/src/PointTooltip.tsx new file mode 100644 index 0000000000..e9dd2bb0b3 --- /dev/null +++ b/packages/line/src/PointTooltip.tsx @@ -0,0 +1,24 @@ +import { memo } from 'react' +import { BasicTooltip } from '@nivo/tooltip' +import { LineDatum, LinePointDatum } from './types' + +interface LinePointTooltipProps { + point: LinePointDatum +} + +const NonMemoizedPointTooltip = ({ + point, +}: LinePointTooltipProps) => ( + + x: {point.data.xFormatted}, y:{' '} + {point.data.yFormatted} + + } + enableChip={true} + color={point.serieColor} + /> +) + +export const PointTooltip = memo(NonMemoizedPointTooltip) as typeof NonMemoizedPointTooltip diff --git a/packages/line/src/Points.js b/packages/line/src/Points.tsx similarity index 79% rename from packages/line/src/Points.js rename to packages/line/src/Points.tsx index 09fcf15b7d..3c95f5ffc3 100644 --- a/packages/line/src/Points.js +++ b/packages/line/src/Points.tsx @@ -1,16 +1,24 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { memo } from 'react' -import PropTypes from 'prop-types' import { getLabelGenerator, DotsItem, useTheme } from '@nivo/core' +import { LinePointDatum, LineDatum } from './types' -const Points = ({ points, symbol, size, borderWidth, enableLabel, label, labelYOffset }) => { +const NonMemoizedPoints = ({ + points, + symbol, + size, + borderWidth, + enableLabel, + label, + labelYOffset, +}: { + points: LinePointDatum[] + symbol: any + size: number + borderWidth: number + enableLabel: boolean + label: any + labelYOffset: number +}) => { const theme = useTheme() const getLabel = getLabelGenerator(label) @@ -54,6 +62,7 @@ const Points = ({ points, symbol, size, borderWidth, enableLabel, label, labelYO ) } +/* Points.propTypes = { points: PropTypes.arrayOf(PropTypes.object), symbol: PropTypes.func, @@ -65,5 +74,6 @@ Points.propTypes = { label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, labelYOffset: PropTypes.number, } +*/ -export default memo(Points) +export const Points = memo(NonMemoizedPoints) as typeof NonMemoizedPoints diff --git a/packages/line/src/ResponsiveLine.js b/packages/line/src/ResponsiveLine.js deleted file mode 100644 index dfa5ce089d..0000000000 --- a/packages/line/src/ResponsiveLine.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { ResponsiveWrapper } from '@nivo/core' -import Line from './Line' - -const ResponsiveLine = props => ( - - {({ width, height }) => } - -) - -export default ResponsiveLine diff --git a/packages/line/src/ResponsiveLine.tsx b/packages/line/src/ResponsiveLine.tsx new file mode 100644 index 0000000000..14bf5675dd --- /dev/null +++ b/packages/line/src/ResponsiveLine.tsx @@ -0,0 +1,16 @@ +import { ResponsiveWrapper } from '@nivo/core' +import { DefaultLineDatum, LineDatum, LineSvgProps } from './types' +import { Line } from './Line' + +export const ResponsiveLine = < + Datum extends LineDatum = DefaultLineDatum, + ExtraProps extends object = Record +>( + props: Omit, 'height' | 'width'> +) => ( + + {({ width, height }) => ( + width={width} height={height} {...props} /> + )} + +) diff --git a/packages/line/src/ResponsiveLineCanvas.js b/packages/line/src/ResponsiveLineCanvas.js deleted file mode 100644 index 05b99f18d4..0000000000 --- a/packages/line/src/ResponsiveLineCanvas.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { forwardRef } from 'react' -import { ResponsiveWrapper } from '@nivo/core' -import LineCanvas from './LineCanvas' - -const ResponsiveLineCanvas = (props, ref) => ( - - {({ width, height }) => } - -) - -export default forwardRef(ResponsiveLineCanvas) diff --git a/packages/line/src/ResponsiveLineCanvas.tsx b/packages/line/src/ResponsiveLineCanvas.tsx new file mode 100644 index 0000000000..1ef3e63f06 --- /dev/null +++ b/packages/line/src/ResponsiveLineCanvas.tsx @@ -0,0 +1,18 @@ +import { ResponsiveWrapper } from '@nivo/core' +import { DefaultLineDatum, LineDatum, LineCanvasProps } from './types' +import LineCanvas from './LineCanvas' + +export const ResponsiveLineCanvas = < + Datum extends LineDatum = DefaultLineDatum, + ExtraProps extends object = Record +>( + props: Omit, 'height' | 'width'> +) => ( + + {({ width, height }) => ( + width={width} height={height} {...props} /> + )} + +) + +// export default forwardRef(ResponsiveLineCanvas) diff --git a/packages/line/src/SliceTooltip.js b/packages/line/src/SliceTooltip.tsx similarity index 56% rename from packages/line/src/SliceTooltip.js rename to packages/line/src/SliceTooltip.tsx index c570f22745..0fdff0a7e6 100644 --- a/packages/line/src/SliceTooltip.js +++ b/packages/line/src/SliceTooltip.tsx @@ -1,17 +1,12 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { memo } from 'react' -import PropTypes from 'prop-types' import { useTheme } from '@nivo/core' import { Chip, TableTooltip } from '@nivo/tooltip' +import { LineDatum, SliceTooltipProps } from './types' -const SliceTooltip = ({ slice, axis }) => { +const NonMemoizedSliceTooltip = ({ + slice, + axis, +}: SliceTooltipProps) => { const theme = useTheme() const otherAxis = axis === 'x' ? 'y' : 'x' @@ -28,9 +23,4 @@ const SliceTooltip = ({ slice, axis }) => { ) } -SliceTooltip.propTypes = { - slice: PropTypes.object.isRequired, - axis: PropTypes.oneOf(['x', 'y']).isRequired, -} - -export default memo(SliceTooltip) +export const SliceTooltip = memo(NonMemoizedSliceTooltip) as typeof NonMemoizedSliceTooltip diff --git a/packages/line/src/Slices.js b/packages/line/src/Slices.js deleted file mode 100644 index 952688cfee..0000000000 --- a/packages/line/src/Slices.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { memo } from 'react' -import PropTypes from 'prop-types' -import SlicesItem from './SlicesItem' - -const Slices = ({ slices, axis, debug, height, tooltip, current, setCurrent }) => { - return slices.map(slice => ( - - )) -} - -Slices.propTypes = { - slices: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.instanceOf(Date), - ]).isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - points: PropTypes.arrayOf(PropTypes.object).isRequired, - }) - ).isRequired, - axis: PropTypes.oneOf(['x', 'y']).isRequired, - debug: PropTypes.bool.isRequired, - height: PropTypes.number.isRequired, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - current: PropTypes.object, - setCurrent: PropTypes.func.isRequired, -} - -export default memo(Slices) diff --git a/packages/line/src/Slices.tsx b/packages/line/src/Slices.tsx new file mode 100644 index 0000000000..4e0e60cf52 --- /dev/null +++ b/packages/line/src/Slices.tsx @@ -0,0 +1,35 @@ +import { memo } from 'react' +import { LineCommonProps, LineDatum, SliceDatum } from './types' +import { SlicesItem } from './SlicesItem' + +const NonMemoizedSlices = ({ + slices, + axis, + debug, + tooltip, + current, + setCurrent, +}: { + slices: SliceDatum[] + axis: Exclude['enableSlices'], false> + debug: boolean + tooltip: LineCommonProps['sliceTooltip'] + current: SliceDatum | null + setCurrent: (slice: SliceDatum | null) => void +}) => ( + <> + {slices.map(slice => ( + + key={slice.id} + slice={slice} + axis={axis} + debug={debug} + tooltip={tooltip} + setCurrent={setCurrent} + isCurrent={current !== null && current.id === slice.id} + /> + ))} + +) + +export const Slices = memo(NonMemoizedSlices) as typeof NonMemoizedSlices diff --git a/packages/line/src/SlicesItem.js b/packages/line/src/SlicesItem.tsx similarity index 62% rename from packages/line/src/SlicesItem.js rename to packages/line/src/SlicesItem.tsx index fb01ca26c3..4c5185e6bc 100644 --- a/packages/line/src/SlicesItem.js +++ b/packages/line/src/SlicesItem.tsx @@ -1,16 +1,22 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { createElement, memo, useCallback } from 'react' -import PropTypes from 'prop-types' import { useTooltip } from '@nivo/tooltip' +import { LineCommonProps, LineDatum, SliceAxis, SliceDatum } from './types' -const SlicesItem = ({ slice, axis, debug, tooltip, isCurrent, setCurrent }) => { +const NonMemoizedSlicesItem = ({ + slice, + axis, + debug, + tooltip, + isCurrent, + setCurrent, +}: { + slice: SliceDatum + axis: SliceAxis + debug: boolean + tooltip: LineCommonProps['sliceTooltip'] + isCurrent: boolean + setCurrent: (slice: SliceDatum | null) => void +}) => { const { showTooltipFromEvent, hideTooltip } = useTooltip() const handleMouseEnter = useCallback( @@ -51,14 +57,4 @@ const SlicesItem = ({ slice, axis, debug, tooltip, isCurrent, setCurrent }) => { ) } -SlicesItem.propTypes = { - slice: PropTypes.object.isRequired, - axis: PropTypes.oneOf(['x', 'y']).isRequired, - debug: PropTypes.bool.isRequired, - height: PropTypes.number.isRequired, - tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - isCurrent: PropTypes.bool.isRequired, - setCurrent: PropTypes.func.isRequired, -} - -export default memo(SlicesItem) +export const SlicesItem = memo(NonMemoizedSlicesItem) as typeof NonMemoizedSlicesItem diff --git a/packages/line/src/defaults.ts b/packages/line/src/defaults.ts new file mode 100644 index 0000000000..7a16820ebc --- /dev/null +++ b/packages/line/src/defaults.ts @@ -0,0 +1,108 @@ +import { DefaultLineDatum, LineCommonProps, LineLayerId } from './types' +import { SliceTooltip } from './SliceTooltip' +import { PointTooltip } from './PointTooltip' + +export const commonDefaultProps: Omit< + LineCommonProps, + | 'margin' + | 'theme' + | 'valueFormat' + | 'onClick' + | 'renderWrapper' + | 'role' + | 'ariaLabel' + | 'ariaLabelledBy' + | 'ariaDescribedBy' +> & { + layers: LineLayerId[] +} = { + layers: [ + 'grid', + 'markers', + 'axes', + 'areas', + 'crosshair', + 'lines', + 'points', + 'slices', + 'mesh', + 'legends', + ] as LineLayerId[], + + xScale: { + type: 'point', + }, + yScale: { + type: 'linear', + min: 0, + max: 'auto', + }, + + enableGridX: false, + enableGridY: false, + + curve: 'linear' as const, + lineWidth: 2, + enableArea: false, + areaBaselineValue: 0, + areaOpacity: 0.2, + areaBlendMode: 'normal' as const, + + enablePoints: true, + pointSize: 6, + pointColor: { from: 'color' }, + pointBorderWidth: 0, + pointBorderColor: { theme: 'background' }, + enablePointLabel: false, + pointLabel: 'yFormatted', + + colors: { scheme: 'nivo' }, + + opacity: 1, + activeOpacity: 1, + inactiveOpacity: 0.15, + borderWidth: 0, + borderColor: { from: 'color', modifiers: [['darker', 0.8]] }, + + enableLabels: true, + label: 'formattedValue', + + legends: [], + markers: [], + annotations: [], + + isInteractive: true, + debugMesh: false, + enableSlices: false, + debugSlices: false, + sliceTooltip: SliceTooltip, + enableCrosshair: true, + crosshairType: 'bottom-left' as const, + tooltip: PointTooltip, + + animate: true, + motionConfig: 'gentle' as const, +} + +export const svgDefaultProps = { + ...commonDefaultProps, + axisTop: {}, + axisRight: null, + axisBottom: null, + axisLeft: {}, + borderRadius: 0, + cellComponent: 'rect' as const, + defs: [], + fill: [], + useMesh: false, +} + +export const canvasDefaultProps = { + ...commonDefaultProps, + axisTop: {}, + axisRight: null, + axisBottom: null, + axisLeft: {}, + renderCell: 'rect' as const, + pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, +} diff --git a/packages/line/src/hooks.js b/packages/line/src/hooks.ts similarity index 54% rename from packages/line/src/hooks.js rename to packages/line/src/hooks.ts index 93e6d0fa3e..eb685a17fc 100644 --- a/packages/line/src/hooks.js +++ b/packages/line/src/hooks.ts @@ -1,74 +1,110 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { useCallback, useMemo, useState } from 'react' import { area, line } from 'd3-shape' import { curveFromProp, useTheme, useValueFormatter } from '@nivo/core' import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors' import { computeXYScalesForSeries } from '@nivo/scales' import { LineDefaultProps } from './props' +import { commonDefaultProps } from './defaults' +import { + CurveInterpolation, + LineCommonProps, + LineDataProps, + LineDatum, + LinePointDatum, + SliceDatum, +} from './types' -export const useLineGenerator = ({ curve }) => { - return useMemo( +export const useLineGenerator = ({ + curve, +}: { + curve: CurveInterpolation +}) => + useMemo( () => - line() + line['position']>() .defined(d => d.x !== null && d.y !== null) .x(d => d.x) .y(d => d.y) .curve(curveFromProp(curve)), [curve] ) -} -export const useAreaGenerator = ({ curve, yScale, areaBaselineValue }) => { - return useMemo(() => { - return area() - .defined(d => d.x !== null && d.y !== null) - .x(d => d.x) - .y1(d => d.y) - .curve(curveFromProp(curve)) - .y0(yScale(areaBaselineValue)) - }, [curve, yScale, areaBaselineValue]) -} +export const useAreaGenerator = ({ + curve, + yScale, + areaBaselineValue, +}: { + curve: CurveInterpolation + yScale: any + areaBaselineValue: Exclude +}) => + useMemo( + () => + area['position']>() + .defined(d => d.x !== null && d.y !== null) + .x(d => d.x) + .y1(d => d.y) + .curve(curveFromProp(curve)) + .y0(yScale(areaBaselineValue)), + [curve, yScale, areaBaselineValue] + ) -const usePoints = ({ series, getPointColor, getPointBorderColor, formatX, formatY }) => { - return useMemo(() => { - return series.reduce((acc, serie) => { - return [ - ...acc, - ...serie.data - .filter(datum => datum.position.x !== null && datum.position.y !== null) - .map((datum, i) => { - const point = { - id: `${serie.id}.${i}`, - index: acc.length + i, - serieId: serie.id, - serieColor: serie.color, - x: datum.position.x, - y: datum.position.y, - } - point.color = getPointColor(serie) - point.borderColor = getPointBorderColor(point) - point.data = { - ...datum.data, - xFormatted: formatX(datum.data.x), - yFormatted: formatY(datum.data.y), - } - - return point - }), - ] - }, []) +const usePoints = ({ + series, + getPointColor, + getPointBorderColor, + formatX, + formatY, +}: { + series: any[] + getPointColor: any + getPointBorderColor: any + formatX: any + formatY: any +}): LinePointDatum[] => + useMemo(() => { + const points: LinePointDatum[] = [] + + series.forEach(serie => { + serie.data + // exclude undefined values + .filter(datum => datum.position.x !== null && datum.position.y !== null) + .forEach((datum, datumIndex) => { + const point = { + id: `${serie.id}.${datumIndex}`, + index: points.length, + serieId: serie.id, + serieColor: serie.color, + x: datum.position.x, + y: datum.position.y, + } + point.color = getPointColor(serie) + point.borderColor = getPointBorderColor(point) + point.data = { + ...datum.data, + xFormatted: formatX(datum.data.x), + yFormatted: formatY(datum.data.y), + } + + points.push(point) + }) + }) + + return points }, [series, getPointColor, getPointBorderColor, formatX, formatY]) -} -export const useSlices = ({ enableSlices, points, width, height }) => { - return useMemo(() => { +export const useSlices = ({ + enableSlices, + points, + width, + height, +}: { + enableSlices: LineCommonProps['enableSlices'] + points: LinePointDatum[] + width: number + height: number +}) => + useMemo(() => { if (enableSlices === false) return [] if (enableSlices === 'x') { @@ -137,30 +173,53 @@ export const useSlices = ({ enableSlices, points, width, height }) => { }) } }, [enableSlices, points]) -} -export const useLine = ({ +export const useLine = ({ data, - xScale: xScaleSpec = LineDefaultProps.xScale, + xScale: xScaleSpec = commonDefaultProps.xScale, xFormat, - yScale: yScaleSpec = LineDefaultProps.yScale, + yScale: yScaleSpec = commonDefaultProps.yScale, yFormat, width, height, colors = LineDefaultProps.colors, - curve = LineDefaultProps.curve, - areaBaselineValue = LineDefaultProps.areaBaselineValue, + curve = commonDefaultProps.curve, + areaBaselineValue = commonDefaultProps.areaBaselineValue, pointColor = LineDefaultProps.pointColor, pointBorderColor = LineDefaultProps.pointBorderColor, - enableSlices = LineDefaultProps.enableSlicesTooltip, + enableSlices = LineDefaultProps.enableSlices, +}: { + data: LineDataProps['data'] + xScale?: LineCommonProps['xScale'] + xFormat?: LineCommonProps['xFormat'] + yScale?: LineCommonProps['yScale'] + yFormat?: LineCommonProps['yFormat'] + width: number + height: number + colors: any + curve?: CurveInterpolation + areaBaselineValue?: Exclude + pointColor: any + pointBorderColor: any + enableSlices?: LineCommonProps['enableSlices'] }) => { const formatX = useValueFormatter(xFormat) const formatY = useValueFormatter(yFormat) + const getColor = useOrdinalColorScale(colors, 'id') const theme = useTheme() const getPointColor = useInheritedColor(pointColor, theme) const getPointBorderColor = useInheritedColor(pointBorderColor, theme) - const [hiddenIds, setHiddenIds] = useState([]) + + const [hiddenIds, setHiddenIds] = useState([]) + const toggleSerie = useCallback((id: string) => { + setHiddenIds(state => + state.includes(id) ? state.filter(item => item !== id) : [...state, id] + ) + }, []) + + const [currentPoint, setCurrentPoint] = useState | null>(null) + const [currentSlice, setCurrentSlice] = useState | null>(null) const { xScale, @@ -169,7 +228,7 @@ export const useLine = ({ } = useMemo( () => computeXYScalesForSeries( - data.filter(item => hiddenIds.indexOf(item.id) === -1), + data.filter(item => !hiddenIds.includes(item.id)), xScaleSpec, yScaleSpec, width, @@ -184,26 +243,25 @@ export const useLine = ({ label: line.id, color: getColor(line), })) + const series = dataWithColor .map(datum => ({ ...rawSeries.find(serie => serie.id === datum.id), color: datum.color, })) .filter(item => Boolean(item.id)) - const legendData = dataWithColor + + const _legendData = dataWithColor .map(item => ({ ...item, hidden: !series.find(serie => serie.id === item.id) })) .reverse() - return { legendData, series } + return { + series, + legendData: _legendData, + } }, [data, rawSeries, getColor]) - const toggleSerie = useCallback(id => { - setHiddenIds(state => - state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id] - ) - }, []) - - const points = usePoints({ + const points = usePoints({ series, getPointColor, getPointBorderColor, @@ -218,8 +276,11 @@ export const useLine = ({ height, }) - const lineGenerator = useLineGenerator({ curve }) - const areaGenerator = useAreaGenerator({ + console.log('points', points) + console.log('slices', slices) + + const lineGenerator = useLineGenerator({ curve }) + const areaGenerator = useAreaGenerator({ curve, yScale, areaBaselineValue, @@ -235,6 +296,10 @@ export const useLine = ({ xScale, yScale, slices, + currentSlice, + setCurrentSlice, points, + currentPoint, + setCurrentPoint, } } diff --git a/packages/line/src/index.js b/packages/line/src/index.js deleted file mode 100644 index bae4590d2b..0000000000 --- a/packages/line/src/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -export { default as Line } from './Line' -export { default as ResponsiveLine } from './ResponsiveLine' -export { default as LineCanvas } from './LineCanvas' -export { default as ResponsiveLineCanvas } from './ResponsiveLineCanvas' -export * from './props' -export * from './hooks' diff --git a/packages/line/src/index.ts b/packages/line/src/index.ts new file mode 100644 index 0000000000..a08ced2a95 --- /dev/null +++ b/packages/line/src/index.ts @@ -0,0 +1,7 @@ +export * from './Line' +export * from './ResponsiveLine' +export { default as LineCanvas } from './LineCanvas' +export * from './ResponsiveLineCanvas' +export * from './props' +export * from './hooks' +export * from './types' diff --git a/packages/line/src/props.js b/packages/line/src/props.tsx similarity index 94% rename from packages/line/src/props.js rename to packages/line/src/props.tsx index b2eeaa9d75..5a9304095c 100644 --- a/packages/line/src/props.js +++ b/packages/line/src/props.tsx @@ -1,18 +1,10 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import PropTypes from 'prop-types' import { lineCurvePropType, blendModePropType, motionPropTypes, defsPropTypes } from '@nivo/core' import { ordinalColorsPropType } from '@nivo/colors' import { axisPropType } from '@nivo/axes' import { LegendPropShape } from '@nivo/legends' -import PointTooltip from './PointTooltip' -import SliceTooltip from './SliceTooltip' +import { PointTooltip } from './PointTooltip' +import { SliceTooltip } from './SliceTooltip' const commonPropTypes = { data: PropTypes.arrayOf( @@ -190,7 +182,7 @@ const commonDefaultProps = { isInteractive: true, tooltip: PointTooltip, - enableSlices: false, + enableSlices: false as const, debugSlices: false, sliceTooltip: SliceTooltip, debugMesh: false, diff --git a/packages/line/src/types.ts b/packages/line/src/types.ts new file mode 100644 index 0000000000..bf95b19b32 --- /dev/null +++ b/packages/line/src/types.ts @@ -0,0 +1,209 @@ +import { AriaAttributes, MouseEvent, FunctionComponent } from 'react' +import { Line, Area } from 'd3-shape' +import { + Box, + Theme, + Dimensions, + ModernMotionProps, + PropertyAccessor, + SvgDefsAndFill, + CssMixBlendMode, + ValueFormat, +} from '@nivo/core' +import { AxisProps, CanvasAxisProps } from '@nivo/axes' +import { CrosshairType } from '@nivo/tooltip' +import { ScaleSpec } from '@nivo/scales' +import { LegendProps } from '@nivo/legends' + +export type LineValue = number | string | Date | null + +export interface LineDatum { + x: LineValue + y: LineValue +} + +export interface DefaultLineDatum { + x: number | null | undefined + y: number | null | undefined +} + +export type LineSeries = { + id: string + data: Datum[] +} & ExtraProps + +export type SliceAxis = 'x' | 'y' + +export interface SliceDatum { + id: 0 + x0: number + x: number + y0: number + y: number + width: number + height: number + points: any[] +} + +export interface PointTooltipProps { + point: LinePointDatum +} +export type PointTooltipComponent = FunctionComponent< + PointTooltipProps +> + +export interface SliceTooltipProps { + slice: SliceDatum + axis: SliceAxis +} +export type SliceTooltipComponent = FunctionComponent< + SliceTooltipProps +> + +export interface LineDataProps { + data: LineSeries[] +} + +export type LineLayerId = + | 'grid' + | 'markers' + | 'axes' + | 'areas' + | 'crosshair' + | 'lines' + | 'slices' + | 'points' + | 'mesh' + | 'legends' + +export type CurveInterpolation = + | 'basis' + | 'cardinal' + | 'catmullRom' + | 'linear' + | 'monotoneX' + | 'monotoneY' + | 'natural' + | 'step' + | 'stepAfter' + | 'stepBefore' + +export type LineGenerator = Line['position']> + +export type AreaGenerator = Area['position']> + +export interface LineStyle { + lineWidth: number + opacity: number +} + +export interface LineSvgStyle extends LineStyle {} + +export interface LineCanvasStyle extends LineStyle {} + +export interface LinePointDatum { + data: Datum + position: { + x: number + y: number + } +} + +export interface PointDatum { + id: string + index: number + serieId: string + serieColor: string + x: number + y: number + data: Datum & { + xFormatted: string + yFormatted: string + } +} + +export type LineCommonProps = { + xScale: ScaleSpec + xFormat: ValueFormat> + yScale: ScaleSpec + yFormat: ValueFormat> + + margin: Box + + layers: LineLayerId[] + + enableGridX: boolean + gridXValues: Exclude[] + enableGridY: boolean + gridYValues: Exclude[] + + curve: CurveInterpolation + lineWidth: number + enableArea: boolean + areaBaselineValue: number + areaOpacity: number + areaBlendMode: CssMixBlendMode + + enablePoints: boolean + pointSize: number + // pointColor: { from: 'color' }, + pointBorderWidth: number + // pointBorderColor: { theme: 'background' }, + enablePointLabel: boolean + // pointLabel: 'yFormatted', + + theme: Theme + + // annotations: AnnotationMatcher>[] + + isInteractive: boolean + debugMesh: boolean + enableSlices: SliceAxis | false + debugSlices: boolean + sliceTooltip: SliceTooltipComponent + enableCrosshair: boolean + crosshairType: CrosshairType + onMouseEnter: (point: LinePointDatum, event: MouseEvent) => void + onMouseMove: (point: LinePointDatum, event: MouseEvent) => void + onMouseLeave: (point: LinePointDatum, event: MouseEvent) => void + onClick: (point: LinePointDatum, event: MouseEvent) => void + tooltip: PointTooltipComponent + + markers: any[] + + legends: LegendProps[] + + renderWrapper: boolean + + role: string + ariaLabel: AriaAttributes['aria-label'] + ariaLabelledBy: AriaAttributes['aria-labelledby'] + ariaDescribedBy: AriaAttributes['aria-describedby'] +} & Required + +export type LineSvgProps = Partial< + LineCommonProps +> & + LineDataProps & + Dimensions & + SvgDefsAndFill & + Partial<{ + axisTop: AxisProps | null + axisRight: AxisProps | null + axisBottom: AxisProps | null + axisLeft: AxisProps | null + useMesh: boolean + }> + +export type LineCanvasProps = Partial< + LineCommonProps +> & + LineDataProps & + Dimensions & + Partial<{ + axisTop: CanvasAxisProps> | null + axisRight: CanvasAxisProps> | null + axisBottom: CanvasAxisProps> | null + axisLeft: CanvasAxisProps> | null + pixelRatio: number + }> diff --git a/packages/line/stories/line.stories.js b/packages/line/stories/Line.stories.tsx similarity index 98% rename from packages/line/stories/line.stories.js rename to packages/line/stories/Line.stories.tsx index b0104b9196..77439d8119 100644 --- a/packages/line/stories/line.stories.js +++ b/packages/line/stories/Line.stories.tsx @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { Component, useState, useEffect } from 'react' import range from 'lodash/range' import last from 'lodash/last' @@ -16,7 +8,7 @@ import { Defs, linearGradientDef } from '@nivo/core' import { area, curveMonotoneX } from 'd3-shape' import * as time from 'd3-time' import { timeFormat } from 'd3-time-format' -import { Line } from '../src' +import { Line, CurveInterpolation } from '../src' const data = generateDrinkStats(18) const commonProperties = { @@ -25,10 +17,16 @@ const commonProperties = { margin: { top: 20, right: 20, bottom: 60, left: 80 }, data, animate: true, - enableSlices: 'x', + enableSlices: 'x' as const, } -const curveOptions = ['linear', 'monotoneX', 'step', 'stepBefore', 'stepAfter'] +const curveOptions: CurveInterpolation[] = [ + 'linear', + 'monotoneX', + 'step', + 'stepBefore', + 'stepAfter', +] const CustomSymbol = ({ size, color, borderWidth, borderColor }) => ( @@ -185,7 +183,10 @@ stories.add('time scale', () => ( )) stories.add('time scale milliseconds precision', () => ( - {...commonProperties} data={[ { diff --git a/packages/line/stories/LineCanvas.stories.js b/packages/line/stories/LineCanvas.stories.js index 695c2d0340..f8187e6e2b 100644 --- a/packages/line/stories/LineCanvas.stories.js +++ b/packages/line/stories/LineCanvas.stories.js @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { useRef } from 'react' import { storiesOf } from '@storybook/react' import { withKnobs, boolean, button } from '@storybook/addon-knobs' diff --git a/packages/line/stories/ResponsiveLineCanvas.stories.js b/packages/line/stories/ResponsiveLineCanvas.stories.js index 8df4f1649f..bd03a82c9c 100644 --- a/packages/line/stories/ResponsiveLineCanvas.stories.js +++ b/packages/line/stories/ResponsiveLineCanvas.stories.js @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import { useRef } from 'react' import { storiesOf } from '@storybook/react' import { withKnobs, boolean, button } from '@storybook/addon-knobs' diff --git a/packages/line/tests/Line.test.js b/packages/line/tests/Line.test.tsx similarity index 56% rename from packages/line/tests/Line.test.js rename to packages/line/tests/Line.test.tsx index 7aa0467776..f1172339ab 100644 --- a/packages/line/tests/Line.test.js +++ b/packages/line/tests/Line.test.tsx @@ -1,9 +1,104 @@ import { mount } from 'enzyme' -import { Axis } from '@nivo/axes' -import Line from '../src/Line' -import SlicesItem from '../src/SlicesItem' -import renderer from 'react-test-renderer' +import { Line } from '../src' +import { SlicesItem } from '../src/SlicesItem' +interface TestDatum { + x: number + y: number +} + +const sampleData = [ + { + id: 'A', + data: [ + { x: 0, y: 3 }, + { x: 1, y: 7 }, + { x: 2, y: 11 }, + { x: 3, y: 9 }, + { x: 4, y: 8 }, + ], + }, + { + id: 'B', + data: [ + { x: 0, y: 1 }, + { x: 1, y: 3 }, + { x: 2, y: 5 }, + { x: 3, y: 7 }, + { x: 4, y: 11 }, + ], + }, +] + +const baseProps = { + width: 500, + height: 300, + data: sampleData, + // this is important to be able to "easily" + // extract line points' position. + curve: 'linear' as const, + animate: false, +} + +it('should render a basic line chart', () => { + const wrapper = mount( {...baseProps} />) + console.log(wrapper.debug()) +}) + +describe('slices', () => { + it('should create slice for each x value', () => { + const data = [ + { + id: 'A', + data: [ + { x: 0, y: 3 }, + { x: 1, y: 7 }, + { x: 2, y: 11 }, + { x: 3, y: 9 }, + { x: 4, y: 8 }, + ], + }, + ] + const wrapper = mount( + + width={500} + height={300} + data={data} + enableSlices="x" + animate={false} + /> + ) + + const slices = wrapper.find(SlicesItem) + expect(slices).toHaveLength(5) + expect(slices.at(0).prop('slice').id).toBe(0) + expect(slices.at(1).prop('slice').id).toBe(125) + expect(slices.at(2).prop('slice').id).toBe(250) + expect(slices.at(3).prop('slice').id).toBe(375) + expect(slices.at(4).prop('slice').id).toBe(500) + }) +}) + +describe('accessibility', () => { + it('should forward root aria properties to the SVG element', () => { + const wrapper = mount( + + {...baseProps} + ariaLabel="AriaLabel" + ariaLabelledBy="AriaLabelledBy" + ariaDescribedBy="AriaDescribedBy" + /> + ) + + const svg = wrapper.find('svg') + + expect(svg.prop('aria-label')).toBe('AriaLabel') + expect(svg.prop('aria-labelledby')).toBe('AriaLabelledBy') + expect(svg.prop('aria-describedby')).toBe('AriaDescribedBy') + }) +}) + +/* it('should render a basic line chart', () => { const data = [ { @@ -53,29 +148,7 @@ it('should support multiple lines', () => { }) it('should create slice for each x value', () => { - const data = [ - { - id: 'A', - data: [ - { x: 0, y: 3 }, - { x: 1, y: 7 }, - { x: 2, y: 11 }, - { x: 3, y: 9 }, - { x: 4, y: 8 }, - ], - }, - ] - const wrapper = mount( - - ) - - const slices = wrapper.find(SlicesItem) - expect(slices).toHaveLength(5) - expect(slices.at(0).prop('slice').id).toBe(0) - expect(slices.at(1).prop('slice').id).toBe(125) - expect(slices.at(2).prop('slice').id).toBe(250) - expect(slices.at(3).prop('slice').id).toBe(375) - expect(slices.at(4).prop('slice').id).toBe(500) + }) it('should have left and bottom axis by default', () => { @@ -148,3 +221,4 @@ describe('curve interpolation', () => { }) } }) +*/ diff --git a/packages/line/tests/__snapshots__/Line.test.js.snap b/packages/line/tests/__snapshots__/Line.test.js.snap deleted file mode 100644 index e8c22a85f3..0000000000 --- a/packages/line/tests/__snapshots__/Line.test.js.snap +++ /dev/null @@ -1,9565 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`curve interpolation should support basis curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support cardinal curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support catmullRom curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support linear curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support monotoneX curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support monotoneY curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support natural curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support step curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support stepAfter curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`curve interpolation should support stepBefore curve interpolation 1`] = ` -
- - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`should render a basic line chart 1`] = ` -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - -
-`; - -exports[`should support multiple lines 1`] = ` -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - - - - 0 - - - - - - 1 - - - - - - 2 - - - - - - 3 - - - - - - 4 - - - - - - 5 - - - - - - 6 - - - - - - 7 - - - - - - 8 - - - - - - 9 - - - - - - 10 - - - - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-`; diff --git a/packages/line/tsconfig.json b/packages/line/tsconfig.json new file mode 100644 index 0000000000..855b4b2b74 --- /dev/null +++ b/packages/line/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.types.json", + "compilerOptions": { + "outDir": "./dist/types", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/tsconfig.monorepo.json b/tsconfig.monorepo.json index 99343d876a..134504bc7d 100644 --- a/tsconfig.monorepo.json +++ b/tsconfig.monorepo.json @@ -28,6 +28,7 @@ { "path": "./packages/circle-packing" }, { "path": "./packages/funnel" }, { "path": "./packages/heatmap" }, + { "path": "./packages/line" }, { "path": "./packages/marimekko" }, { "path": "./packages/network" }, { "path": "./packages/pie" },