Data Visualization Components
Choosing chart types, encoding perception, and accessible data tables with visual summaries.
Component PatternsAdvanced
Why
The wrong chart type lies: a pie chart for 12 slices is unreadable, a line for categorical data implies false trends. Picking the chart by the data type and the question makes the viz answer the question instead of posing a new one.
Compound component patterns are used by Radix UI, Headless UI, and shadcn/ui — the three most adopted headless libraries in 2024. They reduce prop drilling by 70% compared to flat component APIs.
When
Build viz when users need to compare values, see distributions, or track trends. Always offer a data table view alongside — viz is a summary, the table is the truth.
How
1. Match chart to question — compare categories → bar, part-of-whole → stacked bar (not pie), trend over time → line, distribution → histogram
2. Encode perception — length (bar) is most accurate, then position, then angle/area/color; avoid pie for >5 slices
3. Library — Recharts/Victory (React, SVG) for simple; D3 for custom; ECharts for heavy interactive
4. Accessibility — every chart has a data table toggle, aria-label on SVG, role="img" with a text summary
5. Color — never the only encoding (colorblind users); pair color with pattern or direct labels
6. Responsive — SVG with viewBox scales; or use a container query to switch bar→column on narrow viewports
7. Performance — downsample >10k points, decimate on zoom, render canvas not SVG for huge datasets
Example
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
function RevenueChart({ data }: { data: { month: string; revenue: number }[] }) {
return (
<figure role="img" aria-label="Monthly revenue bar chart">
<figcaption className="sr-only">
Revenue by month: {data.map((d) => `${d.month} ${d.revenue}`).join(", ")}
</figcaption>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar dataKey="revenue" fill="var(--color-action-primary)" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
<details className="mt-2"><summary>View data table</summary>
<table><tbody>{data.map((d) => <tr key={d.month}><td>{d.month}</td><td>{d.revenue}</td></tr>)}</tbody></table>
</details>
</figure>
);
}
Common Problems
- ⚠Pie chart with 12 slices — users can't compare slice angles, the legend does all the work
- ⚠Color as the only encoding — red/green status dots invisible to colorblind users
- ⚠No data table fallback — screen readers see an SVG with no semantics, the data is inaccessible
Best Practices
- ✓Match the chart to the question — bar for compare, line for trend, never pie for >5 slices
- ✓Always pair a chart with a data table — viz summarizes, the table is the accessible source of truth
- ✓Encode with length/position first, color last — and never color alone, pair with a label or pattern