Lattice Grid

blog

Heatmap of Sign-ups by Hour and Weekday

Building…
Loading a live grid…

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.

Read next

  • Pie Chart of Revenue Share by Channel

    Bind a pie chart to a grid of revenue by channel: show each channel as a share of the quarter, and filter the rows to redraw the slices.

  • Violin Chart of Salary Bands by Level

    Bind a violin chart to a grid of salaries: see the shape of pay at each level, including two bands inside one, and filter the rows to redraw it.

  • Ridgeline Chart of Hourly Demand by Weekday

    Bind a ridgeline chart to a grid of store orders: stack each weekday by hour to see how Saturday differs from Tuesday, and filter to redraw it.

All posts RSS