Lattice Grid

blog

Floating Bar Chart for Shift Coverage

Building…
Loading a live grid…

You are an operations lead, and you have to know whether every hour of the day has someone on duty. The question is not how many hours each team works. It is where the cover stops: which hour does the front desk go dark, and does anyone else pick it up? A list of start and end times makes you do the sums in your head. A floating bar chart draws each shift as a bar that starts at its first hour and stops at its last, so a gap in cover is white space you can see.

The example below draws one bar per shift, the shifts grouped by team along the bottom (F1 is the first front desk shift, S2 the second support shift), with the hour of the day up the side. The chart is bound to the grid, so it draws exactly the rows the grid is showing. Put a team name in the box, or open the filter on the Team column and keep only two of them, and the other bars leave the chart on the next frame. Clear the filter and they come back.

Why a floating bar and not a plain bar

A plain bar always starts at zero, so it can only say how long something is. A shift is not a length, it is a stretch of the clock with a start and an end, and what you need to see is the stretch between two shifts where nothing is drawn. A floating bar takes a low and a high column and draws only the span between them, so the empty space between bars is the answer. If you would rather read the hours left to right, horizontalFloatingBar takes the same options and lays the day along the bottom.

The chart is a view of the grid

There is no second data set to keep in step. createChart takes the grid itself, reads its filtered rows, and redraws whenever they change. x names the column that gives each shift its own bar, and y is a pair, the column holding the start hour and the column holding the end hour. The axis follows the hours the visible shifts cover. Sort, filter or edit the grid and the chart follows, because a chart of rows you cannot see would be describing a different rota.

The code, in plain JavaScript

import { createGrid } from '@toclocoinc/lattice-grid';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import '@toclocoinc/lattice-grid/css';

const shifts = [
  ['Front desk', 7, 10], ['Front desk', 10, 13], ['Front desk', 14, 17],
  ['Front desk', 18, 21], ['Support', 6, 9], ['Support', 9, 12], ['Support', 13, 16],
  ['Support', 16, 19], ['Support', 20, 23], ['Dispatch', 5, 8], ['Dispatch', 9, 12],
  ['Dispatch', 12, 15], ['Dispatch', 16, 20], ['Dispatch', 21, 24],
  ['Warehouse', 6, 10], ['Warehouse', 11, 14], ['Warehouse', 15, 18],
  ['Warehouse', 19, 22], ['Night ops', 0, 4], ['Night ops', 5, 7],
  ['Night ops', 22, 24],
];
const seen = {};
const rows = shifts.map(([team, start, end], i) => {
  seen[team] = (seen[team] ?? 0) + 1;
  return { id: i + 1, team, shift: `${team[0]}${seen[team]}`, start, end };
});

const grid = createGrid(document.querySelector('#grid'), {
  rowKey: 'id',
  columns: [
    { field: 'team', title: 'Team', filter: { type: 'set' } },
    { field: 'shift', title: 'Shift' },
    { field: 'start', title: 'Start hour', type: 'number', filter: { type: 'number' } },
    { field: 'end', title: 'End hour', type: 'number', filter: { type: 'number' } },
  ],
  rows,
});

createChart({
  grid, container: '#chart', type: 'floatingBar',
  x: 'shift', y: ['start', 'end'],
  title: 'Shift coverage by team, hour of day',
});

Give #chart and #grid a height in your page and that is the whole screen.

The same thing in React

The React adapter is a factory: you pass it React and createGrid, and it returns the component. The component forwards a ref whose .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 { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import '@toclocoinc/lattice-grid/css';

const LatticeGrid = createLatticeGrid({ React, createGrid });
const columns = [
  { field: 'team', title: 'Team', filter: { type: 'set' } },
  { field: 'shift', title: 'Shift' },
  { field: 'start', title: 'Start hour', type: 'number', filter: { type: 'number' } },
  { field: 'end', title: 'End hour', type: 'number', filter: { type: 'number' } },
];

export function ShiftCoverage({ rows }) {
  const gridRef = useRef(null);
  const chartEl = useRef(null);

  useEffect(() => {
    const chart = createChart({
      grid: gridRef.current.grid, container: chartEl.current, type: 'floatingBar',
      x: 'shift', y: ['start', 'end'],
      title: 'Shift coverage by team, hour of day',
    });
    return () => chart.destroy();
  }, []);

  return (
    <>
      <div ref={chartEl} style={{ height: 300 }} />
      <LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 300 }} />
    </>
  );
}

Pass it the same twenty-one rows as the first listing, with team, shift, start and end on each.

The options used

Option Type What it does
grid Grid Required. The grid to read.
container Element or string Required. Where to draw.
type string 'floatingBar' here. 'horizontalFloatingBar' is the same chart turned on its side.
x / y string The category column, one bar per distinct value, and for a floating bar y is a [lowCol, highCol] pair.
title string Drawn above the plot.

Go further

The charts page lists every chart type the same grid can feed, and the bar, line and pie family demo draws the floating bar beside the rest of the Cartesian types. To see how a chart keeps following the grid when you filter by clicking, read how to draw 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.

  • Heatmap of Sign-ups by Hour and Weekday

    Bind a heatmap to a grid of sign-ups: shade every weekday and hour by volume to time a campaign send, and filter the rows to redraw it.

  • 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.

All posts RSS