Animated Time Series

This example shows the use of animationMatchBy prop to customize Line animation. Read more in Animations guide. Start streaming to move the window forward and compare the matching strategies.

import type { AnimationMatchBy } from 'recharts';
import { CartesianGrid, Line, LineChart, matchByDataKey, matchByIndex, Tooltip, XAxis, YAxis } from 'recharts';
import { generateMockData } from '@recharts/devtools';
import type { Lever } from '../../../components/Shared/levers/Levers.tsx';
import { animationMatchByLever } from '../../../components/Shared/levers/gallery/animationMatchByLever.tsx';
import { streamWindowLever } from '../../../components/Shared/levers/gallery/streamWindowLever.tsx';
import { animationDurationLever } from '../../../components/Shared/levers/gallery/animationDurationLever.tsx';

const WINDOW = 6;
const DATA_LENGTH = 30;

const allData = generateMockData(DATA_LENGTH, 90).map((o, i) => ({ ...o, i }));

function getCircularWindowData(windowStart: number) {
  return Array.from({ length: WINDOW }, (_, index) => allData[(windowStart + index) % DATA_LENGTH]!);
}

type ControlsType = {
  animationDuration: number;
  animationMatchBy: 'index' | 'dataKey';
  windowStart: number;
};

export const animatedTimeSeriesDefaultState: ControlsType = {
  animationDuration: 800,
  animationMatchBy: 'dataKey',
  windowStart: 0,
};

export const animatedTimeSeriesLevers = [
  streamWindowLever<ControlsType>({
    wrapAt: DATA_LENGTH,
  }),
  animationDurationLever(),
  animationMatchByLever<ControlsType>({
    options: [
      { value: 'index', label: 'matchByIndex (default)' },
      { value: 'dataKey', label: "matchByDataKey('label')" },
    ],
  }),
] satisfies ReadonlyArray<Lever<ControlsType>>;

export default function AnimatedTimeSeriesExample(props: Partial<ControlsType>) {
  const { animationMatchBy, windowStart, animationDuration } = { ...animatedTimeSeriesDefaultState, ...props };

  const data = getCircularWindowData(windowStart);

  const matchProp: typeof matchByIndex | AnimationMatchBy<{ payload?: unknown }> =
    animationMatchBy === 'dataKey' ? matchByDataKey('label') : matchByIndex;

  return (
    <LineChart
      style={{ width: '100%', aspectRatio: 1.618, maxWidth: 600 }}
      responsive
      data={data}
      margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
    >
      <CartesianGrid />
      <XAxis dataKey="label" allowDataOverflow />
      <YAxis />
      <Tooltip />
      <Line dataKey="y" strokeWidth={2} animationDuration={animationDuration} animationMatchBy={matchProp} />
    </LineChart>
  );
}
800