web-dataviz-recharts · git:20260906.d80c3e7 · 2026-09-06 · sha256 7c7283eae195c5e2
web-dataviz-recharts git:20260906.d80c3e7A
Immutable. This exact content is served forever at /api/v1/blob/7c7283eae195c5e2.
---
name: web-dataviz-recharts
description: Recharts composable chart components - LineChart, BarChart, AreaChart, PieChart, ComposedChart, responsive sizing, custom tooltips, animations
---
# Recharts Patterns
> **Quick Guide:** A Recharts chart is an assembly of independent child components — `XAxis`, `YAxis`, `Tooltip`, `Legend`, `CartesianGrid` and one or more data series — rather than one component taking a config object. Features are added and removed by adding and removing children. Charts render nothing without dimensions, so every chart needs `ResponsiveContainer`, the `responsive` prop, or explicit `width`/`height`. Data and callback props are compared by reference, so an inline `.map()` recomputes the whole chart every render. In v3 `accessibilityLayer` defaults to `true` and internal state is read through hooks rather than cloned props.
**Detailed Resources:**
- [examples/core.md](examples/core.md) — line, bar, area and pie charts, responsive sizing, custom tooltips, multi-axis, stacking
- [examples/advanced.md](examples/advanced.md) — ComposedChart, Brush, reference lines, synchronized charts, real-time updates, scatter shapes, radar
- [reference.md](reference.md) — chart type table and prop cheat sheets for every component
---
<critical_requirements>
## Before writing Recharts code
**Give every chart dimensions** — `ResponsiveContainer`, the `responsive` prop, or explicit `width` and `height`. A chart with none renders an empty SVG and no error.
**Give `ResponsiveContainer` a parent with a resolved height.** It measures its parent, so a parent at `height: 0` or `display: none` measures zero and the chart never appears.
**Hold data arrays and callback props stable across renders** with `useMemo` and `useCallback`. Recharts compares them by reference, so a fresh array or arrow function each render re-derives every point.
**Return HTML from a custom `Tooltip` `content` component.** The tooltip renders into an HTML overlay positioned above the SVG, so SVG elements inside it do not display.
</critical_requirements>
---
**Auto-detection:** recharts, LineChart, BarChart, AreaChart, PieChart, ComposedChart, ScatterChart, RadarChart, RadialBarChart, FunnelChart, Treemap, ResponsiveContainer, XAxis, YAxis, CartesianGrid, dataKey, yAxisId, stackId, syncId, LabelList, ReferenceLine, ReferenceArea, Brush, PolarAngleAxis, isAnimationActive, throttleDelay, accessibilityLayer
**Applies to:**
- Line, bar, area, pie, scatter, radar, radial, funnel and treemap charts
- Composing several series, or several chart types, into one plot
- Multi-axis charts where series carry different units or scales
- Custom tooltips, legends, axis tick formatting and data labels
- Reference lines and areas, brush range selection, cross-chart tooltip sync
- Animation control and real-time update strategies
**Handled elsewhere:**
- Visualizations with no standard chart shape — building marks from scales and path generators is a different job from composing chart components
- Canvas or WebGL rendering for very large point counts — Recharts emits SVG, so every mark is a DOM node
- Physics-based or gesture-driven motion — a series animates between two data states on a duration and an easing, and there is no spring model and no drag-to-animate
- Geographic projections and 3D scenes
- Which colours the series use — `stroke` and `fill` take whatever values a product's visual language supplies
- Accessibility conformance targets — `accessibilityLayer` supplies keyboard navigation and ARIA on the chart, and which level a product must meet is settled elsewhere
---
<philosophy>
Recharts is composition rather than configuration. Adding a `<Tooltip />` adds tooltips; deleting it removes them. There is no options object, and no feature flag — the child list is the configuration.
Two consequences follow. **JSX order is z-order**, because SVG has no `z-index`: an `Area` written before a `Bar` renders behind it. And **`dataKey` is the whole binding contract** — the chart takes one `data` array and each child names the field it reads, so a series without a `dataKey` has nothing to draw.
</philosophy>
---
<decision_framework>
## Which chart type
```
Change over time?
Continuous trend -> LineChart or AreaChart
Discrete periods -> BarChart
Trend over totals -> ComposedChart (Bar + Line)
Part of a whole?
Few categories (< 8) -> PieChart; innerRadius > 0 for a donut
Correlation?
Two variables -> ScatterChart; encode a third in the shape's radius
Multi-dimensional?
3+ measures per item -> RadarChart
Stages of a process?
Successive drop-off -> FunnelChart
Hierarchy?
Nested proportions -> Treemap
```
## Which responsive approach
`ResponsiveContainer` where `debounce`, `aspect`, `onResize`, `minWidth` or `maxHeight` is needed — it measures with a ResizeObserver. The `responsive` prop otherwise, which takes the size from the parent's CSS box and saves a wrapper element.
## When to turn animation off
Data updating more than once a second, point counts in the thousands, print and export, and dashboards rendering many charts at once. Animation redraws the series on every data change.
</decision_framework>
---
<patterns>
## Core patterns
### Pattern 1: Chart composition
A container holding grid, axes, overlays and one child per series. Each child is an independent feature.
```tsx
<ResponsiveContainer width="100%" height={CHART_HEIGHT}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
<Line type="monotone" dataKey="expenses" stroke="#82ca9d" />
</LineChart>
</ResponsiveContainer>
```
Full code: [examples/core.md](examples/core.md)
---
### Pattern 2: Responsive sizing
`ResponsiveContainer` measures its parent and passes concrete pixel dimensions down. The `responsive` prop uses the parent's CSS box directly.
```tsx
<ResponsiveContainer width="100%" aspect={16 / 9} minWidth={MIN_WIDTH}>
<LineChart data={data}>{/* ... */}</LineChart>
</ResponsiveContainer>
// Or, with the parent sized in CSS:
<BarChart data={data} responsive>{/* ... */}</BarChart>
```
Server rendering has no ResizeObserver, so `initialDimension` supplies the first paint's size.
Full code: [examples/core.md](examples/core.md)
---
### Pattern 3: Custom tooltips
`content` takes an element or a function. The component receives `active`, `payload` and `label`, and `payload` is empty until a mark is hovered.
```tsx
function CustomTooltip({
active,
payload,
label,
}: TooltipProps<ValueType, NameType>) {
if (!active || !payload?.length) return null;
return (
<div className="custom-tooltip">
<p>{label}</p>
{payload.map((entry) => (
<p key={entry.name} style={{ color: entry.color }}>
{entry.name}: {entry.value}
</p>
))}
</div>
);
}
<Tooltip content={<CustomTooltip />} />;
```
Passing an element rather than the component is how extra props reach it: `content={<CustomTooltip currencySymbol="EUR " />}`.
Full code: [examples/core.md](examples/core.md)
---
### Pattern 4: Axis configuration
`domain` accepts literals, `"auto"`, `"dataMin"`, `"dataMax"` and arithmetic on those. `tickFormatter` owns label text.
```tsx
<XAxis dataKey="date" tickFormatter={DATE_FORMATTER} angle={-45} textAnchor="end" height={60} />
<YAxis tickFormatter={CURRENCY_FORMATTER} domain={[0, "dataMax + 1000"]} width={80} />
```
`XAxis` defaults to `type="category"` and `YAxis` to `type="number"` — a numeric x-axis needs `type="number"` set explicitly, or the values are treated as labels and spaced evenly.
Full code: [examples/core.md](examples/core.md)
---
### Pattern 5: Pie and donut charts
`PieChart` is the container and `Pie` the series. `Cell` children colour slices individually; `innerRadius` above zero makes it a donut.
```tsx
<PieChart width={CHART_SIZE} height={CHART_SIZE}>
<Pie
data={data}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
outerRadius={OUTER_RADIUS}
innerRadius={INNER_RADIUS}
label
>
{data.map((_, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
```
Full code: [examples/core.md](examples/core.md)
---
### Pattern 6: Multiple axes and multiple series
`yAxisId` pairs a series with an axis. Two axes without distinct ids collide on the default id `0`.
```tsx
<ComposedChart data={data}>
<XAxis dataKey="month" />
<YAxis yAxisId="left" />
<YAxis yAxisId="right" orientation="right" />
<Bar dataKey="sales" yAxisId="left" fill="#8884d8" />
<Line type="monotone" dataKey="trend" yAxisId="right" stroke="#ff7300" />
</ComposedChart>
```
`stackId` is the equivalent for stacking — bars sharing one `stackId` stack rather than group.
Full code: [examples/advanced.md](examples/advanced.md)
---
### Pattern 7: Animation control
Series animate on mount and on data change. The three props are per series.
```tsx
<Line
dataKey="value"
isAnimationActive
animationDuration={ANIMATION_DURATION}
animationEasing="ease-in-out"
animationBegin={0}
/>
```
`animationBegin` staggers series that would otherwise all start together.
Full code: [examples/advanced.md](examples/advanced.md)
---
### Pattern 8: Keeping large or fast-changing charts responsive
Reference stability first, then node count, then event rate.
```tsx
const chartData = useMemo(() => aggregate(rawData), [rawData]); // stable identity
const formatTick = useCallback((value: number) => `$${value}`, []);
<LineChart data={chartData} throttleDelay={THROTTLE_DELAY_MS}>
<Line dataKey="value" isAnimationActive={false} dot={false} />
</LineChart>;
```
`dot={false}` removes one DOM node per point, which is usually the largest single win — aggregating 50,000 rows to 500 before rendering is the next.
Full code: [examples/advanced.md](examples/advanced.md)
</patterns>
---
<red_flags>
## Red flags
**Breaks at runtime:**
- A series with no `dataKey` — `Line`, `Bar`, `Area` and `Pie` each need one to know which field to read
- A custom tooltip with no `active`/`payload` guard — `payload` is empty before the first hover, so `payload[0].value` throws
- `data={rawData.map(...)}` written inline — a new array identity every render, so the chart re-derives everything
- An arrow function as `dataKey` or `tickFormatter` written inline — same reference churn, per tick
- Two `YAxis` elements without distinct `yAxisId` — both claim id `0` and the series bind ambiguously
- `CartesianGrid` left on the default ids while the axes use custom `xAxisId`/`yAxisId` — no grid lines drawn
**Surprising behaviour:**
- SVG has no `z-index` — stacking follows JSX order, so a series written later draws on top
- `ResponsiveContainer` measures with a ResizeObserver, which has not fired during server rendering; `initialDimension` covers the first paint
- `domain` on an axis re-derives when data changes unless `allowDataOverflow` is set
- `syncId` couples every chart carrying the same value — hover and brush state are shared, including with charts elsewhere in the tree
- `Brush` adds a second miniature chart's worth of DOM, which is heavy on a dashboard of many charts
- Pie `label` accepts `true`, an element or a function, and the default labels overlap on small slices — a label function returning `null` below a percentage threshold is the usual fix
- All slices render the same colour until `Cell` children are added
- `accessibilityLayer` is on by default in v3, so keyboard navigation and ARIA attributes are present without being asked for
- Animation redraws the series on every data change, so a chart updating each second is redrawing continuously
</red_flags>