Donut Chart of Budget Used Against Remaining
If you are the project manager reporting on the budget, the question is not how much has been spent. It is how much of the money each cost category has taken, and how much is still left to spend, so the sponsor can see both on one glance.
A donut chart answers that. The ring is the spend, each arc is one category’s share of it, and the hole carries the one figure the ring cannot show: what remains. The chart above is bound to the grid beneath it, one row per category and quarter, so filtering the rows redraws the chart. Filter the Quarter column to Q4 and watch the centre change with the arcs.
Why a donut and not a pie
A pie has no room for a second figure. A donut keeps the same part-to-whole reading of the arcs and spends its hole on the number a project manager is asked for first, the remaining budget, so the reader does not have to subtract anything.
The chart over a grid
The chart reads the grid’s rows, so the arcs need nothing to keep them in step. A donut takes a column for the category, x, and a measure, y, that sizes each arc. Here the category is x and the amount spent is y. The centre is a figure you give it, so the example works out the remaining budget from the grid’s own rows and hands it over again whenever the filter changes. 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 rows = [
{ id: 1, category: 'Labour', spent: 212, budget: 240 },
{ id: 2, category: 'Materials', spent: 148, budget: 170 },
{ id: 3, category: 'Software', spent: 52, budget: 60 },
{ id: 4, category: 'Travel', spent: 24, budget: 40 },
];
const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = '240px';
chartEl.style.height = '320px';
document.body.append(gridEl, chartEl);
const grid = createGrid(gridEl, { rowKey: 'id', rows, columns: [
{ field: 'category', title: 'Category', filter: { type: 'set' } },
{ field: 'spent', title: 'Spent', type: 'number' },
{ field: 'budget', title: 'Budget', type: 'number' },
] });
const remaining = () => {
let left = 0;
grid.rows.forEach((r) => { left += r.data.budget - r.data.spent; });
return left;
};
const chart = createChart({ grid, container: chartEl, type: 'donut',
x: 'category', y: 'spent', legend: { values: true },
centre: { value: remaining(), label: 'remaining' } });
grid.on('filter:changed', () =>
chart.update({ centre: { value: remaining(), label: 'remaining' } }));
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, category: 'Labour', spent: 212, budget: 240 },
{ id: 2, category: 'Materials', spent: 148, budget: 170 },
{ id: 3, category: 'Software', spent: 52, budget: 60 },
{ id: 4, category: 'Travel', spent: 24, budget: 40 },
];
const columns = [
{ field: 'category', title: 'Category', filter: { type: 'set' } },
{ field: 'spent', title: 'Spent', type: 'number' },
{ field: 'budget', title: 'Budget', type: 'number' },
];
export function BudgetDonut() {
const gridRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
const grid = gridRef.current.grid;
const centre = () => {
let left = 0;
grid.rows.forEach((r) => { left += r.data.budget - r.data.spent; });
return { value: left, label: 'remaining' };
};
const chart = createChart({ grid, container: chartRef.current, type: 'donut',
x: 'category', y: 'spent', centre: centre() });
const off = grid.on('filter:changed', () => chart.update({ centre: centre() }));
return () => { off?.(); chart.destroy(); };
}, []);
return (<>
<LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 200 }} />
<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 | 'donut' here, a pie with a hole in it. |
x / y |
string | Category and measure columns. Here x is the cost category, which gets one arc, and y is the amount spent that sizes it. |
legend |
boolean or object | Shows the legend. values: true appends each category’s own total after its name. |
centre |
object | A donut’s own figure, drawn in its hole. value is 'total', a number or a string, and label is a second line beneath it. Here the value is the remaining budget. |
Reading the ring
The wider the arc, the more of the money that category has taken. In the example, Labour and Materials together take well over half of what has been spent, and the figure in the middle is the budget still unspent across the rows on show. Filter to one category and the ring becomes a single arc while the centre shows what that category has left. Filter to one quarter and both move together.
Next
See the donut beside the other part-to-whole charts in the bar, line and pie family demo, read the full list of chart types on the charts page, or follow the how-to for a chart that follows the grid.