diff --git a/packages/axes/package.json b/packages/axes/package.json index 716954fee..2c1088c84 100644 --- a/packages/axes/package.json +++ b/packages/axes/package.json @@ -21,15 +21,14 @@ "dist/" ], "dependencies": { - "@nivo/core": "0.62.0", "@nivo/scales": "0.62.0", "d3-format": "^1.4.4", "d3-time": "^1.0.11", "d3-time-format": "^2.1.3", - "lodash": "^4.17.11", - "react-motion": "^0.5.2" + "react-spring": "^8.0.27" }, "peerDependencies": { + "@nivo/core": "0.62.0", "prop-types": ">= 15.5.10 < 16.0.0", "react": ">= 16.8.4 < 17.0.0" }, diff --git a/packages/axes/src/components/Axis.js b/packages/axes/src/components/Axis.js index 6cb01b0a5..8a77bb191 100644 --- a/packages/axes/src/components/Axis.js +++ b/packages/axes/src/components/Axis.js @@ -6,28 +6,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { memo, Fragment, useMemo } from 'react' +import React, { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { Motion, TransitionMotion, spring } from 'react-motion' +import { useSpring, useTransition, animated } from 'react-spring' import { useTheme, useMotionConfig } from '@nivo/core' import { computeCartesianTicks, getFormatter } from '../compute' import { axisPropTypes } from '../props' import AxisTick from './AxisTick' -const willEnter = () => ({ - rotate: 0, - opacity: 0, - x: 0, - y: 0, -}) - -const willLeave = springConfig => ({ style: { x, y, rotate } }) => ({ - rotate, - opacity: spring(0, springConfig), - x: spring(x.val, springConfig), - y: spring(y.val, springConfig), -}) - const defaultTickRenderer = props => const Axis = ({ @@ -49,7 +35,6 @@ const Axis = ({ onClick, }) => { const theme = useTheme() - const { animate, springConfig } = useMotionConfig() const formatValue = useMemo(() => getFormatter(format, scale), [format, scale]) @@ -109,80 +94,68 @@ const Axis = ({ ) } - if (animate !== true) { - return ( - - {ticks.map((tick, tickIndex) => - React.createElement(renderTick, { - tickIndex, - format: formatValue, - rotate: tickRotation, - textBaseline, - textAnchor: textAlign, - ...tick, - ...(onClick ? { onClick } : {}), - }) - )} - - {legendNode} - - ) - } + const { animate, config: springConfig } = useMotionConfig() + + const animatedProps = useSpring({ + transform: `translate(${x},${y})`, + lineX2: axis === 'x' ? length : 0, + lineY2: axis === 'x' ? 0 : length, + config: springConfig, + immediate: !animate, + }) + + const transitions = useTransition(ticks, tick => tick.key, { + initial: tick => ({ + opacity: 1, + transform: `translate(${tick.x},${tick.y})`, + textTransform: `translate(${tick.textX},${tick.textY}) rotate(${tickRotation})`, + }), + from: tick => ({ + opacity: 0, + transform: `translate(${tick.x},${tick.y})`, + textTransform: `translate(${tick.textX},${tick.textY}) rotate(${tickRotation})`, + }), + enter: tick => ({ + opacity: 1, + transform: `translate(${tick.x},${tick.y})`, + textTransform: `translate(${tick.textX},${tick.textY}) rotate(${tickRotation})`, + }), + update: tick => ({ + opacity: 1, + transform: `translate(${tick.x},${tick.y})`, + textTransform: `translate(${tick.textX},${tick.textY}) rotate(${tickRotation})`, + }), + leave: { + opacity: 0, + }, + config: springConfig, + immediate: !animate, + }) return ( - - {xy => ( - - ({ - key: `${tick.key}`, - data: tick, - style: { - opacity: spring(1, springConfig), - x: spring(tick.x, springConfig), - y: spring(tick.y, springConfig), - rotate: spring(tickRotation, springConfig), - }, - }))} - > - {interpolatedStyles => ( - - {interpolatedStyles.map(({ style, data: tick }, tickIndex) => - React.createElement(renderTick, { - tickIndex, - format: formatValue, - textBaseline, - textAnchor: textAlign, - ...tick, - ...style, - ...(onClick ? { onClick } : {}), - }) - )} - - )} - - - {values => ( - - )} - - {legendNode} - - )} - + + {transitions.map(({ item: tick, props: transitionProps, key }, tickIndex) => { + return React.createElement(renderTick, { + tickIndex, + format: formatValue, + rotate: tickRotation, + textBaseline, + textAnchor: textAlign, + animatedProps: transitionProps, + ...tick, + ...(onClick ? { onClick } : {}), + key, + }) + })} + + {legendNode} + ) } diff --git a/packages/axes/src/components/AxisTick.js b/packages/axes/src/components/AxisTick.js index 47d3bdbad..c708cf2f5 100644 --- a/packages/axes/src/components/AxisTick.js +++ b/packages/axes/src/components/AxisTick.js @@ -7,23 +7,19 @@ * file that was distributed with this source code. */ import React, { memo } from 'react' +import { animated } from 'react-spring' import PropTypes from 'prop-types' import { useTheme } from '@nivo/core' const AxisTick = ({ value: _value, - x, - y, - opacity, - rotate, format, lineX, lineY, onClick, - textX, - textY, textBaseline, textAnchor, + animatedProps, }) => { const theme = useTheme() @@ -32,27 +28,27 @@ const AxisTick = ({ value = format(value) } - let gStyle = { opacity } + let gStyle = { opacity: animatedProps.opacity } if (onClick) { gStyle['cursor'] = 'pointer' } return ( - onClick(e, value) } : {})} style={gStyle} > - {value} - - + + ) } @@ -71,6 +67,7 @@ AxisTick.propTypes = { opacity: PropTypes.number.isRequired, rotate: PropTypes.number.isRequired, onClick: PropTypes.func, + animatedProps: PropTypes.object.isRequired, } AxisTick.defaultProps = { opacity: 1, diff --git a/packages/axes/src/components/GridLine.js b/packages/axes/src/components/GridLine.js index 6300f032c..1e3dbafa9 100644 --- a/packages/axes/src/components/GridLine.js +++ b/packages/axes/src/components/GridLine.js @@ -8,14 +8,21 @@ */ import React, { memo } from 'react' import PropTypes from 'prop-types' +import { animated } from 'react-spring' +import { useTheme } from '@nivo/core' -const GridLine = props => +const GridLine = ({ animatedProps }) => { + const theme = useTheme() + + return +} GridLine.propTypes = { x1: PropTypes.number.isRequired, x2: PropTypes.number.isRequired, y1: PropTypes.number.isRequired, y2: PropTypes.number.isRequired, + animatedProps: PropTypes.object.isRequired, } GridLine.defaultProps = { x1: 0, diff --git a/packages/axes/src/components/GridLines.js b/packages/axes/src/components/GridLines.js index 3bfd866e9..cc1942cef 100644 --- a/packages/axes/src/components/GridLines.js +++ b/packages/axes/src/components/GridLines.js @@ -6,75 +6,57 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { memo, useMemo } from 'react' +import React, { memo } from 'react' import PropTypes from 'prop-types' -import { TransitionMotion, spring } from 'react-motion' -import { useTheme, useMotionConfig } from '@nivo/core' +import { useTransition } from 'react-spring' +import { useMotionConfig } from '@nivo/core' import GridLine from './GridLine' -const GridLines = ({ type, lines }) => { - const theme = useTheme() - const { animate, springConfig } = useMotionConfig() +const GridLines = ({ lines }) => { + const { animate, config: springConfig } = useMotionConfig() - const lineWillEnter = useMemo( - () => ({ style }) => ({ + const transitions = useTransition(lines, line => line.key, { + initial: line => ({ + opacity: 1, + x1: line.x1, + x2: line.x2, + y1: line.y1, + y2: line.y2, + }), + from: line => ({ opacity: 0, - x1: type === 'x' ? 0 : style.x1.val, - x2: type === 'x' ? 0 : style.x2.val, - y1: type === 'y' ? 0 : style.y1.val, - y2: type === 'y' ? 0 : style.y2.val, + x1: line.x1, + x2: line.x2, + y1: line.y1, + y2: line.y2, }), - [type] - ) - - const lineWillLeave = useMemo( - () => ({ style }) => ({ - opacity: spring(0, springConfig), - x1: spring(style.x1.val, springConfig), - x2: spring(style.x2.val, springConfig), - y1: spring(style.y1.val, springConfig), - y2: spring(style.y2.val, springConfig), + enter: line => ({ + opacity: 1, + x1: line.x1, + x2: line.x2, + y1: line.y1, + y2: line.y2, }), - [springConfig] - ) - - if (!animate) { - return ( - - {lines.map(line => ( - - ))} - - ) - } + update: line => ({ + opacity: 1, + x1: line.x1, + x2: line.x2, + y1: line.y1, + y2: line.y2, + }), + leave: { + opacity: 0, + }, + config: springConfig, + immediate: !animate, + }) return ( - { - return { - key: line.key, - style: { - opacity: spring(1, springConfig), - x1: spring(line.x1 || 0, springConfig), - x2: spring(line.x2 || 0, springConfig), - y1: spring(line.y1 || 0, springConfig), - y2: spring(line.y2 || 0, springConfig), - }, - } - })} - > - {interpolatedStyles => ( - - {interpolatedStyles.map(interpolatedStyle => { - const { key, style } = interpolatedStyle - - return - })} - - )} - + + {transitions.map(({ item: line, props: animatedProps, key }) => ( + + ))} + ) } diff --git a/packages/axes/src/compute.js b/packages/axes/src/compute.js index 6625de147..106b06e39 100644 --- a/packages/axes/src/compute.js +++ b/packages/axes/src/compute.js @@ -6,7 +6,6 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import isNumber from 'lodash/isNumber' import { timeMillisecond, utcMillisecond, @@ -77,6 +76,9 @@ const timeByType = { const timeTypes = Object.keys(timeByType) const timeIntervalRegexp = new RegExp(`^every\\s*(\\d+)?\\s*(${timeTypes.join('|')})s?$`, 'i') +const isInteger = value => + typeof value === 'number' && isFinite(value) && Math.floor(value) === value + export const getScaleTicks = (scale, spec) => { // specific values if (Array.isArray(spec)) { @@ -91,7 +93,7 @@ export const getScaleTicks = (scale, spec) => { } // specific tick count - if (isNumber(spec)) { + if (isInteger(spec)) { return scale.ticks(spec) } @@ -208,7 +210,7 @@ export const getFormatter = (format, scale) => { export const computeGridLines = ({ width, height, scale, axis, values: _values }) => { const lineValues = Array.isArray(_values) ? _values : undefined - const lineCount = isNumber(_values) ? _values : undefined + const lineCount = isInteger(_values) ? _values : undefined const values = lineValues || getScaleTicks(scale, lineCount) diff --git a/packages/bar/tests/Bar.test.js b/packages/bar/tests/Bar.test.js index 62a0ed875..cfb192b7d 100644 --- a/packages/bar/tests/Bar.test.js +++ b/packages/bar/tests/Bar.test.js @@ -22,6 +22,7 @@ it('should render a basic bar chart', () => { { id: 'two', value: 20 }, { id: 'three', value: 30 }, ]} + animate={false} /> ) @@ -40,6 +41,7 @@ it('should allow to disable labels', () => { { id: 'two', value: 20 }, { id: 'three', value: 30 }, ]} + animate={false} /> ) @@ -59,6 +61,7 @@ it('should allow grouped mode', () => { { id: 'two', value: 20 }, { id: 'three', value: 30 }, ]} + animate={false} /> ) @@ -78,6 +81,7 @@ it('should allow horizontal layout', () => { { id: 'two', value: 20 }, { id: 'three', value: 30 }, ]} + animate={false} /> ) @@ -98,6 +102,7 @@ it('should allow grouped horizontal layout', () => { { id: 'two', value: 20 }, { id: 'three', value: 30 }, ]} + animate={false} /> ) @@ -125,6 +130,7 @@ it(`should reverse legend items if chart layout is vertical`, () => { itemHeight: 20, }, ]} + animate={false} /> ) @@ -157,6 +163,7 @@ it(`should not reverse legend items if chart layout is vertical reversed`, () => itemHeight: 20, }, ]} + animate={false} /> ) @@ -188,6 +195,7 @@ it(`should not reverse legend items if chart layout is horizontal`, () => { itemHeight: 20, }, ]} + animate={false} /> ) @@ -220,6 +228,7 @@ it(`should reverse legend items if chart layout is horizontal reversed`, () => { itemHeight: 20, }, ]} + animate={false} /> ) diff --git a/packages/bar/tests/__snapshots__/Bar.test.js.snap b/packages/bar/tests/__snapshots__/Bar.test.js.snap index c95f48383..c36a1f5ae 100644 --- a/packages/bar/tests/__snapshots__/Bar.test.js.snap +++ b/packages/bar/tests/__snapshots__/Bar.test.js.snap @@ -748,58 +748,56 @@ exports[`should allow grouped horizontal layout 1`] = ` y2={300} /> - - - - - - - - - - + + + + + + + + @@ -1671,58 +1669,56 @@ exports[`should allow grouped mode 1`] = ` y2={300} /> - - - - - - - - - - + + + + + + + + @@ -2477,58 +2473,56 @@ exports[`should allow horizontal layout 1`] = ` y2={300} /> - - - - - - - - - - + + + + + + + + @@ -3400,58 +3394,56 @@ exports[`should allow to disable labels 1`] = ` y2={300} /> - - - - - - - - - - + + + + + + + + @@ -4323,106 +4315,104 @@ exports[`should render a basic bar chart 1`] = ` y2={300} /> - - - - + + - 10 - - - - - + + + + - 20 - - - - - + + + + - 30 - - + } + textAnchor="middle" + x={72.5} + y={150} + > + 30 + diff --git a/packages/line/tests/Line.test.js b/packages/line/tests/Line.test.js index 33e3622f3..0a610bbc8 100644 --- a/packages/line/tests/Line.test.js +++ b/packages/line/tests/Line.test.js @@ -17,7 +17,7 @@ it('should render a basic line chart', () => { ], }, ] - const component = renderer.create() + const component = renderer.create() let tree = component.toJSON() expect(tree).toMatchSnapshot() @@ -46,7 +46,7 @@ it('should support multiple lines', () => { ], }, ] - const component = renderer.create() + const component = renderer.create() let tree = component.toJSON() expect(tree).toMatchSnapshot() @@ -65,7 +65,9 @@ it('should create slice for each x value', () => { ], }, ] - const wrapper = mount() + const wrapper = mount( + + ) const slices = wrapper.find(SlicesItem) expect(slices).toHaveLength(5) @@ -89,7 +91,7 @@ it('should have left and bottom axis by default', () => { ], }, ] - const wrapper = mount() + const wrapper = mount() const axes = wrapper.find('Axis') expect(axes).toHaveLength(2) diff --git a/packages/scatterplot/tests/ScatterPlot.test.js b/packages/scatterplot/tests/ScatterPlot.test.js index 8239f1835..15ed6e056 100644 --- a/packages/scatterplot/tests/ScatterPlot.test.js +++ b/packages/scatterplot/tests/ScatterPlot.test.js @@ -13,7 +13,12 @@ const sampleData = [ it('should render a basic scatterplot chart', () => { const component = renderer.create( - + ) const tree = component.toJSON() @@ -29,6 +34,7 @@ it('should allow to render several series', () => { { id: 'default', data: sampleData }, { id: 'extra', data: sampleData }, ]} + animate={false} /> ) @@ -43,6 +49,7 @@ it('should allow to customize node size', () => { height={300} nodeSize={12} data={[{ id: 'default', data: sampleData }]} + animate={false} /> ) @@ -74,6 +81,7 @@ it('should allow to use a varying node size', () => { values: [0, 10], sizes: [0, 20], }} + animate={false} /> ) @@ -93,6 +101,7 @@ it('should allow to use a custom node', () => { height={300} data={[{ id: 'default', data: sampleData }]} renderNode={CustomNode} + animate={false} /> ) @@ -119,6 +128,7 @@ it('should allow to disable interactivity', () => { data={[{ id: 'default', data: sampleData }]} isInteractive={false} onClick={() => {}} + animate={false} /> )