demo D222
Chaining, three levels deep in React
sales → revenue per rep → top three and a profile, with tiles reading the middle level; one filter moves all of it
source: { mode: 'derived', from, groupBy, profile } · createStat
This is a sales dataset flowing through three linked levels: raw sales, revenue per rep, then the top three reps and a profile, with summary tiles reading the middle level. Change the filter once and every level moves together, so you can build a layered view without wiring each part to the source by hand.
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.latticegrid.dev/lattice-grid@1.107.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, createStat } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const USD = { style: 'currency', currency: 'USD', decimals: 0 };
const WHOLE = { decimals: 0 };
const bookColumns = [
{ field: 'rep', title: 'Rep' },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'amount', title: 'Deal', type: 'number', format: USD, total: 'sum' },
];
const byRepColumns = [
{ field: 'rep', title: 'Rep' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
{ field: 'deals', title: 'Deals', type: 'number' },
];
const top3Columns = [
{ field: 'rep', title: 'Rep' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
];
const profileColumns = [
{ field: 'column', title: 'Measure' },
{ field: 'mean', title: 'Mean', type: 'number', format: WHOLE },
{ field: 'stddev', title: 'Spread', type: 'number', format: WHOLE },
{ field: 'min', title: 'Weakest', type: 'number', format: WHOLE },
{ field: 'max', title: 'Strongest', type: 'number', format: WHOLE },
];
const rows = [/* e.g. [{ id: 'S0', rep: 'Ana Bright', region: 'EMEA', amount: 2140 }, ...] */];
function App() {
const bookRef = React.useRef(null);
const byRepRef = React.useRef(null);
const tilesRef = React.useRef(null);
const [byRep, setByRep] = React.useState(null);
const [top3, setTop3] = React.useState(null);
const [profile, setProfile] = React.useState(null);
// Level 1: revenue per rep, derived from the book and following its filter.
// The group value is the identity, so there is no rowKey to give.
React.useEffect(() => {
const book = bookRef.current && bookRef.current.grid;
if (book) setByRep({ mode: 'derived', from: book, follow: 'filtered', refresh: 'live', groupBy: 'rep',
select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' } },
sort: [{ col: 'revenue', dir: 'desc' }] });
}, []);
// Level 2: derived from byRep, not from the book, so the chain composes.
// Runs once byRep is a live instance, reached through its ref.
React.useEffect(() => {
const rep = byRepRef.current && byRepRef.current.grid;
if (!byRep || !rep) return;
// Top three reps, and a one-row profile transposing byRep's revenues.
setTop3({ mode: 'derived', from: rep, refresh: 'live', sort: [{ col: 'revenue', dir: 'desc' }], limit: 3 });
setProfile({ mode: 'derived', from: rep, profile: ['revenue'], refresh: 'live' });
// Each tile is one reduced figure over byRep. Concentration is a Gini
// ratio, so it renders as a plain number; show: 'rep' turns max into an
// argmax, naming the rep behind the figure.
const tile = () => tilesRef.current.appendChild(document.createElement('div'));
createStat({ grid: rep, container: tile(), title: 'Revenue concentration', of: 'revenue', fn: 'gini', goodWhen: 'down', footer: '0 = even, 1 = one rep has it all' });
createStat({ grid: rep, container: tile(), title: 'Best rep', of: 'revenue', fn: 'max', show: 'rep' });
createStat({ grid: rep, container: tile(), title: 'Weakest rep', of: 'revenue', fn: 'min', show: 'rep' });
createStat({ grid: rep, container: tile(), title: 'Spread across reps', of: 'revenue', fn: 'stddev' });
createStat({ grid: rep, container: tile(), title: 'Reps selling', fn: 'count' });
}, [byRep]);
return (
<>
<div ref={tilesRef} style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '12px', marginBottom: '16px' }} />
<LatticeGrid
ref={bookRef}
rowKey="id"
toolPanel={{ side: 'left', panels: ['filters', 'columns'] }}
columns={bookColumns}
rows={rows}
style={{ height: '320px' }}
/>
<div style={{ display: 'flex', gap: '16px', marginTop: '16px' }}>
{byRep && <LatticeGrid ref={byRepRef} autoHeight source={byRep} columns={byRepColumns} style={{ flex: 1 }} />}
{top3 && <LatticeGrid autoHeight source={top3} columns={top3Columns} style={{ flex: 1 }} />}
{profile && <LatticeGrid autoHeight source={profile} columns={profileColumns} style={{ flex: 1 }} />}
</div>
</>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>