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

Add animation for ErrorBar #4311

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
47 changes: 42 additions & 5 deletions src/cartesian/ErrorBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
*/
import React, { SVGProps } from 'react';
import invariant from 'tiny-invariant';
import Animate from 'react-smooth';
import { Layer } from '../container/Layer';
import { Props as XAxisProps } from './XAxis';
import { Props as YAxisProps } from './YAxis';
import { D3Scale, DataKey } from '../util/types';
import { AnimationTiming, D3Scale, DataKey } from '../util/types';
import { filterProps } from '../util/ReactUtils';
import { BarRectangleItem } from './Bar';
import { LinePointItem } from './Line';
Expand Down Expand Up @@ -43,12 +44,30 @@ interface ErrorBarProps extends InternalErrorBarProps {
* Only accepts a value of "x" or "y" and makes the error bars lie in that direction.
*/
direction?: 'x' | 'y';
isAnimationActive?: boolean;
animationBegin?: number;
animationDuration?: number;
animationEasing?: AnimationTiming;
ckifer marked this conversation as resolved.
Show resolved Hide resolved
}

export type Props = SVGProps<SVGLineElement> & ErrorBarProps;

export function ErrorBar(props: Props) {
const { offset, layout, width, dataKey, data, dataPointFormatter, xAxis, yAxis, ...others } = props;
const {
offset,
layout,
width,
dataKey,
data,
dataPointFormatter,
xAxis,
yAxis,
isAnimationActive,
animationBegin,
animationDuration,
animationEasing,
...others
} = props;
const svgProps = filterProps(others, false);

invariant(
Expand Down Expand Up @@ -114,9 +133,23 @@ export function ErrorBar(props: Props) {
key={`bar-${lineCoordinates.map(c => `${c.x1}-${c.x2}-${c.y1}-${c.y2}`)}`}
{...svgProps}
>
{lineCoordinates.map(coordinates => (
<line {...coordinates} key={`line-${coordinates.x1}-${coordinates.x2}-${coordinates.y1}-${coordinates.y2}`} />
))}
{lineCoordinates.map(coordinates => {
const lineStyle = isAnimationActive ? { transformOrigin: `${coordinates.x1 - 5}px` } : undefined;
return (
<Animate
from="scale(0, 1)"
to="scale(1, 1)"
attributeName="transform"
begin={animationBegin}
easing={animationEasing}
isActive={isAnimationActive}
duration={animationDuration}
key={`line-${coordinates.x1}-${coordinates.x2}-${coordinates.y1}-${coordinates.y2}`}
>
<line {...coordinates} style={lineStyle} />
</Animate>
);
})}
</Layer>
);
});
Expand All @@ -130,5 +163,9 @@ ErrorBar.defaultProps = {
width: 5,
offset: 0,
layout: 'horizontal',
isAnimationActive: true,
ForestLinSen marked this conversation as resolved.
Show resolved Hide resolved
animationBegin: 0,
animationDuration: 200,
animationEasing: 'ease-in-out',
};
ErrorBar.displayName = 'ErrorBar';
113 changes: 113 additions & 0 deletions test/cartesian/ErrorBar.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Bar, BarChart, Line, LineChart, ErrorBar, XAxis, YAxis } from '../../src';
import '@testing-library/jest-dom';
import { mockAnimation, cleanupMockAnimation } from '../helper/animation-frame-helper';

// asserts an error bar has both a start and end position
const assertErrorBars = (container: HTMLElement, barsExpected: number) => {
Expand All @@ -20,6 +22,27 @@ const assertErrorBars = (container: HTMLElement, barsExpected: number) => {
});
};

function assertAnimationStyles(
container: HTMLElement,
animation: boolean = true,
expectedStyles: { [key: string]: string } = {},
) {
const errorBars = container.querySelectorAll('.recharts-errorBar');
errorBars.forEach(bar => {
const lineElements = bar.querySelectorAll('line');
lineElements.forEach(line => {
if (animation) {
const style = line.getAttribute('style');
Object.entries(expectedStyles).forEach(([key, value]) => {
expect(style).toContain(`${key}: ${value}`);
});
} else {
expect(line.getAttribute('style')).toBeNull();
}
});
});
}

describe('<ErrorBar />', () => {
const barData = [
{ name: 'food', uv: 2000, pv: 2013, time: 1, uvError: [100, 50], pvError: [110, 20] },
Expand Down Expand Up @@ -103,4 +126,94 @@ describe('<ErrorBar />', () => {

expect(container.querySelectorAll('.recharts-errorBar')).toHaveLength(10);
});

test('Renders Error Bars with animation', async () => {
mockAnimation();
ForestLinSen marked this conversation as resolved.
Show resolved Hide resolved

const { container } = render(
<BarChart data={barData} width={500} height={500}>
<Bar isAnimationActive={false} dataKey="uv">
<ErrorBar isAnimationActive dataKey="uvError" />
</Bar>
</BarChart>,
);

assertErrorBars(container, 4);
assertAnimationStyles(container, true, { transition: 'transform 200ms ease-in-out' });

cleanupMockAnimation();
});

test('Renders Error Bars without animation', () => {
mockAnimation();

const { container } = render(
<BarChart data={barData} width={500} height={500}>
<Bar isAnimationActive={false} dataKey="uv">
<ErrorBar isAnimationActive={false} dataKey="uvError" />
</Bar>
</BarChart>,
);

assertErrorBars(container, 4);
assertAnimationStyles(container, false);

cleanupMockAnimation();
});

test('Renders Error Bars with animation delay', () => {
mockAnimation();

const { container } = render(
<BarChart data={barData} width={500} height={500}>
<Bar isAnimationActive={false} dataKey="uv">
<ErrorBar isAnimationActive begin={200} dataKey="uvError" />
</Bar>
</BarChart>,
);

assertErrorBars(container, 4);
assertAnimationStyles(container, true, { transition: 'transform 200ms ease-in-out' });

const errorBars = container.querySelectorAll('.recharts-errorBar');
errorBars.forEach(bar => {
expect(bar.getAttribute('begin')).toBe('200');
});

cleanupMockAnimation();
});

test('Renders Error Bars with animation duration', () => {
mockAnimation();

const { container } = render(
<BarChart data={barData} width={500} height={500}>
<Bar isAnimationActive={false} dataKey="uv">
<ErrorBar isAnimationActive animationDuration={400} dataKey="uvError" />
</Bar>
</BarChart>,
);

assertErrorBars(container, 4);
assertAnimationStyles(container, true, { transition: 'transform 400ms ease-in-out' });

cleanupMockAnimation();
});

test('Renders Error Bars with animation easing', () => {
mockAnimation();

const { container } = render(
<BarChart data={barData} width={500} height={500}>
<Bar isAnimationActive={false} dataKey="uv">
<ErrorBar isAnimationActive animationEasing="linear" dataKey="uvError" />
</Bar>
</BarChart>,
);

assertErrorBars(container, 4);
assertAnimationStyles(container, true, { transition: 'transform 200ms linear' });

cleanupMockAnimation();
});
});
Loading