What is the current behavior?
In recharts v3, when the browser window is resized from a wide width to a narrow width, the Legend overlaps with the chart content. Legend items wrap to multiple lines as the container narrows, increasing the Legend's height, but the chart area does not shrink to accommodate the taller Legend. This results in the Legend rendering on top of the chart bars and axis labels.
This is a regression from v2, where the chart area correctly adjusts when the Legend wraps.
v3 (3.8.1) — after resize from 1200px to 500px:
v2 (2.15.4) — same resize, no overlap:
What is the expected behavior?
When the Legend wraps to multiple lines due to container resize, the chart area should shrink accordingly (via offset recalculation), preventing any overlap between the Legend and chart content — as it does in v2.
Please provide a demo of the problem in a sandbox
Reproduction code (paste into a new project with recharts@3):
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
const data = [
{ name: 'Jan', s1: 400, s2: 300, s3: 200, s4: 278, s5: 189, s6: 239, s7: 349, s8: 200, s9: 300, s10: 400, s11: 500, s12: 100 },
{ name: 'Feb', s1: 300, s2: 200, s3: 400, s4: 200, s5: 300, s6: 100, s7: 250, s8: 350, s9: 150, s10: 250, s11: 450, s12: 200 },
];
const colors = ['#8884d8','#82ca9d','#ffc658','#ff7300','#0088fe','#00c49f','#ffbb28','#ff8042','#a4de6c','#d0ed57','#8dd1e1','#83a6ed'];
function App() {
return (
<div style={{ width: '100%', height: 400 }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
{colors.map((c, i) => <Bar key={i} dataKey={`s${i+1}`} fill={c} name={`Data Series ${i+1}`} />)}
</BarChart>
</ResponsiveContainer>
</div>
);
}
Steps:
- Render the above component at a wide browser width (e.g. 1200px) so Legend fits in ~2 rows
- Resize the browser to a narrow width (e.g. 500px)
- v3 (3.8.1): Legend overlaps with chart bars and X-axis labels
- v2 (2.15.4): Chart area shrinks correctly, no overlap
Which versions of Recharts, and which browser / OS are affected by this issue? Did this work in previous versions of Recharts?
- Recharts: v3.8.1 (bug present), v2.15.4 (works correctly — this is a v3 regression)
- React: 18.2.0
- Browser: Chrome 137
- OS: macOS
Root cause analysis
Three independent bugs contribute to this issue:
Bug 1: useElementOffset does not re-measure on resize
File: src/util/useElementOffset.ts
In v2, Legend was a class component using componentDidUpdate() → updateBBox(), which re-measured the Legend's bounding box on every render. In v3, useElementOffset uses a useCallback ref callback that only fires when React re-attaches the ref. When ResponsiveContainer triggers a resize, the Legend content may wrap to more lines (increasing height), but the ref callback is not re-invoked, so the new height is never measured.
Bug 2: LegendSettingsDispatcher / LegendSizeDispatcher use useEffect (post-paint)
File: src/component/Legend.tsx
Both dispatchers use useEffect to update Redux state with Legend settings and size. Since useEffect runs after paint, there is a frame where the chart renders without the correct Legend offset. Notably, SetLegendPayload.ts already uses useLayoutEffect for legend payload dispatch.
Bug 3: legendSlice initial state verticalAlign mismatch
File: src/state/legendSlice.ts
The Redux initial state has verticalAlign: 'middle', but Legend defaultProps use verticalAlign: 'bottom'. In appendOffsetOfLegend (src/util/ChartUtils.ts), the condition verticalAlign !== 'middle' means that when the initial state is 'middle', the offset adjustment for the Legend height is skipped entirely.
Related
What is the current behavior?
In recharts v3, when the browser window is resized from a wide width to a narrow width, the Legend overlaps with the chart content. Legend items wrap to multiple lines as the container narrows, increasing the Legend's height, but the chart area does not shrink to accommodate the taller Legend. This results in the Legend rendering on top of the chart bars and axis labels.
This is a regression from v2, where the chart area correctly adjusts when the Legend wraps.
v3 (3.8.1) — after resize from 1200px to 500px:
v2 (2.15.4) — same resize, no overlap:
What is the expected behavior?
When the Legend wraps to multiple lines due to container resize, the chart area should shrink accordingly (via offset recalculation), preventing any overlap between the Legend and chart content — as it does in v2.
Please provide a demo of the problem in a sandbox
Reproduction code (paste into a new project with
recharts@3):Steps:
Which versions of Recharts, and which browser / OS are affected by this issue? Did this work in previous versions of Recharts?
Root cause analysis
Three independent bugs contribute to this issue:
Bug 1:
useElementOffsetdoes not re-measure on resizeFile:
src/util/useElementOffset.tsIn v2,
Legendwas a class component usingcomponentDidUpdate()→updateBBox(), which re-measured the Legend's bounding box on every render. In v3,useElementOffsetuses auseCallbackref callback that only fires when React re-attaches the ref. WhenResponsiveContainertriggers a resize, the Legend content may wrap to more lines (increasing height), but the ref callback is not re-invoked, so the new height is never measured.Bug 2:
LegendSettingsDispatcher/LegendSizeDispatcheruseuseEffect(post-paint)File:
src/component/Legend.tsxBoth dispatchers use
useEffectto update Redux state with Legend settings and size. SinceuseEffectruns after paint, there is a frame where the chart renders without the correct Legend offset. Notably,SetLegendPayload.tsalready usesuseLayoutEffectfor legend payload dispatch.Bug 3:
legendSliceinitial stateverticalAlignmismatchFile:
src/state/legendSlice.tsThe Redux initial state has
verticalAlign: 'middle', butLegenddefaultProps useverticalAlign: 'bottom'. InappendOffsetOfLegend(src/util/ChartUtils.ts), the conditionverticalAlign !== 'middle'means that when the initial state is'middle', the offset adjustment for the Legend height is skipped entirely.Related
useElementOffsettiming issue)