Small multiples One chart repeated across a dimension, which only works if every panel shares a y-scale. Free scales make nine panels look alike when one of them is ten times the others. Tokens this needs: --card, --muted-foreground, --border, --ring, --chart-1 ──────────────────────────────────────────────────────────────────────── // SmallMultiples.tsx ──────────────────────────────────────────────────────────────────────── export type Series = { key: string; label: string; values: number[] }; /** Alphabetical is a default, not a decision, so it is not on the list. * `given` means the caller has already ordered them by something. */ export type Order = "magnitude" | "deviation" | "given"; type Props = { series: Series[]; /** The unit, printed once with the shared range. */ unit: string; order: Order; /** See one larger without leaving the grid. */ onSelect?: (key: string) => void; columns?: number; }; const W = 132, H = 40, PAD = 4; const mean = (v: number[]) => v.reduce((a, b) => a + b, 0) / v.length; const sort = (series: Series[], order: Order) => { if (order === "given") return series; const score = order === "magnitude" ? (s: Series) => Math.max(...s.values) : (s: Series) => Math.max(...s.values.map((v) => Math.abs(v - mean(s.values)))); return [...series].sort((a, b) => score(b) - score(a)); }; /** * One chart, repeated. Every panel is drawn against the same domain, computed * here from all of them, so a panel cannot autoscale itself into looking * eventful. The stroke is the same token in every cell for the same reason. */ export function SmallMultiples({ series, unit, order, onSelect, columns = 4 }: Props) { const max = Math.max(1, ...series.flatMap((s) => s.values)); const x = (i: number, n: number) => PAD + (i / (n - 1)) * (W - PAD * 2); const y = (v: number) => H - PAD - (v / max) * (H - PAD * 2); return (
0–{max} {unit}, one scale for every panel