Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(xychart): add AnimatedLineSeries + tests #874

Merged
merged 5 commits into from
Oct 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -112,14 +112,14 @@ export default function Example({ height }: Props) {
)}
{renderLineSeries && (
<>
<LineSeries
<AnimatedLineSeries
dataKey="San Francisco"
data={renderBarStack ? data : data}
xAccessor={accessors.x['San Francisco']}
yAccessor={accessors.y['San Francisco']}
horizontal={!renderHorizontally}
/>
<LineSeries
<AnimatedLineSeries
dataKey="Austin"
data={renderBarStack ? data : data}
xAccessor={accessors.x.Austin}
Expand Down
12 changes: 12 additions & 0 deletions packages/visx-xychart/src/components/series/AnimatedLineSeries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AxisScale } from '@visx/axis';
import React from 'react';
import BaseLineSeries, { BaseLineSeriesProps } from './private/BaseLineSeries';
import AnimatedPath from './private/AnimatedPath';

export default function AnimatedLineSeries<
XScale extends AxisScale,
YScale extends AxisScale,
Datum extends object
>({ ...props }: Omit<BaseLineSeriesProps<XScale, YScale, Datum>, 'PathComponent'>) {
return <BaseLineSeries<XScale, YScale, Datum> {...props} PathComponent={AnimatedPath} />;
}
77 changes: 5 additions & 72 deletions packages/visx-xychart/src/components/series/LineSeries.tsx
Original file line number Diff line number Diff line change
@@ -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<XScale, YScale, Datum> & {
/** Whether line should be rendered horizontally instead of vertically. */
horizontal?: boolean;
};

function LineSeries<XScale extends AxisScale, YScale extends AxisScale, Datum extends object>({
data,
dataKey,
xAccessor,
xScale,
yAccessor,
yScale,
horizontal = true,
...lineProps
}: LineSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
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 (
<LinePath
data={data}
x={getScaledX}
y={getScaledY}
stroke={color}
strokeWidth={2}
{...lineProps}
/>
);
>({ ...props }: Omit<BaseLineSeriesProps<XScale, YScale, Datum>, 'PathComponent'>) {
return <BaseLineSeries<XScale, YScale, Datum> {...props} />;
}

export default withRegisteredData(LineSeries);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { animated, useSpring } from 'react-spring';

export default function AnimatedPath({
d,
stroke,
...lineProps
}: Omit<React.SVGProps<SVGPathElement>, 'ref'>) {
const tweened = useSpring({ d, stroke, config: { precision: 0.01 } });
return <animated.path d={tweened.d} stroke={tweened.stroke} fill="transparent" {...lineProps} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type BaseBarGroupProps<XScale extends PositionScale, YScale extends Posit
padding?: number;
/** Comparator function to sort `dataKeys` within a bar group. By default the DOM rendering order of `BarGroup`s `children` is used. */
sortBars?: (dataKeyA: string, dataKeyB: string) => number;
/** Rendered component which is passed BarsProps by BaseBarSeries after processing. */
/** Rendered component which is passed BarsProps by BaseBarGroup after processing. */
BarsComponent: React.FC<BarsProps<XScale, YScale>>;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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<XScale, YScale, Datum> & {
/** 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<Omit<React.SVGProps<SVGPathElement>, 'ref'>> | 'path';
};

function BaseLineSeries<XScale extends AxisScale, YScale extends AxisScale, Datum extends object>({
data,
dataKey,
xAccessor,
xScale,
yAccessor,
yScale,
horizontal = true,
PathComponent = 'path',
...lineProps
}: BaseLineSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
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 (
<LinePath
data={data}
x={getScaledX}
y={getScaledY}
stroke={color}
strokeWidth={2}
{...lineProps}
>
{({ path }) => (
<PathComponent stroke={color} strokeWidth={2} {...lineProps} d={path(data) || ''} />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything is the same in this file except we use the child render function here.

sorry I tried to git mv and it didn't work in this UI view 💔

)}
</LinePath>
);
}

export default withRegisteredData(BaseLineSeries);
1 change: 1 addition & 0 deletions packages/visx-xychart/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 4 additions & 0 deletions packages/visx-xychart/src/utils/getScaledValueFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function getScaledValueFactory<Scale extends AxisScale, Datum>(
(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;
};
}
24 changes: 22 additions & 2 deletions packages/visx-xychart/test/components/BarGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -77,7 +78,7 @@ describe('<BarGroup />', () => {

setupTooltipTest(
<>
<BarGroup horizontal>
<BarGroup>
<BarSeries dataKey={series1.key} {...series1} />
<BarSeries dataKey={series2.key} {...series2} />
</BarGroup>
Expand All @@ -87,3 +88,22 @@ describe('<BarGroup />', () => {
);
});
});

describe('<AnimatedBarGroup />', () => {
it('should be defined', () => {
expect(AnimatedBarGroup).toBeDefined();
});
it('should render an animated.rect', () => {
const wrapper = mount(
<DataProvider {...providerProps}>
<svg>
<AnimatedBarGroup>
<BarSeries dataKey={series1.key} {...series1} />
<BarSeries dataKey={series2.key} {...series2} />
</AnimatedBarGroup>
</svg>
</DataProvider>,
);
expect(wrapper.find(animated.rect)).toHaveLength(4);
});
});
19 changes: 18 additions & 1 deletion packages/visx-xychart/test/components/BarSeries.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -62,3 +63,19 @@ describe('<BarSeries />', () => {
);
});
});

describe('<AnimatedBarSeries />', () => {
it('should be defined', () => {
expect(AnimatedBarSeries).toBeDefined();
});
it('should render an animated.rect', () => {
const wrapper = mount(
<DataContext.Provider value={getDataContext(series)}>
<svg>
<AnimatedBarSeries dataKey={series.key} {...series} />
</svg>
</DataContext.Provider>,
);
expect(wrapper.find(animated.rect)).toHaveLength(series.data.length);
});
});
31 changes: 29 additions & 2 deletions packages/visx-xychart/test/components/BarStack.test.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -108,7 +116,7 @@ describe('<BarStack />', () => {

setupTooltipTest(
<>
<BarStack horizontal>
<BarStack>
<BarSeries dataKey={series1.key} {...series1} />
<BarSeries dataKey={series2.key} {...series2} />
</BarStack>
Expand All @@ -118,3 +126,22 @@ describe('<BarStack />', () => {
);
});
});

describe('<AnimatedBarStack />', () => {
it('should be defined', () => {
expect(AnimatedBarStack).toBeDefined();
});
it('should render an animated.rect', () => {
const wrapper = mount(
<DataProvider {...providerProps}>
<svg>
<AnimatedBarStack>
<BarSeries dataKey={series1.key} {...series1} />
<BarSeries dataKey={series2.key} {...series2} />
</AnimatedBarStack>
</svg>
</DataProvider>,
);
expect(wrapper.find(animated.rect)).toHaveLength(4);
});
});
Loading