@toclocoinc/lattice-grid 1.29.0 → 1.31.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 +1 -1
- package/docs/API.html +326 -3
- package/docs/api-detail.html +5 -1
- package/lattice-grid.d.ts +226 -1
- package/lattice-grid.esm.min.js +546 -9
- package/lattice-grid.min.cjs +546 -9
- package/lattice-grid.min.js +546 -9
- package/modules/angular.esm.min.js +3 -2
- package/modules/angular.min.cjs +3 -2
- package/modules/angular.min.js +3 -2
- package/modules/charts.esm.min.js +426 -4
- package/modules/charts.min.cjs +426 -4
- package/modules/charts.min.js +426 -4
- package/modules/data-router.esm.min.js +246 -0
- package/modules/data-router.min.cjs +249 -0
- package/modules/data-router.min.js +249 -0
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +4 -4
- package/modules/dhtmlx-compat.min.cjs +4 -4
- package/modules/dhtmlx-compat.min.js +4 -4
- package/modules/htmx.esm.min.js +546 -9
- package/modules/htmx.min.cjs +546 -9
- package/modules/htmx.min.js +546 -9
- package/modules/react.esm.min.js +3 -2
- package/modules/react.min.cjs +3 -2
- package/modules/react.min.js +3 -2
- package/modules/svelte.esm.min.js +3 -2
- package/modules/svelte.min.cjs +3 -2
- package/modules/svelte.min.js +3 -2
- package/modules/vue.esm.min.js +3 -2
- package/modules/vue.min.cjs +3 -2
- package/modules/vue.min.js +3 -2
- package/modules/webcomponent.esm.min.js +546 -9
- package/modules/webcomponent.min.cjs +546 -9
- package/modules/webcomponent.min.js +546 -9
- 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.31.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/docs/API.html
CHANGED
|
@@ -1673,6 +1673,29 @@ grid.presentation.stop(); <span class="cmt">// or Esc</sp
|
|
|
1673
1673
|
</div>
|
|
1674
1674
|
<p>Enlargement is a CSS scale factor multiplied into the same tokens <a href="#config">density</a> uses, so text, rows, padding and controls grow together rather than the grid being zoomed as an image. Font size is damped against it: type that scaled linearly with a 2× row height reads as shouting.</p>
|
|
1675
1675
|
|
|
1676
|
+
<h2 id="annotate">grid.annotate</h2>
|
|
1677
|
+
<p>The drawing layer over the grid: pixels on a transparent canvas, never data. A presenter picks a tool (<code>pen</code>, <code>arrow</code>, <code>rect</code>, <code>highlight</code>) and draws; the layer is inert until one is chosen, so scrolling and selection pass straight through otherwise. Marks are stored in <strong>content coordinates</strong>, so a circle drawn round a cell stays on that cell as the grid scrolls and resizes rather than hanging over the viewport.</p>
|
|
1678
|
+
<p>Marks can also be <strong>seeded and added without drawing</strong> (BACKLOG-0000813), which is what lets a host ship a pre-drawn callout or restore one from storage. A mark descriptor is <code>{ type, points, colour? }</code> — <code>type</code> is <code>freehand</code>, <code>arrow</code>, <code>rect</code>, <code>highlight</code> or <code>text</code> (<code>pen</code> is accepted as an alias for <code>freehand</code>); <code>points</code> are <code>{x, y}</code> in content coordinates (a trail for freehand, the two endpoints for an arrow or rectangle, a single anchor for text). Seeded and added marks are <em>durable</em>: they survive a presentation ending, unlike a live-drawn mark, and they round-trip through <code>getState</code> and a saved view.</p>
|
|
1679
|
+
<p>A <code>text</code> mark (BACKLOG-0000875) is a label anchored at one content point, carrying its <code>text</code> string and a basic style: <code>colour</code>, an optional <code>fontSize</code> in content pixels (default 14, scaled with a presentation), and an optional <code>background</code> colour drawn behind it. Like every mark it is held in content coordinates, so the label tracks the cell it annotates through scroll and resize.</p>
|
|
1680
|
+
<pre><code><span class="cmt">// Seed a mark at construction — rendered on first paint, the way redaction seeds.</span>
|
|
1681
|
+
createGrid(el, {
|
|
1682
|
+
columns, rows,
|
|
1683
|
+
annotate: true,
|
|
1684
|
+
state: { annotations: [
|
|
1685
|
+
{ type: 'arrow', points: [{ x: 40, y: 120 }, { x: 220, y: 80 }], colour: '#e0245e' },
|
|
1686
|
+
{ type: 'text', text: 'Q3 spike', points: [{ x: 232, y: 72 }], colour: '#1a6bc7', background: '#fffbe6' },
|
|
1687
|
+
] },
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
<span class="cmt">// Or add one durably at runtime — no synthesised pointer input.</span>
|
|
1691
|
+
grid.annotate.add({ type: 'rect', points: [{ x: 40, y: 100 }, { x: 260, y: 160 }] });
|
|
1692
|
+
grid.annotate.add({ type: 'text', text: 'review', points: [{ x: 48, y: 108 }], fontSize: 16 });
|
|
1693
|
+
|
|
1694
|
+
<span class="cmt">// Persist and restore: seeded and added marks come back out of the state.</span>
|
|
1695
|
+
const marks = grid.getState().annotations; <span class="cmt">// [{ type, points, colour }, …]</span>
|
|
1696
|
+
grid.state.apply({ annotations: marks }); <span class="cmt">// re-seed a fresh grid</span></code></pre>
|
|
1697
|
+
<div class="note"><p><code>annotate.add</code> adds to the model and paints — it never synthesises pointer events, so a mark is exactly what the descriptor says. <code>undo()</code> removes the most recent mark and <code>clear()</code> removes them all, as before; <code>annotation:changed</code> still fires on every change. A presentation ending clears the presenter's live-drawn marks but keeps the durable ones, which are view state a host means to persist.</p></div>
|
|
1698
|
+
|
|
1676
1699
|
<h2 id="redaction">grid.redaction</h2>
|
|
1677
1700
|
<p>Obscures a column's values on screen while leaving the shape of the data (row count, sort, filters, layout) perfectly readable. Built for presenting and screen sharing. Right-click a column heading for <strong>Redact column</strong>.</p>
|
|
1678
1701
|
<p><strong>This is not a security control.</strong> The values stay in the model, the DOM, the clipboard and every export; anyone with the page can read them from devtools or by turning off one CSS rule. It defeats a camera, which is the whole claim. For a value that must not reach the browser at all, use <a href="#permissions">permissions</a> with <code>writeOnly</code>.</p>
|
|
@@ -2043,6 +2066,115 @@ createGrid(el, {
|
|
|
2043
2066
|
grid.rows.value('r3', 'med'), <span class="cmt">// median of 2,4,5 = 4</span>
|
|
2044
2067
|
].join('|');</code></pre>
|
|
2045
2068
|
|
|
2069
|
+
<h3 id="seasonal-decomposition">Seasonal decomposition</h3>
|
|
2070
|
+
<p>Splitting a series into <strong>trend + seasonal + residual</strong> (BACKLOG-0000873) answers "what's the underlying trend with the weekly pattern removed?". It is classical decomposition — the same algorithm <code>statsmodels.seasonal_decompose</code> uses, verified against it in the reference suite — delivered as four shadow columns over the same ordered pass: <code>tsTrend</code> (a centred moving average), <code>tsSeasonal</code> (the repeating index), <code>tsResidual</code> (what the two leave behind), and <code>tsCoverage</code>.</p>
|
|
2071
|
+
<p>The <code>period</code> is <strong>caller-declared and required</strong> — 7 for a weekly cycle in daily data, 12 for a monthly cycle in monthly data; there is no auto-detection in v1. The model is <code>additive</code> by default; <code>decomposition: 'multiplicative'</code> is a declared option that is undefined on a non-positive series (those rows report null, with a warning). The centred window runs off the ends, so the leading and trailing rows have no trend — they are <em>partial edges</em>, reported as null and stamped <code>tsCoverage: 0</code> rather than emitted as if full.</p>
|
|
2072
|
+
<pre><code>columns: [
|
|
2073
|
+
{ field: 'day', type: 'date' },
|
|
2074
|
+
{ field: 'sales', type: 'number' },
|
|
2075
|
+
{ id: 'trend', title: 'Trend', shadow: { kind: 'tsTrend', of: 'sales', orderBy: 'day', period: 7 } },
|
|
2076
|
+
{ id: 'season', title: 'Weekly', shadow: { kind: 'tsSeasonal', of: 'sales', orderBy: 'day', period: 7 } },
|
|
2077
|
+
{ id: 'resid', title: 'Residual', shadow: { kind: 'tsResidual', of: 'sales', orderBy: 'day', period: 7 } },
|
|
2078
|
+
{ id: 'cover', title: 'Coverage', shadow: { kind: 'tsCoverage', of: 'sales', orderBy: 'day', period: 7 } },
|
|
2079
|
+
]</code></pre>
|
|
2080
|
+
<pre data-run="js" data-expect="14|2|0|1|null|0" data-covers="export:createHeadlessGrid"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2081
|
+
<span class="cmt">// A period-4 series: trend 10+i plus a season [2,-1,0,-1], so value = trend + season.</span>
|
|
2082
|
+
<span class="kw">const</span> season = [2, -1, 0, -1];
|
|
2083
|
+
<span class="kw">const</span> base = { of: 'v', orderBy: 't', within: 'all', period: 4 };
|
|
2084
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
2085
|
+
columns: [
|
|
2086
|
+
{ field: 't', type: 'number' },
|
|
2087
|
+
{ field: 'v', type: 'number' },
|
|
2088
|
+
{ id: 'trend', shadow: { kind: 'tsTrend', ...base } },
|
|
2089
|
+
{ id: 'season', shadow: { kind: 'tsSeasonal', ...base } },
|
|
2090
|
+
{ id: 'resid', shadow: { kind: 'tsResidual', ...base } },
|
|
2091
|
+
{ id: 'cover', shadow: { kind: 'tsCoverage', ...base } },
|
|
2092
|
+
],
|
|
2093
|
+
rows: Array.from({ length: 8 }, (unused, i) => ({ id: String(i), t: i, v: (10 + i) + season[i % 4] })),
|
|
2094
|
+
rowKey: 'id',
|
|
2095
|
+
});
|
|
2096
|
+
<span class="kw">return</span> [
|
|
2097
|
+
grid.rows.value('4', 'trend'), <span class="cmt">// centred MA recovers the trend: 14</span>
|
|
2098
|
+
grid.rows.value('4', 'season'), <span class="cmt">// the phase-0 seasonal index: 2</span>
|
|
2099
|
+
grid.rows.value('4', 'resid'), <span class="cmt">// nothing left over: 0</span>
|
|
2100
|
+
grid.rows.value('4', 'cover'), <span class="cmt">// interior row: full, 1</span>
|
|
2101
|
+
grid.rows.value('0', 'trend') === null ? 'null' : 'x', <span class="cmt">// partial edge: null, not invented</span>
|
|
2102
|
+
grid.rows.value('0', 'cover'), <span class="cmt">// edge stamped partial: 0</span>
|
|
2103
|
+
].join('|');</code></pre>
|
|
2104
|
+
|
|
2105
|
+
<h3 id="exponential-smoothing">Exponential smoothing</h3>
|
|
2106
|
+
<p>Smoothing pulls the signal out of a noisy series (BACKLOG-0000873). <code>tsSmoothed</code> is the fitted <strong>level</strong> — not a forecast of the future — from single exponential smoothing (<code>smoothing: 'ses'</code>, the default) or Holt's level+trend (<code>smoothing: 'holt'</code>). The recursion matches statsmodels and is verified against it in the reference suite. Holt-Winters (seasonal) smoothing is deferred; seasonality is covered by decomposition above.</p>
|
|
2107
|
+
<p>The smoothing factor is either <strong>caller-set</strong> (<code>alpha</code>, and <code>beta</code> for Holt) or <strong>fit by minimising the in-sample SSE</strong> when omitted — and the chosen value is reported, not hidden, by the <code>tsSmoothingAlpha</code> / <code>tsSmoothingBeta</code> companion columns.</p>
|
|
2108
|
+
<pre><code>columns: [
|
|
2109
|
+
{ field: 'day', type: 'date' },
|
|
2110
|
+
{ field: 'sales', type: 'number' },
|
|
2111
|
+
{ id: 'level', title: 'Smoothed', shadow: { kind: 'tsSmoothed', of: 'sales', orderBy: 'day', smoothing: 'holt' } },
|
|
2112
|
+
{ id: 'a', title: 'α', shadow: { kind: 'tsSmoothingAlpha', of: 'sales', orderBy: 'day', smoothing: 'holt' } },
|
|
2113
|
+
{ id: 'b', title: 'β', shadow: { kind: 'tsSmoothingBeta', of: 'sales', orderBy: 'day', smoothing: 'holt' } },
|
|
2114
|
+
]</code></pre>
|
|
2115
|
+
<pre data-run="js" data-expect="6|8|0.5" data-covers="export:createHeadlessGrid"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2116
|
+
<span class="cmt">// SES at alpha 0.5 over 4,8,6,10: level runs 4, 6, 6, 8.</span>
|
|
2117
|
+
<span class="kw">const</span> base = { of: 'v', orderBy: 't', within: 'all', smoothing: 'ses', alpha: 0.5 };
|
|
2118
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
2119
|
+
columns: [
|
|
2120
|
+
{ field: 't', type: 'number' },
|
|
2121
|
+
{ field: 'v', type: 'number' },
|
|
2122
|
+
{ id: 'sm', shadow: { kind: 'tsSmoothed', ...base } },
|
|
2123
|
+
{ id: 'a', shadow: { kind: 'tsSmoothingAlpha', ...base } },
|
|
2124
|
+
],
|
|
2125
|
+
rows: [4, 8, 6, 10].map((v, i) => ({ id: String(i), t: i, v })),
|
|
2126
|
+
rowKey: 'id',
|
|
2127
|
+
});
|
|
2128
|
+
<span class="kw">return</span> [
|
|
2129
|
+
grid.rows.value('1', 'sm'), <span class="cmt">// 0.5*8 + 0.5*4 = 6</span>
|
|
2130
|
+
grid.rows.value('3', 'sm'), <span class="cmt">// 0.5*10 + 0.5*6 = 8</span>
|
|
2131
|
+
grid.rows.value('0', 'a'), <span class="cmt">// the factor used, reported: 0.5</span>
|
|
2132
|
+
].join('|');</code></pre>
|
|
2133
|
+
|
|
2134
|
+
<h3 id="stationarity">Stationarity (ADF)</h3>
|
|
2135
|
+
<p>Before you compare two series or detrend one, it helps to know whether it is <strong>stationary</strong> — reverting to a level or trend — or wandering with a unit root. <code>grid.statistics.adf</code> runs the Augmented Dickey-Fuller test (BACKLOG-0000873) and returns a scalar readout, not a per-row column: the statistic, the augmenting lag chosen by AIC, MacKinnon's critical values, an interpolated p-value (stamped approximate), and a plain-language verdict at the 5% level. The constant+trend regression and the AIC lag choice match statsmodels' <code>adfuller</code>, against which the statistic and lag are verified.</p>
|
|
2136
|
+
<pre data-run="js" data-expect="non-stationary|0" data-covers="method:statistics"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2137
|
+
<span class="cmt">// A random walk (a unit root): it wanders rather than reverting.</span>
|
|
2138
|
+
<span class="kw">const</span> walk = [0.138, -0.725, -1.26, -0.536, -0.267, 0.167, -0.765, -0.146, -0.311, -0.586,
|
|
2139
|
+
0.376, -0.41, 0.282, 0.865, -0.024, 0.149, -0.2, -0.654, -1.338, -1.917, -1.832, -1.333,
|
|
2140
|
+
-0.492, -1.259, -1.89, -2.145, -2.146, -1.297, -1.26, -0.716, -0.846, -1.206, -2.126,
|
|
2141
|
+
-1.724, -0.945, -1.599, -1.316, -0.413, 0.304, 0.732, -0.257, 0.086, -0.572, -0.501,
|
|
2142
|
+
-1.153, -1.186, -1.455, -1.607];
|
|
2143
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
2144
|
+
columns: [{ field: 't', type: 'number' }, { field: 'v', type: 'number' }],
|
|
2145
|
+
rows: walk.map((v, i) => ({ id: String(i), t: i, v })),
|
|
2146
|
+
rowKey: 'id',
|
|
2147
|
+
});
|
|
2148
|
+
<span class="kw">const</span> adf = grid.statistics.adf({ of: 'v', orderBy: 't' });
|
|
2149
|
+
<span class="kw">return</span> [adf.verdict, adf.usedLag].join('|'); <span class="cmt">// non-stationary, 0 lags</span></code></pre>
|
|
2150
|
+
|
|
2151
|
+
<h3 id="autocorrelation">Autocorrelation (ACF / PACF)</h3>
|
|
2152
|
+
<p><code>grid.statistics.acf</code> shows how far back a series depends on itself (BACKLOG-0000873): the autocorrelation (ACF) and partial autocorrelation (PACF) arrays out to a maximum lag, each with the approximate <code>±1.96/√n</code> white-noise band (stamped approximate) — a lag whose bar clears the band is evidence of real dependence. The estimators are the biased ACF and the Yule-Walker (Levinson-Durbin) PACF, matching statsmodels, verified in the reference suite. <strong>Lag 1 is the single source of truth</strong>: <code>acf[1]</code> is the same number <code>statistics.series(...).autocorrelation</code> reports, and <code>pacf[1] === acf[1]</code>.</p>
|
|
2153
|
+
<p>The correlogram is the arrays fed to a bar chart over <a href="#seasonal-decomposition">explicit points</a>, with the band as reference lines — reusing the existing chart primitives:</p>
|
|
2154
|
+
<pre><code>const { acf, bounds } = grid.statistics.acf({ of: 'sales', orderBy: 'day', maxlag: 20 });
|
|
2155
|
+
createChart({
|
|
2156
|
+
grid, container: '#acf', type: 'bar',
|
|
2157
|
+
points: acf.map((v, lag) => ({ x: lag, y: v })),
|
|
2158
|
+
reference: [{ value: bounds.upper }, { value: bounds.lower }, { value: 0 }],
|
|
2159
|
+
});</code></pre>
|
|
2160
|
+
<pre data-run="js" data-expect="1|true|true" data-covers="method:statistics"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2161
|
+
<span class="cmt">// A deterministic AR(1): each reading leans 0.6 on the one before.</span>
|
|
2162
|
+
<span class="kw">let</span> s = 5; <span class="kw">const</span> rand = () => { s = (Math.imul(s, 1664525) + 1013904223) >>> 0; return s / 4294967296 - 0.5; };
|
|
2163
|
+
<span class="kw">const</span> y = []; <span class="kw">let</span> prev = 0;
|
|
2164
|
+
<span class="kw">for</span> (<span class="kw">let</span> i = 0; i < 200; i++) { const v = 0.6 * prev + rand(); y.push(v); prev = v; }
|
|
2165
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
2166
|
+
columns: [{ field: 't', type: 'number' }, { field: 'v', type: 'number' }],
|
|
2167
|
+
rows: y.map((v, i) => ({ id: String(i), t: i, v })),
|
|
2168
|
+
rowKey: 'id',
|
|
2169
|
+
});
|
|
2170
|
+
<span class="kw">const</span> res = grid.statistics.acf({ of: 'v', orderBy: 't', maxlag: 6 });
|
|
2171
|
+
<span class="kw">const</span> series = grid.statistics.series('v', { by: 't' });
|
|
2172
|
+
<span class="kw">return</span> [
|
|
2173
|
+
res.acf[0], <span class="cmt">// lag 0 is always 1</span>
|
|
2174
|
+
res.pacf[1] === res.acf[1], <span class="cmt">// the first partial equals the first acf</span>
|
|
2175
|
+
Math.abs(res.acf[1] - series.autocorrelation) < 1e-9, <span class="cmt">// lag 1 is the single source of truth</span>
|
|
2176
|
+
].join('|');</code></pre>
|
|
2177
|
+
|
|
2046
2178
|
<h2 id="highlight">grid.highlight</h2>
|
|
2047
2179
|
<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>
|
|
2048
2180
|
<pre><code>createGrid(el, {
|
|
@@ -3483,6 +3615,7 @@ off(); <span class="cmt">// on() returns i
|
|
|
3483
3615
|
<tr><td class="name">timeline:detached</td><td class="desc">The brush was removed.</td></tr>
|
|
3484
3616
|
<tr><td class="name">timeline:seek</td><td class="desc">The brush settled on a range.</td></tr>
|
|
3485
3617
|
<tr><td class="name">timeline:seeking</td><td class="desc">The brush is being dragged. Throttled.</td></tr>
|
|
3618
|
+
<tr><td class="name">annotation:changed</td><td class="type">{ tool, count }</td><td class="desc">A drawing annotation was added, edited or cleared. Carries the active <code>tool</code> and the <code>count</code> of marks. A first-class event, so <code>grid.on('annotation:changed', ...)</code> and the framework adapters' <code>onAnnotationChanged</code> reach it directly rather than through the <code>'*'</code> wildcard.</td></tr>
|
|
3486
3619
|
<tr><td class="name">presentation:started</td><td class="desc">Presentation mode began. Carries the scale, options, views and starting index. <code>presentation:changed</code> covers a later change to the same options, so a listener can tell entry from adjustment.</td></tr>
|
|
3487
3620
|
<tr><td class="name">presentation:changed</td><td class="desc">The options of a running presentation changed.</td></tr>
|
|
3488
3621
|
<tr><td class="name">presentation:ended</td><td class="desc">Presentation mode ended. Annotations are cleared here.</td></tr>
|
|
@@ -3511,6 +3644,33 @@ off(); <span class="cmt">// on() returns i
|
|
|
3511
3644
|
|
|
3512
3645
|
<div class="note"><p>This list is complete, and stays complete: <code>tools/check.js</code> compares every <code>emit()</code> in the grid against the declared event names and fails the build on a mismatch. A chart raises its own events, which belong to the charts module rather than to this bus.</p></div>
|
|
3513
3646
|
|
|
3647
|
+
<h3 id="event-known-example">A declared event is reachable directly, executed</h3>
|
|
3648
|
+
<p class="section-note">Every name in the table above is a first-class event: <code>grid.on(name, ...)</code>
|
|
3649
|
+
binds it without the unknown-name warning, and each maps to a framework handler prop. Shown for
|
|
3650
|
+
<code>annotation:changed</code>, which BACKLOG-876 promoted from a wildcard-only emission to a
|
|
3651
|
+
declared event. Run headless on every build.</p>
|
|
3652
|
+
<pre data-run="js" data-expect="true | 0 | onAnnotationChanged" data-covers="event:annotation:changed"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3653
|
+
<span class="kw">const</span> { isKnownEvent } = <span class="kw">await</span> import('../packages/core/src/events/index.js');
|
|
3654
|
+
<span class="kw">const</span> { handlerName } = <span class="kw">await</span> import('../packages/modules/shared/adapter.js');
|
|
3655
|
+
|
|
3656
|
+
<span class="kw">const</span> grid = createHeadlessGrid({ columns: [{ field: 'a' }], rows: [] });
|
|
3657
|
+
|
|
3658
|
+
<span class="cmt">// Binding a declared event does not trip the unknown-name warning that an</span>
|
|
3659
|
+
<span class="cmt">// undeclared one would — that warning is exactly what BACKLOG-876 removed.</span>
|
|
3660
|
+
<span class="kw">const</span> warnings = [];
|
|
3661
|
+
<span class="kw">const</span> original = console.warn;
|
|
3662
|
+
console.warn = (...a) => warnings.push(a.join(' '));
|
|
3663
|
+
<span class="kw">const</span> off = grid.on('annotation:changed', () => {});
|
|
3664
|
+
console.warn = original;
|
|
3665
|
+
off();
|
|
3666
|
+
grid.destroy();
|
|
3667
|
+
|
|
3668
|
+
<span class="kw">return</span> [
|
|
3669
|
+
isKnownEvent('annotation:changed'), <span class="cmt">// declared at on() time</span>
|
|
3670
|
+
warnings.length, <span class="cmt">// 0: no unknown-event warning</span>
|
|
3671
|
+
handlerName('annotation:changed'), <span class="cmt">// the adapter prop the frameworks expose</span>
|
|
3672
|
+
].join(' | ');</code></pre>
|
|
3673
|
+
|
|
3514
3674
|
<h2 id="rules">Conditional formatting</h2>
|
|
3515
3675
|
<p>Rules compile into the function <code>cell.style</code> already takes, so a compiled rule set
|
|
3516
3676
|
installs exactly like a hand-written style function.</p>
|
|
@@ -3752,7 +3912,7 @@ const chart = createChart({
|
|
|
3752
3912
|
<tr><td class="name">type</td><td class="type">string</td><td class="desc">One of the thirty above.</td></tr>
|
|
3753
3913
|
<tr><td class="name">x / y</td><td class="type">string</td><td class="desc">Category and measure columns.</td></tr>
|
|
3754
3914
|
<tr><td class="name">series</td><td class="type">string</td><td class="desc">Splits the measure into one series per distinct value.</td></tr>
|
|
3755
|
-
<tr><td class="name">measures</td><td class="type">object[]</td><td class="desc"><code>{col, fn, type, axis}</code>: several measures at once, each reduced by
|
|
3915
|
+
<tr><td class="name">measures</td><td class="type">object[]</td><td class="desc"><code>{col, fn, type, axis}</code>: several measures at once, each reduced by an aggregation. <code>fn</code> is one of <code>sum</code>, <code>avg</code> (alias <code>mean</code>), <code>min</code>, <code>max</code>, <code>count</code>, <code>countValues</code>, <code>first</code>, <code>last</code>; it defaults to <code>sum</code>. An <code>fn</code> that is none of these is a mistake, not a silent <code>sum</code>: it warns once, naming the value and the supported set, and falls back to <code>sum</code> so the chart still draws.</td></tr>
|
|
3756
3916
|
<tr><td class="name">title</td><td class="type">string</td><td class="desc">Drawn above the plot.</td></tr>
|
|
3757
3917
|
<tr><td class="name">scheme</td><td class="type">string | string[]</td><td class="desc">A named scheme or your own colours. <code>schemeNames()</code> lists the built-in ones, including a colour-blind-safe palette.</td></tr>
|
|
3758
3918
|
<tr><td class="name">legend</td><td class="type">boolean | object</td><td class="desc"><code>position</code>, and <code>isolate</code> so a click shows only that series, which is what a reader with eight series wants, and what plain toggling makes them do in seven clicks.</td></tr>
|
|
@@ -3933,6 +4093,114 @@ createGrid(el, {
|
|
|
3933
4093
|
|
|
3934
4094
|
<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>
|
|
3935
4095
|
|
|
4096
|
+
<h2 id="datarouter">The data router</h2>
|
|
4097
|
+
<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>.</p>
|
|
4098
|
+
<pre><code>import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
|
|
4099
|
+
|
|
4100
|
+
const router = createDataRouter({
|
|
4101
|
+
key: 'entityType', <span class="cmt">// partition: a property, or fn(row) => value</span>
|
|
4102
|
+
rowKey: 'id', <span class="cmt">// identity within a grid: a property, or fn(row)</span>
|
|
4103
|
+
overlap: false, <span class="cmt">// default: first matching route wins</span>
|
|
4104
|
+
onUnrouted: (item) => {}, <span class="cmt">// optional sink for records that match no route</span>
|
|
4105
|
+
});
|
|
4106
|
+
|
|
4107
|
+
router.attach(ordersGrid, 'order'); <span class="cmt">// a property value...</span>
|
|
4108
|
+
router.attach(bigGrid, (row) => row.amt > 1e6); <span class="cmt">// ...or a composite predicate</span>
|
|
4109
|
+
router.attach(headlessGrid, 'metric', { rowKey: 'ts' }); <span class="cmt">// per-attach rowKey override; feeds a chart</span>
|
|
4110
|
+
router.attachDefault(restGrid); <span class="cmt">// the "rest" sink: nothing is dropped</span>
|
|
4111
|
+
|
|
4112
|
+
const counts = router.load(snapshot); <span class="cmt">// keyed diff per grid: [{added,updated,removed}, ...]</span>
|
|
4113
|
+
router.apply([{ op: 'upsert', row }, { op: 'delete', row }]); <span class="cmt">// in-place deltas by rowKey</span></code></pre>
|
|
4114
|
+
<p><strong>A snapshot is a keyed diff, not a replace.</strong> <code>load</code> re-partitions the whole dataset and, per grid, adds the new rows, updates only the changed ones and removes the gone ones — so an unchanged row never repaints and selection, scroll and edit state survive. <strong>Deltas are applied in place</strong> by <code>rowKey</code>, last-writer-wins within a batch; an <code>upsert</code> whose partition property has changed <em>moves</em> the row (it leaves the route it no longer matches and joins the one it now does, never duplicated). A record matching no route is counted in <code>router.unrouted</code>, handed to <code>onUnrouted</code>, and — if an <code>attachDefault</code> grid exists — routed there, so nothing is ever silently lost. By default a record goes to the first route it matches; <code>overlap: true</code> fans it to every matching route.</p>
|
|
4115
|
+
<div class="table-wrap">
|
|
4116
|
+
<table>
|
|
4117
|
+
<thead><tr><th>Member</th><th>Description</th></tr></thead>
|
|
4118
|
+
<tbody>
|
|
4119
|
+
<tr><td class="sig">createDataRouter({ key, rowKey?, overlap?, onUnrouted? })</td><td class="desc">Create a router. <code>key</code> is the partition (property or <code>fn(row)</code>); <code>rowKey</code> the default identity within a grid; <code>overlap</code> fans a record to all matching routes; <code>onUnrouted</code> is a sink for unmatched records.</td></tr>
|
|
4120
|
+
<tr><td class="sig">attach(grid, predicate, { rowKey? })</td><td class="desc">Route to <code>grid</code> when <code>predicate</code> matches: a value compared to <code>key</code>, or a <code>fn(row) => boolean</code>. <code>rowKey</code> overrides the router default for this grid.</td></tr>
|
|
4121
|
+
<tr><td class="sig">attachDefault(grid, { rowKey? })</td><td class="desc">The "rest" sink: the grid that receives every record no explicit route matched.</td></tr>
|
|
4122
|
+
<tr><td class="sig">load(snapshot)</td><td class="desc">Apply a full snapshot as a keyed diff per grid. Returns per-route <code>{ added, updated, removed }</code> counts in attach order.</td></tr>
|
|
4123
|
+
<tr><td class="sig">apply(deltas)</td><td class="desc">Apply <code>{ op: 'upsert' | 'delete', row }</code> deltas in place by <code>rowKey</code>.</td></tr>
|
|
4124
|
+
<tr><td class="sig">unrouted</td><td class="desc">How many records matched no route (reset by <code>load</code>, running for deltas).</td></tr>
|
|
4125
|
+
<tr><td class="sig">link(source, target, relation)</td><td class="desc"><strong>v2:</strong> make a selection in <code>source</code> filter what <code>target</code> receives. <code>relation</code> is a key map <code>{ from, to }</code> (target rows whose <code>to</code> value is among the selected source rows' <code>from</code> values — multi-select is an IN set, ANY match) or a function <code>fn(selectedSourceRows) => (row) => boolean</code>. No selection shows the full partition; changes are debounced.</td></tr>
|
|
4126
|
+
<tr><td class="sig">flush()</td><td class="desc"><strong>v2:</strong> apply any debounced selection refilter now, for a deterministic point (and for tests).</td></tr>
|
|
4127
|
+
<tr><td class="sig">detach(grid)</td><td class="desc">Stop routing to a grid and forget its slice; drop any link it is part of (restoring a filtered sibling). The host still owns and destroys the grid.</td></tr>
|
|
4128
|
+
<tr><td class="sig">destroy()</td><td class="desc">Detach every grid, drop every link and subscription. <strong>Detaches only</strong> — the host owns and destroys its grids.</td></tr>
|
|
4129
|
+
</tbody>
|
|
4130
|
+
</table>
|
|
4131
|
+
</div>
|
|
4132
|
+
<p>The router keeps a small <code>Map<rowKey, row></code> per route to compute the snapshot diff. That is deliberate for v1; a future optimisation could diff against the grid's own key index rather than a shadow copy. Ordering and dedupe across a live feed are the host's to guarantee — a caller that must drop stale out-of-order deltas can carry its own version or sequence field and filter before <code>apply</code>; v1 imposes no version scheme.</p>
|
|
4133
|
+
<p><strong>Cross-grid selection filtering (v2, BACKLOG-0000880).</strong> <code>link</code> keeps each target's <em>full partition</em> separate from what it currently shows: when the source's selection changes, the router recomputes the shown subset from the relation and re-pushes it through the same keyed-diff path, so the target grid stays dumb — it only ever receives rows, never a query or a reference to the source. Selection <em>in</em> the target survives an unrelated refilter, because the keyed path preserves it. No selection (or one the router cannot resolve to routed rows) shows the full partition, and deselecting restores it. The source grid must have selection enabled; still no grid-core change. Debounce is controlled by <code>selectionDebounce</code> (default 16 ms; <code>0</code> is synchronous), and <code>flush()</code> forces it.</p>
|
|
4134
|
+
<h3 id="datarouter-v2-example">Cross-grid selection filtering, executed</h3>
|
|
4135
|
+
<p class="section-note">A customers grid and an orders grid off one feed; selecting customers filters the orders
|
|
4136
|
+
grid to their regions through the keyed-diff path, and deselecting restores the full set. Run headless
|
|
4137
|
+
on every build.</p>
|
|
4138
|
+
<pre data-run="js" data-expect="3 | 2 | 3 | 3" data-covers="export:createDataRouter"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
4139
|
+
<span class="kw">const</span> { createDataRouter } = <span class="kw">await</span> import('../packages/modules/data-router/index.js');
|
|
4140
|
+
|
|
4141
|
+
<span class="kw">const</span> cols = [{ id: 'id', field: 'id' }, { id: 'type', field: 'type' }, { id: 'region', field: 'region', type: 'text' }];
|
|
4142
|
+
<span class="kw">const</span> customers = createHeadlessGrid({ rowKey: 'id', columns: cols, selection: 'multiple' });
|
|
4143
|
+
<span class="kw">const</span> orders = createHeadlessGrid({ rowKey: 'id', columns: cols });
|
|
4144
|
+
|
|
4145
|
+
<span class="cmt">// debounce 0 so a selection refilters synchronously in this example.</span>
|
|
4146
|
+
<span class="kw">const</span> router = createDataRouter({ key: 'type', rowKey: 'id', selectionDebounce: 0 });
|
|
4147
|
+
router.attach(customers, 'customer');
|
|
4148
|
+
router.attach(orders, 'order');
|
|
4149
|
+
router.load([
|
|
4150
|
+
{ id: 'c1', type: 'customer', region: 'emea' },
|
|
4151
|
+
{ id: 'c2', type: 'customer', region: 'amer' },
|
|
4152
|
+
{ id: 'o1', type: 'order', region: 'emea' },
|
|
4153
|
+
{ id: 'o2', type: 'order', region: 'amer' },
|
|
4154
|
+
{ id: 'o3', type: 'order', region: 'emea' },
|
|
4155
|
+
]);
|
|
4156
|
+
<span class="cmt">// A selection in customers filters the orders grid by region.</span>
|
|
4157
|
+
router.link(customers, orders, { from: 'region', to: 'region' });
|
|
4158
|
+
|
|
4159
|
+
<span class="kw">const</span> full = orders.rows.count(); <span class="cmt">// 3: no selection, full partition</span>
|
|
4160
|
+
customers.selection.set(['c1']); <span class="cmt">// emea</span>
|
|
4161
|
+
<span class="kw">const</span> oneRegion = orders.rows.count(); <span class="cmt">// 2: o1, o3</span>
|
|
4162
|
+
customers.selection.set(['c1', 'c2']); <span class="cmt">// emea + amer (IN set)</span>
|
|
4163
|
+
<span class="kw">const</span> both = orders.rows.count(); <span class="cmt">// 3</span>
|
|
4164
|
+
customers.selection.set([]); <span class="cmt">// deselect restores</span>
|
|
4165
|
+
<span class="kw">const</span> restored = orders.rows.count(); <span class="cmt">// 3</span>
|
|
4166
|
+
|
|
4167
|
+
customers.destroy(); orders.destroy(); router.destroy();
|
|
4168
|
+
<span class="kw">return</span> [full, oneRegion, both, restored].join(' | ');</code></pre>
|
|
4169
|
+
<h3 id="datarouter-example">One feed, three grids, executed</h3>
|
|
4170
|
+
<p class="section-note">A single snapshot fanned to an orders grid, an invoices grid and a "rest" sink, then a
|
|
4171
|
+
delta that changes a row's partition — proving the fan-out, the sink, and that a moved row
|
|
4172
|
+
leaves its old grid and joins the new one rather than being duplicated. Run headless on every build.</p>
|
|
4173
|
+
<pre data-run="js" data-expect="2,1,1 | 1/2 | 0" data-covers="export:createDataRouter"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
4174
|
+
<span class="kw">const</span> { createDataRouter } = <span class="kw">await</span> import('../packages/modules/data-router/index.js');
|
|
4175
|
+
|
|
4176
|
+
<span class="kw">const</span> cols = [{ id: 'id', field: 'id' }, { id: 'type', field: 'type' }, { id: 'amt', field: 'amt', type: 'number' }];
|
|
4177
|
+
<span class="kw">const</span> orders = createHeadlessGrid({ rowKey: 'id', columns: cols });
|
|
4178
|
+
<span class="kw">const</span> invoices = createHeadlessGrid({ rowKey: 'id', columns: cols });
|
|
4179
|
+
<span class="kw">const</span> rest = createHeadlessGrid({ rowKey: 'id', columns: cols });
|
|
4180
|
+
|
|
4181
|
+
<span class="cmt">// One router keyed on `type`; each grid sees only its slice, the "rest" sink</span>
|
|
4182
|
+
<span class="cmt">// catches anything that matches no explicit route.</span>
|
|
4183
|
+
<span class="kw">const</span> router = createDataRouter({ key: 'type', rowKey: 'id' });
|
|
4184
|
+
router.attach(orders, 'order');
|
|
4185
|
+
router.attach(invoices, 'invoice');
|
|
4186
|
+
router.attachDefault(rest);
|
|
4187
|
+
|
|
4188
|
+
<span class="cmt">// One snapshot hydrates all three grids at once.</span>
|
|
4189
|
+
router.load([
|
|
4190
|
+
{ id: 'o1', type: 'order', amt: 10 },
|
|
4191
|
+
{ id: 'o2', type: 'order', amt: 20 },
|
|
4192
|
+
{ id: 'i1', type: 'invoice', amt: 99 },
|
|
4193
|
+
{ id: 'x1', type: 'ticket', amt: 1 }, <span class="cmt">// matches no route -> the sink, not dropped</span>
|
|
4194
|
+
]);
|
|
4195
|
+
<span class="kw">const</span> fanned = [orders.rows.count(), invoices.rows.count(), rest.rows.count()].join(',');
|
|
4196
|
+
|
|
4197
|
+
<span class="cmt">// o2's partition changes: it MOVES from orders to invoices, not duplicated.</span>
|
|
4198
|
+
router.apply([{ op: 'upsert', row: { id: 'o2', type: 'invoice', amt: 25 } }]);
|
|
4199
|
+
<span class="kw">const</span> moved = orders.rows.count() + '/' + invoices.rows.count();
|
|
4200
|
+
|
|
4201
|
+
orders.destroy(); invoices.destroy(); rest.destroy(); router.destroy();
|
|
4202
|
+
<span class="kw">return</span> [fanned, moved, router.unrouted].join(' | ');</code></pre>
|
|
4203
|
+
|
|
3936
4204
|
<h2 id="charts">In-cell charts</h2>
|
|
3937
4205
|
<p>Seven chart renderers for a cell. Each is a single SVG whose path data is the only thing a
|
|
3938
4206
|
repaint writes, so they cost the same as any other cell as rows recycle.</p>
|
|
@@ -4482,7 +4750,7 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
4482
4750
|
|
|
4483
4751
|
<h3 id="module-exports-example">Every module export, executed</h3>
|
|
4484
4752
|
<p class="section-note">Nine shipped modules, each export resolved against its own barrel on every build.</p>
|
|
4485
|
-
<pre data-run="js" data-expect="
|
|
4753
|
+
<pre data-run="js" data-expect="62" data-covers="export:ContextMenu export:Messages export:Registry export:autoInit export:createGrid export:createLocalViewStorage export:createMessages export:createStat export:deltaOf export:gridElementsWithin export:hydrateTable export:mountPanel export:readTable export:toneOf export:Chart export:PALETTE export:SCHEMES export:TYPES export:createChart export:chartRange export:canChartRange export:deriveRangeSpec export:regressionPlots export:registerScheme export:resolveScheme export:schemeNames export:setDefaultScheme export:HTML_ROW_WARNING_THRESHOLD export:QUERY_CHANGED_EVENT export:SCROLL_NEAR_END_EVENT export:attach export:destroyWithin export:driveInfiniteScroll export:driveOobUpdates export:driveServerMode export:ingestResponse export:initWithin export:queryParams export:restoreStateWithin export:rowsFromFragment export:rowsFromJson export:saveStateWithin export:ATTRIBUTE_CONFIG export:EVENT_PREFIX export:GridElementController export:LatticeGrid export:TAG_NAME export:createLatticeGridElement export:defineLatticeGrid export:domEventName export:observedAttributeNames export:CONSOLE_ACTIVATION export:createDevtools export:expose export:EVENT_NAMES export:handlerName export:createLatticeGrid export:dashedName export:createLatticeAction export:Grid export:warnIfLargeHtmlPayload export:createDataRouter"><code><span class="cmt">// Every declared export of every shipped module, resolved against its own</span>
|
|
4486
4754
|
<span class="cmt">// barrel. A module that stopped exporting something fails here.</span>
|
|
4487
4755
|
<span class="kw">const</span> modules = [
|
|
4488
4756
|
[<span class="kw">await</span> import('../packages/dom/src/index.js'), [
|
|
@@ -4523,6 +4791,9 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
4523
4791
|
[<span class="kw">await</span> import('../packages/modules/dhtmlx-compat/index.js'), [
|
|
4524
4792
|
'Grid',
|
|
4525
4793
|
]],
|
|
4794
|
+
[<span class="kw">await</span> import('../packages/modules/data-router/index.js'), [
|
|
4795
|
+
'createDataRouter',
|
|
4796
|
+
]],
|
|
4526
4797
|
];
|
|
4527
4798
|
|
|
4528
4799
|
<span class="kw">let</span> present = 0;
|
|
@@ -4897,6 +5168,39 @@ return `methods ${ANOMALY_METHODS.length}; flagged ${z.flagged}; upper ${fence.u
|
|
|
4897
5168
|
<!-- BEGIN GENERATED TYPE REFERENCE -->
|
|
4898
5169
|
<h2 id="type-reference">Type reference</h2>
|
|
4899
5170
|
<p class="section-note">Every interface the library declares, with the type of each member. The sections above describe how the grid is used; this one is the complete surface, generated from the type declarations so that it always matches the release.</p>
|
|
5171
|
+
<h3 id="type-AcfResult">AcfResult</h3>
|
|
5172
|
+
<p class="section-note">Autocorrelation (ACF) and partial autocorrelation (PACF) arrays (BACKLOG-0000873).</p>
|
|
5173
|
+
<div class="table-wrap">
|
|
5174
|
+
<table>
|
|
5175
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5176
|
+
<tbody>
|
|
5177
|
+
<tr><td class="name">acf</td><td class="type">number[]</td><td class="desc">The autocorrelation at each lag; index 0 is lag 0 and is always 1.</td></tr>
|
|
5178
|
+
<tr><td class="name">pacf</td><td class="type">number[]</td><td class="desc">The partial autocorrelation at each lag; index 0 is 1, and `pacf[1] === acf[1]`.</td></tr>
|
|
5179
|
+
<tr><td class="name">bounds</td><td class="type">{ upper: number; lower: number }</td><td class="desc">The approximate ±1.96/√n white-noise confidence band.</td></tr>
|
|
5180
|
+
<tr><td class="name">n</td><td class="type">number</td><td class="desc">The series length the ACF/PACF were computed over.</td></tr>
|
|
5181
|
+
<tr><td class="name">nlags</td><td class="type">number</td><td class="desc">The maximum lag.</td></tr>
|
|
5182
|
+
<tr><td class="name">approximate</td><td class="type">boolean</td><td class="desc">Always true: the ±1.96/√n band is an approximation.</td></tr>
|
|
5183
|
+
</tbody>
|
|
5184
|
+
</table>
|
|
5185
|
+
</div>
|
|
5186
|
+
<h3 id="type-AdfResult">AdfResult</h3>
|
|
5187
|
+
<p class="section-note">The Augmented Dickey-Fuller stationarity test result (BACKLOG-0000873).</p>
|
|
5188
|
+
<div class="table-wrap">
|
|
5189
|
+
<table>
|
|
5190
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5191
|
+
<tbody>
|
|
5192
|
+
<tr><td class="name">statistic</td><td class="type">number</td><td class="desc">The ADF t-statistic on the lagged level.</td></tr>
|
|
5193
|
+
<tr><td class="name">usedLag</td><td class="type">number</td><td class="desc">The number of augmenting lags chosen by AIC.</td></tr>
|
|
5194
|
+
<tr><td class="name">nobs</td><td class="type">number</td><td class="desc">The observations the final regression used.</td></tr>
|
|
5195
|
+
<tr><td class="name">criticalValues</td><td class="type">{ '1%': number; '5%': number; '10%': number }</td><td class="desc">MacKinnon's constant+trend critical values at the 1%, 5% and 10% levels.</td></tr>
|
|
5196
|
+
<tr><td class="name">pValue</td><td class="type">number</td><td class="desc">An approximate p-value, interpolated across the critical-value ladder.</td></tr>
|
|
5197
|
+
<tr><td class="name">pApproximate</td><td class="type">boolean</td><td class="desc">Always true: the p-value is an interpolation, not the MacKinnon surface.</td></tr>
|
|
5198
|
+
<tr><td class="name">stationary</td><td class="type">boolean</td><td class="desc">Whether the series is stationary at the 5% level.</td></tr>
|
|
5199
|
+
<tr><td class="name">verdict</td><td class="type">string</td><td class="desc">The plain-language verdict: `'stationary'` or `'non-stationary'`.</td></tr>
|
|
5200
|
+
<tr><td class="name">regression</td><td class="type">'ct'</td><td class="desc">The regression form used — always `'ct'` (constant + trend) in v1.</td></tr>
|
|
5201
|
+
</tbody>
|
|
5202
|
+
</table>
|
|
5203
|
+
</div>
|
|
4900
5204
|
<h3 id="type-AggregateProvenance">AggregateProvenance</h3>
|
|
4901
5205
|
<p class="section-note">How one aggregate was routed, for `lastPlan()` provenance.</p>
|
|
4902
5206
|
<div class="table-wrap">
|
|
@@ -4942,7 +5246,6 @@ return `methods ${ANOMALY_METHODS.length}; flagged ${z.flagged}; upper ${fence.u
|
|
|
4942
5246
|
</table>
|
|
4943
5247
|
</div>
|
|
4944
5248
|
<h3 id="type-AnnotationApi">AnnotationApi</h3>
|
|
4945
|
-
<p class="section-note">The presenter's drawing layer. Pixels over the grid, it never reads or writes data, and it is inert until a tool is chosen, so scrolling and selection pass straight through. Marks are held in content coordinates, so they stay with the cells they annotate when the grid scrolls, and are cleared when a presentation ends.</p>
|
|
4946
5249
|
<div class="table-wrap">
|
|
4947
5250
|
<table>
|
|
4948
5251
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
@@ -4950,12 +5253,29 @@ return `methods ${ANOMALY_METHODS.length}; flagged ${z.flagged}; upper ${fence.u
|
|
|
4950
5253
|
<tr><td class="name">tool</td><td class="type">'pen' | 'arrow' | 'rect' | 'highlight' | null</td><td class="desc"><small>(read-only)</small></td></tr>
|
|
4951
5254
|
<tr><td class="name">count</td><td class="type">number</td><td class="desc"><small>(read-only)</small></td></tr>
|
|
4952
5255
|
<tr><td class="name">use</td><td class="type">(tool: 'pen' | 'arrow' | 'rect' | 'highlight' | null, opts?: { colour?: string }): string | null</td><td class="desc"></td></tr>
|
|
5256
|
+
<tr><td class="name">add</td><td class="type">(mark: AnnotationMark): number</td><td class="desc">Add a durable mark from a descriptor, without synthesising pointer input (BACKLOG-0000813). The mark is painted, survives a presentation ending, and round-trips through `getState`. Returns the mark count.</td></tr>
|
|
5257
|
+
<tr><td class="name">list</td><td class="type">(): AnnotationMark[]</td><td class="desc">Every mark on the layer, as descriptors — the shape `getState` persists.</td></tr>
|
|
4953
5258
|
<tr><td class="name">undo</td><td class="type">(): number</td><td class="desc"></td></tr>
|
|
4954
5259
|
<tr><td class="name">clear</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4955
5260
|
<tr><td class="name">redraw</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4956
5261
|
</tbody>
|
|
4957
5262
|
</table>
|
|
4958
5263
|
</div>
|
|
5264
|
+
<h3 id="type-AnnotationMark">AnnotationMark</h3>
|
|
5265
|
+
<p class="section-note">A durable annotation mark descriptor (BACKLOG-0000813) — the shape a host seeds through `state.annotations`, adds through {@link AnnotationApi.add}, and reads back through {@link AnnotationApi.list} and `getState`. `points` are in **content coordinates** (the same space user-drawn marks are stored in), so a mark tracks scroll and resize rather than hanging over the viewport. A `freehand` mark is a trail of points; `arrow` and `rect` are their two endpoints. A `text` mark is a label anchored at a single content point, carrying its `text` string and an optional basic style (BACKLOG-0000875). `pen` is accepted as an alias for `freehand` on input; `list()` reports `freehand`.</p>
|
|
5266
|
+
<div class="table-wrap">
|
|
5267
|
+
<table>
|
|
5268
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5269
|
+
<tbody>
|
|
5270
|
+
<tr><td class="name">type</td><td class="type">'freehand' | 'arrow' | 'rect' | 'highlight' | 'text'</td><td class="desc"></td></tr>
|
|
5271
|
+
<tr><td class="name">points</td><td class="type">{ x: number; y: number }[]</td><td class="desc">Content coordinates. A `text` mark carries a single anchor point; `arrow` and `rect` carry their two corners, and `freehand` a trail.</td></tr>
|
|
5272
|
+
<tr><td class="name">colour</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5273
|
+
<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>
|
|
5274
|
+
<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>
|
|
5275
|
+
<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>
|
|
5276
|
+
</tbody>
|
|
5277
|
+
</table>
|
|
5278
|
+
</div>
|
|
4959
5279
|
<h3 id="type-AnomalyReason">AnomalyReason</h3>
|
|
4960
5280
|
<div class="table-wrap">
|
|
4961
5281
|
<table>
|
|
@@ -6588,6 +6908,7 @@ return `methods ${ANOMALY_METHODS.length}; flagged ${z.flagged}; upper ${fence.u
|
|
|
6588
6908
|
<tr><td class="name">pivot</td><td class="type">{ enabled: boolean; columns: string[] }</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6589
6909
|
<tr><td class="name">pivotView</td><td class="type">{ rowsCollapsed: string[]; columnsCollapsed: string[] }</td><td class="desc">The pivot presentation's collapse state (§10, BACKLOG-0000738): which row-axis and column-axis nodes are collapsed. Absent when the matrix is fully expanded, and tolerated as "expand all" when applied. <small>(optional)</small></td></tr>
|
|
6590
6910
|
<tr><td class="name">formatting</td><td class="type">Record<string, FormattingRule[]></td><td class="desc"><small>(optional)</small></td></tr>
|
|
6911
|
+
<tr><td class="name">annotations</td><td class="type">AnnotationMark[]</td><td class="desc">Durable annotation marks (BACKLOG-0000813): seeded from here on first paint, and written back by `getState` so a host can persist and restore them. In content coordinates, so they track scroll and resize. <small>(optional)</small></td></tr>
|
|
6591
6912
|
<tr><td class="name">expanded</td><td class="type">string[]</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6592
6913
|
<tr><td class="name">selection</td><td class="type">string[]</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6593
6914
|
<tr><td class="name">scroll</td><td class="type">{ top: number; left: number }</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -7830,6 +8151,8 @@ return `methods ${ANOMALY_METHODS.length}; flagged ${z.flagged}; upper ${fence.u
|
|
|
7830
8151
|
<tr><td class="name">covariance</td><td class="type">(a: string, b: string, opts?: { population?: boolean }): number | null</td><td class="desc">Covariance, a correlation before the scales are divided out.</td></tr>
|
|
7831
8152
|
<tr><td class="name">regression</td><td class="type">(a: string, b: string): RegressionFit | null</td><td class="desc">Least-squares fit of `b` on `a`: in finance, beta and alpha.</td></tr>
|
|
7832
8153
|
<tr><td class="name">regressionModel</td><td class="type">(spec: RegressionSpec): RegressionModel | null</td><td class="desc">Fit a multi-predictor linear model over the filtered rows and return the full diagnostic set — coefficients with standard errors, t and p; R² and adjusted R²; per-row fitted values, residuals, leverage and Cook's D; VIF per predictor; a Breusch–Pagan heteroscedasticity flag; and, for a single predictor, a pointwise confidence band. `method` is `ols`, `wls` (needs a `weights` column) or `robust`; `quantile` is reserved and the regularised families refuse. Null on degenerate input (BACKLOG-0000792).</td></tr>
|
|
8154
|
+
<tr><td class="name">adf</td><td class="type">(spec: { of: string; orderBy: string; maxlag?: number }): AdfResult | null</td><td class="desc">The Augmented Dickey-Fuller stationarity test over the `of` series in `orderBy` order (BACKLOG-0000873), constant+trend form with the lag order chosen by AIC up to an optional cap. Returns the statistic, the lag used, MacKinnon's critical values, an approximate (interpolated) p-value and a plain-language verdict at the 5% level — a scalar readout, not a column.</td></tr>
|
|
8155
|
+
<tr><td class="name">acf</td><td class="type">(spec: { of: string; orderBy: string; maxlag?: number }): AcfResult | null</td><td class="desc">The autocorrelation (ACF) and partial autocorrelation (PACF) of the `of` series in `orderBy` order out to `maxlag` (BACKLOG-0000873), with the approximate ±1.96/√n band. A short-series readout; feed the arrays to a bar chart over explicit points with the band as reference lines. The lag-1 autocorrelation matches `series(...).autocorrelation`.</td></tr>
|
|
7833
8156
|
<tr><td class="name">spearman</td><td class="type">(a: string, b: string): number | null</td><td class="desc">Spearman's rank correlation, which one outlier cannot drag.</td></tr>
|
|
7834
8157
|
<tr><td class="name">kendall</td><td class="type">(a: string, b: string): number | null</td><td class="desc">Kendall's tau-b. Null past 5,000 rows: it is quadratic.</td></tr>
|
|
7835
8158
|
<tr><td class="name">weightedQuantile</td><td class="type">(colId: string, weightId: string, p?: number): number | null</td><td class="desc">A quantile of one column weighted by another; the median by default.</td></tr>
|
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.
|
|
440
|
+
<p class="rail__sub">Developer guide · v1.31.0</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -4835,6 +4835,8 @@ grid.on('presentation:captured', (e) => {
|
|
|
4835
4835
|
<p class="example__label">Drawing over the grid</p>
|
|
4836
4836
|
<pre><code>grid.annotate.use('pen'); <span class="cmt">// pen · arrow · rect · highlight</span>
|
|
4837
4837
|
grid.annotate.use('arrow', { colour: '#e0245e' });
|
|
4838
|
+
<span class="cmt">// A durable text label anchored to a cell, seeded or added (BACKLOG-875).</span>
|
|
4839
|
+
grid.annotate.add({ type: 'text', text: 'Q3 spike', points: [{ x: 232, y: 72 }], background: '#fffbe6' });
|
|
4838
4840
|
grid.annotate.undo();
|
|
4839
4841
|
grid.annotate.clear();
|
|
4840
4842
|
grid.annotate.use(null); <span class="cmt">// hand the grid back</span></code></pre>
|
|
@@ -6236,6 +6238,7 @@ grid.state.apply(savedView.state);
|
|
|
6236
6238
|
<tr><td class="name">canChartRange</td><td class="desc">Whether <code>chartRange</code> would draw something for the grid’s current selection — the question a menu asks before offering the item.</td></tr>
|
|
6237
6239
|
<tr><td class="name">deriveRangeSpec</td><td class="desc">Decide what a chart of a range should be without drawing it: the type, the category column, the measures, and a spec ready for <code>createChart</code>.</td></tr>
|
|
6238
6240
|
<tr><td class="name">regressionPlots</td><td class="desc">Turn a fitted regression model into diagnostic chart specs ready for <code>createChart</code>: the fit line with its confidence band, residuals-vs-fitted, a QQ plot of the residuals, and a multicollinearity correlogram with the model’s VIF. The plots that need a per-row or per-coefficient quantity the grid has no column for (scale-location, residuals-vs-leverage, the coefficient forest) are returned as a null spec carrying the reason rather than dropped.</td></tr>
|
|
6241
|
+
<tr><td class="name">createDataRouter</td><td class="desc">Split one arriving stream or dataset across many grids by what each record is — a property or a predicate — driving each grid through the public keyed <code>rows.apply</code> path so a snapshot is a diff, a delta is applied in place, a moved partition moves the row rather than duplicating it, and an unmatched record is counted, sunk and never dropped. v2 adds cross-grid selection filtering: <code>link(source, target, relation)</code> makes a selection in one grid filter what another receives — by a key map or a predicate function, multi-select as an IN set, debounced — re-pushed through the same keyed-diff path so the target stays dumb. Detaches its grids on <code>destroy</code>; the host owns them.</td></tr>
|
|
6239
6242
|
<tr><td class="name">createDevtools</td><td class="desc">Mount the devtools panel against a grid, including its accessibility checks.</td></tr>
|
|
6240
6243
|
<tr><td class="name">createLatticeGridElement</td><td class="desc">Build the element class without registering it, for a custom registry.</td></tr>
|
|
6241
6244
|
<tr><td class="name">createMessages</td><td class="desc">Build a message catalogue. A partial set lays over the built-in British English one.</td></tr>
|
|
@@ -6460,6 +6463,7 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
|
|
|
6460
6463
|
<tr><td class="name">timeline:detached</td><td class="desc">The brush was removed.</td></tr>
|
|
6461
6464
|
<tr><td class="name">timeline:seek</td><td class="desc">The brush settled on a range.</td></tr>
|
|
6462
6465
|
<tr><td class="name">timeline:seeking</td><td class="desc">The brush is being dragged. Throttled.</td></tr>
|
|
6466
|
+
<tr><td class="name">annotation:changed</td><td class="desc">A drawing annotation was added, edited or cleared. Carries the active tool and the mark count. Declared (BACKLOG-876) so grid.on('annotation:changed', ...) and the adapters' onAnnotationChanged reach it directly instead of via the '*' wildcard.</td></tr>
|
|
6463
6467
|
<tr><td class="name">view:applied</td><td class="desc">Emits no storage write: applying a view changes nothing to persist.</td></tr>
|
|
6464
6468
|
<tr><td class="name">view:default</td><td class="desc">view is null when the default was cleared.</td></tr>
|
|
6465
6469
|
<tr><td class="name">view:removed</td><td class="desc">A saved view was deleted.</td></tr>
|