ConfusionGrid
ConfusionGrid shows where a classifier's errors land, as a grid of row-normalized shares.
- encodes
- row-normalized cell ink; diagonal accented by shape
- precision
- medium
- nodes
- k² + k + 2k labels
A classifier is 73% accurate; ConfusionGrid shows where the other 27% went, which is what an accuracy number hides. Rows are the actual class, columns the predicted class, and each cell's ink is the row-normalized share: of the actual X, where did the predictions go? The diagonal (agreement) is accented by an inset stroke rather than by color, and the ink ramp is the same for agreement and error cells, so good and bad are positions on the grid. That is what keeps it readable under forced colors. Put the one-line key — rows actual, columns predicted — next to every example.
Install
import { ConfusionGrid } from "@microcharts/react/confusion-grid";// counts — real values under “Sample data” below<ConfusionGrid data={counts} title="Classifier" />Sample data
const counts = { labels: ["cat", "dog"], counts: [ [88, 12], [10, 59], ],};Try it
When to use it
Use it for classifier evaluation and any paired-classification agreement: triage vs outcome, plan vs actual, rater A vs rater B. For a single accuracy number reach for Delta; for more than four classes, a full-size heatmap.
Sizing
Variants
accent="errors" moves the accent from the diagonal to the worst confusion cell. label="accuracy" prints the accuracy
number; it is off by default and never rendered without the grid, so the number does not leave its context.
Edge cases
A fifth class doesn't grow the grid. Legibility caps at 4×4, so a k of 5 or more clamps to the first four labels and
rows/columns, with a dev warning steering to a full-size heatmap. The warning is dev-only, but the render truncates in
production too; pass pre-filtered labels/counts if you need a specific four.
Four homes
Accessibility
The row view answers the question practitioners ask — "of the real cats, how many did we call dogs?" — so the summary
phrasing states the row normalization ("% of cats") and the denominator travels with every number. The grid at the top
of this page reads "Accuracy 73%. Most confused: B predicted as C (43% of Bs)." A perfect classifier reads
"Accuracy 100%. No confusion.", and a class with no samples is named rather than hidden: the cat/dog example
above reads "Accuracy 80%. Most confused: cat predicted as dog (20% of cats). No dog samples." The interactive entry
roves the cells with the arrow keys (Home and End jump to the two ends of the diagonal), announcing each cell's actual
and predicted class as a share of the actual class, with the tally it was computed from: "Actual cat, predicted dog:
12% of cats (12)." A row percentage cannot be inverted back to a count without the row total.
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { labels, counts } | k×k matrix; rows actual, columns predicted. |
| normalize | "row" | "none" | Row = recall view (default). |
| accent | "diagonal" | "errors" | Agreement or the worst confusion. |
| label | "accuracy" | "none" | Overall accuracy in the gutter (opt-in). |
| shape | "square" | "round" | Cell shape from the shared vocabulary (default 'square'). |
| color | string | Accent fill override. |
| size | number | Grid square edge in viewBox units. Defaults to a size that grows with the class count. |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.