demo D227
Cross-filtering, the path back up in React
Two summary panels over one book; click a row in either and it filters the book, so the other narrows
crossFilter: true · crossFilter.toggle
This is two summary panels built over one book of records: click a row in either panel and it filters the book, so the other panel narrows to match. It shows selection flowing back up to the source, so a click in one view reshapes every view that shares it.
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.108.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.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const USD = { style: 'currency', currency: 'USD', decimals: 0 };
const bookColumns = [
{ field: 'rep', title: 'Rep' },
{ field: 'region', title: 'Region' },
{ field: 'amount', title: 'Deal', type: 'number', format: USD, total: 'sum' },
];
const regionColumns = [
{ field: 'region', title: 'Region' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
];
const repColumns = [
{ field: 'rep', title: 'Rep' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
];
const rows = [/* e.g. [{ id: 'S0', rep: 'Ana', region: 'EMEA', amount: 1830 }, ...] */];
function App() {
const bookRef = React.useRef(null);
const regionRef = React.useRef(null);
const repRef = React.useRef(null);
const [byRegion, setByRegion] = React.useState(null);
const [byRep, setByRep] = React.useState(null);
// Each summary panel derives from the live book and follows its filter.
// crossFilter: true lets a panel push the clicked group onto the book as an
// ordinary filter, so the other panel re-derives and both narrow each other
// while staying whole.
React.useEffect(() => {
const book = bookRef.current && bookRef.current.grid;
if (!book) return;
const panel = (col) => ({ mode: 'derived', from: book, follow: 'filtered', refresh: 'live', groupBy: col, crossFilter: true,
select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' } }, sort: [{ col: 'revenue', dir: 'desc' }] });
setByRegion(panel('region'));
setByRep(panel('rep'));
}, []);
// A click on a panel row toggles that group as the book's filter.
React.useEffect(() => {
const g = regionRef.current && regionRef.current.grid;
if (g) g.on('row:click', (e) => g.crossFilter.toggle(e.key));
}, [byRegion]);
React.useEffect(() => {
const g = repRef.current && repRef.current.grid;
if (g) g.on('row:click', (e) => g.crossFilter.toggle(e.key));
}, [byRep]);
return (
<>
<LatticeGrid
ref={bookRef}
rowKey="id"
toolPanel={{ side: 'left', panels: ['filters', 'columns'] }}
columns={bookColumns}
rows={rows}
style={{ height: '300px' }}
/>
<div style={{ display: 'flex', gap: '16px', marginTop: '16px' }}>
{byRegion && <LatticeGrid ref={regionRef} autoHeight source={byRegion} columns={regionColumns} style={{ flex: 1 }} />}
{byRep && <LatticeGrid ref={repRef} autoHeight source={byRep} columns={repColumns} style={{ flex: 1 }} />}
</div>
</>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>