@toclocoinc/lattice-grid 1.7.0 → 1.8.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 +93 -1
- package/docs/API.html +96 -9
- package/docs/api-detail.html +16 -14
- package/lattice-grid.d.ts +96 -4
- package/lattice-grid.esm.min.js +5178 -647
- package/lattice-grid.min.cjs +5178 -647
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +5178 -647
- package/modules/charts.esm.min.js +5858 -0
- package/modules/devtools.esm.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +5178 -647
- package/modules/htmx.esm.min.js +5178 -647
- package/modules/htmx.min.cjs +5178 -647
- package/modules/htmx.min.js +5178 -647
- package/modules/react.esm.min.js +2 -2
- package/modules/svelte.esm.min.js +2 -2
- package/modules/vue.esm.min.js +2 -2
- package/modules/webcomponent.esm.min.js +5178 -647
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
dependencies, no build step required. Optional adapters for React, Vue, Svelte
|
|
5
5
|
and Web Components ship alongside it.
|
|
6
6
|
|
|
7
|
-
Version 1.
|
|
7
|
+
Version 1.8.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -27,6 +27,98 @@ runtime, not a CDN, not a font, not an icon sprite.
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## What it does
|
|
31
|
+
|
|
32
|
+
A short tour. The [API reference](docs/API.html) has the whole of it; this is
|
|
33
|
+
enough to know whether the grid covers what you need.
|
|
34
|
+
|
|
35
|
+
### Data and scale
|
|
36
|
+
|
|
37
|
+
- **Virtualised rows and columns.** Hundreds of thousands of rows on a typed-array
|
|
38
|
+
column store, with dictionary encoding and presence bitsets. Row height can be
|
|
39
|
+
fixed, per-row, or measured from content.
|
|
40
|
+
- **Sort, filter, group, pivot and aggregate**, each as an independent stage
|
|
41
|
+
over the same data. Multi-column sort, a filter grammar with typed operators,
|
|
42
|
+
row grouping to any depth, full pivoting, and a totals row that reduces by any
|
|
43
|
+
of the built-in kernels or one of your own.
|
|
44
|
+
- **Live data.** `rows.apply({add, update, remove})` patches in place: the grid
|
|
45
|
+
re-queries the stages a change actually touched and repaints the cells that
|
|
46
|
+
moved. A feed can be paused and resumed with the queue held.
|
|
47
|
+
- **Any source.** Rows in memory, or a source you write — server-side paging,
|
|
48
|
+
infinite scroll, streaming, or an async provider. Sorting and filtering can be
|
|
49
|
+
handed to the server or left to the grid.
|
|
50
|
+
|
|
51
|
+
### Working with the data
|
|
52
|
+
|
|
53
|
+
- **Editing.** Cell, row and form editing, with over twenty editors — text,
|
|
54
|
+
number, date, time, select, multi-select, colour, rating, slider, segmented,
|
|
55
|
+
code, password, icon picker and more. Validation, async commits, optimistic
|
|
56
|
+
updates with rollback, and a full undo history.
|
|
57
|
+
- **Selection and ranges.** Cell, row, column and rectangular range selection,
|
|
58
|
+
with clipboard behaviour that round-trips through Excel.
|
|
59
|
+
- **Fill, copy and paste** across a range, including formulas.
|
|
60
|
+
- **Formulas.** A closed, safe expression language — no `eval`, no host access —
|
|
61
|
+
with maths, text, logic, date and statistical functions, evaluated against
|
|
62
|
+
other columns.
|
|
63
|
+
- **Export.** CSV with fields sanitised against formula injection, real `.xlsx`
|
|
64
|
+
written without a ZIP dependency, the clipboard as TSV with a matching paste
|
|
65
|
+
parser, and print. All of them take the filters, sort and grouping the user is
|
|
66
|
+
looking at, or the whole set.
|
|
67
|
+
|
|
68
|
+
### Seeing the data
|
|
69
|
+
|
|
70
|
+
- **Charts.** `modules/charts` draws thirty chart types from the grid's own
|
|
71
|
+
data — line, bar, area, scatter, pie, donut, sunburst, treemap, radar, gauge,
|
|
72
|
+
funnel, heatmap, histogram, box plot, candlestick, combo, geomap, sankey,
|
|
73
|
+
chord, network, stream, violin, gantt and more. They follow the grid's
|
|
74
|
+
filters, and clicking a mark can filter it in turn.
|
|
75
|
+
- **Statistics.** `grid.statistics` profiles a column in one pass — count,
|
|
76
|
+
missing, distinct, five-number summary, standard deviation, outliers and a
|
|
77
|
+
histogram — and answers correlations and weighted averages. Twenty-eight
|
|
78
|
+
reduction kernels are available to the totals row, and you can register your
|
|
79
|
+
own.
|
|
80
|
+
- **Shadow columns.** Values the grid maintains about itself: how many times a
|
|
81
|
+
row has changed, what a value was when the page loaded, how fast it is moving,
|
|
82
|
+
its rank, percentile or share of the total. Real columns — sortable,
|
|
83
|
+
filterable, exportable, saved into a view.
|
|
84
|
+
- **Conditional formatting** as runtime state a user can change, with rules that
|
|
85
|
+
either name a threshold or describe the data: the top decile, the outliers,
|
|
86
|
+
two deviations above the mean.
|
|
87
|
+
- **In-cell charts**, sparklines, data bars, progress, ratings and pills.
|
|
88
|
+
- **Header histograms** that double as a filter.
|
|
89
|
+
|
|
90
|
+
### The interface
|
|
91
|
+
|
|
92
|
+
- **Tool panels** for columns, filters, views, quick filter, formatting and
|
|
93
|
+
statistics, docked or as an icon rail.
|
|
94
|
+
- **Saved views** — the whole grid state as a named, shareable object, stored
|
|
95
|
+
on your server or in the browser.
|
|
96
|
+
- **Column menu, context menu and status bar**, each extensible with your own
|
|
97
|
+
items.
|
|
98
|
+
- **Pinned columns and rows**, column groups, resize, reorder, autosize, and a
|
|
99
|
+
density control.
|
|
100
|
+
- **Master–detail rows**, tree data, and full-width rows.
|
|
101
|
+
- **Presence** — live cursors, selections and edit locks for collaborative use,
|
|
102
|
+
carrying intent and never values.
|
|
103
|
+
- **Comments** threaded on cells, and an annotation layer for presenting.
|
|
104
|
+
- **Full-screen mode**, print, and image capture.
|
|
105
|
+
|
|
106
|
+
### Quality of the thing itself
|
|
107
|
+
|
|
108
|
+
- **Accessible.** Keyboard operable throughout, ARIA grid semantics, a live
|
|
109
|
+
region for announcements, honours reduced motion, forced colours and large
|
|
110
|
+
target sizes. The devtools module runs the accessibility checks in place.
|
|
111
|
+
- **Internationalised.** Eighteen complete locales, right-to-left layout, and
|
|
112
|
+
locale-aware number, date and currency formatting throughout.
|
|
113
|
+
- **Themeable** through CSS custom properties, with light and dark built in and
|
|
114
|
+
a reset that keeps a host page's stylesheet out.
|
|
115
|
+
- **Typed.** Complete TypeScript declarations, checked against the runtime on
|
|
116
|
+
every build.
|
|
117
|
+
- **Zero runtime dependencies.** Nothing is fetched at runtime — not a CDN, not
|
|
118
|
+
a font, not an icon sprite.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
30
122
|
## Quick start
|
|
31
123
|
|
|
32
124
|
### No build step
|
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.7.
|
|
363
|
+
<p class="rail__sub">API reference · v1.7.1</p>
|
|
364
364
|
<nav>
|
|
365
365
|
<div class="rail__group">
|
|
366
366
|
<span class="rail__label">Start</span>
|
|
@@ -397,6 +397,7 @@
|
|
|
397
397
|
<a href="#presentation">grid.presentation</a>
|
|
398
398
|
<a href="#redaction">grid.redaction</a>
|
|
399
399
|
<a href="#formatting">grid.formatting</a>
|
|
400
|
+
<a href="#statistics">grid.statistics</a>
|
|
400
401
|
<a href="#highlight">grid.highlight</a>
|
|
401
402
|
<a href="#views">grid.views</a>
|
|
402
403
|
<a href="#diff">grid.diff</a>
|
|
@@ -437,7 +438,7 @@
|
|
|
437
438
|
</header>
|
|
438
439
|
|
|
439
440
|
<p class="chips">
|
|
440
|
-
<span class="chip">Version 1.7.
|
|
441
|
+
<span class="chip">Version 1.7.1</span>
|
|
441
442
|
<span class="chip">Zero dependencies</span>
|
|
442
443
|
<span class="chip"><a href="api-detail.html">Developer guide →</a></span>
|
|
443
444
|
</p>
|
|
@@ -455,8 +456,8 @@
|
|
|
455
456
|
|
|
456
457
|
<p>Or straight from jsDelivr — no npm install, no bundler, no local copy at all:</p>
|
|
457
458
|
|
|
458
|
-
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.
|
|
459
|
-
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.
|
|
459
|
+
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.1/lattice-grid.min.css">
|
|
460
|
+
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.1/lattice-grid.min.js"></script>
|
|
460
461
|
|
|
461
462
|
<script>
|
|
462
463
|
<span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
|
|
@@ -467,7 +468,7 @@
|
|
|
467
468
|
<pre><code><span class="cmt">// With a renderer, in a browser. Any of:</span>
|
|
468
469
|
<span class="kw">import</span> { createGrid } <span class="kw">from</span> './dist/lattice-grid.esm.js';
|
|
469
470
|
<span class="cmt">// import { createGrid } from '@toclocoinc/lattice-grid';</span>
|
|
470
|
-
<span class="cmt">// import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.
|
|
471
|
+
<span class="cmt">// import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.1/lattice-grid.esm.min.js';</span>
|
|
471
472
|
<span class="kw">const</span> grid = createGrid(document.getElementById('grid'), config);
|
|
472
473
|
|
|
473
474
|
<span class="cmt">// Headless: the same API without a renderer. Data, filters, sort,</span>
|
|
@@ -479,7 +480,7 @@
|
|
|
479
480
|
|
|
480
481
|
<div class="note"><p>jsDelivr mirrors every version published to npm at
|
|
481
482
|
<code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@<version>/<file></code>. Pin an exact
|
|
482
|
-
version for anything shipped — <code>@1.7.
|
|
483
|
+
version for anything shipped — <code>@1.7.1</code>, not <code>@latest</code> — so a release does
|
|
483
484
|
not change what a page already in production loads. The same convention reaches any module:
|
|
484
485
|
<code>.../modules/htmx.esm.min.js</code>, <code>.../modules/react.esm.min.js</code>, and so on.</p></div>
|
|
485
486
|
|
|
@@ -793,7 +794,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
793
794
|
<tbody>
|
|
794
795
|
<tr><td class="name">statusBar</td><td class="type">boolean | { panels }</td><td class="desc">Composable panels along the bottom. Default set: <code>rowCount</code>, <code>selectedCount</code>, <code>aggregation</code>, <code>comments</code>, <code>updates</code>, <code>progress</code>. Each is silent when it has nothing to report.</td></tr>
|
|
795
796
|
<tr><td class="name">maximise</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc"><code>false</code> removes the rail button and <code>grid.maximise</code>, for an application with its own full-screen mode.</td></tr>
|
|
796
|
-
<tr><td class="name">toolPanel</td><td class="type">boolean | object</td><td class="desc">Side dock. <code>panels</code>: <code>columns</code>, <code>filters</code>, <code>views</code>, <code>quick</code>, <code>formatting</code>. <code>side: 'left'</code> makes it the icon rail, which also turns on <code>actions</code> (<code>undo</code>, <code>redo</code>, <code>pause</code>, <code>restore</code>, <code>maximise</code>, then the export group: <code>export</code>, <code>excel</code>, <code>clipboard</code>, <code>print</code> — nine in all, and an array takes these names rather than the button labels) and <code>icons</code>. An explicit array <em>replaces</em> that list rather than extending it; a bare <code>'-'</code> in it renders a divider between groups. <code>exportName</code> names the CSV.</td></tr>
|
|
797
|
+
<tr><td class="name">toolPanel</td><td class="type">boolean | object</td><td class="desc">Side dock. <code>panels</code>: <code>columns</code>, <code>filters</code>, <code>views</code>, <code>quick</code>, <code>formatting</code>, <code>statistics</code>. <code>side: 'left'</code> makes it the icon rail, which also turns on <code>actions</code> (<code>undo</code>, <code>redo</code>, <code>pause</code>, <code>restore</code>, <code>maximise</code>, then the export group: <code>export</code>, <code>excel</code>, <code>clipboard</code>, <code>print</code> — nine in all, and an array takes these names rather than the button labels) and <code>icons</code>. An explicit array <em>replaces</em> that list rather than extending it; a bare <code>'-'</code> in it renders a divider between groups. <code>exportName</code> names the CSV.</td></tr>
|
|
797
798
|
<tr><td class="name">contextMenu</td><td class="type">boolean | (p) => MenuItem[]</td><td class="desc">Right-click menu. The function form is <code>(params, defaults) => items</code> — see <a href="#custom-menu">custom items</a>. <code>false</code> suppresses it — what a read-only grid wants, since the default menu offers Paste, Clear and Fill down.</td></tr>
|
|
798
799
|
<tr><td class="name">columnMenu</td><td class="type">boolean | (p) => MenuItem[]</td><td class="desc">The header's 3-dot menu, and a right-click on a column heading. The function form is <code>(params, defaults) => items</code>, with <code>params</code> carrying <code>colId</code>, <code>column</code> and <code>grid</code> — see <a href="#custom-menu">custom items</a>. <code>false</code> suppresses it.</td></tr>
|
|
799
800
|
<tr><td class="name">shortcuts</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc">The <kbd>?</kbd> keyboard shortcut overlay. <code>false</code> suppresses it, for a host that wants <kbd>?</kbd> for itself. See <a href="api-detail.html#keyboard">Keyboard</a>.</td></tr>
|
|
@@ -930,7 +931,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
930
931
|
<table>
|
|
931
932
|
<thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
|
|
932
933
|
<tbody>
|
|
933
|
-
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.7.
|
|
934
|
+
<tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.7.1'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
|
|
934
935
|
<tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
|
|
935
936
|
<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>
|
|
936
937
|
<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>
|
|
@@ -1563,6 +1564,89 @@ grid.formatting.clear('margin'); // or clear() for everything</
|
|
|
1563
1564
|
</div>
|
|
1564
1565
|
<div class="note"><p>A rule held here must be JSON: <code>style</code> may not be a function, because the rules are serialised into views and undo slices. Config-time <code>cell.style</code> still accepts one. Group rows are not formatted, matching the way decoration is dropped for them.</p></div>
|
|
1565
1566
|
|
|
1567
|
+
<h3>Rules that describe the data, not a threshold</h3>
|
|
1568
|
+
<p><code>gt: 100</code> needs somebody to know that 100 is the interesting number. Often nobody does — the interesting cells are <em>the top decile</em>, or <em>the outliers</em>, and where those fall is a property of the data rather than of the rule. These operators say that directly, and the grid works out the threshold from the column itself, over the filtered rows.</p>
|
|
1569
|
+
<pre><code>grid.formatting.add('margin', { when: { op: 'outlier' }, style: { background: '#fbeceb' } });
|
|
1570
|
+
grid.formatting.add('qty', { when: { op: 'topPercent', value: 10 }, style: { bold: true } });
|
|
1571
|
+
grid.formatting.add('score', { scale: { from: 'quantile', colours: ['#f8f9fa', '#1a6bc7'] } });
|
|
1572
|
+
|
|
1573
|
+
grid.formatting.distribution('margin'); // { n, min, max, mean, stddev, median, q1, q3, iqr }
|
|
1574
|
+
grid.formatting.restat(); // re-derive every threshold from the data as it stands</code></pre>
|
|
1575
|
+
<div class="table-wrap">
|
|
1576
|
+
<table>
|
|
1577
|
+
<thead><tr><th>Operator</th><th><code>value</code></th><th>Marks</th></tr></thead>
|
|
1578
|
+
<tbody>
|
|
1579
|
+
<tr><td class="sig">topPercent</td><td class="type">10 or 0.1</td><td class="desc">The top tenth of the column. Written either way; both mean the same thing.</td></tr>
|
|
1580
|
+
<tr><td class="sig">bottomPercent</td><td class="type">10 or 0.1</td><td class="desc">The bottom tenth.</td></tr>
|
|
1581
|
+
<tr><td class="sig">topN</td><td class="type">5</td><td class="desc">The five largest, ties included — three rows sharing second place in a top three all take the colour.</td></tr>
|
|
1582
|
+
<tr><td class="sig">bottomN</td><td class="type">5</td><td class="desc">The five smallest.</td></tr>
|
|
1583
|
+
<tr><td class="sig">aboveMean / belowMean</td><td class="type">—</td><td class="desc">Either side of the mean.</td></tr>
|
|
1584
|
+
<tr><td class="sig">aboveMedian / belowMedian</td><td class="type">—</td><td class="desc">Either side of the median, which is the one to reach for on a skewed column.</td></tr>
|
|
1585
|
+
<tr><td class="sig">zAbove / zBelow</td><td class="type">2</td><td class="desc">That many standard deviations from the mean. A column with no spread marks nothing rather than everything.</td></tr>
|
|
1586
|
+
<tr><td class="sig">outlier</td><td class="type">1.5</td><td class="desc">Outside Tukey's fences at that many IQRs — the same definition a box plot draws, so the marked cells are the ones its whiskers exclude.</td></tr>
|
|
1587
|
+
</tbody>
|
|
1588
|
+
</table>
|
|
1589
|
+
</div>
|
|
1590
|
+
<p>A colour scale can take its bounds the same way, with <code>from</code> in place of <code>min</code> and <code>max</code>: <code>'minmax'</code> spans the data, <code>'quantile'</code> spans <code>low</code> to <code>high</code> (5th to 95th percentile by default), <code>'stddev'</code> spans <code>deviations</code> either side of the mean. The quantile form is the better default on real data — one mistyped order of magnitude otherwise compresses every real value into the first swatch.</p>
|
|
1591
|
+
<div class="note"><p>Thresholds are <strong>pinned</strong> when the rules compile and do not move on their own. That is deliberate: a boundary that re-derived itself as rows were filtered would repaint cells whose values had not changed, and nobody comparing two screenshots could tell which of the two things had moved. <code>grid.formatting.restat()</code> is how you move it, and a "recalculate" control is the natural place to put it.</p></div>
|
|
1592
|
+
|
|
1593
|
+
<h2 id="statistics">grid.statistics</h2>
|
|
1594
|
+
<p>What the grid knows about its own numbers, and about how they have changed since the page loaded. Every figure is computed over the <em>filtered</em> rows, through the same column handles the totals row uses — so a median here and a median in the footer are the same number, by the same definition (R type 7).</p>
|
|
1595
|
+
<pre><code>grid.statistics.profile('margin');
|
|
1596
|
+
// { column, rows, present, missing, distinct, min, max, mean, median,
|
|
1597
|
+
// q1, q3, iqr, stddev, outliers, histogram: [{ from, to, count }, …] }
|
|
1598
|
+
|
|
1599
|
+
grid.statistics.reduce('margin', 'p95'); // any registered kernel
|
|
1600
|
+
grid.statistics.correlation('spend', 'revenue'); // Pearson's r, clamped to [-1, 1]
|
|
1601
|
+
grid.statistics.weightedAverage('price', 'qty');
|
|
1602
|
+
|
|
1603
|
+
grid.statistics.shadow('price', 'delta', 'R42'); // one row's shadow value
|
|
1604
|
+
grid.statistics.rebase('price'); // "mark all" — today's values become the baseline
|
|
1605
|
+
grid.statistics.tracking(); // { columns, rows, forgotten }</code></pre>
|
|
1606
|
+
<p>The <code>statistics</code> tool panel is the end-user half of <code>profile()</code>: a column picker, the twelve figures and a histogram of the column's shape, all following the filters. Add it with <code>toolPanel: { panels: ['columns', 'statistics'] }</code>.</p>
|
|
1607
|
+
|
|
1608
|
+
<h3>Shadow columns</h3>
|
|
1609
|
+
<p>A shadow column is declared against another column and maintained by the grid. It has no field in the data and it is not a pure computed column either, because its value depends on what happened <em>before</em>. It is a real column throughout: sortable, filterable, totalled, grouped, exported, saved into a view — which is what makes "show me every circuit repriced more than twice this session, most-changed first" one gesture rather than a report.</p>
|
|
1610
|
+
<pre><code>columns: [
|
|
1611
|
+
{ field: 'price', type: 'number' },
|
|
1612
|
+
{ id: 'moved', title: 'Change', shadow: { of: 'price', kind: 'delta' } },
|
|
1613
|
+
{ id: 'churn', title: 'Updates', shadow: { of: 'price', kind: 'updates' } },
|
|
1614
|
+
{ id: 'run', title: 'Streak', shadow: 'streak' }, // shorthand: shadows the column beside it
|
|
1615
|
+
]</code></pre>
|
|
1616
|
+
<div class="table-wrap">
|
|
1617
|
+
<table>
|
|
1618
|
+
<thead><tr><th>Kind</th><th>Value</th></tr></thead>
|
|
1619
|
+
<tbody>
|
|
1620
|
+
<tr><td class="sig">updates</td><td class="desc">How many times the row's value has changed. Arrival is not a change, so a freshly loaded grid reads zero rather than one.</td></tr>
|
|
1621
|
+
<tr><td class="sig">updatedAt</td><td class="desc">When it last changed, as a <code>Date</code>.</td></tr>
|
|
1622
|
+
<tr><td class="sig">sinceUpdate</td><td class="desc">Milliseconds since it last changed.</td></tr>
|
|
1623
|
+
<tr><td class="sig">delta</td><td class="desc">Current value minus the baseline.</td></tr>
|
|
1624
|
+
<tr><td class="sig">deltaPercent</td><td class="desc">The same as a percentage. A change from nothing has no percentage and reads null rather than infinity.</td></tr>
|
|
1625
|
+
<tr><td class="sig">rate</td><td class="desc">Change per second, from the last two readings.</td></tr>
|
|
1626
|
+
<tr><td class="sig">history</td><td class="desc">The recent readings, oldest first. <code>depth</code> sets how many; the default is 20.</td></tr>
|
|
1627
|
+
<tr><td class="sig">firstValue</td><td class="desc">The baseline itself.</td></tr>
|
|
1628
|
+
<tr><td class="sig">streak</td><td class="desc">Consecutive moves in one direction, signed. It resets on a turn, because "seven rises" means something and "seven changes" does not.</td></tr>
|
|
1629
|
+
</tbody>
|
|
1630
|
+
</table>
|
|
1631
|
+
</div>
|
|
1632
|
+
<p>A second family answers where the row sits among the others rather than what it did before. They share the same declaration and the same state — the tracker already holds every row's current value <em>and</em> its baseline, which is exactly what a rank and a rank <em>change</em> need.</p>
|
|
1633
|
+
<div class="table-wrap">
|
|
1634
|
+
<table>
|
|
1635
|
+
<thead><tr><th>Kind</th><th>Value</th></tr></thead>
|
|
1636
|
+
<tbody>
|
|
1637
|
+
<tr><td class="sig">rank</td><td class="desc">Competition rank, largest first: ties share the better rank and the next value skips, so two firsts are followed by a third.</td></tr>
|
|
1638
|
+
<tr><td class="sig">rankAsc</td><td class="desc">The same ranking read from the other end.</td></tr>
|
|
1639
|
+
<tr><td class="sig">rankChange</td><td class="desc">Places climbed since the baseline. Positive means climbed, even though the rank number itself falls — this is the "top movers" column.</td></tr>
|
|
1640
|
+
<tr><td class="sig">percentile</td><td class="desc">The share of rows at or below this one, 0 to 100.</td></tr>
|
|
1641
|
+
<tr><td class="sig">quartile</td><td class="desc">1 to 4, agreeing with <code>percentile</code>: the 60th percentile is in the third quartile.</td></tr>
|
|
1642
|
+
<tr><td class="sig">zScore</td><td class="desc">Deviations from the mean. A column with no spread reads null rather than zero.</td></tr>
|
|
1643
|
+
<tr><td class="sig">shareOfTotal</td><td class="desc">The value over the column's total, as a percentage. A total of zero — a column of offsetting positions — reads null rather than a division by it.</td></tr>
|
|
1644
|
+
</tbody>
|
|
1645
|
+
</table>
|
|
1646
|
+
</div>
|
|
1647
|
+
<div class="note"><p>Positional kinds rank over every <em>tracked</em> row, not over the filtered set: a rank that changed as you filtered would make "the top ten movers" depend on what happened to be on screen, and the column would disagree with itself between two views of the same data. For the filtered answer, use <code>grid.statistics.profile()</code> or a distribution formatting rule.</p></div>
|
|
1648
|
+
<div class="note"><p>Shadow state is keyed by row key, never by index — after any sort an index-keyed history would report one row's past against another row's present, and the wrong number would be <em>sortable</em>. Memory is capped at 200,000 tracked rows per column; past that the oldest are dropped and <code>tracking().forgotten</code> says how many, rather than a smaller number being reported as though it were the truth.</p></div>
|
|
1649
|
+
|
|
1566
1650
|
<h2 id="highlight">grid.highlight</h2>
|
|
1567
1651
|
<p>One mechanism for two jobs: the flash a changed cell makes, and a marker you paint deliberately. A target is a cell (<code>{key, colId}</code>), a row (<code>{key}</code>, or a bare row key) or a column (<code>{colId}</code>). Cell beats row beats column, so a specific highlight is never hidden by a broad one laid over it.</p>
|
|
1568
1652
|
<pre><code>createGrid(el, {
|
|
@@ -2110,10 +2194,13 @@ grid.filters.quickState(); <span class="cmt">// { text: 'acme london', mode: '
|
|
|
2110
2194
|
<tr><td class="sig">Rounding</td><td><code>ROUND</code>, <code>ROUNDUP</code>, <code>ROUNDDOWN</code>, <code>FLOOR</code>, <code>CEILING</code></td></tr>
|
|
2111
2195
|
<tr><td class="sig">Logic</td><td><code>IF</code>, <code>AND</code>, <code>OR</code>, <code>NOT</code>, <code>COALESCE</code></td></tr>
|
|
2112
2196
|
<tr><td class="sig">Text</td><td><code>CONCAT</code>, <code>LEN</code>, <code>UPPER</code>, <code>LOWER</code>, <code>TRIM</code>, <code>LEFT</code>, <code>RIGHT</code></td></tr>
|
|
2197
|
+
<tr><td class="sig">Statistics</td><td><code>MEDIAN</code>, <code>PERCENTILE</code>, <code>QUARTILE1</code>, <code>QUARTILE3</code>, <code>IQR</code>, <code>STDEV</code>, <code>STDEVP</code>, <code>VAR</code>, <code>VARP</code>, <code>COUNTDISTINCT</code></td></tr>
|
|
2113
2198
|
</tbody>
|
|
2114
2199
|
</table>
|
|
2115
2200
|
</div>
|
|
2116
2201
|
|
|
2202
|
+
<div class="note"><p>The statistical functions use R type 7 quantiles — the same definition as the totals row, <code>grid.statistics</code> and the distribution formatting rules, so the four never disagree about what a median is. <code>PERCENTILE</code> reads <code>90</code> and <code>0.9</code> as the same request. Over an empty set they return a number rather than null, because a formula is arithmetic and has to keep composing.</p></div>
|
|
2203
|
+
|
|
2117
2204
|
<p>Operators <code>+ - * / ^</code> with parentheses, comparison for <code>IF</code>, and postfix
|
|
2118
2205
|
<code>%</code>. <code>^</code> is right-associative and unary minus binds tighter than it, so
|
|
2119
2206
|
<code>-2^2</code> is 4 — Excel's answer rather than mathematics'.</p>
|
|
@@ -2332,7 +2419,7 @@ createGrid(el, {
|
|
|
2332
2419
|
</div>
|
|
2333
2420
|
|
|
2334
2421
|
<footer>
|
|
2335
|
-
Lattice Grid 1.7.
|
|
2422
|
+
Lattice Grid 1.7.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
2336
2423
|
This document describes the behaviour of the shipped library. Where this guide and the code
|
|
2337
2424
|
disagree, the code wins — please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
2338
2425
|
</footer>
|
package/docs/api-detail.html
CHANGED
|
@@ -437,7 +437,7 @@
|
|
|
437
437
|
<div class="shell">
|
|
438
438
|
<aside class="rail">
|
|
439
439
|
<p class="rail__brand">Lattice Grid</p>
|
|
440
|
-
<p class="rail__sub">Developer guide · v1.7.
|
|
440
|
+
<p class="rail__sub">Developer guide · v1.7.1</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -537,7 +537,7 @@
|
|
|
537
537
|
<a href="API.html">reference tables</a> are the shorter version for when you already know.
|
|
538
538
|
</p>
|
|
539
539
|
<p class="chips">
|
|
540
|
-
<span class="chip">Version 1.7.
|
|
540
|
+
<span class="chip">Version 1.7.1</span>
|
|
541
541
|
<span class="chip">Zero dependencies</span>
|
|
542
542
|
<span class="chip">No build step</span>
|
|
543
543
|
</p>
|
|
@@ -652,8 +652,8 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
652
652
|
|
|
653
653
|
<div class="example">
|
|
654
654
|
<p class="example__label">jsDelivr — no npm install, no bundler</p>
|
|
655
|
-
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.
|
|
656
|
-
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.
|
|
655
|
+
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.1/lattice-grid.min.css">
|
|
656
|
+
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.1/lattice-grid.min.js"></script>
|
|
657
657
|
|
|
658
658
|
<script>
|
|
659
659
|
<span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
|
|
@@ -679,7 +679,7 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
679
679
|
<div class="note">
|
|
680
680
|
<p><strong>jsDelivr mirrors every version published to npm</strong> at
|
|
681
681
|
<code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@<version>/<file></code> — pin an
|
|
682
|
-
exact version, e.g. <code>@1.7.
|
|
682
|
+
exact version, e.g. <code>@1.7.1</code> rather than <code>@latest</code>, so a later release
|
|
683
683
|
does not change what a page already in production loads. The same convention reaches a
|
|
684
684
|
module: <code>.../modules/htmx.esm.min.js</code>, <code>.../modules/dhtmlx-compat.esm.min.js</code>,
|
|
685
685
|
and so on. Type declarations resolve automatically through npm's own <code>types</code> field;
|
|
@@ -1168,7 +1168,7 @@ off(); <span class="cmt">// every subscrip
|
|
|
1168
1168
|
</p>
|
|
1169
1169
|
<div class="example">
|
|
1170
1170
|
<p class="example__label">Which version am I running?</p>
|
|
1171
|
-
<pre><code>grid.getVersion(); <span class="cmt">// '1.7.
|
|
1171
|
+
<pre><code>grid.getVersion(); <span class="cmt">// '1.7.1'</span>
|
|
1172
1172
|
LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
|
|
1173
1173
|
</div>
|
|
1174
1174
|
<p class="lead-in">
|
|
@@ -1652,9 +1652,10 @@ grid.set('theme', <span class="kw">null</span>); <span class="cmt">// back to
|
|
|
1652
1652
|
<div class="why">
|
|
1653
1653
|
<p><strong>Why this is needed at all.</strong> A grid is mounted inside somebody else's
|
|
1654
1654
|
stylesheet. A rule as ordinary as <code>section { padding: 5.5rem 0 }</code> — a marketing
|
|
1655
|
-
page, a CMS theme, a Tailwind preflight —
|
|
1656
|
-
|
|
1657
|
-
elements
|
|
1655
|
+
page, a CMS theme, a Tailwind preflight — matches by tag name, and the grid builds parts of
|
|
1656
|
+
its own interface from those tags: the tool panel's filter rows are
|
|
1657
|
+
<code><section></code> elements. Without the reset, 5.5rem of somebody else's padding
|
|
1658
|
+
lands on every one of them.</p>
|
|
1658
1659
|
<p>The reset uses no <code>!important</code>. It is specificity (0,1,1) and every rule that
|
|
1659
1660
|
dresses a grid element is (0,2,0) or higher, so the grid's own styling always wins and the
|
|
1660
1661
|
reset only fills a gap. Yours wins too, on the same terms: a rule aimed at a Lattice class —
|
|
@@ -4440,10 +4441,11 @@ r.ok ? r.value : r.error; <span class="cmt">// 42</span></code></pre>
|
|
|
4440
4441
|
</p>
|
|
4441
4442
|
<div class="why">
|
|
4442
4443
|
<p><strong>Bare arithmetic is deliberately not a formula.</strong> <code>2-1</code> is a
|
|
4443
|
-
plausible product code and <code>1/2</code> a plausible date.
|
|
4444
|
-
|
|
4445
|
-
<code>2*3</code> as <strong>23</strong
|
|
4446
|
-
refused outright, so the cell keeps what it had rather than
|
|
4444
|
+
plausible product code and <code>1/2</code> a plausible date. A reader that evaluated either on
|
|
4445
|
+
a guess would have to guess wrong sometimes, and the wrong answer is not a visible error but a
|
|
4446
|
+
plausible number — <code>2*3</code> stored as <strong>23</strong> looks like data. Arithmetic
|
|
4447
|
+
without a leading <code>=</code> is refused outright, so the cell keeps what it had rather than
|
|
4448
|
+
taking a number nobody typed.</p>
|
|
4447
4449
|
</div>
|
|
4448
4450
|
|
|
4449
4451
|
<h2 id="custom-menu">Your own menu items and buttons</h2>
|
|
@@ -5124,7 +5126,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
|
|
|
5124
5126
|
|
|
5125
5127
|
<footer>
|
|
5126
5128
|
<p>
|
|
5127
|
-
Lattice Grid 1.7.
|
|
5129
|
+
Lattice Grid 1.7.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
5128
5130
|
Written against the shipped source. Where this guide and the code disagree, the code wins —
|
|
5129
5131
|
please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
5130
5132
|
</p>
|
package/lattice-grid.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Lattice Grid 1.
|
|
2
|
+
* Lattice Grid 1.8.0 — type declarations
|
|
3
3
|
* Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
|
|
4
4
|
* https://latticegrid.dev
|
|
5
5
|
*/
|
|
@@ -1389,15 +1389,31 @@ export interface StateApplyReport {
|
|
|
1389
1389
|
// ---------------------------------------------------------------------------
|
|
1390
1390
|
|
|
1391
1391
|
export interface FormattingCondition {
|
|
1392
|
-
|
|
1392
|
+
/**
|
|
1393
|
+
* A filter operator compared against `value`, or a distribution operator
|
|
1394
|
+
* whose threshold comes from the column itself — `{op: 'topPercent', value: 10}`,
|
|
1395
|
+
* `{op: 'outlier'}`. Distribution thresholds are pinned when the rules
|
|
1396
|
+
* compile; `grid.formatting.restat()` moves them.
|
|
1397
|
+
*/
|
|
1398
|
+
op: Operator | DistributionOp;
|
|
1393
1399
|
value?: unknown;
|
|
1394
1400
|
value2?: unknown;
|
|
1395
1401
|
}
|
|
1396
1402
|
|
|
1397
1403
|
export interface FormattingScale {
|
|
1398
|
-
|
|
1399
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* Where the bounds come from when `min` and `max` are not given.
|
|
1406
|
+
* `'minmax'` spans the data, `'quantile'` spans `low` to `high`
|
|
1407
|
+
* (5th to 95th percentile by default), `'stddev'` spans `deviations`
|
|
1408
|
+
* either side of the mean.
|
|
1409
|
+
*/
|
|
1410
|
+
from?: 'minmax' | 'quantile' | 'stddev';
|
|
1411
|
+
min?: number;
|
|
1412
|
+
max?: number;
|
|
1400
1413
|
mid?: number;
|
|
1414
|
+
low?: number;
|
|
1415
|
+
high?: number;
|
|
1416
|
+
deviations?: number;
|
|
1401
1417
|
colours?: string[];
|
|
1402
1418
|
}
|
|
1403
1419
|
|
|
@@ -1421,6 +1437,58 @@ export interface FormattingRule {
|
|
|
1421
1437
|
/** A column id, or `'*'` for every column. */
|
|
1422
1438
|
export type FormattingScope = string;
|
|
1423
1439
|
|
|
1440
|
+
export interface StatisticsApi {
|
|
1441
|
+
/** One shadow value for one row, by the column it shadows and the kind. */
|
|
1442
|
+
shadow(colId: string, kind: ShadowKind, rowKey: string): unknown;
|
|
1443
|
+
/** Make the current values the new baseline — "mark all". */
|
|
1444
|
+
rebase(colId?: string): void;
|
|
1445
|
+
/** What the shadow histories are costing. */
|
|
1446
|
+
tracking(): { columns: string[]; rows: number; forgotten: number };
|
|
1447
|
+
/** Reduce a column by a named kernel over the filtered rows. */
|
|
1448
|
+
reduce(colId: string, fn: string): unknown;
|
|
1449
|
+
/** Everything worth knowing about one column, in one pass each. */
|
|
1450
|
+
profile(colId: string): ColumnProfile | null;
|
|
1451
|
+
/** Pearson's correlation between two columns. */
|
|
1452
|
+
correlation(a: string, b: string): number | null;
|
|
1453
|
+
/** A weighted average of one column by another. */
|
|
1454
|
+
weightedAverage(colId: string, weightId: string): number | null;
|
|
1455
|
+
/** The key a row's data resolves to. */
|
|
1456
|
+
keyOf(data: unknown): string | null;
|
|
1457
|
+
/** Which reductions can be maintained against a change, and which rescan. */
|
|
1458
|
+
readonly maintenance: Readonly<Record<string, 'maintained' | 'rescan'>>;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
export type ShadowKind =
|
|
1462
|
+
| 'updates' | 'updatedAt' | 'sinceUpdate' | 'delta' | 'deltaPercent'
|
|
1463
|
+
| 'rate' | 'history' | 'firstValue' | 'streak'
|
|
1464
|
+
/** Where the row sits among the others, over every tracked row. */
|
|
1465
|
+
| 'rank' | 'rankAsc' | 'rankChange' | 'percentile' | 'quartile'
|
|
1466
|
+
| 'zScore' | 'shareOfTotal';
|
|
1467
|
+
|
|
1468
|
+
export interface ColumnProfile {
|
|
1469
|
+
column: string;
|
|
1470
|
+
rows: number;
|
|
1471
|
+
present: number;
|
|
1472
|
+
missing: number;
|
|
1473
|
+
distinct: number;
|
|
1474
|
+
min: number | null;
|
|
1475
|
+
max: number | null;
|
|
1476
|
+
mean: number | null;
|
|
1477
|
+
median: number | null;
|
|
1478
|
+
q1: number | null;
|
|
1479
|
+
q3: number | null;
|
|
1480
|
+
iqr: number | null;
|
|
1481
|
+
stddev: number | null;
|
|
1482
|
+
outliers: number;
|
|
1483
|
+
histogram: HistogramBin[];
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
export interface HistogramBin {
|
|
1487
|
+
from: number;
|
|
1488
|
+
to: number;
|
|
1489
|
+
count: number;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1424
1492
|
export interface FormattingApi {
|
|
1425
1493
|
list(scope?: FormattingScope): FormattingRule[];
|
|
1426
1494
|
all(): Record<FormattingScope, FormattingRule[]>;
|
|
@@ -1433,6 +1501,29 @@ export interface FormattingApi {
|
|
|
1433
1501
|
replaceAll(rules: Record<FormattingScope, FormattingRule[]>): void;
|
|
1434
1502
|
clear(scope?: FormattingScope): void;
|
|
1435
1503
|
styleFor(colId: string, value: unknown): CellStyle | null;
|
|
1504
|
+
/** Re-derive the thresholds of distribution rules from the data as it stands. */
|
|
1505
|
+
restat(): void;
|
|
1506
|
+
/** The five numbers a distribution rule resolves against for one column. */
|
|
1507
|
+
distribution(colId: string): ColumnDistribution | null;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/** Operators that resolve against the column's own distribution (spec 8.12). */
|
|
1511
|
+
export type DistributionOp =
|
|
1512
|
+
| 'topPercent' | 'bottomPercent' | 'topN' | 'bottomN'
|
|
1513
|
+
| 'aboveMean' | 'belowMean' | 'aboveMedian' | 'belowMedian'
|
|
1514
|
+
| 'zAbove' | 'zBelow' | 'outlier';
|
|
1515
|
+
|
|
1516
|
+
export interface ColumnDistribution {
|
|
1517
|
+
n: number;
|
|
1518
|
+
min: number;
|
|
1519
|
+
max: number;
|
|
1520
|
+
mean: number;
|
|
1521
|
+
stddev: number;
|
|
1522
|
+
median: number;
|
|
1523
|
+
q1: number;
|
|
1524
|
+
q3: number;
|
|
1525
|
+
iqr: number;
|
|
1526
|
+
sorted: number[];
|
|
1436
1527
|
}
|
|
1437
1528
|
|
|
1438
1529
|
// ---------------------------------------------------------------------------
|
|
@@ -2430,6 +2521,7 @@ export interface Grid {
|
|
|
2430
2521
|
readonly comments: CommentsApi;
|
|
2431
2522
|
readonly presence: PresenceApi;
|
|
2432
2523
|
readonly diagnostics: DiagnosticsApi;
|
|
2524
|
+
readonly statistics: StatisticsApi;
|
|
2433
2525
|
readonly formatting: FormattingApi;
|
|
2434
2526
|
readonly maximise?: MaximiseApi;
|
|
2435
2527
|
/**
|