diff --git a/packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx b/packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx index 3ec17ddd9..365e26c78 100644 --- a/packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx +++ b/packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx @@ -2,12 +2,12 @@ import React from 'react'; import { CityTemperature } from '@visx/mock-data/lib/mocks/cityTemperature'; import { AnimatedAxis, - AnimatedGrid, - DataProvider, AnimatedBarGroup, AnimatedBarSeries, AnimatedBarStack, - LineSeries, + AnimatedGrid, + AnimatedLineSeries, + DataProvider, Tooltip, XYChart, } from '@visx/xychart'; @@ -112,14 +112,14 @@ export default function Example({ height }: Props) { )} {renderLineSeries && ( <> - - ({ ...props }: Omit, 'PathComponent'>) { + return {...props} PathComponent={AnimatedPath} />; +} diff --git a/packages/visx-xychart/src/components/series/LineSeries.tsx b/packages/visx-xychart/src/components/series/LineSeries.tsx index b66d8e02a..b3f6d9341 100644 --- a/packages/visx-xychart/src/components/series/LineSeries.tsx +++ b/packages/visx-xychart/src/components/series/LineSeries.tsx @@ -1,78 +1,11 @@ -import React, { useContext, useCallback } from 'react'; -import LinePath from '@visx/shape/lib/shapes/LinePath'; import { AxisScale } from '@visx/axis'; -import DataContext from '../../context/DataContext'; -import { SeriesProps } from '../../types'; -import withRegisteredData, { WithRegisteredDataProps } from '../../enhancers/withRegisteredData'; -import getScaledValueFactory from '../../utils/getScaledValueFactory'; -import useEventEmitter, { HandlerParams } from '../../hooks/useEventEmitter'; -import findNearestDatumX from '../../utils/findNearestDatumX'; -import TooltipContext from '../../context/TooltipContext'; -import findNearestDatumY from '../../utils/findNearestDatumY'; +import React from 'react'; +import BaseLineSeries, { BaseLineSeriesProps } from './private/BaseLineSeries'; -type LineSeriesProps< +export default function LineSeries< XScale extends AxisScale, YScale extends AxisScale, Datum extends object -> = SeriesProps & { - /** Whether line should be rendered horizontally instead of vertically. */ - horizontal?: boolean; -}; - -function LineSeries({ - data, - dataKey, - xAccessor, - xScale, - yAccessor, - yScale, - horizontal = true, - ...lineProps -}: LineSeriesProps & WithRegisteredDataProps) { - const { colorScale, theme, width, height } = useContext(DataContext); - const { showTooltip, hideTooltip } = useContext(TooltipContext) ?? {}; - const getScaledX = useCallback(getScaledValueFactory(xScale, xAccessor), [xScale, xAccessor]); - const getScaledY = useCallback(getScaledValueFactory(yScale, yAccessor), [yScale, yAccessor]); - const color = colorScale?.(dataKey) ?? theme?.colors?.[0] ?? '#222'; - - const handleMouseMove = useCallback( - (params?: HandlerParams) => { - const { svgPoint } = params || {}; - if (svgPoint && width && height && showTooltip) { - const datum = (horizontal ? findNearestDatumX : findNearestDatumY)({ - point: svgPoint, - data, - xScale, - yScale, - xAccessor, - yAccessor, - width, - height, - }); - if (datum) { - showTooltip({ - key: dataKey, - ...datum, - svgPoint, - }); - } - } - }, - [dataKey, data, xScale, yScale, xAccessor, yAccessor, width, height, showTooltip, horizontal], - ); - useEventEmitter('mousemove', handleMouseMove); - useEventEmitter('mouseout', hideTooltip); - - return ( - - ); +>({ ...props }: Omit, 'PathComponent'>) { + return {...props} />; } - -export default withRegisteredData(LineSeries); diff --git a/packages/visx-xychart/src/components/series/private/AnimatedPath.tsx b/packages/visx-xychart/src/components/series/private/AnimatedPath.tsx new file mode 100644 index 000000000..dbd519cf0 --- /dev/null +++ b/packages/visx-xychart/src/components/series/private/AnimatedPath.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { animated, useSpring } from 'react-spring'; + +export default function AnimatedPath({ + d, + stroke, + ...lineProps +}: Omit, 'ref'>) { + const tweened = useSpring({ d, stroke, config: { precision: 0.01 } }); + return ; +} diff --git a/packages/visx-xychart/src/components/series/private/BaseBarGroup.tsx b/packages/visx-xychart/src/components/series/private/BaseBarGroup.tsx index afc63ad7d..1175bde42 100644 --- a/packages/visx-xychart/src/components/series/private/BaseBarGroup.tsx +++ b/packages/visx-xychart/src/components/series/private/BaseBarGroup.tsx @@ -21,7 +21,7 @@ export type BaseBarGroupProps number; - /** Rendered component which is passed BarsProps by BaseBarSeries after processing. */ + /** Rendered component which is passed BarsProps by BaseBarGroup after processing. */ BarsComponent: React.FC>; }; diff --git a/packages/visx-xychart/src/components/series/private/BaseLineSeries.tsx b/packages/visx-xychart/src/components/series/private/BaseLineSeries.tsx new file mode 100644 index 000000000..571726117 --- /dev/null +++ b/packages/visx-xychart/src/components/series/private/BaseLineSeries.tsx @@ -0,0 +1,85 @@ +import React, { useContext, useCallback } from 'react'; +import LinePath from '@visx/shape/lib/shapes/LinePath'; +import { AxisScale } from '@visx/axis'; +import DataContext from '../../../context/DataContext'; +import { SeriesProps } from '../../../types'; +import withRegisteredData, { WithRegisteredDataProps } from '../../../enhancers/withRegisteredData'; +import getScaledValueFactory from '../../../utils/getScaledValueFactory'; +import useEventEmitter, { HandlerParams } from '../../../hooks/useEventEmitter'; +import findNearestDatumX from '../../../utils/findNearestDatumX'; +import TooltipContext from '../../../context/TooltipContext'; +import findNearestDatumY from '../../../utils/findNearestDatumY'; + +export type BaseLineSeriesProps< + XScale extends AxisScale, + YScale extends AxisScale, + Datum extends object +> = SeriesProps & { + /** Whether line should be rendered horizontally instead of vertically. */ + horizontal?: boolean; + /** Rendered component which is passed path props by BaseLineSeries after processing. */ + PathComponent?: React.FC, 'ref'>> | 'path'; +}; + +function BaseLineSeries({ + data, + dataKey, + xAccessor, + xScale, + yAccessor, + yScale, + horizontal = true, + PathComponent = 'path', + ...lineProps +}: BaseLineSeriesProps & WithRegisteredDataProps) { + const { colorScale, theme, width, height } = useContext(DataContext); + const { showTooltip, hideTooltip } = useContext(TooltipContext) ?? {}; + const getScaledX = useCallback(getScaledValueFactory(xScale, xAccessor), [xScale, xAccessor]); + const getScaledY = useCallback(getScaledValueFactory(yScale, yAccessor), [yScale, yAccessor]); + const color = colorScale?.(dataKey) ?? theme?.colors?.[0] ?? '#222'; + + const handleMouseMove = useCallback( + (params?: HandlerParams) => { + const { svgPoint } = params || {}; + if (svgPoint && width && height && showTooltip) { + const datum = (horizontal ? findNearestDatumX : findNearestDatumY)({ + point: svgPoint, + data, + xScale, + yScale, + xAccessor, + yAccessor, + width, + height, + }); + if (datum) { + showTooltip({ + key: dataKey, + ...datum, + svgPoint, + }); + } + } + }, + [dataKey, data, xScale, yScale, xAccessor, yAccessor, width, height, showTooltip, horizontal], + ); + useEventEmitter('mousemove', handleMouseMove); + useEventEmitter('mouseout', hideTooltip); + + return ( + + {({ path }) => ( + + )} + + ); +} + +export default withRegisteredData(BaseLineSeries); diff --git a/packages/visx-xychart/src/index.ts b/packages/visx-xychart/src/index.ts index 3354fa667..7e36c152f 100644 --- a/packages/visx-xychart/src/index.ts +++ b/packages/visx-xychart/src/index.ts @@ -16,6 +16,7 @@ export { default as LineSeries } from './components/series/LineSeries'; export { default as AnimatedBarSeries } from './components/series/AnimatedBarSeries'; export { default as AnimatedBarStack } from './components/series/AnimatedBarStack'; export { default as AnimatedBarGroup } from './components/series/AnimatedBarGroup'; +export { default as AnimatedLineSeries } from './components/series/AnimatedLineSeries'; // context export { default as DataContext } from './context/DataContext'; diff --git a/packages/visx-xychart/src/utils/getScaledValueFactory.ts b/packages/visx-xychart/src/utils/getScaledValueFactory.ts index 92a744437..579a80564 100644 --- a/packages/visx-xychart/src/utils/getScaledValueFactory.ts +++ b/packages/visx-xychart/src/utils/getScaledValueFactory.ts @@ -16,6 +16,10 @@ export default function getScaledValueFactory( (align === 'start' ? 0 : getScaleBandwidth(scale)) / (align === 'center' ? 2 : 1); return scaledValue + bandwidthOffset; } + // @TODO: NaNs cause react-spring to throw, but the return value of this must be number + // this currently causes issues in vertical <> horizontal transitions because + // x/yAccessors from context are out of sync with props.horizontal + // horizontal should be moved to context return NaN; }; } diff --git a/packages/visx-xychart/test/components/BarGroup.test.tsx b/packages/visx-xychart/test/components/BarGroup.test.tsx index a492b53d5..f7b64decc 100644 --- a/packages/visx-xychart/test/components/BarGroup.test.tsx +++ b/packages/visx-xychart/test/components/BarGroup.test.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; +import { animated } from 'react-spring'; import { mount } from 'enzyme'; -import { BarGroup, BarSeries, DataProvider, useEventEmitter } from '../../src'; +import { AnimatedBarGroup, BarGroup, BarSeries, DataProvider, useEventEmitter } from '../../src'; import setupTooltipTest from '../mocks/setupTooltipTest'; const providerProps = { @@ -77,7 +78,7 @@ describe('', () => { setupTooltipTest( <> - + @@ -87,3 +88,22 @@ describe('', () => { ); }); }); + +describe('', () => { + it('should be defined', () => { + expect(AnimatedBarGroup).toBeDefined(); + }); + it('should render an animated.rect', () => { + const wrapper = mount( + + + + + + + + , + ); + expect(wrapper.find(animated.rect)).toHaveLength(4); + }); +}); diff --git a/packages/visx-xychart/test/components/BarSeries.test.tsx b/packages/visx-xychart/test/components/BarSeries.test.tsx index e6633ad56..19b9bf5d9 100644 --- a/packages/visx-xychart/test/components/BarSeries.test.tsx +++ b/packages/visx-xychart/test/components/BarSeries.test.tsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect } from 'react'; +import { animated } from 'react-spring'; import { mount } from 'enzyme'; -import { DataContext, BarSeries, useEventEmitter } from '../../src'; +import { DataContext, AnimatedBarSeries, BarSeries, useEventEmitter } from '../../src'; import getDataContext from '../mocks/getDataContext'; import setupTooltipTest from '../mocks/setupTooltipTest'; @@ -62,3 +63,19 @@ describe('', () => { ); }); }); + +describe('', () => { + it('should be defined', () => { + expect(AnimatedBarSeries).toBeDefined(); + }); + it('should render an animated.rect', () => { + const wrapper = mount( + + + + + , + ); + expect(wrapper.find(animated.rect)).toHaveLength(series.data.length); + }); +}); diff --git a/packages/visx-xychart/test/components/BarStack.test.tsx b/packages/visx-xychart/test/components/BarStack.test.tsx index a34edaafa..ec50b9414 100644 --- a/packages/visx-xychart/test/components/BarStack.test.tsx +++ b/packages/visx-xychart/test/components/BarStack.test.tsx @@ -1,6 +1,14 @@ import React, { useContext, useEffect } from 'react'; import { mount } from 'enzyme'; -import { BarStack, BarSeries, DataProvider, DataContext, useEventEmitter } from '../../src'; +import { animated } from 'react-spring'; +import { + BarStack, + BarSeries, + DataProvider, + DataContext, + useEventEmitter, + AnimatedBarStack, +} from '../../src'; import setupTooltipTest from '../mocks/setupTooltipTest'; const providerProps = { @@ -108,7 +116,7 @@ describe('', () => { setupTooltipTest( <> - + @@ -118,3 +126,22 @@ describe('', () => { ); }); }); + +describe('', () => { + it('should be defined', () => { + expect(AnimatedBarStack).toBeDefined(); + }); + it('should render an animated.rect', () => { + const wrapper = mount( + + + + + + + + , + ); + expect(wrapper.find(animated.rect)).toHaveLength(4); + }); +}); diff --git a/packages/visx-xychart/test/components/LineSeries.test.tsx b/packages/visx-xychart/test/components/LineSeries.test.tsx index 7a0ab8890..928ae7116 100644 --- a/packages/visx-xychart/test/components/LineSeries.test.tsx +++ b/packages/visx-xychart/test/components/LineSeries.test.tsx @@ -1,7 +1,8 @@ import React, { useContext, useEffect } from 'react'; +import { animated } from 'react-spring'; import { mount } from 'enzyme'; import { LinePath } from '@visx/shape'; -import { DataContext, LineSeries, useEventEmitter } from '../../src'; +import { AnimatedLineSeries, DataContext, LineSeries, useEventEmitter } from '../../src'; import getDataContext from '../mocks/getDataContext'; import setupTooltipTest from '../mocks/setupTooltipTest'; @@ -64,3 +65,19 @@ describe('', () => { ); }); }); + +describe('', () => { + it('should be defined', () => { + expect(AnimatedLineSeries).toBeDefined(); + }); + it('should render an animated.path', () => { + const wrapper = mount( + + + + + , + ); + expect(wrapper.find(animated.path)).toHaveLength(1); + }); +});