Data
Heatmap
A grid of values read by weight.
When to reach for it
Examples
default
| Row | 00 | 03 | 06 | 09 | 12 | 15 | 18 | 21 |
|---|---|---|---|---|---|---|---|---|
| Mon | 8Mon, 00 | 8Mon, 03 | 32Mon, 06 | 53Mon, 09 | 67Mon, 12 | 72Mon, 15 | 67Mon, 18 | 53Mon, 21 |
| Tue | 8Tue, 00 | 8Tue, 03 | 32Tue, 06 | 53Tue, 09 | 67Tue, 12 | 72Tue, 15 | 67Tue, 18 | 53Tue, 21 |
| Wed | 8Wed, 00 | 8Wed, 03 | 32Wed, 06 | 53Wed, 09 | 67Wed, 12 | 72Wed, 15 | 67Wed, 18 | 53Wed, 21 |
| Thu | 8Thu, 00 | 8Thu, 03 | 32Thu, 06 | 53Thu, 09 | 67Thu, 12 | 72Thu, 15 | 67Thu, 18 | 53Thu, 21 |
| Fri | 8Fri, 00 | 8Fri, 03 | 32Fri, 06 | 53Fri, 09 | 67Fri, 12 | 72Fri, 15 | 67Fri, 18 | 53Fri, 21 |
| Sat | 2Sat, 00 | 2Sat, 03 | 9Sat, 06 | 15Sat, 09 | 19Sat, 12 | 20Sat, 15 | 19Sat, 18 | 15Sat, 21 |
| Sun | 2Sun, 00 | 2Sun, 03 | 9Sun, 06 | 15Sun, 09 | 19Sun, 12 | 20Sun, 15 | 19Sun, 18 | 15Sun, 21 |
diverging
| Row | W1 | W2 | W3 | W4 | W5 | W6 |
|---|---|---|---|---|---|---|
| Platform | 0 | 34 | 36 | 6 | -30 | -38 |
| Growth | 40 | 17 | -21 | -40 | -22 | 16 |
| Mobile | -10 | -38 | -31 | Mobile, W4: no data | 36 | 34 |
| Data | -37 | -7 | 29 | 39 | 13 | -25 |
Notes
A grid of values read by weight — a calendar of activity, a confusion matrix, an hour-by-weekday load.
The one form in the set that a monochrome system renders BETTER than a chromatic one. A heatmap needs a scale whose order the eye can read without consulting a legend, and lightness is the only channel that is unambiguously ordered — which is why "one hue, light to dark" is the standing advice everywhere else, and why the rainbow heatmap is a perennial mistake. Here there is no hue left to get wrong.
It is built as a real <table> rather than as an SVG, so the structure a screen reader walks is the structure the eye reads: a header row, a header column, and a cell that announces its own value. Nothing else in the package can say that, and nothing else needs to — which is also why this one takes no rendering engine at all.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| cellsrequired | HeatmapCell[] | — | |
| columnsrequired | string[] | Column headers, in the order they should appear. | |
| rowsrequired | string[] | Row headers, in the order they should appear. | |
| titlerequired | string | What the grid shows. Required, and it names the table. | |
| className | string | — | |
| description | ReactNode | A line under the title — the unit, the window, the caveat. | |
| domain | [number, number] | The domain, as `[min, max]`. Derived from the data when omitted. Pin it whenever two grids are meant to be compared: two heatmaps on independent domains look alike and mean different things, which is the one failure a shared legend cannot fix. | |
| formatValue | (value: number) => string | (value) => value.toLocaleString() | Formats a value for the cell's announcement and its printed label. |
| scale | HeatmapScale | 'sequential' | — |
| showTitle | boolean | false | Prints the title above the grid instead of hiding it from sight. |
| showValues | boolean | false | Prints the number inside each cell. Only legible on a coarse grid. |
Types
export type HeatmapScale = 'sequential' | 'diverging'Accessibility
- A real <table>, not an SVG: the structure a screen reader walks is the structure the eye reads, and every cell announces its own row, column and value.
- Lightness is the only channel that is unambiguously ordered, which is why the standing advice everywhere else is “one hue, light to dark”. Here there is no hue left to get wrong.
- A null is drawn as a dashed outline, never as the palest cell — a missing reading is not a zero.
- Pin domain whenever two grids are compared: on independent domains they look alike and mean different things, which is the one failure a shared legend cannot fix.