Dense small-multiple layout Everything on one screen, legibility traded away deliberately. It works because every cell shares one y-axis, and the moment they do not the grid becomes a wall of shapes that cannot be compared. Tokens this needs: --muted-foreground, --border, --status-warn The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // SmallMultiples.tsx ──────────────────────────────────────────────────────────────────────── import { cn } from "@/lib/utils"; /** * Forty panels, one instrument. * * Every cell is the same size, the same encoding, and the same y-scale, which * is the one thing the component will not let a caller vary: the domain is * computed once across every series, so a rise in one cell is a rise and not * an autoscale. The page is grey until it isn't. A cell whose latest value * has left the band the rest of the page sits in is drawn in the warn colour, * and nothing else on the page is coloured at all. */ export type Panel = { key: string; values: number[] }; type Props = { panels: Panel[]; unit: string; columns?: number; /** Which cells want a person. Defaults to: the latest value is outside * the 5th–95th percentile of everything on the page. */ abnormal?: (panel: Panel, all: Panel[]) => boolean; onOpen?: (panel: Panel) => void; }; const percentile = (sorted: number[], p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))]; const outsideBand = (panel: Panel, all: Panel[]) => { const pool = all.flatMap((p) => p.values).sort((a, b) => a - b); const last = panel.values[panel.values.length - 1]; return last < percentile(pool, 0.05) || last > percentile(pool, 0.95); }; const W = 74, H = 30; export function SmallMultiples({ panels, unit, columns = 8, abnormal = outsideBand, onOpen }: Props) { // One domain for the page. Per-cell autoscaling would make every flat line // look like a mountain range and the real climb look like everything else. const all = panels.flatMap((p) => p.values); const lo = Math.min(...all), hi = Math.max(...all); const span = hi - lo || 1; const n = panels[0]?.values.length ?? 1; const x = (i: number) => 4 + (i / (n - 1)) * (W - 8); const y = (v: number) => H - 3 - ((v - lo) / span) * (H - 6); return (

{panels.length} panels, one scale: {lo}–{hi} {unit}

{panels.map((panel) => { const flagged = abnormal(panel, panels); const d = panel.values.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(1)} ${y(v).toFixed(1)}`).join(" "); return ( ); })}
); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { SmallMultiples, type Panel } from "./SmallMultiples"; /** * p95 latency for forty services over the last seven samples. Thirty-nine sit * between 12 and 18ms. media has climbed to 31 and is the one coloured cell. */ const PANELS: Panel[] = [ { key: "admin", values: [15, 18, 16, 18, 18, 15, 15] }, { key: "api", values: [16, 18, 16, 13, 13, 18, 16] }, { key: "audit", values: [15, 17, 16, 18, 13, 12, 15] }, { key: "auth", values: [14, 13, 12, 16, 18, 17, 17] }, { key: "billing", values: [12, 16, 15, 15, 17, 17, 16] }, { key: "cache", values: [17, 13, 16, 12, 18, 16, 12] }, { key: "cart", values: [12, 12, 13, 13, 16, 12, 18] }, { key: "catalog", values: [15, 14, 15, 16, 18, 13, 16] }, { key: "cdn", values: [13, 17, 14, 15, 12, 17, 12] }, { key: "checkout", values: [15, 17, 14, 15, 16, 18, 12] }, { key: "config", values: [17, 14, 14, 18, 13, 16, 14] }, { key: "email", values: [12, 12, 16, 18, 12, 15, 12] }, { key: "export", values: [18, 14, 15, 12, 12, 18, 17] }, { key: "feed", values: [12, 13, 13, 12, 15, 15, 17] }, { key: "gateway", values: [15, 15, 12, 16, 17, 13, 18] }, { key: "geo", values: [17, 14, 14, 12, 14, 14, 12] }, { key: "images", values: [15, 18, 12, 13, 13, 17, 12] }, { key: "ingress", values: [12, 12, 15, 18, 15, 13, 17] }, { key: "inventory", values: [16, 13, 15, 16, 13, 17, 18] }, { key: "jobs", values: [13, 15, 17, 15, 12, 15, 15] }, { key: "ledger", values: [13, 12, 14, 18, 18, 16, 14] }, { key: "media", values: [12, 13, 13, 15, 17, 24, 31] }, { key: "metrics", values: [18, 16, 17, 16, 12, 12, 13] }, { key: "notify", values: [13, 15, 14, 12, 18, 16, 14] }, { key: "oauth", values: [18, 14, 15, 12, 12, 12, 13] }, { key: "orders", values: [16, 17, 13, 12, 16, 14, 14] }, { key: "payments", values: [16, 15, 13, 16, 15, 18, 16] }, { key: "profile", values: [13, 18, 15, 13, 17, 13, 14] }, { key: "push", values: [13, 18, 16, 13, 17, 13, 13] }, { key: "queue", values: [17, 17, 16, 13, 17, 15, 15] }, { key: "reports", values: [16, 12, 15, 12, 12, 12, 12] }, { key: "reviews", values: [16, 14, 13, 17, 17, 15, 14] }, { key: "scheduler", values: [15, 18, 16, 15, 14, 16, 13] }, { key: "search", values: [17, 12, 13, 13, 15, 16, 17] }, { key: "sessions", values: [18, 16, 16, 12, 14, 13, 13] }, { key: "shipping", values: [17, 12, 12, 14, 15, 15, 13] }, { key: "sms", values: [12, 12, 13, 14, 14, 16, 16] }, { key: "storage", values: [13, 12, 14, 13, 15, 14, 17] }, { key: "tokens", values: [17, 17, 16, 16, 13, 16, 12] }, { key: "webhook", values: [12, 15, 14, 17, 14, 12, 12] }, ]; export default function Demo() { return { location.hash = `#service-${p.key}`; }} />; }