diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts index b777b7f6b..300190ba0 100644 --- a/packages/core/index.d.ts +++ b/packages/core/index.d.ts @@ -1,4 +1,5 @@ import * as React from 'react' +import { OpaqueInterpolation } from 'react-spring' declare module '@nivo/core' { export type DatumValue = string | number | Date @@ -152,6 +153,7 @@ declare module '@nivo/core' { export type DataFormatter = (value: DatumValue) => string | number + export function useAnimatedPath(path: string): OpaqueInterpolation export function useValueFormatter(formatter?: DataFormatter | string): DataFormatter export type LinearGradientDef = { diff --git a/packages/core/package.json b/packages/core/package.json index 3f9a6c43e..b862f8bac 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,6 +18,7 @@ "d3-color": "^1.2.3", "d3-format": "^1.4.4", "d3-hierarchy": "^1.1.8", + "d3-interpolate": "^2.0.1", "d3-scale": "^3.0.0", "d3-scale-chromatic": "^1.3.3", "d3-shape": "^1.3.5", diff --git a/packages/core/src/hooks/index.js b/packages/core/src/hooks/index.js index ad8074fcb..dea1a2c99 100644 --- a/packages/core/src/hooks/index.js +++ b/packages/core/src/hooks/index.js @@ -6,6 +6,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +export * from './useAnimatedPath' export * from './useCurveInterpolation' export * from './useDimensions' export * from './useMeasure' diff --git a/packages/core/src/hooks/useAnimatedPath.js b/packages/core/src/hooks/useAnimatedPath.js new file mode 100644 index 000000000..24bbb09f0 --- /dev/null +++ b/packages/core/src/hooks/useAnimatedPath.js @@ -0,0 +1,31 @@ +import { interpolateString } from 'd3-interpolate' +import { useEffect, useMemo, useRef } from 'react' +import { useMotionConfig } from '../motion' +import { useSpring } from 'react-spring' + +const usePrevious = value => { + const ref = useRef() + + useEffect(() => { + ref.current = value + }, [value]) + + return ref.current +} + +export const useAnimatedPath = path => { + const { animate, config: springConfig } = useMotionConfig() + + const previousPath = usePrevious(path) + const interpolator = useMemo(() => interpolateString(previousPath, path), [previousPath, path]) + + const { value } = useSpring({ + from: { value: 0 }, + to: { value: 1 }, + reset: true, + config: springConfig, + immediate: !animate, + }) + + return value.interpolate(interpolator) +} diff --git a/packages/line/src/Areas.js b/packages/line/src/Areas.js index 9787d8c43..24810330b 100644 --- a/packages/line/src/Areas.js +++ b/packages/line/src/Areas.js @@ -8,44 +8,52 @@ */ import React, { memo } from 'react' import PropTypes from 'prop-types' -import { useSprings, animated } from 'react-spring' -import { useMotionConfig, blendModePropType } from '@nivo/core' +import { useSpring, animated } from 'react-spring' +import { useAnimatedPath, useMotionConfig, blendModePropType } from '@nivo/core' -const Areas = ({ areaGenerator, areaOpacity, areaBlendMode, lines }) => { +const AreaPath = ({ areaBlendMode, areaOpacity, color, fill, path }) => { const { animate, config: springConfig } = useMotionConfig() - const computedLines = lines.slice(0).reverse() + const animatedPath = useAnimatedPath(path) + const animatedProps = useSpring({ + color, + config: springConfig, + immediate: !animate, + }) - const springs = useSprings( - computedLines.length, - computedLines.map(line => { - return { - path: areaGenerator(line.data.map(d => d.position)), - color: line.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 ( - {springs.map((animatedProps, index) => { - const line = computedLines[index] - - return ( - - ) - })} + {computedLines.map(line => ( + d.position))} + {...{ areaOpacity, areaBlendMode, ...line }} + /> + ))} ) } diff --git a/packages/line/src/LinesItem.js b/packages/line/src/LinesItem.js index 764a114f1..450e09c2c 100644 --- a/packages/line/src/LinesItem.js +++ b/packages/line/src/LinesItem.js @@ -6,23 +6,16 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { memo } from 'react' +import React, { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { useSpring, animated } from 'react-spring' -import { useMotionConfig } from '@nivo/core' +import { animated } from 'react-spring' +import { useAnimatedPath } from '@nivo/core' const LinesItem = ({ lineGenerator, points, color, thickness }) => { - const { animate, config: springConfig } = useMotionConfig() + const path = useMemo(() => lineGenerator(points), [lineGenerator, points]) + const animatedPath = useAnimatedPath(path) - const animatedProps = useSpring({ - path: lineGenerator(points), - config: springConfig, - immediate: !animate, - }) - - return ( - - ) + return } LinesItem.propTypes = { diff --git a/yarn.lock b/yarn.lock index d2b97e8c7..b74ddf395 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5017,6 +5017,18 @@ resolved "https://registry.yarnpkg.com/@types/configstore/-/configstore-2.1.1.tgz#cd1e8553633ad3185c3f2f239ecff5d2643e92b6" integrity sha1-zR6FU2M60xhcPy8jns/10mQ+krY= +"@types/d3-path@^1": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c" + integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ== + +"@types/d3-shape@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-2.0.0.tgz#61aa065726f3c2641aedc59c3603475ab11aeb2f" + integrity sha512-NLzD02m5PiD1KLEDjLN+MtqEcFYn4ZL9+Rqc9ZwARK1cpKZXd91zBETbe6wpBB6Ia0D0VZbpmbW3+BsGPGnCpA== + dependencies: + "@types/d3-path" "^1" + "@types/debug@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.29.tgz#a1e514adfbd92f03a224ba54d693111dbf1f3754" @@ -9096,6 +9108,11 @@ d3-color@1, d3-color@^1.2.3: resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.3.tgz#6c67bb2af6df3cc8d79efcc4d3a3e83e28c8048f" integrity sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw== +"d3-color@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== + d3-delaunay@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.1.1.tgz#fa2313f5fff1c6007f7d814276fc6914f370d63c" @@ -9146,6 +9163,13 @@ d3-interpolate@1: dependencies: d3-color "1" +d3-interpolate@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== + dependencies: + d3-color "1 - 2" + d3-path@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.7.tgz#8de7cd693a75ac0b5480d3abaccd94793e58aae8"