Skip to content

HeatmapChart

A magnitude per cell, coloured rather than measured. Two shapes of one idea: a grid, for two categorical axes and one number; and a treemap, for parts of a whole with more parts than a pie can hold.

tsx
import { HeatmapChart } from 'neba';

<HeatmapChart
  label="Sessions by hour and weekday"
  categories={['00', '06', '12', '18']}
  series={[
    { name: 'Mon', data: [4, 24, 51, 18] },
    { name: 'Tue', data: [3, 27, 55, 20] }
  ]}
/>;

The data

The series shape is the one every chart shares: see LineChart for the full definition. Here a series is a row of the grid or a group of the treemap, y is the magnitude, and x names the column or the tile.

A null is a gap and the cell is left as surface. It is not drawn as the bottom of the scale, because "nothing happened" and "the least of anything" are two different readings and only one of them is in the data.

The scale runs across every cell in the chart rather than per row. A colour has to mean the same number wherever it appears, which is the whole promise a heatmap makes; min and max pin the ends where a comparison needs a fixed frame.

Props

PropTypeDefaultDescription
series * NebaChartSeries[]A series is a row of the grid or a group of the treemap. y is the magnitude and x names the column or tile; a null is a gap and its cell is not drawn
categories(string | number | Date)[]The column names. Points may carry their own x instead
shape'grid' | 'treemap''grid'grid is a cell per row and column; treemap packs a tile per datum, sized by its share
scale'sequential' | 'diverging''sequential'sequential is one hue pale to deep; diverging is two hues either side of a neutral grey. Reach for diverging only when there is a real zero to diverge about
midpointnumber0Where a diverging scale turns over
minnumberWhere the scale starts. Taken from the data otherwise, so two charts are not comparable until they are given the same bounds
maxnumberWhere the scale ends
valueLabels'none' | 'all''none'Writes each value on its cell. A label that does not fit is dropped rather than clipped, and its ink is picked from the step underneath it
legendboolean | { side, align }trueA scale bar with its ends labelled, not a list of swatches: nothing here has a name and the order is the meaning
variantshared'solid' | 'outline' | 'text''text'Weight of the surface. A chart is a drawing rather than a sheet, so this defaults to text and a chart inside a Card draws no second edge. Use outline for one that stands alone
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Axis type, line weight, marker size, and the height, when none is given
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The sheet's colour family. A series' colour does not come from here: the palette or series.color decides that
densityshared'default' | 'compact''default'Padding only: never the height, never the type scale
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 means no shadow at all
heightnumber | stringsizeHow tall the drawing is. The axis labels are drawn inside it, so a card sized to the chart is a card the chart fits in
labelstringlocale's wordThe chart's accessible name. Read out in place of the drawing, and the caption of the hidden data table under it. Without it the locale's generic word stands in, but what the chart is *of* can only be said here
formatIntl.NumberFormatOptionsHow numbers are written everywhere they appear: the axis, the tooltip, the value labels, the table. Without it, past ten thousand they are compacted (12.4K)
localestringThe language of the chart's own words and dates
legendboolean | NebaChartLegendseries ≥ 2Shown automatically from two series up and left off below that: a legend with one swatch restates the title
tooltipboolean | NebaChartTooltiptrueWhat the pointer uncovers. It never carries a value that is not also in the hidden table
emptyReactNodeWhat to draw when there is nothing to draw

Every native <div> attribute passes through, along with every Box prop. See prop conventions for the shared axes.

Examples

shape

grid is the shape for two categorical axes and one number: hours against weekdays, a cohort against a week. treemap packs a tile per datum, sized by its share, and fills the box.

A treemap is squarified rather than sliced: tiles are laid in rows and each row is closed the moment its aspect ratios stop improving. Sliced, twenty values end as slivers a pixel wide, and a sliver's area is unreadable however exact it is.

A treemap has no axes (every tile is named on its own face), and a negative value has no area to be, so it stays in the table and off the picture.

scale

sequential is one hue from pale to deep, and it is right whenever more is simply more. diverging is two hues either side of a neutral grey, for a value with a middle that means something: over and under target, gained and lost. midpoint says where that middle is.

Reach for diverging only when there is a real zero to diverge about. On a plain magnitude it invents a boundary the data has none of, and the reader spends the chart looking for what changed at the grey.

Neither ramp is the eight-slot categorical palette: colour here encodes size, not identity, and a heatmap in eight hues says its cells are eight unrelated things.

valueLabels · min · max

valueLabels="all" writes each value on its cell, where the cell is big enough for the text with room either side; a label that does not fit is dropped rather than clipped. Inside a filled cell the label picks its ink from the step underneath it, so it stays readable at both ends of the ramp.

min and max pin the scale. Left out, the ends come from the data: which means two charts of different data are not comparable until they are given the same bounds.

legend

The legend is a scale bar with its two ends labelled, not a list of swatches: nothing here has a name, and the order is the meaning. On a diverging scale the midpoint is named under the middle of the bar. legend={false} leaves it off, and legend's own side moves it.

Accessibility

  • The data is also rendered as a visually hidden table, captioned with label, one row per series and one column per category.
  • The plot is focusable; / walk the cells, Escape clears the tooltip.
  • The scale legend gives the two ends of the range as numbers, so the ramp never has to be read by eye alone.

Released under the MIT License