demo D222
Chaining, three levels deep
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
Loading a live grid…
The configuration
<script>
import { createGrid, createStat } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/lattice-grid.esm.min.js';
import createLatticeAction from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/modules/svelte.esm.min.js';
const lattice = createLatticeAction({ createGrid });
const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 };
// Each grid captured as it is created, so the next level can name it.
let bookGrid;
let byRepGrid;
let tilesEl;
// Level 0 - the book. A plain grid, filterable, over the sales rows.
const bookConfig = {
rowKey: 'id',
toolPanel: { side: 'left', panels: ['filters', 'columns'] },
columns: [
{ field: 'rep', title: 'Rep' },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'amount', title: 'Deal', type: 'number', format: USD, total: 'sum' },
],
rows, // e.g. [{ id: 'S0', rep: 'Ana Bright', region: 'EMEA', amount: 2140 }, ...]
onGrid: (grid) => { bookGrid = grid; },
};
// Level 1 - revenue per rep, derived from the book and following its filter.
// No rows and no rowKey: the group value is the identity. refresh: 'live'
// re-derives on the next frame when the book is filtered. The getter defers
// the read of the source to when this grid is created.
const byRepConfig = {
autoHeight: true,
source: { mode: 'derived', get from() { return bookGrid; }, follow: 'filtered', refresh: 'live', groupBy: 'rep',
select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' } },
sort: [{ col: 'revenue', dir: 'desc' }] },
columns: [
{ field: 'rep', title: 'Rep' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
{ field: 'deals', title: 'Deals', type: 'number' },
],
// A tile is one reduced figure over a grid. These read byRep (level 1). The
// concentration is a Gini coefficient, a ratio, so it renders 0.34 rather
// than borrowing the currency format; show: 'rep' turns max into an argmax.
onGrid: (grid) => {
byRepGrid = grid;
const tile = () => { const d = document.createElement('div'); tilesEl.append(d); return d; };
createStat({ grid, container: tile(), title: 'Revenue concentration', of: 'revenue', fn: 'gini', goodWhen: 'down', footer: '0 = even, 1 = one rep has it all' });
createStat({ grid, container: tile(), title: 'Best rep', of: 'revenue', fn: 'max', show: 'rep' });
createStat({ grid, container: tile(), title: 'Weakest rep', of: 'revenue', fn: 'min', show: 'rep' });
createStat({ grid, container: tile(), title: 'Spread across reps', of: 'revenue', fn: 'stddev' });
createStat({ grid, container: tile(), title: 'Reps selling', fn: 'count' });
},
};
// Level 2 - derived from byRep, not from the book. The chain composes.
const top3Config = {
autoHeight: true,
source: { mode: 'derived', get from() { return byRepGrid; }, refresh: 'live', sort: [{ col: 'revenue', dir: 'desc' }], limit: 3 },
columns: [
{ field: 'rep', title: 'Rep' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
],
};
// Level 2 - the profile transpose: one row of stats over byRep's revenues.
const profileConfig = {
autoHeight: true,
source: { mode: 'derived', get from() { return byRepGrid; }, profile: ['revenue'], refresh: 'live' },
columns: [
{ field: 'column', title: 'Measure' },
{ field: 'mean', title: 'Mean', type: 'number', format: { maximumFractionDigits: 0 } },
{ field: 'stddev', title: 'σ', type: 'number', format: { maximumFractionDigits: 0 } },
{ field: 'min', title: 'Weakest', type: 'number', format: { maximumFractionDigits: 0 } },
{ field: 'max', title: 'Strongest', type: 'number', format: { maximumFractionDigits: 0 } },
],
};
</script>
<svelte:head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/lattice-grid.min.css">
</svelte:head>
<div bind:this={tilesEl} style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 16px"></div>
<div use:lattice={bookConfig} style="height: 320px"></div>
<div style="display: flex; gap: 16px; margin-top: 16px">
<div use:lattice={byRepConfig} style="flex: 1"></div>
<div use:lattice={top3Config} style="flex: 1"></div>
<div use:lattice={profileConfig} style="flex: 1"></div>
</div>