demo D231
A hundred thousand rows in DuckDB, no server in React
DuckDB reads a Parquet file in the browser and the whole set is pulled in, so the plant subtotals and the grand total are computed over all 100,000 rows
createPushdownSource · duckdbAdapter · fullDataset
This grid reads a Parquet file with DuckDB in the browser and pulls the whole hundred-thousand-row set through it, so the subtotals and grand total are computed over every row. It shows analytical figures that cover the full dataset, with no server involved.
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 * as Lattice 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 { createGrid, createPushdownSource, duckdbAdapter } = Lattice;
const LatticeGrid = createLatticeGrid({ React, createGrid });
// The share of a group's rows inside tolerance. Under fullDataset the scope is
// the whole group, so a subtotal is a whole-group rate, not a page's.
const inSpecRate = (values) => {
let seen = 0, ok = 0;
for (const v of values) { if (typeof v === 'boolean') { seen++; if (v) ok++; } }
return seen ? ok / seen : null;
};
const columns = [
{ field: 'plant', title: 'Plant', filter: { type: 'set' }, group: { enabled: true, index: 0 }, total: 'count' },
{ field: 'line', title: 'Line', filter: { type: 'set' } },
{ field: 'product', title: 'Product' },
{ field: 'bore_mm', title: 'Bore', type: 'millimetres', spec: { lower: 9.95, upper: 10.05, target: 10 }, total: 'avg' },
{ field: 'cycle_seconds', title: 'Cycle', type: 'seconds', total: 'avg' },
{ id: 'in_spec_rate', field: 'in_spec', title: 'In-spec rate', type: 'number', format: { style: 'percent', decimals: 2 }, total: inSpecRate },
{ field: 'taken_at', title: 'Taken', type: 'dateString' },
];
function App() {
const [source, setSource] = React.useState(null);
const [status, setStatus] = React.useState('Starting DuckDB in the browser...');
// DuckDB is the caller's engine, loaded from a CDN; the grid imports none of
// it. duckdbAdapter takes the connection you made, so the source drives a
// full analytical engine while the grid stays dependency-free.
React.useEffect(() => {
let disposed = false, connection, worker;
(async () => {
try {
setStatus('Loading DuckDB from a CDN...');
const duckdb = await import('https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm');
const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
worker = await duckdb.createWorker(bundle.mainWorker);
const db = new duckdb.AsyncDuckDB(new duckdb.ConsoleLogger(duckdb.LogLevel.ERROR), worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
if (disposed) return;
connection = await db.connect();
// The engine resolves the URL, so it must be absolute. The file is a
// hundred thousand rows in a 1.4 MB Parquet file, on this origin.
const file = new URL('/demo-data/readings.parquet', location.origin).href;
const adapter = duckdbAdapter({ connection, from: `read_parquet('${file}')` });
// fullDataset pulls the whole matching set in once and holds it, so the
// subtotals and the grand total are over all 100,000 rows, not a page.
setSource(createPushdownSource({
adapter, compute: Lattice, pageSize: 200,
fullDataset: { enabled: true, maxRows: 250000 },
}));
setStatus('100,000 rows pulled into the tab and grouped by plant. Expand a plant, or filter a line, and every figure recomputes over the whole matching set.');
} catch (err) {
setStatus('DuckDB could not start here. It loads from a CDN and needs a browser with WebAssembly and cross-origin isolation.');
}
})();
return () => { disposed = true; try { connection?.close?.(); } catch (e) {} try { worker?.terminate?.(); } catch (e) {} };
}, []);
return (
<>
<div style={{ fontSize: '13px', color: 'var(--ink-2)', lineHeight: 1.5 }}>{status}</div>
{source && (
<LatticeGrid
rowKey="reading_id"
toolPanel={{ side: 'left', panels: ['filters', 'columns'] }}
grandTotalRow="bottom"
groupFooter
showTotalInHeader
columns={columns}
source={source}
style={{ height: '600px', marginTop: '12px' }}
/>
)}
</>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>