import {
Bar,
BarChart,
BarShapeProps,
RechartsTheme,
RechartsThemeProvider,
Rectangle,
Tooltip,
TooltipIndex,
usePlotArea,
XAxis,
YAxis,
} from 'recharts';
import { generateMockData, RechartsDevtools } from '@recharts/devtools';
const data = generateMockData(15, 5);
// A single shape component that handles both active and inactive states.
// It keeps the DOM element mounted, allowing CSS to transition the properties.
const AnimatedShape = (props: BarShapeProps) => {
return (
<>
{/*
* Hitbox Layer:
* Always renders as the full-width (transparent) slot.
* This ensures the mouse-leave event doesn't fire prematurely
* if the mouse moves into the whitespace created by the shrinking visual bar.
*
* Also allows us to trigger mouse events, and hover states, over the entire bar area,
* not just the narrow inactive part.
*/}
<Rectangle {...props} fill="transparent" />
{/*
Visual Layer:
Animates the width and position smoothly.
pointerEvents: 'none' ensures the hitbox (parent) handles interaction.
*/}
<Rectangle
{...props}
style={{
transform: props.isActive ? undefined : `scaleX(20%)`,
transformOrigin: `${props.x}px center`,
transition: 'all 0.2s ease-out',
pointerEvents: 'none',
}}
/>
</>
);
};
const BottomTooltip = ({ defaultIndex }: { defaultIndex?: TooltipIndex }) => {
const plotArea = usePlotArea();
if (plotArea == null) {
return null;
}
return <Tooltip defaultIndex={defaultIndex} cursor={false} position={{ y: plotArea.y + plotArea.height - 100 }} />;
};
const MonochromeTheme: RechartsTheme = {
graphicalItems: [
{
fill: 'black',
stroke: 'none',
fillOpacity: 1,
},
],
};
export default function AnimatedBarWidthExample({
isAnimationActive = true,
defaultIndex,
}: {
isAnimationActive?: boolean;
defaultIndex?: TooltipIndex;
}) {
return (
<RechartsThemeProvider value={MonochromeTheme}>
<BarChart
style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
barCategoryGap={4}
>
<XAxis dataKey="label" mirror padding={{ right: 30 }} interval={1} />
<YAxis
mirror
orientation="right"
tickLine={false}
tick={{ angle: 90, textAnchor: 'start' }}
padding={{ bottom: 30 }}
/>
<Bar
dataKey="y"
isAnimationActive={isAnimationActive}
activeBar={AnimatedShape}
shape={AnimatedShape}
label={{ fill: 'white', position: 'insideTopRight', angle: 90, textAnchor: 'start' }}
/>
<BottomTooltip defaultIndex={defaultIndex} />
<RechartsDevtools />
</BarChart>
</RechartsThemeProvider>
);
}