@toclocoinc/lattice-grid 1.63.3 → 1.65.0
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 +4 -2
- package/docs/API.html +408 -25
- package/docs/CHART-CODES.md +24 -0
- package/docs/api-detail.html +115 -5
- package/lattice-grid.d.ts +333 -205
- package/lattice-grid.esm.min.js +155 -96
- package/lattice-grid.min.cjs +155 -96
- package/lattice-grid.min.js +155 -96
- package/modules/ai.d.ts +10 -13
- package/modules/ai.esm.min.js +3 -6
- package/modules/ai.min.cjs +3 -6
- package/modules/ai.min.js +3 -6
- package/modules/angular.d.ts +2 -2
- package/modules/angular.esm.min.js +433 -10
- package/modules/angular.min.cjs +433 -10
- package/modules/angular.min.js +433 -10
- package/modules/chart-alluvial.d.ts +2 -2
- 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 +2 -2
- 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 +2 -2
- package/modules/chart-bubblemap.esm.min.js +1 -1
- package/modules/chart-bubblemap.min.cjs +1 -1
- package/modules/chart-bubblemap.min.js +1 -1
- package/modules/chart-bump.d.ts +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/modules/chart-hexmap.esm.min.js +1 -1
- package/modules/chart-hexmap.min.cjs +1 -1
- package/modules/chart-hexmap.min.js +1 -1
- package/modules/chart-icicle.d.ts +2 -2
- 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-markermap.d.ts +29 -0
- package/modules/chart-markermap.esm.min.js +313 -0
- package/modules/chart-markermap.min.cjs +317 -0
- package/modules/chart-markermap.min.js +317 -0
- package/modules/chart-parallel.d.ts +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +4 -5
- package/modules/charts.esm.min.js +395 -33
- package/modules/charts.min.cjs +395 -33
- package/modules/charts.min.js +395 -33
- package/modules/data-router.d.ts +5 -6
- package/modules/data-router.esm.min.js +3 -6
- package/modules/data-router.min.cjs +3 -6
- package/modules/data-router.min.js +3 -6
- package/modules/devtools.d.ts +1 -1
- package/modules/devtools.esm.min.js +1 -4
- package/modules/devtools.min.cjs +1 -4
- package/modules/devtools.min.js +1 -4
- package/modules/dhtmlx-compat.d.ts +1 -1
- package/modules/dhtmlx-compat.esm.min.js +3 -6
- package/modules/dhtmlx-compat.min.cjs +3 -6
- package/modules/dhtmlx-compat.min.js +3 -6
- package/modules/gantt.d.ts +37 -43
- package/modules/gantt.esm.min.js +3 -6
- package/modules/gantt.min.cjs +3 -6
- package/modules/gantt.min.js +3 -6
- package/modules/geo-europe-nuts.d.ts +2 -2
- package/modules/geo-europe-nuts.esm.min.js +1 -1
- package/modules/geo-uk.d.ts +2 -2
- package/modules/geo-uk.esm.min.js +1 -1
- package/modules/geo-us-states.d.ts +2 -2
- package/modules/geo-us-states.esm.min.js +1 -1
- package/modules/geo-world-110m.d.ts +2 -2
- package/modules/geo-world-110m.esm.min.js +1 -1
- package/modules/geo-world-50m.d.ts +2 -2
- package/modules/geo-world-50m.esm.min.js +1 -1
- package/modules/htmx.d.ts +2 -2
- package/modules/htmx.esm.min.js +155 -96
- package/modules/htmx.min.cjs +155 -96
- package/modules/htmx.min.js +155 -96
- package/modules/kanban.d.ts +14 -14
- package/modules/kanban.esm.min.js +3 -6
- package/modules/kanban.min.cjs +3 -6
- package/modules/kanban.min.js +3 -6
- package/modules/kpi.d.ts +57 -4
- package/modules/kpi.esm.min.js +202 -10
- package/modules/kpi.min.cjs +202 -10
- package/modules/kpi.min.js +202 -10
- package/modules/layout.d.ts +2 -2
- package/modules/layout.esm.min.js +3 -6
- package/modules/layout.min.cjs +3 -6
- package/modules/layout.min.js +3 -6
- package/modules/mock-socket.d.ts +1 -1
- package/modules/mock-socket.esm.min.js +1 -4
- package/modules/mock-socket.min.cjs +1 -4
- package/modules/mock-socket.min.js +1 -4
- package/modules/react.d.ts +4 -5
- package/modules/react.esm.min.js +3 -6
- package/modules/react.min.cjs +3 -6
- package/modules/react.min.js +3 -6
- package/modules/svelte.d.ts +2 -3
- package/modules/svelte.esm.min.js +1 -4
- package/modules/svelte.min.cjs +1 -4
- package/modules/svelte.min.js +1 -4
- package/modules/tabs.d.ts +2 -3
- package/modules/tabs.esm.min.js +3 -6
- package/modules/tabs.min.cjs +3 -6
- package/modules/tabs.min.js +3 -6
- package/modules/vue.d.ts +1 -1
- package/modules/vue.esm.min.js +1 -4
- package/modules/vue.min.cjs +1 -4
- package/modules/vue.min.js +1 -4
- package/modules/webcomponent.d.ts +2 -2
- package/modules/webcomponent.esm.min.js +155 -96
- package/modules/webcomponent.min.cjs +155 -96
- package/modules/webcomponent.min.js +155 -96
- 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.65.0</p>
|
|
364
364
|
<nav>
|
|
365
365
|
<div class="rail__group">
|
|
366
366
|
<span class="rail__label">Start</span>
|
|
@@ -413,6 +413,12 @@
|
|
|
413
413
|
<a href="#sources">Sources</a>
|
|
414
414
|
<a href="#events">Event reference</a>
|
|
415
415
|
</div>
|
|
416
|
+
<div class="rail__group">
|
|
417
|
+
<span class="rail__label">Frameworks</span>
|
|
418
|
+
<a href="#adapters">Framework adapters</a>
|
|
419
|
+
<a href="#react-v2">React</a>
|
|
420
|
+
<a href="#angular-v2">Angular</a>
|
|
421
|
+
</div>
|
|
416
422
|
<div class="rail__group">
|
|
417
423
|
<span class="rail__label">Registries</span>
|
|
418
424
|
<a href="#names">Built-in names</a>
|
|
@@ -444,7 +450,7 @@
|
|
|
444
450
|
</header>
|
|
445
451
|
|
|
446
452
|
<p class="chips">
|
|
447
|
-
<span class="chip">Version 1.
|
|
453
|
+
<span class="chip">Version 1.65.0</span>
|
|
448
454
|
<span class="chip">Zero dependencies</span>
|
|
449
455
|
<span class="chip"><a href="api-detail.html">Developer guide →</a></span>
|
|
450
456
|
</p>
|
|
@@ -520,6 +526,7 @@
|
|
|
520
526
|
<thead><tr><th>Entry point</th><th>Factory</th><th>Needs</th></tr></thead>
|
|
521
527
|
<tbody>
|
|
522
528
|
<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>
|
|
529
|
+
<tr><td class="name">@toclocoinc/<wbr>lattice-grid-angular</td><td class="sig"><lattice-grid [config]="…"></td><td class="desc">A package of its own, not a bundle: standalone components compiled ahead of time, one per viewer, plus the data router as a service. See <a href="#angular-v2">Angular</a>. <code>modules/angular</code>, which needs the JIT compiler, is deprecated.</td></tr>
|
|
523
530
|
<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>
|
|
524
531
|
<tr><td class="name">modules/svelte</td><td class="sig">createLatticeAction({ createGrid })</td><td class="desc">Returns a <code>use:</code> action.</td></tr>
|
|
525
532
|
</tbody>
|
|
@@ -706,6 +713,120 @@ controller.destroy();
|
|
|
706
713
|
a line that could never run. The build now deletes that branch from every emitted artefact.
|
|
707
714
|
</p>
|
|
708
715
|
|
|
716
|
+
<h3 id="angular-v2">Angular: a compiled package, <code>@toclocoinc/lattice-grid-angular</code></h3>
|
|
717
|
+
<p>
|
|
718
|
+
Angular's components are not objects a library can assemble at run time in a production
|
|
719
|
+
build. <code>modules/angular</code> did assemble them that way, which needs Angular's JIT
|
|
720
|
+
compiler in the page — present on a development server, absent from every AOT build.
|
|
721
|
+
So Angular gets a package of its own: TypeScript components compiled by
|
|
722
|
+
<code>@angular/compiler-cli</code> into a partial-Ivy library, which your build's Angular
|
|
723
|
+
Linker turns into definitions exactly as it does for any other Angular library you install.
|
|
724
|
+
<strong>No compiler in your bundle, and one standalone component per viewer.</strong>
|
|
725
|
+
</p>
|
|
726
|
+
<pre><code>npm install @toclocoinc/lattice-grid @toclocoinc/lattice-grid-angular</code></pre>
|
|
727
|
+
<pre><code><span class="kw">import</span> { bootstrapApplication } <span class="kw">from</span> '@angular/platform-browser';
|
|
728
|
+
<span class="kw">import</span> { createGrid } <span class="kw">from</span> '@toclocoinc/lattice-grid';
|
|
729
|
+
<span class="kw">import</span> { createKPI } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/kpi';
|
|
730
|
+
<span class="kw">import</span> { createChart } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/charts';
|
|
731
|
+
<span class="kw">import</span> {
|
|
732
|
+
LatticeGridComponent, LatticeKpiComponent, LatticeChartComponent, provideLattice,
|
|
733
|
+
} <span class="kw">from</span> '@toclocoinc/lattice-grid-angular';
|
|
734
|
+
|
|
735
|
+
@Component({
|
|
736
|
+
selector: 'app-dashboard',
|
|
737
|
+
imports: [LatticeGridComponent, LatticeKpiComponent, LatticeChartComponent],
|
|
738
|
+
template: `
|
|
739
|
+
<lattice-grid #grid name="quakes" [config]="config" [quickFilter]="search()"
|
|
740
|
+
(cell-changed)="save($event)" />
|
|
741
|
+
<lattice-kpi gridName="quakes" [config]="{ tiles }" />
|
|
742
|
+
<lattice-chart gridName="quakes" [config]="{ type: 'bar', x: 'region', y: 'count' }" />
|
|
743
|
+
`,
|
|
744
|
+
})
|
|
745
|
+
<span class="kw">export class</span> Dashboard {
|
|
746
|
+
<span class="cmt">// The live grid, the same object createGrid returns.</span>
|
|
747
|
+
grid = viewChild<LatticeGridComponent>('grid');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
<span class="cmt">// Each factory is injected, not imported by the library: an application that</span>
|
|
751
|
+
<span class="cmt">// shows a grid downloads the grid, and never the board or the eighteen charts.</span>
|
|
752
|
+
bootstrapApplication(Dashboard, {
|
|
753
|
+
providers: [provideLattice({ createGrid, createKPI, createChart })],
|
|
754
|
+
});</code></pre>
|
|
755
|
+
|
|
756
|
+
<div class="table-wrap">
|
|
757
|
+
<table>
|
|
758
|
+
<thead><tr><th>Export</th><th>Element</th><th>What it is</th></tr></thead>
|
|
759
|
+
<tbody>
|
|
760
|
+
<tr><td class="name">LatticeGridComponent</td><td class="sig"><lattice-grid></td><td class="desc">The grid. <code>[config]</code> is every configuration key; <code>[sort]</code>, <code>[filters]</code>, <code>[quickFilter]</code> and <code>[selectedKeys]</code> are applied through the matching API; <code>[rowUpdates]</code> and <code>[predicates]</code> drive a live feed. Every grid event is an output under its kebab-case name, and <code>(grid-ready)</code> hands you the instance. Give the element a height.</td></tr>
|
|
761
|
+
<tr><td class="name">LatticeGridDirective</td><td class="sig">[latticeGrid]</td><td class="desc">The same component on an element your template already owns: <code><div [latticeGrid]="config" class="tall"></div></code>. Same inputs, outputs and <code>grid</code> reference.</td></tr>
|
|
762
|
+
<tr><td class="name">LatticeKpiComponent</td><td class="sig"><lattice-kpi></td><td class="desc">The KPI panel. Grid-bound by default — <code>[gridName]</code> picks which published grid — or give it <code>[rows]</code> for a panel with no grid.</td></tr>
|
|
763
|
+
<tr><td class="name">LatticeChartComponent</td><td class="sig"><lattice-chart></td><td class="desc">A chart. Requires a grid, so nothing is built until one exists; a changed spec key goes to <code>chart.update()</code> and the chart redraws rather than being rebuilt. <code>(click)</code>, <code>(hover)</code> and <code>(leave)</code> on this element are the chart's events, carrying the datum under the pointer.</td></tr>
|
|
764
|
+
<tr><td class="name">LatticeKanbanComponent</td><td class="sig"><lattice-kanban></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 inputs.</td></tr>
|
|
765
|
+
<tr><td class="name">LatticeGanttComponent</td><td class="sig"><lattice-gantt></td><td class="desc">The plan. <code>[tasks]</code> and <code>[dependencies]</code> are live inputs. It takes an explicit <code>[grid]</code> but never adopts a published one.</td></tr>
|
|
766
|
+
<tr><td class="name">LatticeLayoutComponent</td><td class="sig"><lattice-layout></td><td class="desc">The dashboard layout. Windows are driven through the instance; its events arrive as <code>(layout-changed)</code>, <code>(window-moved)</code> and the rest.</td></tr>
|
|
767
|
+
<tr><td class="name">LatticeTabsComponent<br>LatticeTabDirective</td><td class="sig"><lattice-tabs><br>ng-template[latticeTab]</td><td class="desc">The tab strip, with <strong>Angular-rendered tab content</strong>: a tab's content is an <code><ng-template latticeTab="id"></code> in your own template, rendered into the strip's panel through this component's <code>ViewContainerRef</code> — so a tab's grid is a real <code><lattice-grid></code> with inputs, a reference and your injectors above it. A tab with no template is left to the module, so configuration-driven grid tabs still work and the two kinds mix on one strip.</td></tr>
|
|
768
|
+
<tr><td class="name">LatticeGridRegistry</td><td class="sig">inject(LatticeGridRegistry)</td><td class="desc">Where <code><lattice-grid name="…"></code> publishes itself and grid-bound viewers find it, as a signal per name — a panel declared before its grid exists mounts itself the moment the grid arrives. <code>providedIn: 'root'</code>; put it in a component's own <code>providers</code> to scope a registry to that subtree.</td></tr>
|
|
769
|
+
<tr><td class="name">provideLattice</td><td class="sig">provideLattice({ …factories })</td><td class="desc">The factories the components build through. Give it the ones your application uses; a component whose factory is missing names the import and the provider call that fixes it.</td></tr>
|
|
770
|
+
<tr><td class="name">provideLatticeRouter<br>LatticeRouter</td><td class="sig">providers: [provideLatticeRouter(cfg)]</td><td class="desc">The data router as a service. Put it in a component's <code>providers</code> and it is created when the first <code><lattice-grid route="…"></code> under it attaches, and destroyed with that component — its configuration read once, because rebuilding would drop every attached grid and every row it holds. Each grid detaches before it is destroyed, so the router never holds a dead grid.</td></tr>
|
|
771
|
+
</tbody>
|
|
772
|
+
</table>
|
|
773
|
+
</div>
|
|
774
|
+
|
|
775
|
+
<p class="section-note">
|
|
776
|
+
<strong>Change detection: zone or zoneless, unconfigured.</strong> The grid is created inside
|
|
777
|
+
<code>NgZone.runOutsideAngular</code>, because it installs its own scroll, wheel and pointer
|
|
778
|
+
listeners and running change detection on every scroll frame of a million-row grid is the
|
|
779
|
+
difference between smooth and unusable. An event that reaches an output you have
|
|
780
|
+
<em>bound</em> re-enters the zone, so <code>(cell-changed)="count = count + 1"</code> repaints
|
|
781
|
+
exactly as you expect; an output nobody bound costs nothing. Under zoneless change detection
|
|
782
|
+
that machinery is Angular's own no-op and a signal you set in a handler repaints the view.
|
|
783
|
+
The one thing to know when reading the grid's DOM from Angular: <strong>the grid paints on its
|
|
784
|
+
own schedule, not Angular's</strong>, so measure a cell in a grid event, not in
|
|
785
|
+
<code>ngAfterViewInit</code>.
|
|
786
|
+
</p>
|
|
787
|
+
<p class="section-note">
|
|
788
|
+
<strong><code>OnPush</code> is safe everywhere.</strong> None of these components asks its
|
|
789
|
+
parent to re-render: each owns one element, builds one instance in
|
|
790
|
+
<code>afterNextRender</code> and pushes changed inputs into it from <code>ngOnChanges</code>.
|
|
791
|
+
A host on <code>OnPush</code> that never re-renders still gets a fully live grid, because the
|
|
792
|
+
grid is not rendered by Angular.
|
|
793
|
+
</p>
|
|
794
|
+
<p class="section-note">
|
|
795
|
+
<strong>Inputs are diffed by identity, and never rebuild the instance.</strong> A changed
|
|
796
|
+
input reaches the viewer that is already on screen — scroll position, selection,
|
|
797
|
+
expansion and any open editor intact. Two things rebuild rather than update: the grid a
|
|
798
|
+
viewer is bound to, and an input it cannot exist without. An input a viewer has no live
|
|
799
|
+
setter for is named once in a warning rather than silently dropped. And because the
|
|
800
|
+
comparison is <code>Object.is</code>, an inline <code>[config]="{ rows: rows }"</code> is a
|
|
801
|
+
new object on every pass: hold it in a field or a signal.
|
|
802
|
+
</p>
|
|
803
|
+
<p class="section-note">
|
|
804
|
+
<strong>Cleanup is the component's.</strong> <code>ngOnDestroy</code> detaches from the
|
|
805
|
+
router, withdraws the grid from the registry and destroys the instance — in that order.
|
|
806
|
+
An <code>@if</code> that closes and opens again leaves exactly one instance alive, and
|
|
807
|
+
destroying the application leaves no instance, interval or listener behind. That is asserted
|
|
808
|
+
with counters in a real browser, against the linked package, in
|
|
809
|
+
<code>test/angular-package-browser.test.js</code>.
|
|
810
|
+
</p>
|
|
811
|
+
<p class="section-note">
|
|
812
|
+
<strong>Angular 17 and up, browser only.</strong> The library is compiled partially, so your
|
|
813
|
+
own Angular version compiles it: its declarations need a linker no newer than 14, and its peer
|
|
814
|
+
range is <code>>=17</code>. Nothing is created on the server —
|
|
815
|
+
<code>isPlatformBrowser</code> guards every build and <code>afterNextRender</code> does not
|
|
816
|
+
run there — so a server-rendered page emits the empty host element and the grid is built
|
|
817
|
+
on hydration. Angular Universal is not otherwise supported.
|
|
818
|
+
</p>
|
|
819
|
+
<p class="section-note">
|
|
820
|
+
<strong><code>modules/angular</code> is deprecated.</strong> The old bundle still works where
|
|
821
|
+
it always worked — a page with <code>@angular/compiler</code> loaded — and it now
|
|
822
|
+
says so once, and fails with a <code>[lattice]</code> message naming this package when it
|
|
823
|
+
finds a real Angular with no JIT compiler, instead of leaving you with Angular's own. In
|
|
824
|
+
1.65 it also gained the fix that <code><div [latticeGrid]="config"></code> binds the
|
|
825
|
+
configuration through the directive's selector, as its documentation always said it did. It
|
|
826
|
+
will be removed in a later release; move to <code>@toclocoinc/lattice-grid-angular</code>,
|
|
827
|
+
which covers every viewer rather than the grid alone.
|
|
828
|
+
</p>
|
|
829
|
+
|
|
709
830
|
<div class="note">
|
|
710
831
|
<p><strong>Published as <code>@toclocoinc/lattice-grid</code></strong>: <code>npm install
|
|
711
832
|
@toclocoinc/lattice-grid</code>, then <code>import { createLatticeGrid } from
|
|
@@ -1393,7 +1514,7 @@ grid.destroy();
|
|
|
1393
1514
|
<table>
|
|
1394
1515
|
<thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
|
|
1395
1516
|
<tbody>
|
|
1396
|
-
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.
|
|
1517
|
+
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.65.0'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
|
|
1397
1518
|
<tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
|
|
1398
1519
|
<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>
|
|
1399
1520
|
<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>
|
|
@@ -5164,7 +5285,7 @@ const chart = createChart({
|
|
|
5164
5285
|
<tr><td class="sig">Part to whole</td><td><code>pie</code>, <code>donut</code>, <code>sunburst</code>, <code>treemap</code></td><td><code>x</code>, <code>y</code>; or the grid's grouping, see below</td></tr>
|
|
5165
5286
|
<tr><td class="sig">Specialist</td><td><code>radar</code>, <code>gauge</code>, <code>funnel</code>, <code>candlestick</code></td><td>varies; candlestick takes four <code>measures</code> in open, high, low, close order</td></tr>
|
|
5166
5287
|
<tr><td class="sig">Geographic</td><td><code>geomap</code></td><td><code>x</code> as an ISO code, <code>y</code> as the value</td></tr>
|
|
5167
|
-
<tr><td class="sig">Flow</td><td><code>sankey</code>, <code>chord</code>, <code>network</code></td><td><code>source</code>, <code>target</code>, <code>y</code></td></tr>
|
|
5288
|
+
<tr><td class="sig">Flow</td><td><code>sankey</code>, <code>chord</code>, <code>network</code></td><td><code>source</code>, <code>target</code>, <code>y</code>; a <code>network</code> also takes <code>nodes</code> — see <a href="#network-map">Network diagrams</a></td></tr>
|
|
5168
5289
|
<tr><td class="sig">Over time</td><td><code>stream</code>, <code>marimekko</code>, <code>violin</code>, <code>gantt</code></td><td>varies; gantt takes <code>label</code>, <code>start</code>, <code>end</code></td></tr>
|
|
5169
5290
|
</tbody>
|
|
5170
5291
|
</table>
|
|
@@ -5211,6 +5332,9 @@ const chart = createChart({
|
|
|
5211
5332
|
<tr><td class="name">code / codeProperty</td><td class="type">string</td><td class="desc">A geomap's ISO code column, and the property carrying the code in your <code>shapes</code>.</td></tr>
|
|
5212
5333
|
<tr><td class="name">columns / method / values</td><td class="type">string[] / string / boolean</td><td class="desc">Correlogram: which columns to correlate, by <code>pearson</code>, <code>spearman</code> or <code>kendall</code>, and whether to print the coefficients in the cells.</td></tr>
|
|
5213
5334
|
<tr><td class="name">iterations</td><td class="type">number</td><td class="desc">Network layouts: how many relaxation passes to run.</td></tr>
|
|
5335
|
+
<tr><td class="name">nodes</td><td class="type">ChartNode[]</td><td class="desc">A <code>network</code>'s nodes, named by you rather than inferred from the rows: <code>{ id, label, icon, x, y }</code>. <code>id</code> matches a value in the <code>source</code> or <code>target</code> column; <code>icon</code> is any name in the grid's icon registry; <code>x</code>/<code>y</code> are fractions of the plot (0 to 1) and <strong>pin</strong> the node there, out of the force simulation. A node listed here that appears in no row is still drawn. See <a href="#network-map">Network diagrams</a>.</td></tr>
|
|
5336
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">The default glyph for a <code>network</code> node that names none of its own. Unset, an undeclared node is a plain disc.</td></tr>
|
|
5337
|
+
<tr><td class="name">linkWidth</td><td class="type">number</td><td class="desc">Fix a <code>network</code> link's stroke width in pixels. Unset, width follows the link's value as a share of the heaviest link.</td></tr>
|
|
5214
5338
|
<tr><td class="name">spec / baseline / rules / confidence</td><td class="type">object / number / string / number</td><td class="desc">Control and capability charts: a tolerance overriding the column's own <code>spec</code>, how many leading readings fix the control limits, which rule set judges the violations (<code>westernElectric</code> or <code>nelson</code>), and the level for the capability interval.</td></tr>
|
|
5215
5339
|
</tbody>
|
|
5216
5340
|
</table>
|
|
@@ -5486,6 +5610,97 @@ createChart({ grid, container: '#us', type: 'geomap', code: 'state', y: 'sales',
|
|
|
5486
5610
|
|
|
5487
5611
|
<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>
|
|
5488
5612
|
|
|
5613
|
+
<h3 id="network-map">Network diagrams — icon nodes, links coloured by their value</h3>
|
|
5614
|
+
<p>A <code>network</code> draws the grid's rows as a graph: <code>source</code> and <code>target</code> name the two endpoint columns and <code>y</code> carries the value on the link between them. Three things make it a picture of <em>your</em> network rather than a generic hairball, and each is a fact only you have.</p>
|
|
5615
|
+
<p><strong>Nodes you name.</strong> <code>nodes: [{ id, label, icon, x, y }]</code> gives a node a glyph from the grid's own icon registry — a built-in name, or one you registered — and a label drawn beneath it. A node that appears in the rows but not in <code>nodes</code> takes the chart's <code>icon</code> default (a plain disc when there is none) and its own id as its label. A node listed in <code>nodes</code> that appears in <em>no</em> row is still drawn: a device with no links is a fact worth seeing. The glyphs are SVG paths from the same registry the cells paint from, so they are sharp at any chart size and take the chart's theme colours.</p>
|
|
5616
|
+
<p><strong>Positions you choose.</strong> <code>x</code> and <code>y</code> are fractions of the plot, measured from its top-left. A node giving <em>both</em> is <strong>pinned</strong> there and takes no part in the force simulation; everything else is laid out around it by the same deterministic relaxation as before, so “core on top, regions below” needs no hand-placed SVG. Half a position is not a position: a node with only <code>x</code> is laid out. A fraction outside 0 to 1 clamps to the edge of the plot rather than drawing where nobody can see it. Pinning one node never reshuffles the others — the layout's seeding draws for every node, pinned or not, precisely so that it cannot.</p>
|
|
5617
|
+
<p><strong>Colours from the rules you already wrote.</strong> Each link's stroke comes from the value column's own conditional-formatting rules, through <code>grid.formatting.styleFor(col, value)</code> — the colour order is <code>background</code>, then <code>backgroundColor</code>, then <code>color</code>; a gradient (a data bar, an icon set) is not a colour and is not read. A link no rule matches keeps the chart's default link colour. <strong>There is no chart-level threshold option, deliberately</strong>: a second place to say “red above 80” is a second place for the chart and the cell to disagree. The legend lists the rules that actually fired, with their own labels and their own swatches; a rule that matched nothing is not advertised. Change a rule and the links recolour on the next frame without the layout re-running, so nothing moves.</p>
|
|
5618
|
+
<p><strong>Links are undirected, and parallel links stay parallel.</strong> There are no arrowheads, and <code>A,B</code> is the same pair as <code>B,A</code> — a cable has two ends and no direction. <strong>Several rows between the same pair are drawn as several lines</strong>, side by side, offset perpendicular to the pair by 4 px and symmetric about it, in row order, each with its own value and its own colour. They are <em>not</em> summed: three circuits between two sites are three readings, and one line carrying 120% would be a number nothing measured. The tooltip on any one line names both endpoints and that line's own value, and the pointer picks out the line you are actually over rather than the pair. Width follows the value unless <code>linkWidth</code> fixes it.</p>
|
|
5619
|
+
<p><strong>Linked like every other chart.</strong> The graph is drawn from the grid's filtered rows and follows filter and sort. With <code>selection: true</code>, clicking a link selects its row and clicking a node selects every row it is an end of; the grid's selection then lights those links and nodes and dims the rest. A <code>click</code> handler still fires first and can <code>preventDefault()</code>.</p>
|
|
5620
|
+
|
|
5621
|
+
<h4 id="network-map-example">The picture, executed</h4>
|
|
5622
|
+
<p class="section-note">Two core routers pinned across the top, three regional routers pinned below, two circuits between every pair, and one rule set on the <code>load</code> column doing all the colouring. Thirteen rows, thirteen lines, three colours, five glyphs, and a legend that names the three rules.</p>
|
|
5623
|
+
<pre data-run="js" data-expect="13 links, #107c41/#a4262c/#f0b400 | pinned true | gap 4 | 5 icons | Healthy,Busy,Saturated" data-covers="export:createChart export:createGrid config:formatting config:selection"><code>const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
|
|
5624
|
+
const { createGrid } = await import('../packages/dom/src/index.js');
|
|
5625
|
+
const { createChart } = await import('../packages/modules/charts/index.js');
|
|
5626
|
+
|
|
5627
|
+
const { document, root } = createTestDom({ width: 640, height: 420 });
|
|
5628
|
+
|
|
5629
|
+
// Two circuits between every core and every region: six pairs, twelve rows.
|
|
5630
|
+
const rows = [];
|
|
5631
|
+
for (const core of ['core-1', 'core-2']) {
|
|
5632
|
+
for (const site of ['emea', 'amer', 'apac']) {
|
|
5633
|
+
for (const [n, load] of [['a', 18], ['b', 92]]) {
|
|
5634
|
+
rows.push({ id: `${core}/${site}/${n}`, from: core, to: site, load });
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
rows.push({ id: 'core-1/core-2/x', from: 'core-1', to: 'core-2', load: 61 });
|
|
5639
|
+
|
|
5640
|
+
const grid = createGrid(root, {
|
|
5641
|
+
rowKey: 'id',
|
|
5642
|
+
selection: 'multiple',
|
|
5643
|
+
columns: [{ field: 'from' }, { field: 'to' }, { field: 'load', type: 'number' }],
|
|
5644
|
+
rows,
|
|
5645
|
+
// One rule set on the column. The cells and the links read it together.
|
|
5646
|
+
formatting: {
|
|
5647
|
+
load: [
|
|
5648
|
+
{ id: 'ok', label: 'Healthy', when: { op: 'lt', value: 40 }, style: { background: '#107c41' } },
|
|
5649
|
+
{ id: 'busy', label: 'Busy', when: { op: 'lt', value: 80 }, style: { background: '#f0b400' } },
|
|
5650
|
+
{ id: 'hot', label: 'Saturated', when: { op: 'gte', value: 80 }, style: { background: '#a4262c' } },
|
|
5651
|
+
],
|
|
5652
|
+
},
|
|
5653
|
+
});
|
|
5654
|
+
|
|
5655
|
+
const container = document.createElement('div');
|
|
5656
|
+
container.rect = { width: 600, height: 400, top: 0, left: 0 };
|
|
5657
|
+
root.appendChild(container);
|
|
5658
|
+
|
|
5659
|
+
const chart = createChart({
|
|
5660
|
+
grid, container, type: 'network', source: 'from', target: 'to',
|
|
5661
|
+
y: { col: 'load', fn: 'sum' }, selection: true,
|
|
5662
|
+
nodes: [
|
|
5663
|
+
{ id: 'core-1', label: 'Core', icon: 'square', x: 0.3, y: 0.15 },
|
|
5664
|
+
{ id: 'core-2', label: 'Core', icon: 'square', x: 0.7, y: 0.15 },
|
|
5665
|
+
{ id: 'emea', label: 'EMEA', icon: 'circleFilled', x: 0.2, y: 0.8 },
|
|
5666
|
+
{ id: 'amer', label: 'AMER', icon: 'circleFilled', x: 0.5, y: 0.8 },
|
|
5667
|
+
{ id: 'apac', label: 'APAC', icon: 'circleFilled', x: 0.8, y: 0.8 },
|
|
5668
|
+
],
|
|
5669
|
+
});
|
|
5670
|
+
|
|
5671
|
+
const find = (tag, cls) => [...container.querySelectorAll(tag)]
|
|
5672
|
+
.filter((n) => (n.getAttribute('class') || '').includes(cls));
|
|
5673
|
+
const at = (key) => find('circle', '__node').find((c) => c.getAttribute('data-node') === key);
|
|
5674
|
+
const num = (el, name) => Number(el.getAttribute(name));
|
|
5675
|
+
|
|
5676
|
+
// The picture: two cores on one row, three regions on another below them.
|
|
5677
|
+
const top = [at('core-1'), at('core-2')].map((c) => num(c, 'cy'));
|
|
5678
|
+
const bottom = ['emea', 'amer', 'apac'].map((k) => num(at(k), 'cy'));
|
|
5679
|
+
const rowsPinned = top[0] === top[1] && bottom.every((y) => y === bottom[0]) && bottom[0] > top[0]
|
|
5680
|
+
&& num(at('core-1'), 'cx') < num(at('core-2'), 'cx');
|
|
5681
|
+
|
|
5682
|
+
// Every link its own line, coloured by the rule its value matched.
|
|
5683
|
+
const edges = find('path', '__edge');
|
|
5684
|
+
const stroke = (e) => ((e.getAttribute('style') || '').match(/stroke:\s*([^;]+)/) || [])[1];
|
|
5685
|
+
const colours = [...new Set(edges.map(stroke))].sort();
|
|
5686
|
+
|
|
5687
|
+
// Two circuits between core-1 and emea, drawn side by side 4px apart.
|
|
5688
|
+
const ends = (d) => d.match(/-?\d+(?:\.\d+)?/g).map(Number);
|
|
5689
|
+
const pair = edges.slice(0, 2).map((e) => ends(e.getAttribute('d')));
|
|
5690
|
+
const gap = Math.round(Math.hypot(pair[0][0] - pair[1][0], pair[0][1] - pair[1][1]));
|
|
5691
|
+
|
|
5692
|
+
// Five glyphs, drawn from the registry the host extended.
|
|
5693
|
+
const glyphs = find('path', '__node-icon').length;
|
|
5694
|
+
|
|
5695
|
+
// The legend names the rules that fired, not a palette.
|
|
5696
|
+
const legend = [...container.querySelectorAll('button')]
|
|
5697
|
+
.filter((b) => (b.getAttribute('class') || '').includes('__legend-item'))
|
|
5698
|
+
.map((b) => b.textContent).join(',');
|
|
5699
|
+
|
|
5700
|
+
chart.destroy();
|
|
5701
|
+
grid.destroy();
|
|
5702
|
+
return `${edges.length} links, ${colours.join('/')} | pinned ${rowsPinned} | gap ${gap} | ${glyphs} icons | ${legend}`;</code></pre>
|
|
5703
|
+
|
|
5489
5704
|
<h3 id="chart-extension-types">Extension chart types — pay only for what you draw</h3>
|
|
5490
5705
|
<p>The base charts bundle draws the built-in <code>TYPES</code> and nothing else. A new chart type is a <strong>separate, opt-in module</strong> a caller imports only if they use it (BACKLOG-0000886, on the slim-core seam BACKLOG-0000884). Importing it self-registers the type with the base module through <code>registerChartType</code>; the base <code>Chart</code> consults that registry for any type it does not draw natively. Because the base never imports the extension, the base bundle <strong>does not grow</strong> for a type a caller never uses.</p>
|
|
5491
5706
|
<pre><code><span class="kw">import</span> '@toclocoinc/lattice-grid/modules/charts'; <span class="cmt">// the base</span>
|
|
@@ -5601,6 +5816,62 @@ grid.destroy();
|
|
|
5601
5816
|
typeof hexmap.drawHexMap, typeof hexmap.bindHexMap,
|
|
5602
5817
|
].join(' | ');</code></pre>
|
|
5603
5818
|
|
|
5819
|
+
<h3 id="chart-markermap">Map markers — a figure per location, coloured by its own rule</h3>
|
|
5820
|
+
<p><code>modules/chart-markermap</code> registers <code>markermap</code>: one marker per row, placed by <code>lon</code>/<code>lat</code> over a geometry pack's outlines, showing the row's <code>label</code> and its <code>value</code> beside the dot. Two things come from the grid rather than from the chart, and that is the whole point of the type. The <strong>number</strong> is the value column's own formatted cell text, so a percentage, a currency or a unit reads on the map exactly as it reads in the table. The <strong>colour</strong> is whatever <code>grid.formatting.styleFor(valueColumn, value)</code> returns for that row — the rule's <code>background</code>, or its <code>color</code> where it sets no background — so a red / amber / green availability wall is one rule set on one column plus one chart configuration. There is deliberately no chart-level thresholds option and no colour column: the rules are the one source, and a legend lists the rules that actually fired, with each rule's own swatch.</p>
|
|
5821
|
+
<p>With <code>shapes</code> it draws the pack's regions underneath, through the pack's own <code>projection</code>, and pans and zooms exactly as a <a href="#geometry-packs">geomap</a> of that pack does; without <code>shapes</code> the markers fall back to the projection alone. A row whose coordinates are absent, non-numeric or outside ±180 / ±90 draws no marker and is counted in <code>chart.data().unplaced</code>, which the map also writes under itself. Labels are deconflicted by trying four positions in a fixed order — right of the dot, then left, then above, then below — and a label with nowhere to go is dropped rather than overprinted; <code>labels: false</code> turns them all off on a dense map and leaves the tooltip, which carries the name, the value, the coordinates and the status. With <code>selection: true</code> a click on a marker selects that row in the grid, and the grid's selection emphasises the marker.</p>
|
|
5822
|
+
<pre data-run="js" data-expect="#1b7f3b #1b7f3b #c8a415 #c0392b | London 99.95% / Sydney 99.91% / Frankfurt 99.62% / New York 98.05% | unplaced 0" data-covers="export:drawMarkerMap export:bindMarkerMap"><code><span class="kw">const</span> { createTestDom } = <span class="kw">await</span> import('../packages/dom/src/renderer/testdom.js');
|
|
5823
|
+
<span class="kw">const</span> { document, root } = createTestDom({ width: 700, height: 460 });
|
|
5824
|
+
<span class="kw">const</span> panel = document.createElement('div');
|
|
5825
|
+
panel.rect = { width: 700, height: 460, top: 0, left: 0 };
|
|
5826
|
+
root.appendChild(panel);
|
|
5827
|
+
|
|
5828
|
+
<span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
5829
|
+
<span class="kw">const</span> { createChart } = <span class="kw">await</span> import('../packages/modules/charts/index.js');
|
|
5830
|
+
<span class="cmt">// Importing the module registers `markermap`; its drawer is drawMarkerMap.</span>
|
|
5831
|
+
<span class="kw">const</span> markermap = <span class="kw">await</span> import('../packages/modules/chart-markermap/index.js');
|
|
5832
|
+
<span class="kw">const</span> { pack } = <span class="kw">await</span> import('../packages/modules/geo-world-110m/index.js');
|
|
5833
|
+
|
|
5834
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
5835
|
+
rowKey: 'id',
|
|
5836
|
+
columns: [
|
|
5837
|
+
{ field: 'site', type: 'text' }, { field: 'lng', type: 'number' },
|
|
5838
|
+
{ field: 'lat', type: 'number' }, { field: 'avail', type: 'number', format: '0.00%' },
|
|
5839
|
+
],
|
|
5840
|
+
rows: [
|
|
5841
|
+
{ id: 'ldn', site: 'London', lng: -0.13, lat: 51.5, avail: 0.9995 },
|
|
5842
|
+
{ id: 'syd', site: 'Sydney', lng: 151.2, lat: -33.87, avail: 0.9991 },
|
|
5843
|
+
{ id: 'fra', site: 'Frankfurt', lng: 8.68, lat: 50.11, avail: 0.9962 },
|
|
5844
|
+
{ id: 'nyc', site: 'New York', lng: -74.0, lat: 40.71, avail: 0.9805 },
|
|
5845
|
+
],
|
|
5846
|
+
});
|
|
5847
|
+
<span class="cmt">// Three rules on the column. Nothing below repeats a threshold or a colour.</span>
|
|
5848
|
+
grid.formatting.add('avail', { when: { op: 'gte', value: 0.999 }, style: { background: '#1b7f3b' }, label: 'Healthy' });
|
|
5849
|
+
grid.formatting.add('avail', { when: { op: 'gte', value: 0.99 }, style: { background: '#c8a415' }, label: 'Watch' });
|
|
5850
|
+
grid.formatting.add('avail', { when: { op: 'lt', value: 0.99 }, style: { background: '#c0392b' }, label: 'Breached' });
|
|
5851
|
+
|
|
5852
|
+
<span class="kw">const</span> chart = createChart({
|
|
5853
|
+
grid, container: panel, type: 'markermap',
|
|
5854
|
+
lon: 'lng', lat: 'lat', label: 'site', value: 'avail', shapes: pack,
|
|
5855
|
+
});
|
|
5856
|
+
|
|
5857
|
+
<span class="cmt">// What was painted: a fill per marker, and the text beside each dot.</span>
|
|
5858
|
+
<span class="kw">const</span> fills = [];
|
|
5859
|
+
<span class="kw">const</span> labels = [];
|
|
5860
|
+
<span class="kw">const</span> walk = (node) => {
|
|
5861
|
+
<span class="kw">for</span> (<span class="kw">const</span> child <span class="kw">of</span> node.children || []) {
|
|
5862
|
+
<span class="kw">const</span> cls = String(child.getAttribute('class') || '');
|
|
5863
|
+
<span class="kw">if</span> (cls.includes('markermap-dot')) fills.push(child.getAttribute('fill'));
|
|
5864
|
+
<span class="kw">if</span> (cls.includes('data-label')) labels.push(child.textContent);
|
|
5865
|
+
walk(child);
|
|
5866
|
+
}
|
|
5867
|
+
};
|
|
5868
|
+
walk(chart.element);
|
|
5869
|
+
|
|
5870
|
+
<span class="cmt">// The binder is public too, for a host that wants the placed rows itself.</span>
|
|
5871
|
+
<span class="kw">const</span> bound = markermap.bindMarkerMap(grid, { lon: 'lng', lat: 'lat', label: 'site', value: 'avail' });
|
|
5872
|
+
chart.destroy();
|
|
5873
|
+
<span class="kw">return</span> `${fills.join(' ')} | ${labels.join(' / ')} | unplaced ${bound.unplaced}`;</code></pre>
|
|
5874
|
+
|
|
5604
5875
|
<h2 id="datarouter">The data router</h2>
|
|
5605
5876
|
<p><code>modules/data-router</code> is a host-layer demultiplexer: it takes <strong>one</strong> arriving stream or dataset, splits it by what each record <em>is</em>, and routes each partition to its own grid — or to a headless grid driving a chart. One round-trip, or one live feed, hydrates a whole screen of grids that each see only their slice. It is optional, imports nothing from the grid, and adds no core hook: every grid is driven through the <strong>public</strong> incremental path, <code>grid.rows.apply({ add, update, remove })</code>. <strong>The router never opens a connection itself — the host owns the connection (a <code>WebSocket</code>, SSE, CDC, a message bus, a plain fetch), and the router owns everything once a message has arrived</strong>; see <a href="#datarouter-websocket-example">a live WebSocket feed</a> for the worked, runnable integration.</p>
|
|
5606
5877
|
<pre><code>import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
|
|
@@ -5869,7 +6140,7 @@ socketA.close();
|
|
|
5869
6140
|
<span class="kw">const</span> droppedBefore = router.dropped;
|
|
5870
6141
|
<span class="kw">const</span> socketB = <span class="kw">new</span> MockWebSocket({ feed: feedB(), rate: 5, jitter: 0, snapshotDelay: 5 });
|
|
5871
6142
|
wireRouter(router, socketB);
|
|
5872
|
-
<span class="kw">await</span> wait(
|
|
6143
|
+
<span class="kw">await</span> wait(80);
|
|
5873
6144
|
<span class="kw">const</span> afterReconnect = g.rows.value('a', 'label'); <span class="cmt">// A@4 — only the new delta advanced it</span>
|
|
5874
6145
|
<span class="kw">const</span> replaysDropped = router.dropped - droppedBefore; <span class="cmt">// 2 — both replays dropped</span>
|
|
5875
6146
|
socketB.close();
|
|
@@ -6681,6 +6952,52 @@ const kpi = createKPI(document.querySelector('#kpis'), {
|
|
|
6681
6952
|
</table>
|
|
6682
6953
|
</div>
|
|
6683
6954
|
<p><strong>Interaction is light and host-driven.</strong> A tile emits <code>tile:click</code> (also from the keyboard) carrying the tile model, so a host can drill down or, in a demo, filter a routed grid — the wiring lives in the host, not the module. This is deliberately not a dashboard layout engine (that is the parked dashboard generator) and charting beyond a minimal sparkline belongs to the charts module.</p>
|
|
6955
|
+
<h3 id="kpi-clock">The clock tile: the device clock, not an aggregate</h3>
|
|
6956
|
+
<p><code>{ kind: 'clock', label, timeZone?, locale?, seconds?, date? }</code> in <code>tiles</code> renders a tile that shows the current date and time instead of a figure — the date on one line, the time on the next, e.g. <code>Mon, 21 Apr 2025</code> over <code>14:32:18</code> — styled like any other tile, so a panel of zone clocks beside a panel of figures reads as one visual system with no host CSS.</p>
|
|
6957
|
+
<pre><code>const kpi = createKPI(document.querySelector('#kpis'), {
|
|
6958
|
+
tiles: [
|
|
6959
|
+
{ kind: 'clock', label: 'London', timeZone: 'Europe/London' },
|
|
6960
|
+
{ kind: 'clock', label: 'New York', timeZone: 'America/New_York', locale: 'en-US' },
|
|
6961
|
+
{ id: 'open', label: 'Open deals', aggregation: 'count', filter: (r) => r.stage !== 'won' },
|
|
6962
|
+
],
|
|
6963
|
+
});</code></pre>
|
|
6964
|
+
<p><strong>Where the time comes from.</strong> The device clock, read every second in exact alignment with the second boundary — not a fixed <code>setInterval(1000)</code>, which drifts — so every clock tile on a panel ticks in the same repaint. One timer serves the <em>whole panel</em>, not one per tile: a panel of three zone clocks runs one shared interval, not three. The timer stops when the panel is <code>destroy()</code>ed or the document goes into the background (<code>document.hidden</code>) and resumes correctly — catching up immediately, then re-aligning — when the tab returns. A panel with no clock tile starts no timer at all.</p>
|
|
6965
|
+
<p><strong>Zone and format.</strong> <code>timeZone</code> is any IANA zone name; omitted, the tile shows the viewer's local time. A name <code>Intl.DateTimeFormat</code> does not recognise is reported through the usual <code>[lattice]</code> diagnostics warning, by name, and the tile falls back to local time rather than rendering nothing. The date and time are formatted for <code>locale</code> — the tile's own, else the panel's <code>locale</code>, else the browser's default — entirely through <code>Intl.DateTimeFormat</code>: a 24-hour clock where the locale uses one, 12-hour with an AM/PM marker where it does not, because that is the locale's own convention rather than a second option to set. <code>seconds: false</code> drops the seconds from the time line; <code>date: false</code> drops the date line entirely. Every formatter is built once, at tile resolution, and reused on every tick.</p>
|
|
6966
|
+
<p><strong>Inert everywhere a stat tile measures.</strong> A clock tile takes none of a stat tile's measurement options — <code>aggregation</code>, <code>field</code>, <code>format</code>, <code>thresholds</code>, <code>bands</code>, <code>target</code>, <code>baseline</code>, <code>sparkline</code> — because a tile that measures nothing has nothing for them to apply to. Supplying any of them is reported as a configuration warning by name and ignored: the tile still renders the clock, nothing else. It contributes nothing to the panel's totals, to a parent's rolled-up status in a <a href="#kpi-tree">tree</a> (its own <code>status</code> is always <code>null</code>, never <code>unknown</code> — a clock tile is never “not measured”, it always has a reading) and nothing to what a host reads out of <code>onChange</code> beyond its own text. It otherwise behaves exactly like any other tile: it sits in <code>tiles()</code>/<code>tile(id)</code> and a <a href="#kpi-tree">tree</a> alongside stat tiles, responds to <code>columns</code>, fires <code>tile:click</code>/<code>tile:dblclick</code>/<code>tile:contextmenu</code>, and its accessible name (<code>aria-label</code>) carries the same date and time text the two visible lines show.</p>
|
|
6967
|
+
<h3 id="kpi-clock-example">A clock tile, two zones, executed</h3>
|
|
6968
|
+
<p class="section-note">Structural assertions only — the clock reads the real device clock, so a doc example run on every build cannot pin a literal
|
|
6969
|
+
time without freezing it. The <a href="#kpi-clock">formatting itself</a> is pinned for two locales and two zones with a fixed clock
|
|
6970
|
+
in the test suite. Run headless on every build.</p>
|
|
6971
|
+
<pre data-run="js" data-expect="clock clock null null null | true true | clock" data-covers="export:createKPI"><code><span class="kw">const</span> { createKPI } = <span class="kw">await</span> import('../packages/modules/kpi/index.js');
|
|
6972
|
+
|
|
6973
|
+
<span class="kw">const</span> kpi = createKPI(null, {
|
|
6974
|
+
tiles: [
|
|
6975
|
+
{ kind: 'clock', id: 'london', label: 'London', timeZone: 'Europe/London', locale: 'en-GB' },
|
|
6976
|
+
<span class="cmt">// aggregation/thresholds are measurement options a clock tile refuses (warned, ignored):</span>
|
|
6977
|
+
{ kind: 'clock', id: 'ny', label: 'New York', timeZone: 'America/New_York', locale: 'en-US',
|
|
6978
|
+
aggregation: 'sum', thresholds: { warn: 1, critical: 2 } },
|
|
6979
|
+
],
|
|
6980
|
+
});
|
|
6981
|
+
|
|
6982
|
+
<span class="kw">const</span> london = kpi.tile('london');
|
|
6983
|
+
<span class="kw">const</span> ny = kpi.tile('ny');
|
|
6984
|
+
|
|
6985
|
+
<span class="cmt">// kind, status and bar are the same three neutral values on every clock tile,</span>
|
|
6986
|
+
<span class="cmt">// whatever was supplied for the ignored options above (String(), because</span>
|
|
6987
|
+
<span class="cmt">// Array#join renders null as '' rather than 'null'):</span>
|
|
6988
|
+
<span class="kw">const</span> shapes = [london.kind, ny.kind, String(london.status), String(ny.status), String(ny.bar)].join(' '); <span class="cmt">// clock clock null null null</span>
|
|
6989
|
+
|
|
6990
|
+
<span class="cmt">// The date and time lines hold the shape the formatting spec promises:</span>
|
|
6991
|
+
<span class="kw">const</span> shaped = [
|
|
6992
|
+
/^[A-Za-z]{3}, \d{2} [A-Za-z]{3,4} \d{4}$/.test(london.clock.date),
|
|
6993
|
+
/^\d{2}:\d{2}:\d{2}(\s?[AP]M)?$/.test(ny.clock.time),
|
|
6994
|
+
].join(' '); <span class="cmt">// true true</span>
|
|
6995
|
+
|
|
6996
|
+
<span class="cmt">// The ignored `aggregation: 'sum'` never took effect:</span>
|
|
6997
|
+
<span class="kw">const</span> ignoredAgg = ny.aggregation; <span class="cmt">// clock</span>
|
|
6998
|
+
|
|
6999
|
+
kpi.destroy();
|
|
7000
|
+
<span class="kw">return</span> [shapes, shaped, ignoredAgg].join(' | ');</code></pre>
|
|
6684
7001
|
<h3 id="kpi-tree">The KPI tree: top-level items that expand to the indicators beneath them</h3>
|
|
6685
7002
|
<p>Set <code>tree</code> and the panel becomes a <strong>rail</strong> instead of a grid of tiles: a small number of top-level items, each expanding to the indicators underneath it, with the parent telling you at a glance whether anything below needs attention. <code>Compute</code> expands to <code>psi</code> and <code>cpu</code>; collapsed, it still shows you that one of them is in breach.</p>
|
|
6686
7003
|
<pre><code>const kpi = createKPI(document.querySelector('#rail'), {
|
|
@@ -6877,7 +7194,7 @@ grid.destroy();
|
|
|
6877
7194
|
<span class="kw">return</span> `${by['risk.atRisk'].display} at risk | SPI ${by['risk.spi'].display} | ${by['risk.sla.breaches'].display} breaches`;</code></pre>
|
|
6878
7195
|
|
|
6879
7196
|
<h2 id="tabs">The tabbed grid</h2>
|
|
6880
|
-
<p><code>modules/tabs</code> is an opt-in top-of-grid tab strip where <strong>each tab is its own full, independently-configured grid instance</strong> — "configure each tab as per a normal grid" rather than one grid whose state is swapped. That is a deliberate rejection of the cheaper alternative: <code>grid.state.get()</code>/<code>.apply()</code> only repositions, hides, resizes and sorts <strong>existing</strong> columns by id (no field, type, editor or row data), so a state-swap only works when every tab shares one column schema and one source — strictly less than the ask. A tab may instead declare <code>from: '<tabId>'</code> plus a narrowing (<code>where</code>, <code>group</code>, <code>join</code>, …), and the module wires a <code>source: { mode: 'derived', from: <the parent tab's live grid>, … }</code> for it — the shipped derived-source mechanism, not a new config-inheritance one. <code>createGrid</code> is <strong>injected</strong> (the same pattern the React/Vue/Svelte adapters use), so the module imports no engine code regardless of how it is loaded — its own minified ESM build (<code>tabs.esm.min.js</code>) is
|
|
7197
|
+
<p><code>modules/tabs</code> is an opt-in top-of-grid tab strip where <strong>each tab is its own full, independently-configured grid instance</strong> — "configure each tab as per a normal grid" rather than one grid whose state is swapped. That is a deliberate rejection of the cheaper alternative: <code>grid.state.get()</code>/<code>.apply()</code> only repositions, hides, resizes and sorts <strong>existing</strong> columns by id (no field, type, editor or row data), so a state-swap only works when every tab shares one column schema and one source — strictly less than the ask. A tab may instead declare <code>from: '<tabId>'</code> plus a narrowing (<code>where</code>, <code>group</code>, <code>join</code>, …), and the module wires a <code>source: { mode: 'derived', from: <the parent tab's live grid>, … }</code> for it — the shipped derived-source mechanism, not a new config-inheritance one. <code>createGrid</code> is <strong>injected</strong> (the same pattern the React/Vue/Svelte adapters use), so the module imports no engine code regardless of how it is loaded — its own minified ESM build (<code>tabs.esm.min.js</code>) is about 12KB gzipped — the module and nothing else. A module bundle carries its own code plus a small shared runtime, not the engine: the framework adapters are 3–11KB, the KPI module about 50KB, while a module that inlines the whole engine (the web component, htmx) ships at roughly 760KB.</p>
|
|
6881
7198
|
<pre><code>import { createGrid } from '@toclocoinc/lattice-grid';
|
|
6882
7199
|
import { createTabs } from '@toclocoinc/lattice-grid/modules/tabs';
|
|
6883
7200
|
|
|
@@ -7019,7 +7336,7 @@ tabs.destroy();
|
|
|
7019
7336
|
|
|
7020
7337
|
<h2 id="layout">The dashboard layout</h2>
|
|
7021
7338
|
<p><code>modules/layout</code> is an opt-in <strong>reconfigurable dashboard surface</strong>: a cell grid inside an element, and a set of windows placed on it that a user can move, resize and close — by drag <em>or</em> by keyboard. It is the thing a customer would otherwise reach for GridStack or react-grid-layout to get, which means a second dependency, a second sizing model, and a seam where the viewers in it do not resize properly.</p>
|
|
7022
|
-
<p><strong>It is payload-agnostic, and that is the whole design.</strong> A window body is a <code>div</code> with an <code>id</code>. The module creates it, sizes it, and never reads or writes its contents — it does not import <code>createGrid</code>, does not know what a payload is, and never calls into one. What it does instead is emit <code>window:resized</code> with the measured content box, which is the contract. That rule is what keeps
|
|
7339
|
+
<p><strong>It is payload-agnostic, and that is the whole design.</strong> A window body is a <code>div</code> with an <code>id</code>. The module creates it, sizes it, and never reads or writes its contents — it does not import <code>createGrid</code>, does not know what a payload is, and never calls into one. What it does instead is emit <code>window:resized</code> with the measured content box, which is the contract. That rule is what keeps the whole module to <strong>about 17,500 bytes gzipped</strong> (measured on the built bundle: its own code over a shared module runtime of roughly 2KB) and what makes it usable for a payload we have not written yet.</p>
|
|
7023
7340
|
<pre><code>import { createLayout } from '@toclocoinc/lattice-grid/modules/layout';
|
|
7024
7341
|
|
|
7025
7342
|
const layout = createLayout(document.querySelector('#dash'), {
|
|
@@ -8685,6 +9002,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8685
9002
|
<tr><td class="name">text</td><td class="type">string</td><td class="desc">The label of a `text` mark. Required for `text`, ignored for other types. <small>(optional)</small></td></tr>
|
|
8686
9003
|
<tr><td class="name">fontSize</td><td class="type">number</td><td class="desc">A `text` mark's font size in content pixels (before presentation scale). Defaults to 14. <small>(optional)</small></td></tr>
|
|
8687
9004
|
<tr><td class="name">background</td><td class="type">string</td><td class="desc">An optional backing colour drawn behind a `text` mark's label. <small>(optional)</small></td></tr>
|
|
9005
|
+
<tr><td class="name">region</td><td class="type">'start' | 'centre' | 'end'</td><td class="desc">Which columns the mark belongs to: a pinned region holds still while the grid scrolls sideways, the centre moves with it. Set from where a stroke began; omitted (the centre) for every mark that is not over a pinned column, so a mark saved before this existed reads unchanged. <small>(optional)</small></td></tr>
|
|
8688
9006
|
</tbody>
|
|
8689
9007
|
</table>
|
|
8690
9008
|
</div>
|
|
@@ -8959,6 +9277,20 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8959
9277
|
</tbody>
|
|
8960
9278
|
</table>
|
|
8961
9279
|
</div>
|
|
9280
|
+
<h3 id="type-ChartNode">ChartNode</h3>
|
|
9281
|
+
<p class="section-note">One node of a `network` chart, as the host declares it. `x` and `y` are fractions of the plot, 0 to 1, measured from its top-left. A node giving both is **pinned** there and takes no part in the force simulation; the rest are laid out around it, deterministically. Giving only one of the two is not a position and the node is laid out.</p>
|
|
9282
|
+
<div class="table-wrap">
|
|
9283
|
+
<table>
|
|
9284
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
9285
|
+
<tbody>
|
|
9286
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc">Matches a value in the `source` or `target` column.</td></tr>
|
|
9287
|
+
<tr><td class="name">label</td><td class="type">string</td><td class="desc">Drawn beneath the node. The id is used when this is absent. <small>(optional)</small></td></tr>
|
|
9288
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">A name in the grid's icon registry, drawn inside the node's disc. <small>(optional)</small></td></tr>
|
|
9289
|
+
<tr><td class="name">x</td><td class="type">number</td><td class="desc">Where to pin it, as a fraction of the plot's width. <small>(optional)</small></td></tr>
|
|
9290
|
+
<tr><td class="name">y</td><td class="type">number</td><td class="desc">Where to pin it, as a fraction of the plot's height. <small>(optional)</small></td></tr>
|
|
9291
|
+
</tbody>
|
|
9292
|
+
</table>
|
|
9293
|
+
</div>
|
|
8962
9294
|
<h3 id="type-ChartSpec">ChartSpec</h3>
|
|
8963
9295
|
<p class="section-note">What a chart draws and how. `grid` and `container` are required; everything else describes the chart. A chart reads the grid's *filtered* rows, so it follows the grid without being told to.</p>
|
|
8964
9296
|
<div class="table-wrap">
|
|
@@ -8997,6 +9329,9 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8997
9329
|
<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>
|
|
8998
9330
|
<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>
|
|
8999
9331
|
<tr><td class="name">codeProperty</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9332
|
+
<tr><td class="name">lon</td><td class="type">string</td><td class="desc">The longitude column, for the types that place a row by where it is rather than by a code: `markermap`, `bubblemap` and `hexmap`. Degrees east, -180 to 180; a row outside that, or with no reading, is left off the map and counted. <small>(optional)</small></td></tr>
|
|
9333
|
+
<tr><td class="name">lat</td><td class="type">string</td><td class="desc">The latitude column, beside {@link ChartSpec.lon}. Degrees north, -90 to 90, on the same terms. <small>(optional)</small></td></tr>
|
|
9334
|
+
<tr><td class="name">value</td><td class="type">string</td><td class="desc">The measure a `markermap` writes beside each dot and colours it by. Its text is the column's own formatted cell text and its colour is whatever the column's conditional-formatting rules give that value, so a map and the table beside it say the same thing about the same number. <small>(optional)</small></td></tr>
|
|
9000
9335
|
<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>
|
|
9001
9336
|
<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>
|
|
9002
9337
|
<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>
|
|
@@ -9023,6 +9358,9 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
9023
9358
|
<tr><td class="name">method</td><td class="type">'pearson' | 'spearman' | 'kendall'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9024
9359
|
<tr><td class="name">values</td><td class="type">boolean</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9025
9360
|
<tr><td class="name">iterations</td><td class="type">number</td><td class="desc">Network layouts: how many relaxation passes to run. <small>(optional)</small></td></tr>
|
|
9361
|
+
<tr><td class="name">nodes</td><td class="type">ChartNode[]</td><td class="desc">The nodes of a `network`, named by the host rather than inferred from the rows: an icon per device, a label, and a position the layout must honour. A node listed here that appears in no row is still drawn. A node in the rows that is not listed here takes the chart's `icon` default and its own id as its label. <small>(optional)</small></td></tr>
|
|
9362
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">The default glyph for a `network` node that names none of its own: any name in the grid's icon registry (see {@link Grid.icons}). Unset, a node with no icon is a plain disc. <small>(optional)</small></td></tr>
|
|
9363
|
+
<tr><td class="name">linkWidth</td><td class="type">number</td><td class="desc">A `network` link's stroke width in pixels, fixed. Unset, width follows the link's value as a share of the heaviest link, as it always has. <small>(optional)</small></td></tr>
|
|
9026
9364
|
<tr><td class="name">spec</td><td class="type">{ lower?: number; upper?: number; target?: number }</td><td class="desc">Control and capability charts: a tolerance overriding the column's own `spec`, how many leading readings fix the control limits, which rule set the violations are judged against, and the level for the capability interval. <small>(optional)</small></td></tr>
|
|
9027
9365
|
<tr><td class="name">baseline</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9028
9366
|
<tr><td class="name">rules</td><td class="type">'westernElectric' | 'nelson'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -10840,6 +11178,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
10840
11178
|
<tr><td class="name">setPinnedRows</td><td class="type">(rows: unknown[], opts?: { edge?: 'top' | 'bottom' }): void</td><td class="desc">Pin rows above or below the scrolling body. The rows render through the ordinary column pipeline but are not part of the data: not counted, sorted, filtered, grouped, selectable or exported. Pass a new array rather than mutating the one you passed before: array identity is how the grid knows the pinned rows have changed.</td></tr>
|
|
10841
11179
|
<tr><td class="name">getPinnedRows</td><td class="type">(opts?: { edge?: 'top' | 'bottom' }): unknown[]</td><td class="desc">The objects currently pinned at one edge, as a copy.</td></tr>
|
|
10842
11180
|
<tr><td class="name">form</td><td class="type">RowFormApi</td><td class="desc">The row form. Declines when `rowForm` is not configured. <small>(read-only)</small></td></tr>
|
|
11181
|
+
<tr><td class="name">icons</td><td class="type">IconRegistryApi</td><td class="desc">The grid's icon registry, read-only. The same sprite set `registerIcon` writes to and every cell paints from, reachable from the grid instance so that code outside the grid bundle — an optional module drawing its own glyph, a network chart putting a `router` on a node — draws from the one registry rather than a second, empty copy of it. Register with `registerIcon` or `config.icons`, as before. <small>(read-only)</small></td></tr>
|
|
10843
11182
|
<tr><td class="name">getVersion</td><td class="type">(): string</td><td class="desc">The library version.</td></tr>
|
|
10844
11183
|
<tr><td class="name">destroy</td><td class="type">(): void</td><td class="desc">Release everything: listeners, timers, workers and the DOM the grid made.</td></tr>
|
|
10845
11184
|
</tbody>
|
|
@@ -10865,6 +11204,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
10865
11204
|
<tr><td class="name">pipes</td><td class="type">Record<string, (value: unknown, ...args: string[]) => string></td><td class="desc">Named text transforms usable from a format mask or a template. <small>(optional)</small></td></tr>
|
|
10866
11205
|
<tr><td class="name">totalFns</td><td class="type">Record<string, TotalFn></td><td class="desc">Your own reductions, alongside the built-in ones. <small>(optional)</small></td></tr>
|
|
10867
11206
|
<tr><td class="name">variants</td><td class="type">Record<string, VariantDefinition></td><td class="desc">Named appearance variants a row or cell can be switched into by a rule. <small>(optional)</small></td></tr>
|
|
11207
|
+
<tr><td class="name">icons</td><td class="type">Record<string, IconDefinition></td><td class="desc">Your own SVG glyphs, registered by name before the first paint. The same registry `registerIcon` writes to and every cell, header control, rail button and chart glyph is painted from, so a name given here is usable anywhere a glyph name is: a column's `icon` decoration, a rail action's `icon`, a network node's `icon`. Registering a built-in name overrides it. Read the result back through {@link Grid.icons}. <small>(optional)</small></td></tr>
|
|
10868
11208
|
<tr><td class="name">tree</td><td class="type">TreeConfig</td><td class="desc">Hierarchical rows: where the parent link or the path lives. <small>(optional)</small></td></tr>
|
|
10869
11209
|
<tr><td class="name">detail</td><td class="type">DetailConfig</td><td class="desc">The expandable panel beneath a row. <small>(optional)</small></td></tr>
|
|
10870
11210
|
<tr><td class="name">selection</td><td class="type">SelectionConfig | 'single' | 'multiple' | 'none'</td><td class="desc">What the user may select, and how selection behaves across groups. The `'none'` shorthand is `{ mode: 'none' }` and behaves identically: no row selection, and no cell ranges or fill handle either. <small>(optional)</small></td></tr>
|
|
@@ -10944,7 +11284,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
10944
11284
|
<tr><td class="name">rowClass</td><td class="type">string | string[] | ((p: RowStyleParams) => string | string[])</td><td class="desc">A class, or classes, for every row. Re-evaluated on each repaint. <small>(optional)</small></td></tr>
|
|
10945
11285
|
<tr><td class="name">rowStyle</td><td class="type">CellStyle | ((p: RowStyleParams) => CellStyle)</td><td class="desc">Inline styles for every row. Camel-case or hyphenated property names. <small>(optional)</small></td></tr>
|
|
10946
11286
|
<tr><td class="name">toolPanel</td><td class="type">boolean | {</td><td class="desc"><small>(optional)</small></td></tr>
|
|
10947
|
-
<tr><td class="name">groupPanel</td><td class="type">boolean | {</td><td class="desc">A drag-and-drop group-by strip above the column header — the pattern AG Grid calls the row-group panel. Drag a column heading into it to group by that column; the active groups show as removable, reorderable chips, and reordering the chips changes the nesting order. It is keyboard-operable (arrows navigate, Shift+arrow reorders, Delete ungroups, and an add control groups any column), and every change is announced through the live region, which is why it also addresses the drag-only complaint
|
|
11287
|
+
<tr><td class="name">groupPanel</td><td class="type">boolean | {</td><td class="desc">A drag-and-drop group-by strip above the column header — the pattern AG Grid calls the row-group panel. Drag a column heading into it to group by that column; the active groups show as removable, reorderable chips, and reordering the chips changes the nesting order. It is keyboard-operable (arrows navigate, Shift+arrow reorders, Delete ungroups, and an add control groups any column), and every change is announced through the live region, which is why it also addresses the drag-only complaint (BACKLOG-0000429). Off by default and non-breaking, matching `toolPanel`. It drives the same grouping model as `grid.columns.group()`; it reimplements nothing. <small>(optional)</small></td></tr>
|
|
10948
11288
|
<tr><td class="name">kpis</td><td class="type">Array<Omit<StatConfig, 'grid' | 'container'>></td><td class="desc">A built-in KPI/stat strip: a labelled band of {@link createStat} tiles the grid places for you, above the column header. Each entry is a stat spec — the same fields {@link StatConfig} takes, minus `grid` and `container`, which the grid supplies — so a strip tile and a hand-placed one are the same object. The tiles follow the grid's filters, recomputing on every change exactly as a stand-alone stat does. Off by default and non-breaking, matching `groupPanel`: no `kpis` means no band and no cost. It reuses `createStat` and reimplements no compute. <small>(optional)</small></td></tr>
|
|
10949
11289
|
<tr><td class="name">quickFilterText</td><td class="type">string</td><td class="desc">The quick filter's initial text. <small>(optional)</small></td></tr>
|
|
10950
11290
|
<tr><td class="name">permissions</td><td class="type">PermissionPolicy</td><td class="desc">Per-column read/write/hidden policy. A usability control, not a security boundary: hidden data is still resident in the store. Enforce the same policy server-side with `permittedColumns` / `permittedExport`. <small>(optional)</small></td></tr>
|
|
@@ -11167,6 +11507,29 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11167
11507
|
</tbody>
|
|
11168
11508
|
</table>
|
|
11169
11509
|
</div>
|
|
11510
|
+
<h3 id="type-IconGlyph">IconGlyph</h3>
|
|
11511
|
+
<p class="section-note">One sprite: its view box, its path data, and how it is painted.</p>
|
|
11512
|
+
<div class="table-wrap">
|
|
11513
|
+
<table>
|
|
11514
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11515
|
+
<tbody>
|
|
11516
|
+
<tr><td class="name">viewBox</td><td class="type">string</td><td class="desc"></td></tr>
|
|
11517
|
+
<tr><td class="name">paths</td><td class="type">string[]</td><td class="desc"></td></tr>
|
|
11518
|
+
<tr><td class="name">paint</td><td class="type">'stroke' | 'fill'</td><td class="desc"></td></tr>
|
|
11519
|
+
</tbody>
|
|
11520
|
+
</table>
|
|
11521
|
+
</div>
|
|
11522
|
+
<h3 id="type-IconRegistryApi">IconRegistryApi</h3>
|
|
11523
|
+
<p class="section-note">Read access to the grid's icon sprite set (see {@link Grid.icons}).</p>
|
|
11524
|
+
<div class="table-wrap">
|
|
11525
|
+
<table>
|
|
11526
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11527
|
+
<tbody>
|
|
11528
|
+
<tr><td class="name">get</td><td class="type">(name: string): IconGlyph | null</td><td class="desc">One glyph, as a copy, or null when the name is not registered.</td></tr>
|
|
11529
|
+
<tr><td class="name">names</td><td class="type">(): string[]</td><td class="desc">Every registered name, in registration order.</td></tr>
|
|
11530
|
+
</tbody>
|
|
11531
|
+
</table>
|
|
11532
|
+
</div>
|
|
11170
11533
|
<h3 id="type-IconSetSpec">IconSetSpec</h3>
|
|
11171
11534
|
<p class="section-note">An icon set (BACKLOG-0000955): a glyph placed beside the value by the band it falls in. Drawn as a `background-image` with padding, so it too needs no extra element and stays a plain style value. `set` names a built-in — `'arrows'`, `'trafficLights'` or `'ratings'` (see {@link ICON_SETS}) — or supply your own ordered `icons` (SVG documents, data URIs or `url(...)` values). Bands are split at `thresholds` (ascending, one fewer than the icons); without them the column's distribution is cut into equal-count bands. `reverse` flips the order so a high value can read as red.</p>
|
|
11172
11535
|
<div class="table-wrap">
|
|
@@ -11633,6 +11996,22 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11633
11996
|
</tbody>
|
|
11634
11997
|
</table>
|
|
11635
11998
|
</div>
|
|
11999
|
+
<h3 id="type-KPIClockTile">KPIClockTile</h3>
|
|
12000
|
+
<p class="section-note">A clock tile: the device clock, not an aggregate (BACKLOG-0001640) — the date on one line and the time on the next, ticking once a second from one shared panel timer. It takes none of a stat tile's measurement options (`aggregation`, `field`, `format`, `thresholds`, `bands`, `target`, `baseline`, `sparkline`): supplying any of them is reported as a configuration warning by name and ignored, because a tile that measures nothing has nothing for them to apply to.</p>
|
|
12001
|
+
<div class="table-wrap">
|
|
12002
|
+
<table>
|
|
12003
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
12004
|
+
<tbody>
|
|
12005
|
+
<tr><td class="name">kind</td><td class="type">'clock'</td><td class="desc">Discriminates a clock tile from an aggregate stat tile.</td></tr>
|
|
12006
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc">A stable identity for the tile (defaults to the label, then the index). <small>(optional)</small></td></tr>
|
|
12007
|
+
<tr><td class="name">label</td><td class="type">string</td><td class="desc">The tile's accessible label (e.g. the city or zone it names). <small>(optional)</small></td></tr>
|
|
12008
|
+
<tr><td class="name">timeZone</td><td class="type">string</td><td class="desc">Any IANA zone name (`'Europe/London'`). Omitted, the tile shows the viewer's local time. A name `Intl.DateTimeFormat` does not recognise is reported through the usual diagnostics warning and the tile falls back to local time rather than rendering nothing. <small>(optional)</small></td></tr>
|
|
12009
|
+
<tr><td class="name">locale</td><td class="type">string</td><td class="desc">The locale the date and time are formatted in — the tile's own, else the panel's `KPIConfig.locale`, else the browser's default. A 24-hour clock or a 12-hour one with an AM/PM marker follows from the locale itself (`Intl.DateTimeFormat`'s own convention), never a separate option. <small>(optional)</small></td></tr>
|
|
12010
|
+
<tr><td class="name">seconds</td><td class="type">boolean</td><td class="desc">Show the seconds on the time line. Default `true`. <small>(optional)</small></td></tr>
|
|
12011
|
+
<tr><td class="name">date</td><td class="type">boolean</td><td class="desc">Show the date line at all. Default `true`. <small>(optional)</small></td></tr>
|
|
12012
|
+
</tbody>
|
|
12013
|
+
</table>
|
|
12014
|
+
</div>
|
|
11636
12015
|
<h3 id="type-KPIConfig">KPIConfig</h3>
|
|
11637
12016
|
<p class="section-note">KPI panel configuration.</p>
|
|
11638
12017
|
<div class="table-wrap">
|
|
@@ -11647,6 +12026,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11647
12026
|
<tr><td class="name">columns</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11648
12027
|
<tr><td class="name">ariaLabel</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11649
12028
|
<tr><td class="name">nullText</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
12029
|
+
<tr><td class="name">locale</td><td class="type">string</td><td class="desc">The default locale a clock tile formats in when the tile itself declares none (BACKLOG-0001640); falls back to the browser's default. No effect on a stat tile, which takes its own `format.locale`. <small>(optional)</small></td></tr>
|
|
11650
12030
|
<tr><td class="name">tree</td><td class="type">KPITreeConfig | false</td><td class="desc">Arrange the tiles as a hierarchy; `false` keeps the panel flat. <small>(optional)</small></td></tr>
|
|
11651
12031
|
<tr><td class="name">messages</td><td class="type">{ t(key: string, params?: Record<string, unknown>): string }</td><td class="desc">The catalogue the panel's own text is read from. A panel routinely has no grid to borrow one off — two of its three input modes have none — so this is the first-class way to translate it. A grid's own `messages` satisfies the shape; a key it does not carry falls back to English. <small>(optional)</small></td></tr>
|
|
11652
12032
|
<tr><td class="name">onTileClick</td><td class="type">(event: KPIEvent) => void</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -11717,24 +12097,13 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11717
12097
|
</tbody>
|
|
11718
12098
|
</table>
|
|
11719
12099
|
</div>
|
|
11720
|
-
<h3 id="type-
|
|
11721
|
-
<p class="section-note">
|
|
11722
|
-
<div class="table-wrap">
|
|
11723
|
-
<table>
|
|
11724
|
-
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11725
|
-
<tbody>
|
|
11726
|
-
<tr><td class="name">warn</td><td class="type">number</td><td class="desc"></td></tr>
|
|
11727
|
-
<tr><td class="name">critical</td><td class="type">number</td><td class="desc"></td></tr>
|
|
11728
|
-
<tr><td class="name">direction</td><td class="type">'higherIsBetter' | 'lowerIsBetter'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11729
|
-
</tbody>
|
|
11730
|
-
</table>
|
|
11731
|
-
</div>
|
|
11732
|
-
<h3 id="type-KPITile">KPITile</h3>
|
|
11733
|
-
<p class="section-note">One tile: an aggregate over the routed rows, with optional filter, format, threshold and trend.</p>
|
|
12100
|
+
<h3 id="type-KPIStatTile">KPIStatTile</h3>
|
|
12101
|
+
<p class="section-note">An aggregate stat tile: the routed rows reduced to one number, with optional filter, format, threshold and trend.</p>
|
|
11734
12102
|
<div class="table-wrap">
|
|
11735
12103
|
<table>
|
|
11736
12104
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
11737
12105
|
<tbody>
|
|
12106
|
+
<tr><td class="name">kind</td><td class="type">'stat'</td><td class="desc">Absent, or `'stat'`: the default tile kind. <small>(optional)</small></td></tr>
|
|
11738
12107
|
<tr><td class="name">id</td><td class="type">string</td><td class="desc">A stable identity for the tile (defaults to the label, then the index). <small>(optional)</small></td></tr>
|
|
11739
12108
|
<tr><td class="name">label</td><td class="type">string</td><td class="desc">The tile's accessible label. <small>(optional)</small></td></tr>
|
|
11740
12109
|
<tr><td class="name">aggregation</td><td class="type">KPIAggregation | ((rows: KPIRow[], tile: object) => unknown)</td><td class="desc">The aggregation kind, or a reducer `(rows, tile) => value` for a custom tile. <small>(optional)</small></td></tr>
|
|
@@ -11750,6 +12119,18 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11750
12119
|
</tbody>
|
|
11751
12120
|
</table>
|
|
11752
12121
|
</div>
|
|
12122
|
+
<h3 id="type-KPIThresholds">KPIThresholds</h3>
|
|
12123
|
+
<p class="section-note">A semantic threshold: two cut points and a direction. `higherIsBetter` (the default) makes a value at/above `warn` good, at/above `critical` a warning, below it critical; `lowerIsBetter` mirrors it. Colour is a host concern.</p>
|
|
12124
|
+
<div class="table-wrap">
|
|
12125
|
+
<table>
|
|
12126
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
12127
|
+
<tbody>
|
|
12128
|
+
<tr><td class="name">warn</td><td class="type">number</td><td class="desc"></td></tr>
|
|
12129
|
+
<tr><td class="name">critical</td><td class="type">number</td><td class="desc"></td></tr>
|
|
12130
|
+
<tr><td class="name">direction</td><td class="type">'higherIsBetter' | 'lowerIsBetter'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
12131
|
+
</tbody>
|
|
12132
|
+
</table>
|
|
12133
|
+
</div>
|
|
11753
12134
|
<h3 id="type-KPITileModel">KPITileModel</h3>
|
|
11754
12135
|
<p class="section-note">A computed tile, as it appears in the model.</p>
|
|
11755
12136
|
<div class="table-wrap">
|
|
@@ -11758,10 +12139,12 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
11758
12139
|
<tbody>
|
|
11759
12140
|
<tr><td class="name">id</td><td class="type">string</td><td class="desc"></td></tr>
|
|
11760
12141
|
<tr><td class="name">label</td><td class="type">string</td><td class="desc"></td></tr>
|
|
12142
|
+
<tr><td class="name">kind</td><td class="type">'stat' | 'clock'</td><td class="desc">`'stat'` for an aggregate tile, `'clock'` for a clock tile (BACKLOG-0001640).</td></tr>
|
|
11761
12143
|
<tr><td class="name">aggregation</td><td class="type">string</td><td class="desc"></td></tr>
|
|
11762
12144
|
<tr><td class="name">field</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11763
|
-
<tr><td class="name">value</td><td class="type">unknown</td><td class="desc"
|
|
11764
|
-
<tr><td class="name">formatted</td><td class="type">string</td><td class="desc"
|
|
12145
|
+
<tr><td class="name">value</td><td class="type">unknown</td><td class="desc">For a clock tile, the read instant as epoch milliseconds.</td></tr>
|
|
12146
|
+
<tr><td class="name">formatted</td><td class="type">string</td><td class="desc">For a clock tile, the date and time text joined by a space (the same text `clock.date` and `clock.time` carry separately).</td></tr>
|
|
12147
|
+
<tr><td class="name">clock</td><td class="type">{ date: string | null; time: string }</td><td class="desc">Present only on a clock tile: the date and time lines rendered separately. `date` is `null` when the tile was given `date: false`. <small>(optional)</small></td></tr>
|
|
11765
12148
|
<tr><td class="name">status</td><td class="type">'good' | 'warn' | 'critical' | 'unknown' | null</td><td class="desc">The tile's semantic band, or `unknown` when the tile measured nothing. `unknown` is decided from data presence before any threshold is consulted: an aggregation over nothing returns the identity of its operation (`sum` and `count` return 0), and 0 is a number a threshold grades, so without it an empty panel would report as a healthy one. Two things make a tile `unknown`: the panel holds no rows at all, or the tile's `field` names no column on the bound grid, so it never read a cell to reduce over. A tile whose `filter` matches none of the rows the panel *does* hold is neither — it has measured a real zero and is banded normally. `null` means the tile has no thresholds or bands configured.</td></tr>
|
|
11766
12149
|
<tr><td class="name">target</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
11767
12150
|
<tr><td class="name">baseline</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -13804,7 +14187,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
13804
14187
|
<!-- END GENERATED TYPE REFERENCE -->
|
|
13805
14188
|
|
|
13806
14189
|
<footer>
|
|
13807
|
-
Lattice Grid 1.
|
|
14190
|
+
Lattice Grid 1.65.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
13808
14191
|
This document describes the behaviour of the shipped library. Where this guide and the code
|
|
13809
14192
|
disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
13810
14193
|
</footer>
|