@toclocoinc/lattice-grid 1.62.1 → 1.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/API.html +526 -16
- package/docs/CHART-CODES.md +141 -2
- package/docs/api-detail.html +194 -5
- package/lattice-grid.d.ts +235 -5
- package/lattice-grid.esm.min.js +870 -73
- package/lattice-grid.min.cjs +870 -73
- package/lattice-grid.min.js +870 -73
- package/modules/ai.d.ts +1 -1
- package/modules/ai.esm.min.js +13 -16
- package/modules/ai.min.cjs +13 -16
- package/modules/ai.min.js +13 -16
- package/modules/angular.d.ts +1 -1
- package/modules/angular.esm.min.js +2 -2
- package/modules/angular.min.cjs +2 -2
- package/modules/angular.min.js +2 -2
- package/modules/chart-alluvial.d.ts +1 -1
- package/modules/chart-alluvial.esm.min.js +1 -1
- package/modules/chart-alluvial.min.cjs +1 -1
- package/modules/chart-alluvial.min.js +1 -1
- package/modules/chart-arc.d.ts +1 -1
- package/modules/chart-arc.esm.min.js +1 -1
- package/modules/chart-arc.min.cjs +1 -1
- package/modules/chart-arc.min.js +1 -1
- package/modules/chart-bubblemap.d.ts +1 -1
- package/modules/chart-bubblemap.esm.min.js +42 -6
- package/modules/chart-bubblemap.min.cjs +42 -6
- package/modules/chart-bubblemap.min.js +42 -6
- package/modules/chart-bump.d.ts +1 -1
- package/modules/chart-bump.esm.min.js +1 -1
- package/modules/chart-bump.min.cjs +1 -1
- package/modules/chart-bump.min.js +1 -1
- package/modules/chart-calendar.d.ts +1 -1
- package/modules/chart-calendar.esm.min.js +1 -1
- package/modules/chart-calendar.min.cjs +1 -1
- package/modules/chart-calendar.min.js +1 -1
- package/modules/chart-decomposition.d.ts +1 -1
- package/modules/chart-decomposition.esm.min.js +1 -1
- package/modules/chart-decomposition.min.cjs +1 -1
- package/modules/chart-decomposition.min.js +1 -1
- package/modules/chart-diverging.d.ts +1 -1
- package/modules/chart-diverging.esm.min.js +1 -1
- package/modules/chart-diverging.min.cjs +1 -1
- package/modules/chart-diverging.min.js +1 -1
- package/modules/chart-dumbbell.d.ts +1 -1
- package/modules/chart-dumbbell.esm.min.js +1 -1
- package/modules/chart-dumbbell.min.cjs +1 -1
- package/modules/chart-dumbbell.min.js +1 -1
- package/modules/chart-fan.d.ts +1 -1
- package/modules/chart-fan.esm.min.js +1 -1
- package/modules/chart-fan.min.cjs +1 -1
- package/modules/chart-fan.min.js +1 -1
- package/modules/chart-hexbin.d.ts +1 -1
- package/modules/chart-hexbin.esm.min.js +1 -1
- package/modules/chart-hexbin.min.cjs +1 -1
- package/modules/chart-hexbin.min.js +1 -1
- package/modules/chart-hexmap.d.ts +1 -1
- package/modules/chart-hexmap.esm.min.js +46 -6
- package/modules/chart-hexmap.min.cjs +46 -6
- package/modules/chart-hexmap.min.js +46 -6
- package/modules/chart-icicle.d.ts +1 -1
- package/modules/chart-icicle.esm.min.js +1 -1
- package/modules/chart-icicle.min.cjs +1 -1
- package/modules/chart-icicle.min.js +1 -1
- package/modules/chart-parallel.d.ts +1 -1
- package/modules/chart-parallel.esm.min.js +1 -1
- package/modules/chart-parallel.min.cjs +1 -1
- package/modules/chart-parallel.min.js +1 -1
- package/modules/chart-ridgeline.d.ts +1 -1
- package/modules/chart-ridgeline.esm.min.js +1 -1
- package/modules/chart-ridgeline.min.cjs +1 -1
- package/modules/chart-ridgeline.min.js +1 -1
- package/modules/chart-roc.d.ts +1 -1
- package/modules/chart-roc.esm.min.js +1 -1
- package/modules/chart-roc.min.cjs +1 -1
- package/modules/chart-roc.min.js +1 -1
- package/modules/chart-slope.d.ts +1 -1
- package/modules/chart-slope.esm.min.js +1 -1
- package/modules/chart-slope.min.cjs +1 -1
- package/modules/chart-slope.min.js +1 -1
- package/modules/chart-splom.d.ts +1 -1
- package/modules/chart-splom.esm.min.js +1 -1
- package/modules/chart-splom.min.cjs +1 -1
- package/modules/chart-splom.min.js +1 -1
- package/modules/chart-waffle.d.ts +1 -1
- package/modules/chart-waffle.esm.min.js +1 -1
- package/modules/chart-waffle.min.cjs +1 -1
- package/modules/chart-waffle.min.js +1 -1
- package/modules/charts.d.ts +1 -1
- package/modules/charts.esm.min.js +1917 -662
- package/modules/charts.min.cjs +1917 -662
- package/modules/charts.min.js +1917 -662
- package/modules/data-router.d.ts +1 -1
- package/modules/data-router.esm.min.js +129 -20
- package/modules/data-router.min.cjs +129 -20
- package/modules/data-router.min.js +129 -20
- package/modules/devtools.d.ts +1 -1
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.d.ts +1 -1
- package/modules/dhtmlx-compat.esm.min.js +4 -16
- package/modules/dhtmlx-compat.min.cjs +4 -16
- package/modules/dhtmlx-compat.min.js +4 -16
- package/modules/gantt.d.ts +1 -1
- package/modules/gantt.esm.min.js +187 -82
- package/modules/gantt.min.cjs +187 -82
- package/modules/gantt.min.js +187 -82
- package/modules/geo-europe-nuts.d.ts +16 -0
- package/modules/geo-europe-nuts.esm.min.js +29 -0
- package/modules/geo-uk.d.ts +18 -0
- package/modules/geo-uk.esm.min.js +29 -0
- package/modules/geo-us-states.d.ts +16 -0
- package/modules/geo-us-states.esm.min.js +29 -0
- package/modules/geo-world-110m.d.ts +17 -0
- package/modules/geo-world-110m.esm.min.js +29 -0
- package/modules/geo-world-50m.d.ts +16 -0
- package/modules/geo-world-50m.esm.min.js +29 -0
- package/modules/htmx.d.ts +1 -1
- package/modules/htmx.esm.min.js +870 -73
- package/modules/htmx.min.cjs +870 -73
- package/modules/htmx.min.js +870 -73
- package/modules/kanban.d.ts +1 -1
- package/modules/kanban.esm.min.js +4 -16
- package/modules/kanban.min.cjs +4 -16
- package/modules/kanban.min.js +4 -16
- package/modules/kpi.d.ts +1 -1
- package/modules/kpi.esm.min.js +76 -28
- package/modules/kpi.min.cjs +76 -28
- package/modules/kpi.min.js +76 -28
- package/modules/layout.d.ts +1 -1
- package/modules/layout.esm.min.js +4 -16
- package/modules/layout.min.cjs +4 -16
- package/modules/layout.min.js +4 -16
- package/modules/mock-socket.d.ts +1 -1
- package/modules/mock-socket.esm.min.js +2 -2
- package/modules/mock-socket.min.cjs +2 -2
- package/modules/mock-socket.min.js +2 -2
- package/modules/react.d.ts +308 -3
- package/modules/react.esm.min.js +1072 -22
- package/modules/react.min.cjs +1056 -21
- package/modules/react.min.js +1056 -21
- package/modules/svelte.d.ts +1 -1
- package/modules/svelte.esm.min.js +2 -2
- package/modules/svelte.min.cjs +2 -2
- package/modules/svelte.min.js +2 -2
- package/modules/tabs.d.ts +1 -1
- package/modules/tabs.esm.min.js +4 -16
- package/modules/tabs.min.cjs +4 -16
- package/modules/tabs.min.js +4 -16
- package/modules/vue.d.ts +1 -1
- package/modules/vue.esm.min.js +2 -2
- package/modules/vue.min.cjs +2 -2
- package/modules/vue.min.js +2 -2
- package/modules/webcomponent.d.ts +1 -1
- package/modules/webcomponent.esm.min.js +870 -73
- package/modules/webcomponent.min.cjs +870 -73
- package/modules/webcomponent.min.js +870 -73
- package/package.json +1 -1
package/docs/API.html
CHANGED
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
<div class="shell">
|
|
361
361
|
<aside class="rail">
|
|
362
362
|
<p class="rail__brand">Lattice Grid</p>
|
|
363
|
-
<p class="rail__sub">API reference · v1.
|
|
363
|
+
<p class="rail__sub">API reference · v1.63.1</p>
|
|
364
364
|
<nav>
|
|
365
365
|
<div class="rail__group">
|
|
366
366
|
<span class="rail__label">Start</span>
|
|
@@ -443,7 +443,7 @@
|
|
|
443
443
|
</header>
|
|
444
444
|
|
|
445
445
|
<p class="chips">
|
|
446
|
-
<span class="chip">Version 1.
|
|
446
|
+
<span class="chip">Version 1.63.1</span>
|
|
447
447
|
<span class="chip">Zero dependencies</span>
|
|
448
448
|
<span class="chip"><a href="api-detail.html">Developer guide →</a></span>
|
|
449
449
|
</p>
|
|
@@ -518,7 +518,7 @@
|
|
|
518
518
|
<table>
|
|
519
519
|
<thead><tr><th>Entry point</th><th>Factory</th><th>Needs</th></tr></thead>
|
|
520
520
|
<tbody>
|
|
521
|
-
<tr><td class="name">modules/react</td><td class="sig">createLatticeGrid({ React, createGrid })</td><td class="desc">Returns a component. Forwards a ref exposing <code>.grid</code
|
|
521
|
+
<tr><td class="name">modules/react</td><td class="sig">createLatticeGrid({ React, createGrid })</td><td class="desc">Returns a component. Forwards a ref exposing <code>.grid</code>. Since 1.63 the same entry point also builds a component for <a href="#react-v2">every other viewer</a> and the data router.</td></tr>
|
|
522
522
|
<tr><td class="name">modules/vue</td><td class="sig">createLatticeGrid({ vue, createGrid })</td><td class="desc">Returns a Vue 3 component definition.</td></tr>
|
|
523
523
|
<tr><td class="name">modules/svelte</td><td class="sig">createLatticeAction({ createGrid })</td><td class="desc">Returns a <code>use:</code> action.</td></tr>
|
|
524
524
|
</tbody>
|
|
@@ -540,6 +540,171 @@
|
|
|
540
540
|
</table>
|
|
541
541
|
</div>
|
|
542
542
|
|
|
543
|
+
<h3 id="react-v2">React: every viewer, not only the grid</h3>
|
|
544
|
+
<p>
|
|
545
|
+
Until 1.63 the React adapter wrapped <code>createGrid</code> and nothing else. Every other
|
|
546
|
+
shipped viewer — the KPI panel, a chart, the board, the Gantt, the layout, the tab
|
|
547
|
+
strip — and the data router had no React surface, so a React application wrote its own
|
|
548
|
+
<code>useEffect</code> per viewer. It does not have to any more: there is one component per
|
|
549
|
+
viewer, and each keeps the contract the grid component already kept.
|
|
550
|
+
</p>
|
|
551
|
+
<pre><code><span class="kw">import</span> React <span class="kw">from</span> 'react';
|
|
552
|
+
<span class="kw">import</span> ReactDOM <span class="kw">from</span> 'react-dom';
|
|
553
|
+
<span class="kw">import</span> { createGrid } <span class="kw">from</span> '@toclocoinc/lattice-grid';
|
|
554
|
+
<span class="kw">import</span> { createKPI } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/kpi';
|
|
555
|
+
<span class="kw">import</span> { createChart } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/charts';
|
|
556
|
+
<span class="kw">import</span> { createDataRouter } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/data-router';
|
|
557
|
+
<span class="kw">import</span> { createLatticeReact } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/react';
|
|
558
|
+
|
|
559
|
+
<span class="cmt">// Built once, at module scope. Building components inside a component hands React a</span>
|
|
560
|
+
<span class="cmt">// new type on every render, and a new type is a different element: the subtree would</span>
|
|
561
|
+
<span class="cmt">// unmount and remount, destroying and rebuilding the grid on every keystroke.</span>
|
|
562
|
+
<span class="kw">const</span> L = createLatticeReact({
|
|
563
|
+
React, ReactDOM, createGrid, createKPI, createChart, createDataRouter,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
<span class="kw">function</span> Dashboard({ rows }) {
|
|
567
|
+
<span class="kw">const</span> router = L.useLatticeRouter(ROUTER_CONFIG);
|
|
568
|
+
<span class="kw">return</span> (
|
|
569
|
+
<L.LatticeRouterProvider router={router}>
|
|
570
|
+
<L.LatticeGridProvider>
|
|
571
|
+
<L.LatticeGrid name="quakes" route="all" {...GRID_CONFIG} rows={rows} />
|
|
572
|
+
<L.LatticeKPI gridName="quakes" tiles={TILES} columns={5} />
|
|
573
|
+
<L.LatticeChart gridName="quakes" type="bar" x="region" y="count" />
|
|
574
|
+
</L.LatticeGridProvider>
|
|
575
|
+
</L.LatticeRouterProvider>
|
|
576
|
+
);
|
|
577
|
+
}</code></pre>
|
|
578
|
+
<div class="table-wrap">
|
|
579
|
+
<table>
|
|
580
|
+
<thead><tr><th>Export</th><th>Signature</th><th>What it is</th></tr></thead>
|
|
581
|
+
<tbody>
|
|
582
|
+
<tr><td class="name">createLatticeGrid</td><td class="sig">({ React, createGrid })</td><td class="desc">The grid component. Extended in 1.63 with <code>rowUpdates</code>, <code>predicates</code>, <code>onGridReady</code>, <code>onGridDestroy</code>, <code>name</code> and <code>route</code>.</td></tr>
|
|
583
|
+
<tr><td class="name">createLatticeKPI</td><td class="sig">({ React, createKPI })</td><td class="desc">The KPI panel. Grid-bound through context by default; pass <code>rows</code> instead for a panel with no grid.</td></tr>
|
|
584
|
+
<tr><td class="name">createLatticeChart</td><td class="sig">({ React, createChart })</td><td class="desc">A chart. Requires a grid, so nothing is mounted until one exists; a changed spec key goes to <code>chart.update()</code> and the chart redraws rather than being rebuilt.</td></tr>
|
|
585
|
+
<tr><td class="name">createLatticeKanban</td><td class="sig">({ React, createKanban })</td><td class="desc">The board. <code>rows</code>, <code>quickFilter</code>, <code>sprint</code>, <code>epic</code>, <code>loading</code> and <code>error</code> are live props.</td></tr>
|
|
586
|
+
<tr><td class="name">createLatticeGantt</td><td class="sig">({ React, createGantt })</td><td class="desc">The Gantt. <code>tasks</code> and <code>dependencies</code> are live props.</td></tr>
|
|
587
|
+
<tr><td class="name">createLatticeLayout</td><td class="sig">({ React, createLayout })</td><td class="desc">The layout. Windows are driven through the ref; its events arrive as <code>onLayoutChanged</code>, <code>onWindowMoved</code> and the rest.</td></tr>
|
|
588
|
+
<tr><td class="name">createLatticeTabs</td><td class="sig">({ React, ReactDOM, createTabs, createGrid? })</td><td class="desc">The tab strip, with <strong>React-rendered tab content</strong>: a tab's <code>content</code> is a React element (or a function returning one) rendered into the strip's own panel through <code>createPortal</code>, so a tab's grid is a real <code><LatticeGrid></code> with props, a ref and the surrounding context. A tab with no <code>content</code> is left to the module, so the two kinds mix on one strip.</td></tr>
|
|
589
|
+
<tr><td class="name">createLatticeGridContext</td><td class="sig">({ React })</td><td class="desc">Returns <code>{ LatticeGridProvider, useLatticeGrid }</code>. A grid-bound viewer needs the grid <em>instance</em>, which appears after the first render; a ref cannot help because writing to a ref re-renders nobody. Several grids may publish under one provider — <code>name</code> on the grid, <code>gridName</code> on the viewer.</td></tr>
|
|
590
|
+
<tr><td class="name">createLatticeRouter</td><td class="sig">({ React, createDataRouter })</td><td class="desc">Returns <code>{ useLatticeRouter, LatticeRouterProvider, useRouter }</code>. The hook creates the router in an effect and destroys it in that effect's cleanup, so it returns <code>null</code> on the first render; the config is read once, because rebuilding would drop every attached grid and every row held.</td></tr>
|
|
591
|
+
<tr><td class="name">createLatticeViewer</td><td class="sig">({ React, viewer, mount, … })</td><td class="desc">The generic behind all of the above, and the escape hatch for a viewer with no named factory yet.</td></tr>
|
|
592
|
+
<tr><td class="name">createLatticeReact</td><td class="sig">({ React, ReactDOM?, …factories })</td><td class="desc">Every binding from one call: pass the factories the application uses and it builds those components, leaving the rest undefined. Nothing is imported, so an application that never uses the board never loads the board.</td></tr>
|
|
593
|
+
<tr><td class="name">createViewerController</td><td class="sig">({ viewer, mount, element, props })</td><td class="desc">The framework-free viewer lifecycle — mount once, push changed props into the live instance, destroy — shared by every adapter.</td></tr>
|
|
594
|
+
<tr><td class="name">VIEWER_EVENTS</td><td class="type">Record<string, readonly string[]></td><td class="desc">Every event each non-grid viewer emits, keyed by viewer name.</td></tr>
|
|
595
|
+
<tr><td class="name">VIEWER_APPLY</td><td class="type">Record<string, Record<string, Function>></td><td class="desc">Which props each viewer can take <em>live</em>, and the instance call each becomes. Anything not listed is mount-time configuration.</td></tr>
|
|
596
|
+
<tr><td class="name">viewerHandlerName</td><td class="type">(event) => string</td><td class="desc">A viewer event as its React prop: <code>card:move</code> becomes <code>onCardMove</code>.</td></tr>
|
|
597
|
+
<tr><td class="name">DEFAULT_GRID_NAME</td><td class="type">string</td><td class="desc">The name a grid publishes itself under when you do not choose one: <code>default</code>.</td></tr>
|
|
598
|
+
</tbody>
|
|
599
|
+
</table>
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
<pre data-run="js" data-expect="8 components, onCardMove, default, rows 2, events 5/6" data-covers="export:createLatticeReact export:createLatticeGrid export:createLatticeKPI export:createLatticeChart export:createLatticeKanban export:createLatticeGantt export:createLatticeLayout export:createLatticeTabs export:createLatticeViewer export:createLatticeGridContext export:createLatticeRouter export:createViewerController export:viewerHandlerName export:VIEWER_EVENTS export:VIEWER_APPLY export:DEFAULT_GRID_NAME"><code><span class="kw">const</span> A = <span class="kw">await</span> import('../packages/modules/react/index.js');
|
|
603
|
+
|
|
604
|
+
<span class="cmt">// A stand-in for React and react-dom. The adapter never imports either, so a</span>
|
|
605
|
+
<span class="cmt">// factory only needs the handful of names it destructures — which is exactly</span>
|
|
606
|
+
<span class="cmt">// why this example runs in Node with no framework installed.</span>
|
|
607
|
+
<span class="kw">const</span> React = { createElement: () => ({}), createContext: () => ({}), forwardRef: (f) => f };
|
|
608
|
+
<span class="kw">const</span> ReactDOM = { createPortal: () => ({}) };
|
|
609
|
+
<span class="kw">const</span> stub = () => ({ on: () => () => {}, destroy() {} });
|
|
610
|
+
|
|
611
|
+
<span class="cmt">// One call builds whichever components the factories you pass support.</span>
|
|
612
|
+
<span class="kw">const</span> L = A.createLatticeReact({
|
|
613
|
+
React, ReactDOM, createGrid: stub, createKPI: stub, createChart: stub, createDataRouter: stub,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
<span class="cmt">// …or one factory at a time.</span>
|
|
617
|
+
<span class="kw">const</span> components = [
|
|
618
|
+
A.createLatticeGrid({ React, createGrid: stub }),
|
|
619
|
+
A.createLatticeKPI({ React, createKPI: stub }),
|
|
620
|
+
A.createLatticeChart({ React, createChart: stub }),
|
|
621
|
+
A.createLatticeKanban({ React, createKanban: stub }),
|
|
622
|
+
A.createLatticeGantt({ React, createGantt: stub }),
|
|
623
|
+
A.createLatticeLayout({ React, createLayout: stub }),
|
|
624
|
+
A.createLatticeTabs({ React, ReactDOM, createTabs: stub }),
|
|
625
|
+
A.createLatticeViewer({ React, viewer: 'kpi', mount: stub }),
|
|
626
|
+
].filter((c) => typeof c === 'function');
|
|
627
|
+
|
|
628
|
+
<span class="cmt">// The provider/hook pair and the router hook are built the same way.</span>
|
|
629
|
+
<span class="kw">const</span> { LatticeGridProvider, useLatticeGrid } = A.createLatticeGridContext({ React });
|
|
630
|
+
<span class="kw">const</span> { useLatticeRouter } = A.createLatticeRouter({ React, createDataRouter: stub });
|
|
631
|
+
|
|
632
|
+
<span class="cmt">// And the lifecycle underneath, driven with no framework at all: mount once,</span>
|
|
633
|
+
<span class="cmt">// push a changed live prop into the instance that already exists, destroy.</span>
|
|
634
|
+
<span class="kw">const</span> seen = [];
|
|
635
|
+
<span class="kw">const</span> controller = A.createViewerController({
|
|
636
|
+
viewer: 'kpi',
|
|
637
|
+
element: {},
|
|
638
|
+
props: { rows: [{ id: 1 }] },
|
|
639
|
+
mount: () => ({ setRows: (r) => seen.push(r.length), on: () => () => {}, destroy() {} }),
|
|
640
|
+
});
|
|
641
|
+
controller.update({ rows: [{ id: 1 }, { id: 2 }] });
|
|
642
|
+
controller.destroy();
|
|
643
|
+
|
|
644
|
+
<span class="kw">return</span> `${components.length} components, ${A.viewerHandlerName('card:move')}, `
|
|
645
|
+
+ `${A.DEFAULT_GRID_NAME}, rows ${seen.join('/')}, `
|
|
646
|
+
+ `events ${A.VIEWER_EVENTS.kpi.length}/${Object.keys(A.VIEWER_APPLY).length}`;</code></pre>
|
|
647
|
+
|
|
648
|
+
<p class="section-note">
|
|
649
|
+
<strong>Live props versus mount-time props.</strong> The grid takes any changed configuration
|
|
650
|
+
key through one call (<code>grid.setAll</code>); no other viewer does. So each viewer declares
|
|
651
|
+
which props it can take while it is running — the table above, and
|
|
652
|
+
<code>VIEWER_APPLY</code> at runtime — and everything else is mount-time configuration.
|
|
653
|
+
A mount-time prop that changes is <em>not</em> silently ignored and <em>not</em> silently
|
|
654
|
+
remounted (that would throw away scroll position, selection and expansion): it is named once
|
|
655
|
+
in a warning that tells you to give the component a <code>key</code> that changes when the
|
|
656
|
+
rebuild is wanted, or to drive the instance through the ref.
|
|
657
|
+
</p>
|
|
658
|
+
<p class="section-note">
|
|
659
|
+
<strong>Two props rebuild a viewer rather than update it:</strong> the grid it is bound to,
|
|
660
|
+
and anything it cannot exist without. A chart holding a destroyed grid is not stale, it is
|
|
661
|
+
invalid, so a new grid tears the old chart down and builds a new one against it — in
|
|
662
|
+
that order, never the reverse.
|
|
663
|
+
</p>
|
|
664
|
+
<p class="section-note">
|
|
665
|
+
<strong>A live feed: <code>rowUpdates</code> and <code>predicates</code>.</strong>
|
|
666
|
+
<code>rowUpdates</code> is a keyed diff handed straight to <code>grid.rows.apply()</code>,
|
|
667
|
+
applied when the object's identity changes — a feed produces a new change object per
|
|
668
|
+
batch, so identity is the right trigger and re-applying the same object would re-land rows the
|
|
669
|
+
grid already has. <code>predicates</code> is <code>{ name: fn }</code> mapped to
|
|
670
|
+
<code>grid.filters.where(name, fn)</code>, diffed by name, with a name that has gone removed.
|
|
671
|
+
Those <em>compose</em> with whatever filter the reader set in the tool panel; the
|
|
672
|
+
<code>filters</code> prop cannot, because it maps to <code>filters.set</code> and replaces the
|
|
673
|
+
whole condition tree.
|
|
674
|
+
</p>
|
|
675
|
+
<p class="section-note">
|
|
676
|
+
<strong>StrictMode creates one instance.</strong> React 18's StrictMode deliberately mounts,
|
|
677
|
+
unmounts and mounts again in development. Every component here builds its instance in an
|
|
678
|
+
effect with an empty dependency list and destroys it in that effect's cleanup, so the first is
|
|
679
|
+
destroyed before the second is built and exactly one survives. There is no module-level
|
|
680
|
+
"already mounted" flag, because that would defeat a genuine remount.
|
|
681
|
+
</p>
|
|
682
|
+
<p class="section-note">
|
|
683
|
+
<strong>Props are diffed with <code>Object.is</code>, and that is a promise about identity.</strong>
|
|
684
|
+
An inline <code>columns={[{ field: 'a' }]}</code> is a new array on every render, so it counts
|
|
685
|
+
as changed every render and reconfigures the grid every render. Hoist it to module scope or
|
|
686
|
+
wrap it in <code>useMemo</code>. This is not a defect to work around: a deep compare of a
|
|
687
|
+
million-row array on every render would cost more than the reload it prevents.
|
|
688
|
+
</p>
|
|
689
|
+
<p class="section-note">
|
|
690
|
+
<strong>Sharing one <code>rows</code> array between grids is safe.</strong> Since 1.63 the
|
|
691
|
+
grid copies the array it is handed on ingest, so two grids given the same
|
|
692
|
+
<code>rows={EMPTY}</code> default no longer contaminate each other. The row <em>objects</em>
|
|
693
|
+
are still shared, as they always were — mutate one and both grids see it.
|
|
694
|
+
</p>
|
|
695
|
+
<p class="section-note">
|
|
696
|
+
<strong>React 18 and 19, no SSR.</strong> Nothing in the adapter uses an API added in 19 or
|
|
697
|
+
removed in 19 (<code>forwardRef</code> is still supported there). Every component owns a real
|
|
698
|
+
DOM element, so there is no server rendering and no React Server Component support: render
|
|
699
|
+
them on the client.
|
|
700
|
+
</p>
|
|
701
|
+
<p class="section-note">
|
|
702
|
+
<strong>One build warning is gone.</strong> The version resolver carried a Node-only fallback
|
|
703
|
+
whose <code>import('node:' + 'module')</code> Vite could not analyse statically, so every Vite
|
|
704
|
+
build of every application printed "The above dynamic import cannot be analyzed by Vite" about
|
|
705
|
+
a line that could never run. The build now deletes that branch from every emitted artefact.
|
|
706
|
+
</p>
|
|
707
|
+
|
|
543
708
|
<div class="note">
|
|
544
709
|
<p><strong>Published as <code>@toclocoinc/lattice-grid</code></strong>: <code>npm install
|
|
545
710
|
@toclocoinc/lattice-grid</code>, then <code>import { createLatticeGrid } from
|
|
@@ -845,7 +1010,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
845
1010
|
<tbody>
|
|
846
1011
|
<tr><td class="name">columns</td><td class="type">(Column | ColumnGroup)[]</td><td class="dflt">, </td><td class="desc">Column definitions. Groups may nest.</td></tr>
|
|
847
1012
|
<tr><td class="name">columnGroups</td><td class="type">ColumnGroup[]</td><td class="dflt">, </td><td class="desc">Header grouping declared separately from the columns.</td></tr>
|
|
848
|
-
<tr><td class="name">rows</td><td class="type">unknown[]</td><td class="dflt">, </td><td class="desc">Row objects.
|
|
1013
|
+
<tr><td class="name">rows</td><td class="type">unknown[]</td><td class="dflt">, </td><td class="desc">Row objects. <strong>The array is copied on ingest; the row objects in it are not.</strong> The grid keeps a shallow copy of the array you pass here (and of <code>source.rows</code>, and of the array given to <code>rows.load()</code>), so your array is never written to: after <code>rows.apply</code>, a sort, a group or an edit it holds exactly what it held when you passed it, and two grids built from one array are independent. The <em>objects</em> inside it are still yours — <code>row.data</code> is the object you supplied and <code>rows.data()</code> returns those same objects, so identity round-trips (see <code>ingest.retainSource</code>). <code>rows.apply({ update })</code> does not write through either: it merges into a <em>new</em> object, which replaces that slot in the grid's copy only, so <code>row.data</code> for an updated row is a new object and the one you passed is untouched. An <strong>in-place cell edit does</strong>: <code>edit.setCells</code>, or typing in a cell, writes the new value into the shared object, which is the other face of <code>row === sourceObject</code>. <code>ingest.retainSource: false</code> and <code>ingest.dropSourceRows</code> opt out of sharing altogether.</td></tr>
|
|
849
1014
|
<tr><td class="name">rowKey</td><td class="type">string | (row) => string</td><td class="dflt">, </td><td class="desc">Stable row identity. Without it the grid assigns a key per row object and warns: enough for sorting, filtering, selection and copying within a session, but change tracking, streaming dedupe, selection persistence and remote reload all switch off, because new objects are new rows. If it is configured but resolves to nothing for some rows — a field absent, or present on some rows only — those rows collapse onto one key and the grid warns once, naming the field(s) and how many rows were affected, on <code>rows.load()</code> as well as at construction.</td></tr>
|
|
850
1015
|
<tr><td class="name">source</td><td class="type">SourceConfig</td><td class="dflt">memory</td><td class="desc">Where rows come from: <code>memory</code>, <code>paged</code>, <code>remote</code> or <code>stream</code>. See <a href="#sources">Sources</a>.</td></tr>
|
|
851
1016
|
<tr><td class="name">ingest</td><td class="type">IngestConfig</td><td class="dflt">, </td><td class="desc"><code>{ retainSource, dropSourceRows }</code>. How rows enter the column store. <code>retainSource</code> defaults to <code>true</code>: the caller's row objects are held by reference so <code>rows.data()</code> returns them unchanged and <code>row === sourceObject</code> holds. Set it <code>false</code> to stop the store retaining them and reconstruct a row on demand — but the source layer and grid config still hold the array, so the resident footprint does not actually fall. <code>dropSourceRows: true</code> closes that gap: it releases the objects from the source layer too, so the packed columns become the only copy and the footprint drops by roughly an order of magnitude at scale. Either way <code>rows.data()</code> then returns freshly reconstructed objects, so identity checks and <code>row.sourceObject</code> no longer hold and equality becomes value-based. Cell values are unchanged.</td></tr>
|
|
@@ -1023,7 +1188,16 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
1023
1188
|
<code>type: 'date'</code> column groups into day buckets. Date filters are unaffected either way
|
|
1024
1189
|
— they compare on the day and return the same rows. Declare <code>type</code> and none of this applies:
|
|
1025
1190
|
<code>'date'</code> truncates on purpose, <code>'datetime'</code> keeps a wall clock, and
|
|
1026
|
-
<code>'timestamp'</code> keeps the instant to the millisecond.
|
|
1191
|
+
<code>'timestamp'</code> keeps the instant to the millisecond.
|
|
1192
|
+
<strong><code>rows.value()</code> returns that stored form — and the same one on every
|
|
1193
|
+
path:</strong> the rows you passed at construction, <code>rows.load()</code>,
|
|
1194
|
+
<code>rows.apply({ add })</code> and <code>rows.apply({ update })</code>, <code>edit.setCells</code>
|
|
1195
|
+
and a typed cell edit, a stream chunk and a stream re-send, a store-backed or columnar store,
|
|
1196
|
+
off-thread ingest, a bound KPI tile and a chart binding all answer the same shape for the same
|
|
1197
|
+
instant, so a host can do arithmetic on it without testing what it got.
|
|
1198
|
+
<code>rows.text()</code> is the formatted form, and <code>row.data</code> is always the raw
|
|
1199
|
+
value you supplied, unconverted. For millisecond arithmetic use <code>type: 'timestamp'</code>,
|
|
1200
|
+
which answers the epoch number on every one of those paths. A column of ISO timestamps that
|
|
1027
1201
|
must stay a calendar day opts out with an explicit <code>type: 'date'</code> — no warning is
|
|
1028
1202
|
logged for that column, because the value is preserved (declared, not narrowed) and there is
|
|
1029
1203
|
nothing to disclose.</p>
|
|
@@ -1218,7 +1392,7 @@ grid.destroy();
|
|
|
1218
1392
|
<table>
|
|
1219
1393
|
<thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
|
|
1220
1394
|
<tbody>
|
|
1221
|
-
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.
|
|
1395
|
+
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.63.1'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
|
|
1222
1396
|
<tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
|
|
1223
1397
|
<tr><td class="sig">set(key, value)</td><td class="type">void</td><td class="desc">Write one key. Every key is live; nothing needs a rebuild.</td></tr>
|
|
1224
1398
|
<tr><td class="sig">setAll(values)</td><td class="type">void</td><td class="desc">Write several in one pass. Emits one <code>config:changed</code> for the batch, not one per key.</td></tr>
|
|
@@ -1257,6 +1431,7 @@ grid.overlay.hide();</code></pre>
|
|
|
1257
1431
|
<tr><td class="sig">get(index)</td><td class="type">Row</td><td class="desc">By display index, after filtering, grouping and flattening.</td></tr>
|
|
1258
1432
|
<tr><td class="sig">byKey(key)</td><td class="type">Row</td><td class="desc">By row key, whether or not it is on screen.</td></tr>
|
|
1259
1433
|
<tr><td class="sig">matchCount()</td><td class="type">number</td><td class="desc">Data rows passing the filters, across every page. Excludes group headers, footers and totals, the numerator of "1,204 of 100,000".</td></tr>
|
|
1434
|
+
<tr><td class="sig">coverage()</td><td class="type">{ covered, total, windowed }</td><td class="desc">How much of the data a figure computed from this grid covers — the question the counters above cannot answer, because on a windowed source they all report the rows it is <em>holding</em> and so agree with each other while the data that has been through is larger. <code>covered</code> is the rows a figure would be computed over; <code>total</code> is the rows the source knows about, or <strong><code>null</code> when it cannot know</strong> (a stream still open that has evicted nothing has no idea how many rows are coming, and says so rather than repeating <code>covered</code>); <code>windowed</code> is true when a window bounded the computation. <strong><code>covered < total</code>, or <code>total === null</code>, means the figure is approximate</strong> — that is the test to write. False <code>windowed</code> means the figure is over all of it: a memory source, or a stream that finished having dropped nothing. Read on demand and cheap (one <code>matchCount()</code> and one <code>progress()</code> on the source, nothing cached), so call it beside every figure you publish rather than once at setup — on a live stream the answer moves.</td></tr>
|
|
1260
1435
|
<tr><td class="sig">count()</td><td class="type">number</td><td class="desc">Display rows: group rows included, collapsed children excluded.</td></tr>
|
|
1261
1436
|
<tr><td class="sig">totalCount()</td><td class="type">number</td><td class="desc">Source rows before filtering. The denominator of "1,204 of 100,000".</td></tr>
|
|
1262
1437
|
<tr><td class="sig">value(key, colId)</td><td class="type">unknown</td><td class="desc">The stored value.</td></tr>
|
|
@@ -2495,6 +2670,7 @@ createGrid(el, {
|
|
|
2495
2670
|
|
|
2496
2671
|
<h3 id="stationarity">Stationarity (ADF)</h3>
|
|
2497
2672
|
<p>Before you compare two series or detrend one, it helps to know whether it is <strong>stationary</strong> — reverting to a level or trend — or wandering with a unit root. <code>grid.statistics.adf</code> runs the Augmented Dickey-Fuller test (BACKLOG-0000873) and returns a scalar readout, not a per-row column: the statistic, the augmenting lag chosen by AIC, MacKinnon's critical values, an interpolated p-value (stamped approximate), and a plain-language verdict at the 5% level. The constant+trend regression and the AIC lag choice match statsmodels' <code>adfuller</code>, against which the statistic and lag are verified.</p>
|
|
2673
|
+
<p><strong>The lag search and what it costs.</strong> <code>maxlag</code> caps the number of augmenting lags the AIC search considers; left out, it is the Schwert rule <code>⌈12·(n/100)^0.25⌉</code> — 34 candidates on 7,000 rows — itself capped so the fixed sample keeps degrees of freedom. The candidates are nested, so the search builds one design matrix at the cap and reads every smaller candidate off it — one pass to accumulate the normal equations, then a small solve and a single residual pass per candidate, rather than a fresh fit each time (BACKLOG-0001347). The default search over 7,000 rows is a matter of milliseconds. Setting <code>maxlag</code> narrows the search, never the arithmetic: the lag chosen, the statistic and the p-value are whatever the data says, and a cap that still contains the AIC-preferred lag returns exactly the same readout.</p>
|
|
2498
2674
|
<pre data-run="js" data-expect="non-stationary|0" data-covers="method:statistics"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2499
2675
|
<span class="cmt">// A random walk (a unit root): it wanders rather than reverting.</span>
|
|
2500
2676
|
<span class="kw">const</span> walk = [0.138, -0.725, -1.26, -0.536, -0.267, 0.167, -0.765, -0.146, -0.311, -0.586,
|
|
@@ -2921,6 +3097,18 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
|
|
|
2921
3097
|
|
|
2922
3098
|
<h2 id="sources">Sources</h2>
|
|
2923
3099
|
<p class="section-note">Where rows come from. <code>memory</code> is the default and needs no configuration.</p>
|
|
3100
|
+
<p><strong>A memory grid's rows may be declared either way.</strong> The top-level
|
|
3101
|
+
<code>rows</code> option is the usual one and what every example here uses; the same array
|
|
3102
|
+
inside the block works identically — <code>createGrid(el, { columns, source: { mode:
|
|
3103
|
+
'memory', rows } })</code> opens with exactly those rows, with the same
|
|
3104
|
+
<code>count()</code>, <code>value()</code>, <code>text()</code>, type inference and bound KPI
|
|
3105
|
+
and chart readings as <code>createGrid(el, { columns, rows })</code>, and
|
|
3106
|
+
<code>rows.load()</code> and <code>rows.apply()</code> behave the same afterwards. Either
|
|
3107
|
+
array is copied on ingest, so the one you passed is never written to (see
|
|
3108
|
+
<code>rows</code> under <a href="#config">Configuration</a>). <strong>Declare them once:</strong>
|
|
3109
|
+
a grid given <em>both</em> uses the top-level <code>rows</code>, ignores the block's, and warns
|
|
3110
|
+
once naming both places. Only <code>memory</code> reads a <code>rows</code> key from the block
|
|
3111
|
+
— the fetching modes take theirs from the server.</p>
|
|
2924
3112
|
<div class="table-wrap">
|
|
2925
3113
|
<table>
|
|
2926
3114
|
<thead><tr><th>Mode</th><th>Needs</th><th>Description</th></tr></thead>
|
|
@@ -3498,7 +3686,7 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
3498
3686
|
<tbody>
|
|
3499
3687
|
<tr><td class="name">odataAdapter</td><td class="desc">Any OData v4 endpoint</td><td class="desc">Writes <code>$filter</code>, <code>$orderby</code>, <code>$top</code>, <code>$skip</code> and <code>$count</code>. System options keep their <code>$</code> unencoded, which several servers require.</td></tr>
|
|
3500
3688
|
<tr><td class="name">restAdapter</td><td class="desc">The API you already have</td><td class="desc">Parameter names are yours to choose. Paging and sorting are assumed; filtering is assumed absent until you declare <code>operators</code>, because an adapter that claims to filter when the endpoint ignores it returns the wrong rows silently.</td></tr>
|
|
3501
|
-
<tr><td class="name">duckdbAdapter</td><td class="desc">A DuckDB connection</td><td class="desc">Writes SQL and takes the whole query: filter tree, multi-column sort and
|
|
3689
|
+
<tr><td class="name">duckdbAdapter</td><td class="desc">A DuckDB connection</td><td class="desc">Writes SQL and takes the whole query: filter tree, multi-column sort, paging and <a href="#duckdb-grouping">grouping</a> — a grouped grid is answered by <code>GROUP BY</code>, one level at a time, with the group counts, the subtotals, the matching count and the grand total all computed in the engine. <code>from</code> is any FROM expression, so <code>read_parquet('s3://bucket/*.parquet')</code> is as valid as a table name. The engine is yours to create and install; this imports nothing, so the bundle is unchanged whether you use it or not.</td></tr>
|
|
3502
3690
|
<tr><td class="name">dfqlAdapter</td><td class="desc">DemandFlow entities</td><td class="desc">Speaks <code>POST /v1/query</code>. Sends the entity, the key attribute and the prefix to match, a field projection and one field-and-term filter, matched as a case-insensitive substring. It cannot sort or page, so the grid does both, and every request carries a <code>countOnly</code> line because <code>limit</code> caps rows <em>scanned</em> rather than matched: a filtered query returns an arbitrary subset, and the count is the only thing that reveals it.</td></tr>
|
|
3503
3691
|
<tr><td class="name">graphqlAdapter</td><td class="desc">Any GraphQL endpoint</td><td class="desc">Configured, not zero-config: GraphQL has no fixed query semantics, so you pass <code>buildQuery</code> to turn the plan into a <code>{ query, variables }</code> operation and <code>parseResponse</code> to read <code>data</code> back into rows and total. Defaults cover an offset/limit list with <code>totalCount</code> and a Relay cursor connection (<code>first</code>/<code>after</code> with <code>pageInfo</code>). The default pushes only the window and the total; declare <code>operators</code> or <code>capabilities</code> for filter/sort only alongside a <code>buildQuery</code> that emits them. A cursor connection is forward-only, so a deep window costs round trips proportional to its offset.</td></tr>
|
|
3504
3692
|
</tbody>
|
|
@@ -3620,7 +3808,9 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
3620
3808
|
before <code>LIMIT</code>, so one round trip returns both the window and the size of the set it
|
|
3621
3809
|
was cut from. Against a local table it is free. Against a remote Parquet it is the most
|
|
3622
3810
|
expensive thing the adapter does — a window function has to see every matching row of the
|
|
3623
|
-
<em>projected</em> columns, so row-group pruning
|
|
3811
|
+
<em>projected</em> columns, so whatever row-group pruning or range reads DuckDB and the file
|
|
3812
|
+
host might otherwise manage between them (see <a href="#duckdb-range-reads">whether a Parquet
|
|
3813
|
+
file streams or downloads whole</a>) cannot help, and the whole file
|
|
3624
3814
|
crosses the wire to produce one page. Measured in Chrome on <code>duckdb-eh.wasm</code> against
|
|
3625
3815
|
a 10,000,000-row Parquet of 162,386,227 bytes — <strong>162.4 MB</strong> decimal,
|
|
3626
3816
|
154.9 MiB binary, and every transfer figure on this page is decimal MB so that it can be
|
|
@@ -3696,6 +3886,25 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
3696
3886
|
real row 900 means paging to it. Turn the count off for a grid whose users scroll; leave it on
|
|
3697
3887
|
for one whose users jump.
|
|
3698
3888
|
</p>
|
|
3889
|
+
<p class="section-note" id="duckdb-range-reads">
|
|
3890
|
+
<strong>Whether a Parquet file streams or downloads whole is DuckDB's and the file host's
|
|
3891
|
+
doing, not the grid's.</strong> <code>duckdbAdapter</code> only writes SQL; it never opens a
|
|
3892
|
+
file, so it has no say in whether <code>read_parquet(...)</code> reads the whole thing or only
|
|
3893
|
+
the row groups a query needs. Measured against a 1.5 MB Parquet on GitHub Pages
|
|
3894
|
+
(BACKLOG-0001324): DuckDB-Wasm 1.32.0's default HTTP path issued <strong>0 Range requests and
|
|
3895
|
+
read 100%</strong> of the file, for every query including a single-value chip filter. Running
|
|
3896
|
+
<code>LOAD httpfs;</code> on the connection before the first <code>read_parquet(...)</code>
|
|
3897
|
+
call changed that to <strong>25 Range requests and 30%</strong> of the file for the same query.
|
|
3898
|
+
(DuckDB-Wasm 1.29.0 read 119% of the file on the same test — its ranges overlapped — so the
|
|
3899
|
+
exact figures are a version's, not a promise.) A file registered with
|
|
3900
|
+
<code>db.registerFileBuffer(...)</code> is always read whole, whatever version is loaded: a
|
|
3901
|
+
buffer has already been downloaded in full before DuckDB ever sees it. And the file host has to
|
|
3902
|
+
cooperate: it must answer <code>HEAD</code>, advertise <code>Accept-Ranges: bytes</code> and
|
|
3903
|
+
answer a range request with <code>206 Partial Content</code> and a <code>Content-Range</code>
|
|
3904
|
+
header — GitHub Pages does all three — and, cross-origin, its CORS policy must expose
|
|
3905
|
+
<code>Content-Range</code> and <code>Content-Length</code> or the browser cannot read them back.
|
|
3906
|
+
Any of that missing and DuckDB falls back to reading the file whole, silently.
|
|
3907
|
+
</p>
|
|
3699
3908
|
<p class="section-note">
|
|
3700
3909
|
<strong>Typed binding for timestamp and date columns.</strong> A prepared statement binds a
|
|
3701
3910
|
filter value with the value's own type, not the column's: the grid sends an instant as an
|
|
@@ -3732,6 +3941,94 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
3732
3941
|
changed). Against a <code>DATE</code> column an instant is truncated to its UTC day.
|
|
3733
3942
|
</p>
|
|
3734
3943
|
|
|
3944
|
+
<h5 id="duckdb-grouping"><code>duckdbAdapter</code>: grouping runs in the engine, one level at a time</h5>
|
|
3945
|
+
<p>
|
|
3946
|
+
Grouping a hundred million Parquet rows used to mean fetching a hundred million Parquet rows:
|
|
3947
|
+
the adapter declared no <code>group</code> capability, so the push router never sent it a
|
|
3948
|
+
grouped request and the grid grouped whatever it held. <code>duckdbAdapter</code> now declares
|
|
3949
|
+
<code>group: true</code> and answers the grouped view with <code>GROUP BY</code> — one
|
|
3950
|
+
statement per grid level, paged like any other window.
|
|
3951
|
+
</p>
|
|
3952
|
+
<p class="section-note">
|
|
3953
|
+
Nothing is configured. Group a grid over a DuckDB source and the grouping is pushed:
|
|
3954
|
+
</p>
|
|
3955
|
+
<pre><code><span class="kw">const</span> grid = createGrid(el, {
|
|
3956
|
+
columns: [
|
|
3957
|
+
{ id: <span class="str">'region'</span> },
|
|
3958
|
+
{ id: <span class="str">'tier'</span> },
|
|
3959
|
+
{ id: <span class="str">'amount'</span>, type: <span class="str">'number'</span>, total: <span class="str">'sum'</span> },
|
|
3960
|
+
],
|
|
3961
|
+
groupBy: [<span class="str">'region'</span>, <span class="str">'tier'</span>],
|
|
3962
|
+
source: createPushdownSource({
|
|
3963
|
+
adapter: duckdbAdapter({ connection, from: <span class="str">"read_parquet('s3://bucket/sales/*.parquet')"</span> }),
|
|
3964
|
+
}),
|
|
3965
|
+
});</code></pre>
|
|
3966
|
+
<p class="section-note">
|
|
3967
|
+
The root level is one statement — <code>SELECT "region", count(*), sum("amount") … GROUP BY
|
|
3968
|
+
"region" ORDER BY "region" ASC NULLS LAST LIMIT ? OFFSET ?</code> — so the group rows on screen
|
|
3969
|
+
cost a grouped scan and no leaf crosses the wire. Expanding a group narrows the next level by
|
|
3970
|
+
its parent's key; expanding the deepest one runs the ordinary row query with the same predicate
|
|
3971
|
+
ANDed on, so the leaves arrive paged and sorted exactly as they would without grouping. Every
|
|
3972
|
+
identifier goes through the same validation the row query uses and every value is bound, so the
|
|
3973
|
+
grouped path is no more exposed than the read path.
|
|
3974
|
+
</p>
|
|
3975
|
+
<p class="section-note">
|
|
3976
|
+
<strong>Subtotals come from the engine, and a statistic it cannot express shows nothing rather
|
|
3977
|
+
than something.</strong> Each totalled column contributes one aggregate expression, taken from
|
|
3978
|
+
the same verified pushdown map the statistics panel uses (see
|
|
3979
|
+
<a href="#pushdown-aggregates">pushing statistics down</a>) — <code>sum</code>,
|
|
3980
|
+
<code>avg</code>, <code>min</code>, <code>max</code>, <code>count</code>, the quantiles, and the
|
|
3981
|
+
rest. A column whose total is a host function, a two-column statistic such as
|
|
3982
|
+
<code>weightedAvg</code> (a grouped request carries no weight column), and the one genuine
|
|
3983
|
+
fallback <code>weightedQuantile</code> are <em>not</em> sent, are named in
|
|
3984
|
+
<code>source.lastPlan().aggregates.client</code> with the reason, and warn once. The group row
|
|
3985
|
+
then carries no value for that column. That is deliberate: the leaves of an unexpanded group
|
|
3986
|
+
are not in the browser, so the only alternative to the engine's figure is a figure computed
|
|
3987
|
+
over something that is not the group.
|
|
3988
|
+
</p>
|
|
3989
|
+
<p class="section-note">
|
|
3990
|
+
<strong>The counts a grouped grid shows are the engine's.</strong> Under grouping the display
|
|
3991
|
+
count is group headers plus whatever is expanded, which is how a grid showing three rows under
|
|
3992
|
+
one group once reported “4 of 3”. The root level's fetch therefore also asks for
|
|
3993
|
+
<code>count(*)</code> over the matching set and the grand total over it, in one extra
|
|
3994
|
+
statement, and an unfiltered <code>count(*)</code> once per adapter (on a Parquet file that is
|
|
3995
|
+
a footer read, not a scan). <code>rows.matchCount()</code> and <code>rows.totalCount()</code>
|
|
3996
|
+
read them, and <code>grandTotalRow: 'bottom'</code> draws its row from them.
|
|
3997
|
+
</p>
|
|
3998
|
+
<p class="section-note">
|
|
3999
|
+
<strong>Group order, and the collation decision.</strong> Group rows are ordered by their own
|
|
4000
|
+
key, ascending unless the sort names that column, which is exactly what the grid does to
|
|
4001
|
+
sibling group rows in memory — a sort naming some other column does not reorder groups in
|
|
4002
|
+
either. Absent keys sort last ascending and first descending, written as explicit
|
|
4003
|
+
<code>NULLS LAST</code>/<code>NULLS FIRST</code> rather than left to the connection's
|
|
4004
|
+
<code>default_null_order</code>. <strong>No <code>COLLATE</code> is written and the ICU
|
|
4005
|
+
extension is not loaded</strong>: the grid compares group keys with <code><</code> on the
|
|
4006
|
+
JavaScript string (UTF-16 code-unit order) and DuckDB's default <code>VARCHAR</code> ordering
|
|
4007
|
+
is UTF-8 byte order, and the two agree for every character in the Basic Multilingual Plane.
|
|
4008
|
+
They part only for supplementary-plane characters (emoji, CJK extension B and above) compared
|
|
4009
|
+
against U+E000–U+FFFF. An ICU collation would disagree with the grid everywhere instead, so
|
|
4010
|
+
binary ordering is the pin. Practically: <code>'North'</code> sorts before <code>'north'</code>
|
|
4011
|
+
in both.
|
|
4012
|
+
</p>
|
|
4013
|
+
<p class="section-note">
|
|
4014
|
+
<strong>When grouping is <em>not</em> pushed, it says so.</strong> Grouping is all or nothing —
|
|
4015
|
+
group rows counted over the wrong set are wrong rows, not slow ones — so the whole level is
|
|
4016
|
+
refused if anything else in the query stayed behind: a filter that did not fully push, a sort
|
|
4017
|
+
the engine could not take, a quick search, a host <code>where</code> predicate, a grouping key
|
|
4018
|
+
that is not a plain column, or <code>fullDataset</code> (which holds the whole set and groups it
|
|
4019
|
+
client-side on purpose). <code>source.lastPlan()</code> then reports
|
|
4020
|
+
<code>grouped: false</code>, <code>'group'</code> in <code>unpushed</code> and a
|
|
4021
|
+
<code>groupReason</code> sentence, and a one-time warning names it.
|
|
4022
|
+
</p>
|
|
4023
|
+
<p class="section-note">
|
|
4024
|
+
<strong>One known difference from a memory grid.</strong> A column holding both
|
|
4025
|
+
<code>NULL</code> and the empty string produces two groups in the engine and one in a memory
|
|
4026
|
+
grid, because the grid keys its group nodes on a display path where an absent value and an
|
|
4027
|
+
empty string are both <code>''</code>. The engine's answer is the right one; the two are
|
|
4028
|
+
otherwise identical group for group, count for count and subtotal for subtotal, which the
|
|
4029
|
+
parity suite asserts at every level.
|
|
4030
|
+
</p>
|
|
4031
|
+
|
|
3735
4032
|
<h5 id="dfql-options"><code>dfqlAdapter</code></h5>
|
|
3736
4033
|
<div class="table-wrap">
|
|
3737
4034
|
<table>
|
|
@@ -4959,6 +5256,42 @@ grid.destroy();
|
|
|
4959
5256
|
naming the span and how old the newest reading is, so a dead feed reads as no data rather
|
|
4960
5257
|
than as a chart that quietly stopped moving.
|
|
4961
5258
|
</p>
|
|
5259
|
+
<p class="section-note">
|
|
5260
|
+
<strong>The x scale comes from the column's type, and nothing else</strong>
|
|
5261
|
+
(BACKLOG-0001344). A temporal type — <code>date</code>, <code>datetime</code>,
|
|
5262
|
+
<code>timestamp</code> or <code>dateString</code> — draws a time axis; a numeric type
|
|
5263
|
+
draws a linear axis <em>whatever its distinct count</em>; every other type draws bands, one
|
|
5264
|
+
per distinct value. A declared numeric or temporal column is therefore never demoted to a
|
|
5265
|
+
band, which it used to be below thirteen distinct values — silently turning off
|
|
5266
|
+
<code>fit</code>, <code>band</code> and everything else that needs a continuous x. Where the
|
|
5267
|
+
column's type is not what you want, pin the scale with
|
|
5268
|
+
<code>axis: { x: { scale: 'band' | 'linear' | 'time' } }</code>: <code>'band'</code> is how a
|
|
5269
|
+
numeric code column (a quarter, a rating, a star count) asks for its bands back, and
|
|
5270
|
+
<code>'time'</code> or <code>'linear'</code> lifts a column the grid types as
|
|
5271
|
+
<code>text</code> onto a continuous axis. That last case is worth knowing about: a grid built
|
|
5272
|
+
with <strong>no rows</strong> infers <code>text</code> and does not revisit it when rows
|
|
5273
|
+
arrive, so a streamed date column binds bands where the same column beside a populated grid
|
|
5274
|
+
binds time. The chart warns once when it bands a column whose values all read as dates or as
|
|
5275
|
+
numbers, naming the column, its type and the option that overrules it. A band scale always
|
|
5276
|
+
draws: a line, area or step on one is drawn through the band centres, and its labels are
|
|
5277
|
+
thinned to the pitch the font can be read at rather than one per row
|
|
5278
|
+
(<code>axis.x.every</code> overrides the count).
|
|
5279
|
+
</p>
|
|
5280
|
+
<p class="section-note">
|
|
5281
|
+
<strong>The margin a chart leaves for its labels is measured from the labels</strong>
|
|
5282
|
+
(BACKLOG-0001343). Where a chart names its rows down the left — a
|
|
5283
|
+
<code>correlogram</code>'s columns, a <code>horizontalBar</code>'s categories, a
|
|
5284
|
+
<code>gantt</code>'s tasks, a <code>forest</code>'s coefficients, a <code>heatmap</code>'s
|
|
5285
|
+
rows — the gutter is the widest name it will draw, <strong>capped at two fifths of the
|
|
5286
|
+
chart</strong>. Past that cap a name is ellipsised and keeps the whole of itself as
|
|
5287
|
+
<code>aria-label</code>, so a screen reader announces the real name and the glyphs still stop
|
|
5288
|
+
inside the chart; no label is ever cut mid-glyph, and the gutter is re-measured whenever the
|
|
5289
|
+
chart is resized. Widening the chart therefore gives the names more room, which is the thing
|
|
5290
|
+
a fixed margin could not do. Where the left-hand gutter holds a <em>measure</em> axis the
|
|
5291
|
+
room is estimated from five digits instead, because the numbers on it are not known until
|
|
5292
|
+
after the plot has been laid out. <code>margin: { left }</code> is added to whatever the
|
|
5293
|
+
labels need rather than competing with it.
|
|
5294
|
+
</p>
|
|
4962
5295
|
|
|
4963
5296
|
<h3>The chart</h3>
|
|
4964
5297
|
<div class="table-wrap">
|
|
@@ -5062,7 +5395,7 @@ createGrid(el, {
|
|
|
5062
5395
|
explicit-bound error-bar primitive. A preset a given model cannot support (no multicollinearity
|
|
5063
5396
|
for one predictor, no band for several) is returned as a null spec carrying a machine-readable
|
|
5064
5397
|
reason rather than silently dropped.</p>
|
|
5065
|
-
<pre data-run="js" data-expect="scatter|5|scatter|qq|bubble|cook|scatter|forest" data-covers="export:regressionPlots"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
5398
|
+
<pre data-run="js" data-expect="scatter|5|scatter|qq|bubble|cook|scatter|forest" data-covers="export:regressionPlots config:columnarBelow"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
5066
5399
|
<span class="kw">const</span> { regressionPlots } = <span class="kw">await</span> import('../packages/modules/charts/index.js');
|
|
5067
5400
|
|
|
5068
5401
|
<span class="cmt">// x=1..5, y=2,4,5,4,5. Shadow columns carry the model's per-row diagnostics —</span>
|
|
@@ -5103,8 +5436,52 @@ createGrid(el, {
|
|
|
5103
5436
|
].join('|');</code></pre>
|
|
5104
5437
|
|
|
5105
5438
|
<h3>Maps</h3>
|
|
5106
|
-
<p>A <code>geomap</code> takes an ISO code from one column and a value from another. Alpha-2, alpha-3 and numeric codes are all accepted, and continent codes draw a continent map without any outline data.
|
|
5107
|
-
|
|
5439
|
+
<p>A <code>geomap</code> takes an ISO code from one column and a value from another. Alpha-2, alpha-3 and numeric codes are all accepted, and continent codes draw a continent map without any outline data. Codes that match nothing are counted and reported on the chart rather than dropped, a map missing half its data looks exactly like a map of a world where half the data is zero. The full code tables are in <a href="CHART-CODES.md">CHART-CODES.md</a>.</p>
|
|
5440
|
+
|
|
5441
|
+
<h4 id="geometry-packs">Real outlines: geometry packs</h4>
|
|
5442
|
+
<p><strong>A pack is an optional module you import only if you draw that map.</strong> Real boundaries are tens to hundreds of kilobytes, so none of them are in the charts bundle and the grid still fetches nothing at runtime: you import the pack you want, exactly as you import an extension chart type, and hand it to <code>shapes</code>. Each pack is <a href="https://github.com/topojson/topojson-specification">TopoJSON</a> — quantised and delta-encoded, decoded by the chart — generated from the published source below by <code>tools/build-geo-packs.mjs</code>, and it carries its own provenance: the source URL, the version, the date it was retrieved, the licence, and the attribution line that licence requires.</p>
|
|
5443
|
+
<pre><code><span class="kw">import</span> { pack } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/geo-world-110m';
|
|
5444
|
+
|
|
5445
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'iso', y: 'revenue',
|
|
5446
|
+
shapes: pack }); <span class="cmt">// Equal Earth, fitted to the pack</span></code></pre>
|
|
5447
|
+
<div class="table-wrap">
|
|
5448
|
+
<table>
|
|
5449
|
+
<thead><tr><th>Module</th><th>Regions</th><th>Source and licence</th><th>Size (gzipped)</th><th>Attribution required</th></tr></thead>
|
|
5450
|
+
<tbody>
|
|
5451
|
+
<tr><td class="sig">modules/geo-world-110m</td><td>177 countries</td><td>Natural Earth 1:110m Admin 0, via world-atlas — public domain</td><td>39 KB</td><td>None</td></tr>
|
|
5452
|
+
<tr><td class="sig">modules/geo-world-50m</td><td>241 countries</td><td>Natural Earth 1:50m Admin 0, via world-atlas — public domain</td><td>225 KB</td><td>None</td></tr>
|
|
5453
|
+
<tr><td class="sig">modules/geo-us-states</td><td>50 states + DC</td><td>US Census cartographic boundaries, via us-atlas — public domain</td><td>36 KB</td><td>None</td></tr>
|
|
5454
|
+
<tr><td class="sig">modules/geo-europe-nuts</td><td>NUTS 0–2</td><td>Eurostat GISCO NUTS 2021 1:20m — free re-use with attribution</td><td>95 KB</td><td><code>© EuroGeographics for the administrative boundaries</code></td></tr>
|
|
5455
|
+
<tr><td class="sig">modules/geo-uk</td><td>9 regions, 361 local authorities, 650 constituencies</td><td>ONS Open Geography, generalised clipped — Open Government Licence v3.0</td><td>292 KB</td><td><code>Contains OS data © Crown copyright and database right 2026; Source: Office for National Statistics licensed under the Open Government Licence v.3.0</code></td></tr>
|
|
5456
|
+
</tbody>
|
|
5457
|
+
</table>
|
|
5458
|
+
</div>
|
|
5459
|
+
<p><strong>Joining.</strong> A pack is keyed by the code its source is published under and by the codes that source also knows: the world packs by ISO alpha-2, with alpha-3 and numeric accepted; <code>geo-us-states</code> by the two-letter USPS abbreviation, with the FIPS code accepted; <code>geo-europe-nuts</code> by NUTS id (<code>DE</code>, <code>DE1</code>, <code>DE11</code>); <code>geo-uk</code> by ONS code (<code>E12000007</code>). A region the pack does not know is reported as unmatched exactly as before.</p>
|
|
5460
|
+
<p><strong>Choosing a grain.</strong> <code>geo-uk</code> ships three layers in one module because they share a coastline: <code>layer: 'regions'</code> (the default), <code>'local-authorities'</code> or <code>'constituencies'</code>.</p>
|
|
5461
|
+
<p><strong>Two notes from the data, not from us.</strong> Natural Earth at 1:110m leaves out the micro-states — Singapore, Malta, Monaco have no outline at that scale — so bind country-level data to <code>geo-world-50m</code> if those matter. And <code>geo-us-states</code> places Alaska and Hawaii at their true longitudes rather than in the insets an Albers USA composite uses, so a map of all 51 spans the Pacific; the five US territories are left out of the pack for the same reason.</p>
|
|
5462
|
+
|
|
5463
|
+
<h4 id="map-projections">Projections</h4>
|
|
5464
|
+
<p>Every map names a projection, and a pack declares the right one for itself, so <code>shapes: pack</code> alone gives a sensible map. <code>projection</code> overrides it; <code>projectionOptions</code> passes <code>parallels</code> and <code>centre</code> to the two that take them. The drawn geometry is then <strong>fitted</strong> to the panel, so a map of the UK fills its box rather than sitting inside the whole globe's.</p>
|
|
5465
|
+
<div class="table-wrap">
|
|
5466
|
+
<table>
|
|
5467
|
+
<thead><tr><th>Name</th><th>What it is</th><th>Use it for</th></tr></thead>
|
|
5468
|
+
<tbody>
|
|
5469
|
+
<tr><td class="sig">equalEarth</td><td>Equal-area (Šavrič, Patterson & Jenny 2018)</td><td>A world map — the default. Areas are honest and the shapes are recognisable.</td></tr>
|
|
5470
|
+
<tr><td class="sig">robinson</td><td>Compromise, tabulated</td><td>A world map where the poles matter more than area.</td></tr>
|
|
5471
|
+
<tr><td class="sig">mercator</td><td>Conformal, cut at ±85.05°</td><td>Matching a web-map basemap.</td></tr>
|
|
5472
|
+
<tr><td class="sig">albers</td><td>Conic equal-area, two standard parallels</td><td>A country or continent in the mid-latitudes — the US and Europe packs default to it.</td></tr>
|
|
5473
|
+
<tr><td class="sig">transverseMercator</td><td>Conformal about a central meridian</td><td>A tall, narrow country. The UK pack defaults to it through 2°W, which is what stands Britain upright.</td></tr>
|
|
5474
|
+
<tr><td class="sig">equirectangular</td><td>Longitude and latitude straight onto x and y</td><td>Back-compatibility: the projection every map here drew before 1.63.</td></tr>
|
|
5475
|
+
</tbody>
|
|
5476
|
+
</table>
|
|
5477
|
+
</div>
|
|
5478
|
+
<pre><code>createChart({ grid, container: '#uk', type: 'geomap', code: 'lad', y: 'claims',
|
|
5479
|
+
shapes: ukPack, layer: 'local-authorities' }); <span class="cmt">// transverse Mercator</span>
|
|
5480
|
+
|
|
5481
|
+
createChart({ grid, container: '#us', type: 'geomap', code: 'state', y: 'sales',
|
|
5482
|
+
shapes: usPack, projection: 'albers',
|
|
5483
|
+
projectionOptions: { parallels: [29.5, 45.5], centre: [-96, 37.5] } });</code></pre>
|
|
5484
|
+
<p><strong>The antimeridian is handled in the chart.</strong> A country whose outline crosses ±180° — Russia, Fiji, New Zealand's Chathams — is split there before it is projected, so it draws as the parts it is rather than as a band running the wrong way across the map. Antarctica is cropped until it carries a value, by its country code <code>AQ</code> as well as the continent code <code>AN</code>.</p>
|
|
5108
5485
|
|
|
5109
5486
|
<div class="note"><p>The module imports nothing from the grid: <code>createChart</code> is handed a grid rather than importing one. That is what keeps the charts bundle to the drawing, and it is why the grid must be created first, and why a chart cannot outlive it.</p></div>
|
|
5110
5487
|
|
|
@@ -5781,6 +6158,22 @@ gantt.mount(document.querySelector('#plan'), {
|
|
|
5781
6158
|
width: 'container', // the default: fill the container, and keep following it
|
|
5782
6159
|
});</code></pre>
|
|
5783
6160
|
<p><strong>Sizing (BACKLOG-0001079).</strong> <code>width</code> defaults to <code>'container'</code>: the view measures the box it was mounted into and redraws itself whenever that box changes, so a plan in a tab, a drawer, an accordion, a responsive panel or a split pane fits without the host writing a <code>ResizeObserver</code> of its own. A container with no box — a hidden tab, or an element that has not been laid out yet — is not treated as a container of zero width: the view holds a 720px fallback and adopts the real width the moment there is one. Pass a <strong>number</strong> to take the decision yourself; a numeric <code>width</code> is honoured exactly, installs no observer, and keeps the eight-tick axis it always had — only a container-sized plot thins its tick labels to the width it was given, because only a container-sized plot can be somewhere it had not been before. <code>zoom</code> and a numeric <code>width</code> are mutually exclusive: <strong>zoom wins</strong> — it fixes the pixels-per-day and lets the plot scroll past the container — and passing both now warns rather than discarding the <code>width</code> in silence.</p>
|
|
6161
|
+
<p><strong>The time axis thins its own labels (BACKLOG-0001319).</strong> A Gantt picks its tick
|
|
6162
|
+
rhythm from the calendar — every day at <code>zoom: 'day'</code>, every seven days at
|
|
6163
|
+
<code>'week'</code>, every month above that — and that rhythm knows nothing about how wide
|
|
6164
|
+
a date is. Where a label is wider than the gap between two ticks, only <strong>every nth</strong>
|
|
6165
|
+
label is drawn: the largest regular stride that leaves at least 6px of clear space between
|
|
6166
|
+
neighbours, so the axis keeps an even rhythm a reader can count on rather than the uneven gaps a
|
|
6167
|
+
greedy left-to-right fit would leave. <strong>The gridlines and the split view's week separators
|
|
6168
|
+
are not thinned with the text</strong> — the fine rhythm is information and costs nothing to
|
|
6169
|
+
read; it was only ever the text that collided. Both surfaces do it: <code>mount</code>'s axis and
|
|
6170
|
+
<code>mountSplit</code>'s timeline header, which is where a week-zoom header used to read
|
|
6171
|
+
<code>Sun 05 Oct 2025 Sun 12 Oct 2025 Sun 19 Oct 2025</code> with each date painted across the
|
|
6172
|
+
next. Widening the Gantt now shows more dates rather than the same overlap, and the thinning is
|
|
6173
|
+
re-decided on every draw, so a resize or a zoom change re-fits the labels. An <strong>un-zoomed</strong>
|
|
6174
|
+
axis is untouched: it already chooses how many ticks to draw from the plot's own width, so its
|
|
6175
|
+
labels always fitted, and a host that passed an explicit <code>width</code> keeps exactly the
|
|
6176
|
+
drawing it had.</p>
|
|
5784
6177
|
<p><strong>The project anchor (BACKLOG-0001079).</strong> A <code>mount</code> option, not a <code>mountSplit</code> one: the joined split view below takes neither <code>projectEpoch</code> nor a date-valued <code>today</code>, and its weekend shading is unanchored. The engine's time line is whole days since the Unix epoch, so a plan written as day offsets (<code>0, 4, 9…</code>) legitimately renders as January 1970 — day 0 <em>is</em> 1970-01-01, and the module cannot tell an offset from a real epoch day, so it cannot warn about it. <code>projectEpoch</code> says which calendar date plan day 0 stands for. It and <code>today</code> take an ISO date string, a <code>Date</code> or a day number. <strong>A <code>Date</code> is read as the calendar date its local wall clock shows</strong> (BACKLOG-0001104), the way a <code>date</code> column reads one: <code>new Date(2026, 2, 2)</code> is 2 March in every time zone, and a <code>Date</code> that carries a time of day is the local day it falls on. Before 1104 the UTC instant was floored, which put local midnight a day early everywhere east of Greenwich. A <code>Date</code> is therefore decided by the reader's zone; a string is the same day on every machine — <code>'2026-03-02'</code> is 2 March in Sydney and in New York alike — which is the form to prefer for an anchor stored with the plan. Recognise your own case: if you worked around the old behaviour by passing <code>new Date(Date.UTC(y, m, d))</code>, a reader west of Greenwich now sees the previous day, because UTC midnight is still the evening before in New York — pass <code>new Date(y, m, d)</code> or the ISO string instead. It is <strong>display-only</strong>: axis ticks, bar labels, tooltips, screen-reader text and the built-in weekend shading move with it, and nothing the scheduler, <code>getState</code>, the CSV or the MSPDI export produces does — every <code>es</code>/<code>ef</code> you read back is still the number you supplied. A host-supplied <code>nonWorking</code> function keeps receiving raw plan days, since it was written against your day numbers. Use <code>projectStart</code> instead when you want the model itself to be on calendar dates.</p>
|
|
5785
6178
|
<pre><code>// A relative plan: offsets in the data, real dates on the screen.
|
|
5786
6179
|
gantt.mount(el, {
|
|
@@ -6723,6 +7116,15 @@ createGrid(el, {
|
|
|
6723
7116
|
versions give one column <code>layout: { flex: 1 }</code> so the cells reach the edge and there
|
|
6724
7117
|
is no tail to click.</p>
|
|
6725
7118
|
|
|
7119
|
+
<p><strong>Expand all</strong> and <strong>Collapse all</strong> are built-in row/grid items
|
|
7120
|
+
(BACKLOG-0001305): present, in this order, on the row context menu and on every column's
|
|
7121
|
+
header menu (the 3-dot button and a right-click on the heading) whenever the grid is grouped,
|
|
7122
|
+
hidden rather than disabled otherwise. Each drives the public
|
|
7123
|
+
<code>grid.rows.expandAll()</code> / <code>collapseAll()</code>, is matched by its translated
|
|
7124
|
+
<code>name</code> like any other built-in item (catalogue keys <code>menu.expandAll</code> /
|
|
7125
|
+
<code>menu.collapseAll</code>), and passes through the same <code>contextMenu</code> /
|
|
7126
|
+
<code>columnMenu</code> chain above.</p>
|
|
7127
|
+
|
|
6726
7128
|
<p><code>columnMenu</code> takes the same form for the header's menu: both the 3-dot button
|
|
6727
7129
|
and a right-click on a heading. Its <code>params</code> is
|
|
6728
7130
|
<code>{ colId, column, grid }</code>. Anything of your own that you put on a column definition
|
|
@@ -7291,7 +7693,7 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
7291
7693
|
|
|
7292
7694
|
<h3 id="module-exports-example">Every module export, executed</h3>
|
|
7293
7695
|
<p class="section-note">Every shipped module’s exports, resolved against its own barrel on every build.</p>
|
|
7294
|
-
<pre data-run="js" data-expect="
|
|
7696
|
+
<pre data-run="js" data-expect="68" data-covers="export:ContextMenu export:Messages export:Registry export:autoInit export:createGrid export:createLocalViewStorage export:createMessages export:createStat export:deltaOf export:gridElementsWithin export:hydrateTable export:mountPanel export:readTable export:toneOf export:Chart export:PALETTE export:SCHEMES export:TYPES export:createChart export:chartRange export:canChartRange export:deriveRangeSpec export:regressionPlots export:registerScheme export:resolveScheme export:schemeNames export:setDefaultScheme export:HTML_ROW_WARNING_THRESHOLD export:QUERY_CHANGED_EVENT export:SCROLL_NEAR_END_EVENT export:attach export:destroyWithin export:driveInfiniteScroll export:driveOobUpdates export:driveServerMode export:ingestResponse export:initWithin export:queryParams export:restoreStateWithin export:rowsFromFragment export:rowsFromJson export:saveStateWithin export:ATTRIBUTE_CONFIG export:EVENT_PREFIX export:GridElementController export:LatticeGrid export:TAG_NAME export:createLatticeGridElement export:defineLatticeGrid export:domEventName export:observedAttributeNames export:CONSOLE_ACTIVATION export:createDevtools export:expose export:EVENT_NAMES export:handlerName export:createLatticeGrid export:dashedName export:createLatticeAction export:Grid export:warnIfLargeHtmlPayload export:createDataRouter export:MockWebSocket export:rng export:opsFeed export:priceFeed export:createKanban export:pack"><code><span class="cmt">// Every declared export of every shipped module, resolved against its own</span>
|
|
7295
7697
|
<span class="cmt">// barrel. A module that stopped exporting something fails here.</span>
|
|
7296
7698
|
<span class="kw">const</span> modules = [
|
|
7297
7699
|
[<span class="kw">await</span> import('../packages/dom/src/index.js'), [
|
|
@@ -7341,6 +7743,9 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
7341
7743
|
[<span class="kw">await</span> import('../packages/modules/kanban/index.js'), [
|
|
7342
7744
|
'createKanban',
|
|
7343
7745
|
]],
|
|
7746
|
+
[<span class="kw">await</span> import('../packages/modules/geo-world-110m/index.js'), [
|
|
7747
|
+
'pack',
|
|
7748
|
+
]],
|
|
7344
7749
|
];
|
|
7345
7750
|
|
|
7346
7751
|
<span class="kw">let</span> present = 0;
|
|
@@ -8336,6 +8741,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8336
8741
|
<tr><td class="name">format</td><td class="type">string | ((value: unknown) => string)</td><td class="desc">A format mask, or a function of the value. <small>(optional)</small></td></tr>
|
|
8337
8742
|
<tr><td class="name">grid</td><td class="type">boolean</td><td class="desc">Draw the gridlines this axis owns. Default true for the measure axis. <small>(optional)</small></td></tr>
|
|
8338
8743
|
<tr><td class="name">labels</td><td class="type">boolean</td><td class="desc">Draw the tick labels. <small>(optional)</small></td></tr>
|
|
8744
|
+
<tr><td class="name">scale</td><td class="type">'auto' | 'linear' | 'time' | 'band' | 'category'</td><td class="desc">Pin the x axis's scale rather than taking it from the column's type (BACKLOG-0001344). The default, `'auto'`, is the rule stated in the charts section: a temporal column type (`date`, `datetime`, `timestamp`, `dateString`) draws a time axis, a numeric one draws a linear axis whatever its distinct count, and everything else draws bands. `'band'` is how a numeric code column — a quarter, a rating, a star count — asks for its bands back; `'linear'` and `'time'` put a column the grid types as text onto a continuous axis. Only the x axis reads it. <small>(optional)</small></td></tr>
|
|
8339
8745
|
<tr><td class="name">every</td><td class="type">number</td><td class="desc">Show every nth category label, on a crowded category axis. <small>(optional)</small></td></tr>
|
|
8340
8746
|
<tr><td class="name">rotate</td><td class="type">boolean | 'auto'</td><td class="desc">Force the category labels' rotation rather than deciding it. <small>(optional)</small></td></tr>
|
|
8341
8747
|
<tr><td class="name">window</td><td class="type">Pick<WindowSpec, 'kind' | 'span'></td><td class="desc">A rolling window for the axis domain (BACKLOG-0001036), in the shipped `WindowSpec` vocabulary that rolling statistics already use. Only `{ kind: 'time', span }` applies to an axis: the domain becomes the last `span` milliseconds ending **now**, so the chart keeps scrolling left while the feed is silent — the thing a count window cannot do, because with no rows arriving nothing changes. Advanced on a low-frequency clock (a quarter of the window, between 50 ms and 1 s), never per frame, and stopped when the chart is destroyed or its document is hidden. Needs a continuous x axis carrying wall-clock times; `{ kind: 'count' }` is the source's `maxRows` and is refused here rather than given a second meaning. <small>(optional)</small></td></tr>
|
|
@@ -8404,8 +8810,12 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8404
8810
|
<tr><td class="name">annotations</td><td class="type">ChartAnnotation[]</td><td class="desc">The declarative annotation layer: reference and target lines, shaded bands and callouts, each naming the axis it reads and each described into the accessible table as a sentence. A value may be a constant or `compute`d from the data it annotates, so it follows the chart as the grid is filtered. <small>(optional)</small></td></tr>
|
|
8405
8811
|
<tr><td class="name">buckets</td><td class="type">number</td><td class="desc">Bins for a histogram; the default is twelve. <small>(optional)</small></td></tr>
|
|
8406
8812
|
<tr><td class="name">diverging</td><td class="type">boolean</td><td class="desc">A diverging colour ramp, for heatmap and geomap. <small>(optional)</small></td></tr>
|
|
8407
|
-
<tr><td class="name">shapes</td><td class="type">unknown</td><td class="desc">Country outlines, for a geomap drawing countries rather than continents. <small>(optional)</small></td></tr>
|
|
8813
|
+
<tr><td class="name">shapes</td><td class="type">unknown</td><td class="desc">Country outlines, for a geomap drawing countries rather than continents. Either GeoJSON, an object of code to SVG path data, or a geometry {@link GeoPack} imported from an optional `modules/geo-*` package (BACKLOG-0001321) — as the pack itself, or as `{ pack: id }` once its module has been imported and registered. <small>(optional)</small></td></tr>
|
|
8408
8814
|
<tr><td class="name">codeProperty</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
8815
|
+
<tr><td class="name">layer</td><td class="type">string</td><td class="desc">Which layer of a multi-layer geometry pack to draw — the UK pack, for instance, ships `regions`, `local-authorities` and `constituencies` together (BACKLOG-0001321). Ignored for a single-layer pack. <small>(optional)</small></td></tr>
|
|
8816
|
+
<tr><td class="name">projection</td><td class="type">'equalEarth' | 'robinson' | 'mercator' | 'equirectangular' | 'albers'</td><td class="desc">The map projection a geomap draws through (BACKLOG-0001321): `'equalEarth'` (the default for a world), `'robinson'`, `'mercator'`, `'equirectangular'`, `'albers'`, `'transverseMercator'`, or a projection function of the caller's own `(lon: number, lat: number) => [number, number]`. Left unset, a geometry pack draws through the projection it declares. <small>(optional)</small></td></tr>
|
|
8817
|
+
<tr><td class="name">projectionOptions</td><td class="type">{ parallels?: [number, number]; centre?: [number, number] }</td><td class="desc">Parameters for the projections that take them: `parallels` and `centre` for `albers`, `centre` for `transverseMercator` (BACKLOG-0001321). <small>(optional)</small></td></tr>
|
|
8818
|
+
<tr><td class="name">graticule</td><td class="type">boolean | { step?: number }</td><td class="desc">A lon/lat reference grid under a geomap's regions, off by default (BACKLOG-0001321 part 2). Only drawn over a geometry pack's fitted projection — the schematic continents have no fitted projection to draw one against. `step` is the spacing between lines in degrees (default 30). <small>(optional)</small></td></tr>
|
|
8409
8819
|
<tr><td class="name">multiples</td><td class="type">string</td><td class="desc">One chart per distinct value of this column. <small>(optional)</small></td></tr>
|
|
8410
8820
|
<tr><td class="name">canvas</td><td class="type">boolean | number</td><td class="desc">Draw to canvas past this many points. <small>(optional)</small></td></tr>
|
|
8411
8821
|
<tr><td class="name">downsample</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -10113,6 +10523,26 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
10113
10523
|
</tbody>
|
|
10114
10524
|
</table>
|
|
10115
10525
|
</div>
|
|
10526
|
+
<h3 id="type-GeoPack">GeoPack</h3>
|
|
10527
|
+
<p class="section-note">An optional geometry pack for a geomap, as one of the `modules/geo-*` packages exports (BACKLOG-0001321). Generated at build time from a named public source; `source`, `licence` and `attribution` record where the geometry came from and what its licence requires. A single-layer pack carries `topology` directly; a multi-layer pack (the UK) carries `layers` instead, keyed by layer name, each with its own `topology`.</p>
|
|
10528
|
+
<div class="table-wrap">
|
|
10529
|
+
<table>
|
|
10530
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
10531
|
+
<tbody>
|
|
10532
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc"></td></tr>
|
|
10533
|
+
<tr><td class="name">title</td><td class="type">string</td><td class="desc"></td></tr>
|
|
10534
|
+
<tr><td class="name">kind</td><td class="type">string</td><td class="desc"></td></tr>
|
|
10535
|
+
<tr><td class="name">projection</td><td class="type">ChartSpec['projection']</td><td class="desc"><small>(optional)</small></td></tr>
|
|
10536
|
+
<tr><td class="name">projectionOptions</td><td class="type">ChartSpec['projectionOptions']</td><td class="desc"><small>(optional)</small></td></tr>
|
|
10537
|
+
<tr><td class="name">source</td><td class="type">{ name: string; url: string; version: string; retrieved: string }</td><td class="desc"></td></tr>
|
|
10538
|
+
<tr><td class="name">licence</td><td class="type">{ name: string; url: string }</td><td class="desc"></td></tr>
|
|
10539
|
+
<tr><td class="name">attribution</td><td class="type">string</td><td class="desc">The attribution line the licence requires, verbatim, or `''` when it asks for none.</td></tr>
|
|
10540
|
+
<tr><td class="name">topology</td><td class="type">object</td><td class="desc"><small>(optional)</small></td></tr>
|
|
10541
|
+
<tr><td class="name">layers</td><td class="type">Record<string, { name: string; topology: object }></td><td class="desc"><small>(optional)</small></td></tr>
|
|
10542
|
+
<tr><td class="name">defaultLayer</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
10543
|
+
</tbody>
|
|
10544
|
+
</table>
|
|
10545
|
+
</div>
|
|
10116
10546
|
<h3 id="type-Grid">Grid</h3>
|
|
10117
10547
|
<div class="table-wrap">
|
|
10118
10548
|
<table>
|
|
@@ -11115,6 +11545,54 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11115
11545
|
</tbody>
|
|
11116
11546
|
</table>
|
|
11117
11547
|
</div>
|
|
11548
|
+
<h3 id="type-LatticeGridHandle">LatticeGridHandle</h3>
|
|
11549
|
+
<p class="section-note">The live instance a `<LatticeGrid>` ref exposes; `null` before mount.</p>
|
|
11550
|
+
<div class="table-wrap">
|
|
11551
|
+
<table>
|
|
11552
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11553
|
+
<tbody>
|
|
11554
|
+
<tr><td class="name">grid</td><td class="type">Grid | null</td><td class="desc"><small>(read-only)</small></td></tr>
|
|
11555
|
+
</tbody>
|
|
11556
|
+
</table>
|
|
11557
|
+
</div>
|
|
11558
|
+
<h3 id="type-LatticeTabSpec">LatticeTabSpec</h3>
|
|
11559
|
+
<p class="section-note">One tab of a `<LatticeTabs>`; `content` makes it React's rather than the module's.</p>
|
|
11560
|
+
<div class="table-wrap">
|
|
11561
|
+
<table>
|
|
11562
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11563
|
+
<tbody>
|
|
11564
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc"></td></tr>
|
|
11565
|
+
<tr><td class="name">label</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11566
|
+
<tr><td class="name">content</td><td class="type">unknown | (() => unknown)</td><td class="desc">A React element, or a function returning one, rendered through a portal. <small>(optional)</small></td></tr>
|
|
11567
|
+
</tbody>
|
|
11568
|
+
</table>
|
|
11569
|
+
</div>
|
|
11570
|
+
<h3 id="type-LatticeViewerCommonProps">LatticeViewerCommonProps</h3>
|
|
11571
|
+
<p class="section-note">What every viewer component takes beyond its own configuration (BACKLOG-0001307): the grid it binds to, which published grid to take when that is left off, the lifecycle callbacks, and the host-element props.</p>
|
|
11572
|
+
<div class="table-wrap">
|
|
11573
|
+
<table>
|
|
11574
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11575
|
+
<tbody>
|
|
11576
|
+
<tr><td class="name">grid</td><td class="type">Grid | null</td><td class="desc">The grid this viewer is built against; taken from context when absent. <small>(optional)</small></td></tr>
|
|
11577
|
+
<tr><td class="name">gridName</td><td class="type">string</td><td class="desc">Which published grid to take from context; `'default'` when absent. <small>(optional)</small></td></tr>
|
|
11578
|
+
<tr><td class="name">onReady</td><td class="type">(instance: Instance) => void</td><td class="desc">Told when the viewer exists. <small>(optional)</small></td></tr>
|
|
11579
|
+
<tr><td class="name">onDestroy</td><td class="type">() => void</td><td class="desc">Told just before it is destroyed. <small>(optional)</small></td></tr>
|
|
11580
|
+
<tr><td class="name">className</td><td class="type">string</td><td class="desc">Applied to the host element rather than to the viewer. <small>(optional)</small></td></tr>
|
|
11581
|
+
<tr><td class="name">style</td><td class="type">Record<string, unknown></td><td class="desc">Applied to the host element rather than to the viewer. <small>(optional)</small></td></tr>
|
|
11582
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc">Applied to the host element rather than to the viewer. <small>(optional)</small></td></tr>
|
|
11583
|
+
</tbody>
|
|
11584
|
+
</table>
|
|
11585
|
+
</div>
|
|
11586
|
+
<h3 id="type-LatticeViewerHandle">LatticeViewerHandle</h3>
|
|
11587
|
+
<p class="section-note">The live instance a viewer component's ref exposes; `null` before mount.</p>
|
|
11588
|
+
<div class="table-wrap">
|
|
11589
|
+
<table>
|
|
11590
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11591
|
+
<tbody>
|
|
11592
|
+
<tr><td class="name">instance</td><td class="type">Instance | null</td><td class="desc"><small>(read-only)</small></td></tr>
|
|
11593
|
+
</tbody>
|
|
11594
|
+
</table>
|
|
11595
|
+
</div>
|
|
11118
11596
|
<h3 id="type-Layout">Layout</h3>
|
|
11119
11597
|
<p class="section-note">A reconfigurable dashboard: a cell grid inside an element, and a set of windows on it that a user can move, resize and close by pointer or by keyboard (BACKLOG-0001108). The module is **payload-agnostic**: a window body is a container with an id, which this module creates and sizes and never reads. It tells a payload it was resized by emitting `window:resized`; it never calls into one, because it cannot know what one is.</p>
|
|
11120
11598
|
<div class="table-wrap">
|
|
@@ -11373,6 +11851,17 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11373
11851
|
</tbody>
|
|
11374
11852
|
</table>
|
|
11375
11853
|
</div>
|
|
11854
|
+
<h3 id="type-MemorySourceConfig">MemorySourceConfig</h3>
|
|
11855
|
+
<div class="table-wrap">
|
|
11856
|
+
<table>
|
|
11857
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11858
|
+
<tbody>
|
|
11859
|
+
<tr><td class="name">mode</td><td class="type">'memory'</td><td class="desc"></td></tr>
|
|
11860
|
+
<tr><td class="name">columnarBelow</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11861
|
+
<tr><td class="name">rows</td><td class="type">unknown[]</td><td class="desc">The rows a memory source opens with; equivalent to top-level `rows`, which wins if both are given. <small>(optional)</small></td></tr>
|
|
11862
|
+
</tbody>
|
|
11863
|
+
</table>
|
|
11864
|
+
</div>
|
|
11376
11865
|
<h3 id="type-MenuItem">MenuItem</h3>
|
|
11377
11866
|
<div class="table-wrap">
|
|
11378
11867
|
<table>
|
|
@@ -11483,6 +11972,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11483
11972
|
<tr><td class="name">notation</td><td class="type">'standard' | 'compact' | 'scientific'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11484
11973
|
<tr><td class="name">negative</td><td class="type">'minus' | 'parentheses' | 'suffix'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11485
11974
|
<tr><td class="name">negativeClass</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11975
|
+
<tr><td class="name">signed</td><td class="type">boolean</td><td class="desc">Show a leading `+` on a positive value (`+5`, `+£5.00`, `+12%`). A negative value keeps whatever `negative` says regardless of this flag, and zero shows no sign either way (BACKLOG-0001095). Off by default. <small>(optional)</small></td></tr>
|
|
11486
11976
|
<tr><td class="name">prefix</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11487
11977
|
<tr><td class="name">suffix</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11488
11978
|
<tr><td class="name">zeroDisplay</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -11769,6 +12259,8 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11769
12259
|
<tr><td class="name">name</td><td class="type">string</td><td class="desc">Used in diagnostics and in the message when work cannot be pushed. <small>(optional)</small></td></tr>
|
|
11770
12260
|
<tr><td class="name">capabilities</td><td class="type">PushdownCapabilities</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11771
12261
|
<tr><td class="name">execute</td><td class="type">(query: RemoteRequest, request?: RemoteRequest):</td><td class="desc">Run the part of the query the adapter declared it could handle.</td></tr>
|
|
12262
|
+
<tr><td class="name">executeGroupLevel</td><td class="type">(</td><td class="desc">Answer one level of a grouped grid (BACKLOG-0001325). Present only when `capabilities.group` opts in. The level is `query.groupValues.length`: the root asks for the outermost grouping column's distinct values, expanding a group asks for the next column's values within it, and past the last grouping column the children are the leaves (`leaves: true`). A group row comes back in the shape the remote source already reads from a grouping server: the grouping column's own id carries the key, `leafCount` the group's row count, `totals` the subtotals keyed by column id. `total` is how many group rows the level holds. At the root, `matchCount` and `grand` carry the whole-set figures a grouped window cannot derive — the rows the filter matched, and the grand total over them. `aggregates` is the subtotal list the source routed to the engine; anything it could not route is named in `PushdownPlan.aggregates.client` and left absent from the group row rather than computed over the wrong set. <small>(optional)</small></td></tr>
|
|
12263
|
+
<tr><td class="name">unfilteredCount</td><td class="type">(): Promise<number | null></td><td class="desc">The row count before any filter (BACKLOG-0001325) — the denominator of "1,204 of 100,000" under grouping, where the display count is group headers rather than rows. Optional; a source falls back to the display count. <small>(optional)</small></td></tr>
|
|
11772
12264
|
<tr><td class="name">mutate</td><td class="type">(op: MutationOp, request?: RemoteRequest): Promise<MutationResult></td><td class="desc">Persist one mutation (§4.2). Present only when `capabilities.mutate` opts in. `createPushdownSource` synthesises an `edit.commit` that calls this for cell updates (§4.3 Option A); `request` threads the abort signal through the way `execute` receives it, and auth already lives on the adapter. <small>(optional)</small></td></tr>
|
|
11773
12265
|
</tbody>
|
|
11774
12266
|
</table>
|
|
@@ -11796,7 +12288,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11796
12288
|
<tr><td class="name">quick</td><td class="type">boolean</td><td class="desc">Whether a free-text search across columns can be pushed. <small>(optional)</small></td></tr>
|
|
11797
12289
|
<tr><td class="name">range</td><td class="type">boolean</td><td class="desc">Whether the engine can return a window rather than the whole result. <small>(optional)</small></td></tr>
|
|
11798
12290
|
<tr><td class="name">total</td><td class="type">boolean</td><td class="desc">Whether it can report the count of matching rows. <small>(optional)</small></td></tr>
|
|
11799
|
-
<tr><td class="name">group</td><td class="type">boolean</td><td class="desc">Whether it can group and
|
|
12291
|
+
<tr><td class="name">group</td><td class="type">boolean</td><td class="desc">Whether it can answer the grid's grouped view — group rows, their counts, their subtotals and their order — one level at a time, instead of returning the leaves for the grid to group in the browser (BACKLOG-0001325). All or nothing, unlike `filter`. A filter splits because the engine narrowing a superset and the grid narrowing what is left reach the same set; a grouping cannot, because group rows counted over the wrong set are wrong rows, not slow ones. So the push router refuses the whole grouped level — and says why in `PushdownPlan.groupReason` — whenever anything else in the query failed to push. An adapter declaring this must implement `executeGroupLevel`; one that declares it without the method is re-planned without grouping and warned about, rather than half-pushed. <small>(optional)</small></td></tr>
|
|
11800
12292
|
<tr><td class="name">mutate</td><td class="type">false | MutateCapability</td><td class="desc">What the adapter can persist back — the write-back contract (§4.1). `false` (the default) is read-only by declaration. A declared block opts kinds in; `capabilitiesOf` resolves it to a full `MutateCapability` (or `false`). <small>(optional)</small></td></tr>
|
|
11801
12293
|
</tbody>
|
|
11802
12294
|
</table>
|
|
@@ -11822,7 +12314,10 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11822
12314
|
<tr><td class="name">pushed</td><td class="type">RemoteRequest</td><td class="desc">The query the adapter was given.</td></tr>
|
|
11823
12315
|
<tr><td class="name">residual</td><td class="type">{</td><td class="desc">What the grid applied afterwards. `where` is the host predicate runtime when one survived the `whereRowLimit` gate, and `null` when none was registered or the gate refused it (BACKLOG-0001268).</td></tr>
|
|
11824
12316
|
<tr><td class="name">needsAll</td><td class="type">boolean</td><td class="desc">Whether the whole result had to be fetched rather than a window.</td></tr>
|
|
11825
|
-
<tr><td class="name">unpushed</td><td class="type">string[]</td><td class="desc">Which parts could not be pushed: `filter`, `sort`, `quick`, `where`.</td></tr>
|
|
12317
|
+
<tr><td class="name">unpushed</td><td class="type">string[]</td><td class="desc">Which parts could not be pushed: `filter`, `sort`, `quick`, `where`, `group`.</td></tr>
|
|
12318
|
+
<tr><td class="name">grouped</td><td class="type">boolean</td><td class="desc">Whether the engine answered the grid's grouped view for this request (BACKLOG-0001325). False for an ungrouped query and for a grouped one the engine was refused — `groupReason` says which.</td></tr>
|
|
12319
|
+
<tr><td class="name">groupLevel</td><td class="type">number</td><td class="desc">Which grouping level a pushed grouped request asked for: 0 at the root, 1 inside a group, and so on. Zero when nothing was grouped.</td></tr>
|
|
12320
|
+
<tr><td class="name">groupReason</td><td class="type">string</td><td class="desc">Why a grouped request was *not* pushed, in a sentence, or `''` when it was pushed or when nothing was grouped. Grouping is all or nothing, so this is the whole story rather than a residual.</td></tr>
|
|
11826
12321
|
<tr><td class="name">full</td><td class="type">boolean</td><td class="desc">Whether the whole result was fetched because `fullDataset` is on, rather than only because residual work forced it. When true, totals and statistics reduce over the whole matching set and the windowed-stat warning is silent.</td></tr>
|
|
11827
12322
|
<tr><td class="name">aggregates</td><td class="type">{</td><td class="desc">Per-aggregate provenance, present only when the last request computed aggregates (BACKLOG-0000730 Part B): which statistics the engine computed and which the client did, with the class the pushdown map assigned each. Under grouping it also carries the `groupBy` the subtotals were computed over. Build-time inspection, not a runtime per-figure marker. <small>(optional)</small></td></tr>
|
|
11828
12323
|
</tbody>
|
|
@@ -12004,8 +12499,10 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
12004
12499
|
<tr><td class="name">protocol</td><td class="type">1</td><td class="desc"></td></tr>
|
|
12005
12500
|
<tr><td class="name">range</td><td class="type">{ start: number; end: number }</td><td class="desc"></td></tr>
|
|
12006
12501
|
<tr><td class="name">groupPath</td><td class="type">string[]</td><td class="desc"></td></tr>
|
|
12502
|
+
<tr><td class="name">groupValues</td><td class="type">unknown[]</td><td class="desc">The same ancestry as `groupPath`, but as the values the server returned rather than their display strings (BACKLOG-0001325). Always present, empty at the root, so a source can tell "no ancestors" from "a host that does not send this". `groupPath` is stringified because it is a stable *identity* for expansion state, and that is what it must stay: a numeric key `3` is `'3'` there and an absent key is `''`, indistinguishable from a group whose key really is the empty string. Useless for narrowing a query, then — which is what a grouping engine needs it for — so the typed values travel beside it.</td></tr>
|
|
12007
12503
|
<tr><td class="name">groupBy</td><td class="type">ColumnRef[]</td><td class="desc"></td></tr>
|
|
12008
12504
|
<tr><td class="name">totals</td><td class="type">ColumnRef[]</td><td class="desc"></td></tr>
|
|
12505
|
+
<tr><td class="name">totalFns</td><td class="type">Record<string, string></td><td class="desc">The named statistic each totalled column reduces with — `{ amount: 'sum' }` (BACKLOG-0001325). `totals` has always said *which* columns want a subtotal and never *what*, because the client reads the reduction off the column model and a server had no way to. Only string reductions appear: a column totalling with a host function has no name to send, and naming one that merely resembles it would put a plausible wrong number on every group row. `groupTotal` wins over `total`, the same precedence the client applies for the group scope.</td></tr>
|
|
12009
12506
|
<tr><td class="name">pivotBy</td><td class="type">ColumnRef[]</td><td class="desc"></td></tr>
|
|
12010
12507
|
<tr><td class="name">pivotMode</td><td class="type">boolean</td><td class="desc"></td></tr>
|
|
12011
12508
|
<tr><td class="name">filters</td><td class="type">FilterSet</td><td class="desc"></td></tr>
|
|
@@ -12200,6 +12697,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
12200
12697
|
<tr><td class="name">count</td><td class="type">(): number</td><td class="desc"></td></tr>
|
|
12201
12698
|
<tr><td class="name">totalCount</td><td class="type">(): number</td><td class="desc">Rows in the source before filtering; under pagination, across every page.</td></tr>
|
|
12202
12699
|
<tr><td class="name">matchCount</td><td class="type">(): number</td><td class="desc">Data rows matching the filters, excluding group, footer and total rows.</td></tr>
|
|
12700
|
+
<tr><td class="name">coverage</td><td class="type">(): StatCoverage</td><td class="desc">How much of the data a figure computed from this grid covers, so a statistic over a windowed source can say it is approximate.</td></tr>
|
|
12203
12701
|
<tr><td class="name">data</td><td class="type">(): unknown[]</td><td class="desc"></td></tr>
|
|
12204
12702
|
<tr><td class="name">forEach</td><td class="type">(fn: (row: Row, index: number) => void): void</td><td class="desc"></td></tr>
|
|
12205
12703
|
<tr><td class="name">forEachAll</td><td class="type">(fn: (row: Row, index: number) => void): void</td><td class="desc">Every row in the data, before any filter. Leaf rows, in physical order.</td></tr>
|
|
@@ -12402,6 +12900,18 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
12402
12900
|
</tbody>
|
|
12403
12901
|
</table>
|
|
12404
12902
|
</div>
|
|
12903
|
+
<h3 id="type-StatCoverage">StatCoverage</h3>
|
|
12904
|
+
<p class="section-note">How much of the data a computed figure actually covers. `covered < total`, or `total === null`, means the figure is approximate.</p>
|
|
12905
|
+
<div class="table-wrap">
|
|
12906
|
+
<table>
|
|
12907
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
12908
|
+
<tbody>
|
|
12909
|
+
<tr><td class="name">covered</td><td class="type">number</td><td class="desc">Rows the figure was computed over.</td></tr>
|
|
12910
|
+
<tr><td class="name">total</td><td class="type">number | null</td><td class="desc">Rows the source knows about, or `null` when it cannot know — never a guess.</td></tr>
|
|
12911
|
+
<tr><td class="name">windowed</td><td class="type">boolean</td><td class="desc">True when a window bounded the computation, so the figure covers part of the data.</td></tr>
|
|
12912
|
+
</tbody>
|
|
12913
|
+
</table>
|
|
12914
|
+
</div>
|
|
12405
12915
|
<h3 id="type-StateApi">StateApi</h3>
|
|
12406
12916
|
<div class="table-wrap">
|
|
12407
12917
|
<table>
|
|
@@ -12934,7 +13444,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
12934
13444
|
<!-- END GENERATED TYPE REFERENCE -->
|
|
12935
13445
|
|
|
12936
13446
|
<footer>
|
|
12937
|
-
Lattice Grid 1.
|
|
13447
|
+
Lattice Grid 1.63.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
12938
13448
|
This document describes the behaviour of the shipped library. Where this guide and the code
|
|
12939
13449
|
disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
12940
13450
|
</footer>
|