demo D272
A panel of stat tiles in React
The same feed as a row of live figures beside a grid: a total, an average against a baseline, a count with good, warn and critical bands, and a sparkline, moving as data flows
createKPI
This puts a row of live stat tiles beside the grid reading the very same rows: a total, an average against a baseline, a count with good, warn and critical bands, and a sparkline. It keeps the figures and the data in step because they are one source, so the tiles never drift from the table.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">
<div id="app"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.esm.min.js';
import { createLatticeReact } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/react.esm.min.js';
import { createKPI } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/kpi.esm.min.js';
const { LatticeGrid, LatticeKPI } = createLatticeReact({ React, createGrid, createKPI });
const MONEY = { style: 'currency', currency: 'USD', decimals: 0 };
const OPEN = { warn: 14, critical: 22, direction: 'lowerIsBetter' };
const columns = [
{ field: 'id', title: 'Ref', layout: { width: 96, pin: 'start' } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'stage', title: 'Stage', filter: { type: 'set' } },
{ field: 'value', title: 'Value', type: 'number', format: MONEY, total: 'sum' },
];
const rows = [/* deal records: id, region, stage, value */];
// Each tile is an aggregate over the same rows: a running total, an average
// against a baseline, a count with a good/warn/critical band, and a
// sparkline over the biggest deal.
const tiles = [
{ id: 'revenue', label: 'Pipeline value', aggregation: 'sum', field: 'value', format: 'currency' },
{ id: 'avg', label: 'Average deal vs $12k', aggregation: 'avg', field: 'value', format: { type: 'currency', decimals: 0 }, baseline: 12000 },
{ id: 'open', label: 'Still open', aggregation: 'count', filter: (r) => r.stage !== 'won', thresholds: OPEN },
{ id: 'biggest', label: 'Biggest deal', aggregation: 'max', field: 'value', format: 'currency', sparkline: 'value' },
];
function App() {
const [grid, setGrid] = React.useState(null);
// The panel takes the live grid as a prop, once the grid reports it is
// ready, and reads the same rows a chart or a board would.
return (
<>
{grid && <LatticeKPI grid={grid} columns={4} tiles={tiles} style={{ marginBottom: '12px' }} />}
<LatticeGrid rowKey="id" columns={columns} rows={rows} onGridReady={setGrid} style={{ height: '320px' }} />
</>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>
The headline numbers, off the same feed as the grid
A dashboard usually keeps its figures apart from its data: one query fills the table, another fills the tiles, and the two drift the moment anything moves. Here they are the same rows. A row of stat tiles sits beside the grid and reads the very feed the grid reads, so the total, the average and the count on screen are the ones in the table underneath, not a second version of them computed somewhere else.
Each tile is one figure over the rows: a running total, an average measured against a baseline so you see the gap at a glance, a live count, the biggest value in the set, a distinct count, or a figure you work out yourself. Give a tile good, warn and critical bands and it changes state the instant a number crosses a line, so a threshold you agreed reads as a colour rather than a note someone has to check. Add a sparkline and the tile carries its own recent shape beside the number. A tile is a labelled figure a keyboard can reach and a screen reader can read aloud, and the sparkline holds still for anyone who has asked for less motion.
Because every viewer reads a dataset the same way, one live feed can drive a grid, a board, a schedule and this panel of tiles at once. Route the feed once and the tiles move for the same reason the rows do: a new record lands and the count ticks up, a deal closes and the total drops, all without a page asking the server the same question twice. And it stays quick under a fast feed, because each tile keeps a running figure and every arriving change nudges only the numbers it touches rather than adding the whole set back up.
A silent feed is not the same as a genuine zero
The two small panels below the main strip compute the very same total over the very same feed, and only one of them is told what “recent” means. A feed that stopped six hours ago still has rows, all old, and a plain aggregate over them still returns a number - correct as an aggregate, and indistinguishable from a healthy reading. maxAge and ageBy are a setting on the panel, not on one tile: declare them (here, a 30-second window read off each deal’s own observedAt) and the panel can tell the two apart. Past the window with nothing recent, every tile on it reports unknown - a null value, a dashed edge, and a “No recent data” caption - rather than a number at all; a panel with no maxAge keeps grading whatever it holds, however old. Stop the feed above and wait: the freshness panel goes quiet on schedule, and the one beside it does not, because nothing has told it a silence looks any different from a total that simply stopped changing.
How do I show my data as a panel of stat tiles?
Load the KPI module and call createKPI(element, config), or drop the script in and reach for LatticeGridKPI on the page. Give it rows (or a live grid to read) and a tiles list. Each tile names an aggregation: sum, avg, min, max, count, countDistinct, or a reducer of your own; the field it reads; and an optional filter for the rows it counts. Set format for currency, percent or compact numbers, baseline for a delta, thresholds for the good, warn and critical bands, and sparkline to plot a series in the tile. To drive it from one feed beside your other views, hand the same records to a Data Router and attach the panel as a route: the router feeds the tiles through the same change path it feeds a grid, so they update live for free.