Area Chart for Stacked Cloud Spend
If you run a platform team, you get asked the same question every month: the cloud bill went up, so which service did it, and what did the total do? A table of weekly costs per service holds the answer and hides it. You can read every row and still not see that one service has been climbing for six weeks while the rest sat flat.
A stacked area chart answers it at a glance. Each service is a band, the bands sit on top of each other, and the top edge is the total. The band that thickens is the one that grew. The chart above is bound to the grid beneath it, so filtering the rows redraws the chart. Filter the Service column to compute and database and the other bands disappear, and the total drops to what those two cost.
Why an area chart and not a line chart
A line chart gives every service its own line, which is right when you are comparing services. It cannot show the total, because the total is not a line on the chart, it is the sum of all of them. Stacking the areas puts the total and the shares in one picture. Use lines when you want to compare services with each other, and a stacked area when you want to know what the whole did and who moved it.
The chart over a grid
The chart reads the grid’s rows, so there is nothing to keep in step. Here is the whole example in plain JavaScript. It builds the grid from thirty inline rows, then draws the chart from it.
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
const weeks = ['2026-09-07', '2026-09-14', '2026-09-21', '2026-09-28', '2026-10-05', '2026-10-12'];
const spend = {
Compute: [4200, 4350, 4900, 5600, 6400, 7300],
Storage: [1800, 1850, 1900, 1950, 2050, 2100],
Database: [2600, 2600, 2700, 2900, 2950, 3000],
Network: [900, 950, 1300, 1250, 1100, 1150],
Queues: [400, 420, 410, 450, 470, 520],
};
const rows = Object.entries(spend).flatMap(([service, costs], s) =>
costs.map((cost, w) => ({ id: s * 6 + w, week: weeks[w], service, cost })));
const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = chartEl.style.height = '300px';
document.body.append(gridEl, chartEl);
const grid = createGrid(gridEl, {
rowKey: 'id',
rows,
columns: [
{ field: 'week', title: 'Week', type: 'dateString' },
{ field: 'service', title: 'Service', filter: { type: 'set' } },
{ field: 'cost', title: 'Cost', type: 'number' },
],
});
createChart({ grid, container: chartEl, type: 'area', x: 'week', y: 'cost',
series: 'service', stack: true, title: 'Weekly cloud spend by service' });
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 weeks = ['2026-09-07', '2026-09-14', '2026-09-21', '2026-09-28', '2026-10-05', '2026-10-12'];
const spend = { Compute: [4200, 4350, 4900, 5600, 6400, 7300],
Storage: [1800, 1850, 1900, 1950, 2050, 2100],
Database: [2600, 2600, 2700, 2900, 2950, 3000] };
const rows = Object.entries(spend).flatMap(([service, costs], s) =>
costs.map((cost, w) => ({ id: s * 6 + w, week: weeks[w], service, cost })));
const columns = [
{ field: 'week', title: 'Week', type: 'dateString' },
{ field: 'service', title: 'Service', filter: { type: 'set' } },
{ field: 'cost', title: 'Cost', type: 'number' },
];
export function SpendChart() {
const gridRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
type: 'area', x: 'week', y: 'cost', series: 'service', stack: true });
return () => chart.destroy();
}, []);
return (<>
<LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 300 }} />
<div ref={chartRef} style={{ height: 300 }} />
</>);
}
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 | 'area' here. |
x / y |
string | The category and measure columns. |
series |
string | Splits the measure into one series per distinct value. |
stack |
boolean or 'percent' |
Stacks the series. 'percent' stacks to 100%, so you see shares instead of amounts. |
title |
string | Drawn above the plot. |
The forecast band
The same data can carry a forecast. The rangeArea type takes a low and a high measure through measures and fills the band between them at each week, so a spend forecast reads as a range instead of a single guess.
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.