Custom Animation Example
Disable the custom shape to see the default clip-path reveal hide most of the grow-from-bottom interpolation.
import {
AnimationInterpolateFn,
Area,
AreaChart,
CartesianGrid,
Tooltip,
usePlotArea,
XAxis,
YAxis,
AreaRevealShape,
AreaPointItem,
interpolate,
CartesianLayout,
AreaRevealShapeProps,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
import type { Lever } from '../../../../components/Shared/levers/Levers.tsx';
import { createCheckboxLever } from '../../../../components/Shared/levers/Levers.tsx';
import { animationDurationLever } from '../../../../components/Shared/levers/gallery/animationDurationLever.tsx';
import { replayAnimationLever } from '../../../../components/Shared/levers/gallery/replayAnimationLever.tsx';
import { swapDataSetLever } from '../../../../components/Shared/levers/gallery/swapDataLever.tsx';
const dataA = [
{ name: 'Page A', uv: 4000, pv: 2400, amt: 2400 },
{ name: 'Page B', uv: 3000, pv: 1398, amt: 2210 },
{ name: 'Page C', uv: 2000, pv: 9800, amt: 2290 },
{ name: 'Page D', pv: 3908, amt: 2000 },
{ name: 'Page E', uv: 1890, pv: 4800, amt: 2181 },
{ name: 'Page F', uv: 2390, pv: 3800, amt: 2500 },
{ name: 'Page G', uv: 3490, pv: 4300, amt: 2100 },
];
const dataB = [
{ name: 'Page A', uv: 1800, pv: 2500, amt: 2100 },
{ name: 'Page B', uv: 4600, pv: 1900, amt: 2400 },
{ name: 'Page C', uv: 3200, pv: 7000, amt: 2600 },
{ name: 'Page D', uv: 1100, pv: 3400, amt: 2300 },
{ name: 'Page E', pv: 5200, amt: 2000 },
{ name: 'Page F', uv: 2900, pv: 2700, amt: 2400 },
{ name: 'Page G', uv: 4100, pv: 3900, amt: 2200 },
];
type ControlsType = {
animationDuration: number;
replayKey: number;
useCustomInterpolation: boolean;
useCustomShape: boolean;
dataSet: 'a' | 'b';
};
export const areaChartCustomAnimationDefaultState: ControlsType = {
animationDuration: 900,
replayKey: 0,
useCustomInterpolation: true,
useCustomShape: true,
dataSet: 'a',
};
/**
* Custom animation: grow from the bottom of the chart.
* Uses animationInterpolateFn to move points from the chart bottom upwards.
* Combined with a custom shape (no clipPath) so the reveal animation doesn't interfere.
*/
const useAnimateFromBottom = (): AnimationInterpolateFn<AreaPointItem, CartesianLayout> => {
const plotArea = usePlotArea();
const zero = plotArea ? plotArea.y + plotArea.height : 0;
return (items, animationElapsedTime) => {
if (items == null) return [];
if (animationElapsedTime === 1) return items.flatMap(item => (item.status === 'removed' ? [] : [item.next]));
return items.flatMap(item => {
if (item.status === 'removed') return [];
const { next } = item;
return [{ ...next, y: interpolate(zero, next.y, animationElapsedTime) }];
});
};
};
function GrowFromBottomShape(props: AreaRevealShapeProps) {
return <AreaRevealShape {...props} isEntrance={false} />;
}
function MyAnimatedArea({
animationDuration,
useCustomInterpolation,
useCustomShape,
}: Pick<ControlsType, 'animationDuration' | 'useCustomInterpolation' | 'useCustomShape'>) {
const growFromBottom = useAnimateFromBottom();
return (
<Area
type="monotone"
dataKey="uv"
fillOpacity={1}
fill="url(#colorUv)"
animationDuration={animationDuration}
animationInterpolateFn={useCustomInterpolation ? growFromBottom : undefined}
shape={useCustomShape ? GrowFromBottomShape : undefined}
/>
);
}
export const areaChartCustomAnimationLevers = [
replayAnimationLever<ControlsType>({
buttonLabel: 'Replay entrance animation',
}),
swapDataSetLever<ControlsType>({
buttonLabel: '⇄ Swap dataset (Shows update animation)',
}),
animationDurationLever<ControlsType>(),
createCheckboxLever<ControlsType>({
key: 'useCustomInterpolation',
label: 'animationInterpolateFn',
getValue: state => state.useCustomInterpolation,
onChange: (useCustomInterpolation, state) => ({ ...state, useCustomInterpolation }),
}),
createCheckboxLever<ControlsType>({
key: 'useCustomShape',
label: 'shape={GrowFromBottomShape}',
getValue: state => state.useCustomShape,
onChange: (useCustomShape, state) => ({ ...state, useCustomShape }),
}),
] satisfies ReadonlyArray<Lever<ControlsType>>;
export default function AreaChartCustomAnimationExample(props: Partial<ControlsType>) {
const { animationDuration, replayKey, useCustomInterpolation, useCustomShape, dataSet } = {
...areaChartCustomAnimationDefaultState,
...props,
};
return (
<AreaChart
style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={dataSet === 'a' ? dataA : dataB}
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid />
<XAxis dataKey="name" />
<YAxis width="auto" />
<Tooltip />
<MyAnimatedArea
key={replayKey}
animationDuration={animationDuration}
useCustomInterpolation={useCustomInterpolation}
useCustomShape={useCustomShape}
/>
<RechartsDevtools />
</AreaChart>
);
}