Lattice Grid

blog

Bullet Chart for KPI Targets by Region

Building…
Loading a live grid…

If you direct sales, the monthly review asks one thing of every region: did it make its number, and if not, how far short did it fall? A table of revenue and target gives you both figures, but you still do the subtraction in your head, one region at a time, and “close” and “badly off” look the same in a column of digits.

A bullet chart answers it in one glance per row. Each region is a bar of revenue, a marker across it for the target, and shaded poor, fair and good bands behind. A bar that stops inside the poor band is a region to talk about; a bar that reaches the marker is not. The chart above is bound to the grid beneath it, so filtering the rows redraws the chart. Filter the Territory column to EMEA and only those regions are left.

Why a bullet chart and not a gauge

A gauge gives one KPI a whole dial, so twenty-four regions would be twenty-four dials you cannot compare. A bullet chart is a gauge laid flat: every region shares one scale, so the bars compare against each other as well as against their own targets.

The chart over a grid

The chart reads the grid’s rows, so there is nothing to keep in step. The bands are columns too, so each region can have its own. Here is the example in plain JavaScript, with six inline regions standing in for your own rows.

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

const rows = [
  { id: 1, region: 'North East', territory: 'Americas', actual: 620, target: 700, poor: 420, fair: 595, good: 840 },
  { id: 2, region: 'Midwest', territory: 'Americas', actual: 710, target: 680, poor: 408, fair: 578, good: 816 },
  { id: 3, region: 'DACH', territory: 'EMEA', actual: 820, target: 780, poor: 468, fair: 663, good: 936 },
  { id: 4, region: 'Iberia', territory: 'EMEA', actual: 290, target: 360, poor: 216, fair: 306, good: 432 },
  { id: 5, region: 'Japan', territory: 'APAC', actual: 640, target: 660, poor: 396, fair: 561, good: 792 },
  { id: 6, region: 'Greater China', territory: 'APAC', actual: 560, target: 700, poor: 420, fair: 595, good: 840 },
];

const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = '260px';
chartEl.style.height = '300px';
document.body.append(gridEl, chartEl);

const grid = createGrid(gridEl, {
  rowKey: 'id',
  rows,
  columns: [
    { field: 'region', title: 'Region' },
    { field: 'territory', title: 'Territory', filter: { type: 'set' } },
    { field: 'actual', title: 'Revenue', type: 'number' },
    { field: 'target', title: 'Target', type: 'number' },
  ],
});

createChart({ grid, container: chartEl, type: 'bullet', x: 'region', y: 'actual', target: 'target',
  ranges: ['poor', 'fair', 'good'], min: 0, title: 'Revenue against target by region ($k)' });

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, region: 'North East', territory: 'Americas', actual: 620, target: 700 },
  { id: 2, region: 'DACH', territory: 'EMEA', actual: 820, target: 780 },
  { id: 3, region: 'Iberia', territory: 'EMEA', actual: 290, target: 360 },
  { id: 4, region: 'Japan', territory: 'APAC', actual: 640, target: 660 },
];
const columns = [
  { field: 'region', title: 'Region' },
  { field: 'territory', title: 'Territory', filter: { type: 'set' } },
  { field: 'actual', title: 'Revenue', type: 'number' },
  { field: 'target', title: 'Target', type: 'number' },
];

export function RegionTargets() {
  const gridRef = useRef(null);
  const chartRef = useRef(null);
  useEffect(() => {
    const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
      type: 'bullet', x: 'region', y: 'actual', target: 'target', ranges: [400, 600, 900], min: 0 });
    return () => chart.destroy();
  }, []);
  return (<>
    <LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 240 }} />
    <div ref={chartRef} style={{ height: 260 }} />
  </>);
}

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 'bullet' here.
x / y string The column naming each row (the region) and the column drawn as the bar (revenue).
target string On a bullet, the column holding each row’s target, drawn as a marker across the bar.
ranges (number or string)[] The bands’ upper edges, ascending. A number is the same for every row; a column name is read per row. [400, 600, 900] shades the scale up to 400, 400 to 600 and 600 to 900.
min / max number Pin the ends of the scale.
title string Drawn above the plot.

Keep one scale, or give each row its own

By default every region is read against one scale and one axis, so a long bar means more revenue. If your regions differ so much in size that the small ones vanish, the charts reference describes a bullet option whose scale can be 'row', which gives each row its own scale, as a bullet in a grid cell has, with no shared axis. Its orientation can also be 'vertical'.

Next

See every Cartesian type on one dataset in the Cartesian chart 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.

  • 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