Heatmap of Sign-ups by Hour and Weekday
If you are the growth lead deciding when to send the next campaign, the question is not how many people signed up last week. It is which hour of which weekday they sign up in, so the send lands while attention is already there rather than after it has gone.
A heatmap answers that. Weekdays run down the side, hours run across the top, and each cell is shaded by the sign-ups in it, so the busy crossings stand out at a glance. The chart above is bound to the grid beneath it, one row per weekday and hour, so filtering the rows redraws the chart. Filter the Weekday column to Saturday and Sunday and see whether the weekend peaks when the week does.
Why a heatmap and not a line chart
A line chart gives you one line per weekday, and seven lines over four or more hours is a tangle you have to untangle by eye. A heatmap puts every weekday and hour in its own cell, so you read the pattern as colour and look up the figure only for the cell you care about.
The chart over a grid
The chart reads the grid’s rows, so there is nothing to keep in step. A heatmap takes a column for the columns of the matrix, x, a column for its rows, series, and a measure, y, that shades each cell. Here the hour is x, the weekday is series and the sign-up count is y. Here is the example in plain JavaScript, with sample rows inline in place of your own.
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
const hours = ['08:00', '12:00', '16:00', '20:00'];
const counts = {
Mon: [42, 61, 48, 30], Tue: [58, 74, 66, 35], Wed: [55, 70, 62, 33],
Thu: [60, 78, 64, 36], Fri: [40, 52, 38, 24], Sat: [14, 22, 26, 31],
Sun: [12, 18, 24, 38],
};
const rows = Object.entries(counts)
.flatMap(([weekday, list]) => list.map((signups, i) => ({ weekday, hour: hours[i], signups })))
.map((r, i) => ({ id: i + 1, ...r }));
const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = '260px';
chartEl.style.height = '320px';
document.body.append(gridEl, chartEl);
const grid = createGrid(gridEl, {
rowKey: 'id',
rows,
columns: [
{ field: 'weekday', title: 'Weekday', filter: { type: 'set' } },
{ field: 'hour', title: 'Hour' },
{ field: 'signups', title: 'Sign-ups', type: 'number' },
],
});
createChart({ grid, container: chartEl, type: 'heatmap', x: 'hour', y: 'signups',
series: 'weekday', legend: true, title: 'Sign-ups by weekday and hour' });
The same thing in React
The React adapter is a factory, so you build the component once at module scope. The ref’s grid is the real grid, which is what the chart binds to.
import React, { useEffect, useRef } from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const rows = [
{ id: 1, weekday: 'Tue', hour: '08:00', signups: 58 }, { id: 2, weekday: 'Tue', hour: '12:00', signups: 74 },
{ id: 3, weekday: 'Tue', hour: '16:00', signups: 66 }, { id: 4, weekday: 'Tue', hour: '20:00', signups: 35 },
{ id: 5, weekday: 'Sat', hour: '08:00', signups: 14 }, { id: 6, weekday: 'Sat', hour: '12:00', signups: 22 },
{ id: 7, weekday: 'Sat', hour: '16:00', signups: 26 }, { id: 8, weekday: 'Sat', hour: '20:00', signups: 31 },
];
const columns = [
{ field: 'weekday', title: 'Weekday', filter: { type: 'set' } },
{ field: 'hour', title: 'Hour' },
{ field: 'signups', title: 'Sign-ups', type: 'number' },
];
export function SignupsHeatmap() {
const gridRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
type: 'heatmap', x: 'hour', y: 'signups', series: 'weekday', legend: true });
return () => chart.destroy();
}, []);
return (<>
<LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 220 }} />
<div ref={chartRef} style={{ height: 280 }} />
</>);
}
The options used
These are the rows of the charts reference for the options in the listings above.
| Option | Type | What it does |
|---|---|---|
grid |
Grid | Required. The grid to read. |
container |
Element or string | Required. Where to draw. |
type |
string | 'heatmap' here, one of the chart types the charts module draws. |
x / y |
string | Category and measure columns. Here x is the hour, which gets one column of cells, and y is the sign-up count that shades each cell. |
series |
string | Splits the measure into one series per distinct value. Here each weekday is a row of the matrix. |
legend |
boolean or object | Shows the colour scale, low to high. |
title |
string | Drawn above the plot. |
Reading the cells
The darker the cell, the more sign-ups landed in that hour. In the example, Tuesday to Thursday peak at midday and stay strong through the afternoon, which is the window for a weekday send. Saturday and Sunday turn that around: they are quietest in the morning and build toward the evening, so a weekend campaign belongs after 20:00 rather than at lunch. The grid still drives it: filter to one signup source or one region and every cell is shaded again from the rows that are left.
Next
See the heatmap beside the other matrix charts in the heatmap demo, read the full list of chart types on the charts page, or follow the how-to for a chart that follows the grid.