ComponentAdded in v15.1

Heatmap Chart

A matrix of values, each cell a level of one colour, where two categories meet.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrHeatmapChart } from 'ngwr/charts/heatmap-chart';

@Component({ imports: [WrHeatmapChart] })
export class MyComponent {}

Basic

One array per row, one value per column. The scale runs from the lowest value to the highest, cut into five levels.

<wr-heatmap-chart
  [rows]="['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']"
  [columns]="['08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00']"
  [data]="visits"
  ariaLabel="Visits by weekday and time"
/>
visits: WrHeatmapMatrix = [
  [12, 28, 46, 31, 27, 44, 19], // Mon
  [14, 30, 49, 33, 29, 47, 21], // Tue
  // …one array per row, one value per column
];
08:00 10:00 12:00 14:00 16:00 18:00 20:00
MonTueWedThuFriSatSun

Values

showValues prints each value in its cell, in black or white, whichever reads better there. A value wider than its cell is left out, never cut.

<wr-heatmap-chart
  [rows]="days"
  [columns]="slots"
  [data]="visits"
  ariaLabel="Visits by weekday and time"
  showValues
/>
08:00 10:00 12:00 14:00 16:00 18:00 20:00
MonTueWedThuFriSatSun

Scale

min and max hold the scale, so two charts read the same. levels sets how many shares it is cut into, from 2 to 10.

<!-- Held at 0 to 100, in four levels -->
<wr-heatmap-chart
  [rows]="teams"
  [columns]="months"
  [data]="resolved"
  [min]="0"
  [max]="100"
  [levels]="4"
  [valueFormat]="percent"
  ariaLabel="Tickets resolved within a day, by team and month"
  showValues
/>
percent = (value: number) => `${value}%`;
Jan Feb Mar Apr May Jun
BillingShippingReturnsAccounts

Missing values

A null cell is no data, not zero: it is hatched and outlined, and the legend keys it. noDataLabel names it.

<wr-heatmap-chart
  [rows]="stores"
  [columns]="days"
  [data]="sales"
  ariaLabel="Sales by store and day"
  noDataLabel="Closed"
  showValues
/>
sales: WrHeatmapMatrix = [
  [42, 38, 45, 51, 63, 71, 34],
  [55, 52, 58, 60, 74, 66, null], // closed on Sunday
  [31, 35, 33, 36, 40, null, 18], // closed on Saturday
];
Mon Tue Wed Thu Fri Sat Sun
CentreAirportStation

Many columns

Names wider than their columns are thinned to every second, third or later one. A screen reader, the tooltip and the table still get every name.

<wr-heatmap-chart [rows]="days" [columns]="hours" [data]="orders" ariaLabel="Orders by weekday and hour" />
hours = Array.from({ length: 24 }, (_, h) => `${String(h).padStart(2, '0')}:00`);
// 00:00 … 23:00, drawn as every second or third name where they do not fit
00:00 01:00 02:00 03:00 04:00 05:00 06:00 07:00 08:00 09:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 20:00 21:00 22:00 23:00
MonTueWedThuFriSatSun

Colour

color is the highest level. Every lower level is mixed from it toward the surface.

<wr-heatmap-chart
  [rows]="stores"
  [columns]="days"
  [data]="sales"
  color="var(--wr-color-success)"
  ariaLabel="Sales by store and day"
  noDataLabel="Closed"
  showValues
/>
Mon Tue Wed Thu Fri Sat Sun
CentreAirportStation

Data table

dataTable adds a table only screen readers reach: a row per row, a column per column. Nothing changes on screen.

<wr-heatmap-chart [rows]="days" [columns]="slots" [data]="visits" ariaLabel="Visits by weekday and time" dataTable />
08:00 10:00 12:00 14:00 16:00 18:00 20:00
MonTueWedThuFriSatSun
Visits by weekday and time
08:0010:0012:0014:0016:0018:0020:00
Mon12284631274419
Tue14304933294721
Wed11274430264118
Thu15325235315024
Fri13294733343830
Sat6183538363022
Sun4142933312415

Accessibility

PartBehaviour
CellsOne role="img", named by ariaLabel. The cells announce nothing.
Value Colour is the only thing that encodes it, as in every heatmap. showValues and dataTable give it in text.
Equal values One level, the lowest, and one swatch in the legend: an all-zero matrix is a faint tint, not the peak.
Empty cellHatched and outlined, so it differs from the lowest level without colour.
Row and column names Real text. Column names that do not fit are thinned to every n-th, and the rest stay readable to a screen reader. A long row name is cut with an ellipsis. The tooltip and the table carry every name whole.
LegendHidden from screen readers, like an axis. The table has the figures.
KeyboardNo tab stop. The tooltip is pointer only.
RTLThe matrix mirrors, like a table: the row names move to the right.

API

NameDescriptionTypeDefault
dataThe values, rows × columns: one array per row, in the order of rows, each with one value per column, in the order of columns. A null, a value that is not a finite number, or one past the end of a shorter row is an EMPTY cell — no data, drawn hatched and outlined rather than as the lowest level.WrHeatmapMatrix[]
rowsThe rows' names, top to bottom, beside the matrix at inline start. The matrix is as tall as the longer of this list and data; a row with no name here is named by its position, from 1. A name wider than --wr-heatmap-chart-row-label-max-width (10rem) is cut with an ellipsis, and the tooltip and the data table carry it whole.readonly string[][]
columnsThe columns' names, from inline start, above the matrix. The matrix is as wide as the longer of this list and the longest row of data; a column with no name here is named by its position, from 1. Where the names are wider than their columns, the chart draws every second, third or later one and leaves the rest out of sight, still read by a screen reader; the tooltip and the data table name every column. A single column too narrow for its name cuts it with an ellipsis.readonly string[][]
colorThe colour of the highest level: any CSS colour, var(--wr-…) included. Every lower level is mixed from it toward the surface, the lowest a faint tint.string'var(--wr-color-primary)'
minThe value of the lowest level. null takes the lowest value in data; a value given holds the scale there, and a cell below it takes the lowest level.number | nullnull
maxThe value of the highest level. null takes the highest value in data; a value given holds the scale there, and a cell above it takes the highest level. Bounds given in the wrong order are swapped. A scale with no span — every value the same, or min equal to max — has one level, the lowest: every value is drawn on it, an all-zero matrix as a faint tint rather than as the peak, and the legend keys that one level.number | nullnull
levelsHow many levels the scale is cut into, each an equal share of it from min to max: whole numbers from 2 to 10. Fewer levels are easier to tell apart; the value text and the data table carry what the levels round.number5
showValuesPrint each value in its cell, compact from a thousand (1.2K). A value is printed whole or not at all: one wider than its cell is left out rather than cut, and the tooltip and the data table still carry it. Black or white, whichever reads better on the cell — picked with CSS relative colour syntax; a browser without it (Firefox before 128) prints the value on a small chip of the surface in the chart's text colour instead, which reads on every level. An empty cell prints nothing.booleanfalse
showLegendShow the colour-scale legend under the matrix, from min to max, with a key for an empty cell while there is one.booleantrue
tooltipShow a tooltip for the cell under the pointer: its row's name, its column's name and its value, or noDataLabel for an empty cell. Pointer only — the data table is where the same numbers are for a keyboard or a screen reader.booleantrue
valueFormatWrites every number the chart shows: the values in the cells, the legend, the tooltip and the data table. null uses Intl.NumberFormat in LOCALE_ID, compact in the cells from a thousand up.((value: number) => string) | nullnull
dataTableRender the chart's figures as a table that only assistive technology reaches: a row per row, headed by its name, and a column per column, every value written the way the tooltip writes it, and an empty cell as noDataLabel. The cells are one named role="img", which hides everything inside it, so without the table the numbers are nowhere a screen reader can walk. It is rendered beside the matrix, clipped to nothing: it takes no room, draws nothing and holds no tab stop.booleanfalse
ariaLabelAccessible name of the matrix. Its cells carry no text a screen reader reaches, so this is where to say what the chart shows — or turn on dataTable. Falls back to heatmapChart.label, which is also the data table's caption.string | nullnull
noDataLabelWhat an empty cell is called, in the tooltip, the legend's key and the data table — "Closed", "Not measured". Falls back to heatmapChart.noData.string | nullnull

Types

type WrHeatmapMatrix = readonly (readonly (number | null)[])[];
NameDescriptionTypeDefault
WrHeatmapMatrixThe values, rows × columns. A null, a value that is not a finite number or one past the end of a shorter row is an empty cell.readonly (readonly (number | null)[])[]—

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/charts/heatmap-chart's own element.

VariableDefaultDeclared on
--wr-heatmap-chart-active-ringvar(--wr-color-on-surface).wr-heatmap-chart
--wr-heatmap-chart-active-ring-width2px.wr-heatmap-chart
--wr-heatmap-chart-cell-height2rem.wr-heatmap-chart
--wr-heatmap-chart-cell-padding-xvar(--wr-space-xs).wr-heatmap-chart
--wr-heatmap-chart-cell-radius2px.wr-heatmap-chart
--wr-heatmap-chart-colorvar(--wr-color-on-surface).wr-heatmap-chart
--wr-heatmap-chart-empty-bordervar(--wr-color-on-surface-muted).wr-heatmap-chart +1 variant override
--wr-heatmap-chart-empty-hatchvar(--wr-border-strong).wr-heatmap-chart +1 variant override
--wr-heatmap-chart-font-familyvar(--wr-font-family-base).wr-heatmap-chart
--wr-heatmap-chart-gap2px.wr-heatmap-chart
--wr-heatmap-chart-label-colorvar(--wr-color-on-surface-muted).wr-heatmap-chart
--wr-heatmap-chart-label-font-sizevar(--wr-text-xs).wr-heatmap-chart
--wr-heatmap-chart-label-gapvar(--wr-space-sm).wr-heatmap-chart
--wr-heatmap-chart-label-line-heightvar(--wr-text-xs-leading).wr-heatmap-chart
--wr-heatmap-chart-legend-colorvar(--wr-color-on-surface-muted).wr-heatmap-chart
--wr-heatmap-chart-legend-font-sizevar(--wr-text-xs).wr-heatmap-chart
--wr-heatmap-chart-legend-gapvar(--wr-space-sm).wr-heatmap-chart
--wr-heatmap-chart-legend-margin-topvar(--wr-space-md).wr-heatmap-chart
--wr-heatmap-chart-min-share15%.wr-heatmap-chart
--wr-heatmap-chart-row-label-max-width10rem.wr-heatmap-chart
--wr-heatmap-chart-surfacevar(--wr-color-surface).wr-heatmap-chart +1 variant override
--wr-heatmap-chart-swatch-height0.75rem.wr-heatmap-chart
--wr-heatmap-chart-swatch-width1.25rem.wr-heatmap-chart
--wr-heatmap-chart-value-font-sizevar(--wr-text-xs).wr-heatmap-chart
--wr-heatmap-chart-value-font-weightvar(--wr-font-weight-medium).wr-heatmap-chart

Dependencies

Built onPopover

See also