@toclocoinc/lattice-grid 1.12.2 → 1.13.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 +56 -13
- package/docs/API.html +221 -10
- package/docs/CHART-CODES.md +1 -1
- package/docs/api-detail.html +488 -40
- package/lattice-grid.d.ts +185 -7
- package/lattice-grid.esm.min.js +684 -46
- package/lattice-grid.min.cjs +667 -46
- package/lattice-grid.min.js +667 -46
- package/modules/charts.esm.min.js +1 -1
- package/modules/devtools.esm.min.js +1 -1
- package/modules/dhtmlx-compat.esm.min.js +667 -46
- package/modules/htmx.esm.min.js +650 -46
- package/modules/htmx.min.cjs +650 -46
- package/modules/htmx.min.js +650 -46
- package/modules/react.esm.min.js +1 -1
- package/modules/svelte.esm.min.js +1 -1
- package/modules/vue.esm.min.js +1 -1
- package/modules/webcomponent.esm.min.js +667 -46
- 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.13.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ Every module is optional and none of them is loaded unless you import it.
|
|
|
27
27
|
|
|
28
28
|
| Module | What it is |
|
|
29
29
|
|---|---|
|
|
30
|
-
| `modules/charts.esm.min.js` | Thirty chart types drawn from the grid's data. |
|
|
30
|
+
| `modules/charts.esm.min.js` | Thirty-seven chart types drawn from the grid's data, including control and capability charts. |
|
|
31
31
|
| `modules/react.esm.min.js` | React adapter. |
|
|
32
32
|
| `modules/vue.esm.min.js` | Vue adapter. |
|
|
33
33
|
| `modules/svelte.esm.min.js` | Svelte adapter. |
|
|
@@ -55,9 +55,37 @@ enough to know whether the grid covers what you need.
|
|
|
55
55
|
- **Live data.** `rows.apply({add, update, remove})` patches in place: the grid
|
|
56
56
|
re-queries the stages a change actually touched and repaints the cells that
|
|
57
57
|
moved. A feed can be paused and resumed with the queue held.
|
|
58
|
-
- **Any source.** Rows in memory,
|
|
59
|
-
|
|
58
|
+
- **Any source.** Rows in memory, server-side paging, infinite scroll,
|
|
59
|
+
streaming, or a grid derived from another grid. Sorting and filtering can be
|
|
60
60
|
handed to the server or left to the grid.
|
|
61
|
+
- **Pushdown adapters.** One portable query, translated to whatever an engine
|
|
62
|
+
speaks. An adapter declares what it can answer, the grid works out what to
|
|
63
|
+
send and finishes the rest itself, and reports the split so a slow query can
|
|
64
|
+
be diagnosed. Adapters ship for OData, an ordinary REST endpoint, DemandFlow
|
|
65
|
+
and DuckDB.
|
|
66
|
+
- **A full analytical engine, without carrying one.** `duckdbAdapter` takes a
|
|
67
|
+
DuckDB connection you created and imports nothing, so a grid can query a
|
|
68
|
+
Parquet file of millions of rows in the browser while this package stays at
|
|
69
|
+
zero dependencies. `demo/duckdb.html` does exactly that with no server at
|
|
70
|
+
all.
|
|
71
|
+
|
|
72
|
+
### Grids that read from other grids
|
|
73
|
+
|
|
74
|
+
- **Derived grids.** A grid whose rows are produced from another grid rather
|
|
75
|
+
than loaded: grouped and aggregated, unnested, filtered, ranked or profiled,
|
|
76
|
+
in its own element with its own columns. It follows the source live, and a
|
|
77
|
+
change is patched into the last grouping rather than re-derived, so five
|
|
78
|
+
hundred updates against a 200,000-row source cost under 300 ms in total.
|
|
79
|
+
- **Cross-filtering.** A derived panel can filter the grid it summarises.
|
|
80
|
+
Clicking one row narrows the source without collapsing the panel that was
|
|
81
|
+
clicked, so there is always something else to click, and several panels
|
|
82
|
+
compose.
|
|
83
|
+
- **Joins.** Two grids holding their own data and a third showing where they
|
|
84
|
+
meet. `inner` keeps what matched; `left` keeps everything and leaves the
|
|
85
|
+
unmatched rows visible, which is the shape you want when the unmatched rows
|
|
86
|
+
are the finding. Both sides stay live.
|
|
87
|
+
- **Statistic tiles.** `createStat` renders a headline figure over a grid, with
|
|
88
|
+
a change indicator, threshold bands and its confidence interval.
|
|
61
89
|
|
|
62
90
|
### Working with the data
|
|
63
91
|
|
|
@@ -68,7 +96,7 @@ enough to know whether the grid covers what you need.
|
|
|
68
96
|
- **Selection and ranges.** Cell, row, column and rectangular range selection,
|
|
69
97
|
with clipboard behaviour that round-trips through Excel.
|
|
70
98
|
- **Fill, copy and paste** across a range, including formulas.
|
|
71
|
-
- **Formulas.** A closed, safe expression language, no `eval`, no host access
|
|
99
|
+
- **Formulas.** A closed, safe expression language, no `eval`, no host access,
|
|
72
100
|
with maths, text, logic, date and statistical functions, evaluated against
|
|
73
101
|
other columns.
|
|
74
102
|
- **Export.** CSV with fields sanitised against formula injection, real `.xlsx`
|
|
@@ -78,16 +106,31 @@ enough to know whether the grid covers what you need.
|
|
|
78
106
|
|
|
79
107
|
### Seeing the data
|
|
80
108
|
|
|
81
|
-
- **Charts.** `modules/charts` draws thirty-
|
|
82
|
-
data: line, bar, area, scatter, pie, donut, sunburst, treemap, radar, gauge,
|
|
109
|
+
- **Charts.** `modules/charts` draws thirty-seven chart types from the grid's
|
|
110
|
+
own data: line, bar, area, scatter, pie, donut, sunburst, treemap, radar, gauge,
|
|
83
111
|
funnel, heatmap, histogram, box plot, candlestick, combo, geomap, sankey,
|
|
84
|
-
chord, network, stream, violin, gantt
|
|
85
|
-
filters, and clicking a mark
|
|
112
|
+
chord, network, stream, violin, gantt, Q-Q, ECDF, Lorenz, correlogram,
|
|
113
|
+
control and capability. They follow the grid's filters, and clicking a mark
|
|
114
|
+
can filter it in turn. A scatter can carry its own least-squares fit, and any
|
|
115
|
+
mark can carry a whisker for the uncertainty behind it.
|
|
86
116
|
- **Statistics.** `grid.statistics` profiles a column in one pass: count,
|
|
87
117
|
missing, distinct, five-number summary, standard deviation, outliers and a
|
|
88
|
-
histogram, and answers correlations and weighted averages.
|
|
89
|
-
reduction kernels are available to the totals row, and you can
|
|
90
|
-
own.
|
|
118
|
+
histogram, and answers correlations, regressions and weighted averages.
|
|
119
|
+
Thirty-eight reduction kernels are available to the totals row, and you can
|
|
120
|
+
register your own. A profile also says what is worth looking at: a constant
|
|
121
|
+
column, a key that is not unique, a fifth of the rows missing.
|
|
122
|
+
- **Statistical process control.** Cp and Cpk against short-term variation from
|
|
123
|
+
the moving range, Pp and Ppk against overall, and the share of parts outside
|
|
124
|
+
the customer's tolerance, which is declared once on the column so the indices,
|
|
125
|
+
the charts and any cell rule cannot disagree about it. Control charts name
|
|
126
|
+
their lines and number every rule break, under Western Electric's four rules
|
|
127
|
+
or Nelson's eight. A capability report draws the readings against the
|
|
128
|
+
tolerance with a curve for each of the two spreads, and a moving range chart
|
|
129
|
+
completes the pair.
|
|
130
|
+
- **Confidence intervals.** On a mean using the *t* distribution, on a rate
|
|
131
|
+
using the Wilson score, on a regression slope, and on a capability index. The
|
|
132
|
+
line the product draws is that it quantifies uncertainty and does not
|
|
133
|
+
adjudicate hypotheses: there are no p-values and no significance tests.
|
|
91
134
|
- **Shadow columns.** Values the grid maintains about itself: how many times a
|
|
92
135
|
row has changed, what a value was when the page loaded, how fast it is moving,
|
|
93
136
|
its rank, percentile or share of the total. Real columns: sortable,
|
|
@@ -200,7 +243,7 @@ Two integrations that are not framework adapters.
|
|
|
200
243
|
|
|
201
244
|
**htmx.** `modules/htmx` lets a grid survive htmx's own DOM swaps, hydrate from
|
|
202
245
|
a server-rendered `<table>`, and drive sort, filter and infinite scroll over
|
|
203
|
-
plain htmx requests. It is a complete package rather than an add-on
|
|
246
|
+
plain htmx requests. It is a complete package rather than an add-on,
|
|
204
247
|
`createGrid`, `autoInit`, `hydrateTable`, `readTable`, `serialiseState` and
|
|
205
248
|
`restoreState` are re-exported alongside its own functions, so a page using it
|
|
206
249
|
imports this and never the base package as well.
|
package/docs/API.html
CHANGED
|
@@ -848,7 +848,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
848
848
|
<tr><td class="name">id</td><td class="type">string</td><td class="desc">Defaults to <code>field</code>. Required when there is no field.</td></tr>
|
|
849
849
|
<tr><td class="name">field</td><td class="type">string</td><td class="desc">Dotted paths supported: <code>'site.address.postcode'</code>.</td></tr>
|
|
850
850
|
<tr><td class="name">title</td><td class="type">string</td><td class="desc">Header text. Defaults to a humanised <code>field</code>.</td></tr>
|
|
851
|
-
<tr><td class="name">type</td><td class="type">TypeName | false</td><td class="desc">A data type bundles format, parse, compare, storage, editor, filter, renderer and Excel behaviour. <code>false</code> disables inference. <code>'image'</code> treats the value as a URL and draws it: see <a href="#image-guide">image columns</a>.</td></tr>
|
|
851
|
+
<tr><td class="name">type</td><td class="type">TypeName | false</td><td class="desc">A data type bundles format, parse, compare, storage, editor, filter, renderer and Excel behaviour. <code>false</code> disables inference. <code>'image'</code> treats the value as a URL and draws it: see <a href="api-detail.html#image-guide">image columns</a>.</td></tr>
|
|
852
852
|
<tr><td class="name">preset</td><td class="type">string | string[]</td><td class="desc">Named bundles from <code>columnPresets</code>.</td></tr>
|
|
853
853
|
<tr><td class="name">tags</td><td class="type">string | string[]</td><td class="dflt">, </td><td class="desc">Labels grouping columns together, used by the column tag bar. A bare string is accepted for one tag.</td></tr>
|
|
854
854
|
<tr><td class="name">format</td><td class="type">FormatSpec | string</td><td class="desc">Shorthand strings like <code>'percent:1'</code> or <code>'date:dd MMM yyyy'</code>.</td></tr>
|
|
@@ -2078,7 +2078,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
|
|
|
2078
2078
|
can fail at three in the morning, a key carries its own answer and the grid reads it locally,
|
|
2079
2079
|
fresh on every load. A key for the wrong domain, or a key that will not read at all, does the
|
|
2080
2080
|
same thing a genuinely expired trial key does: log one console warning and show the watermark.</p>
|
|
2081
|
-
<div class="note"><p>Install the key before creating a grid. Setting one later still works
|
|
2081
|
+
<div class="note"><p>Install the key before creating a grid. Setting one later still works,
|
|
2082
2082
|
<code>licence:changed</code> fires and the watermark is removed, but the first frames of the
|
|
2083
2083
|
grid will carry it.</p></div>
|
|
2084
2084
|
|
|
@@ -2260,8 +2260,8 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
|
|
|
2260
2260
|
</p>
|
|
2261
2261
|
<p class="section-note">
|
|
2262
2262
|
<strong>Report the capability interval.</strong> Its absence is the commonest way a capability
|
|
2263
|
-
study overstates itself. A Cpk of 1.35 measured on thirty parts has a lower bound below 1.0
|
|
2264
|
-
|
|
2263
|
+
study overstates itself. A Cpk of 1.35 measured on thirty parts has a lower bound below 1.0,
|
|
2264
|
+
so a process that has "passed" a 1.33 requirement on thirty parts has demonstrated very
|
|
2265
2265
|
little. The point estimate alone does not say that; the interval does.
|
|
2266
2266
|
</p>
|
|
2267
2267
|
<p class="section-note">
|
|
@@ -2355,7 +2355,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
|
|
|
2355
2355
|
<p class="section-note">
|
|
2356
2356
|
<strong>Fitted lines.</strong> <code>fit: true</code> draws a least-squares line through a
|
|
2357
2357
|
scatter with its R² beside it; <code>fit: 'line'</code> draws the line alone. A cloud of points
|
|
2358
|
-
invites a reader to draw the line themselves, and people are consistently poor at it
|
|
2358
|
+
invites a reader to draw the line themselves, and people are consistently poor at it,
|
|
2359
2359
|
the eye is pulled by the extremes, which is exactly what least squares is not.
|
|
2360
2360
|
</p>
|
|
2361
2361
|
<p class="section-note">
|
|
@@ -2365,6 +2365,165 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
|
|
|
2365
2365
|
makes it the place an interval earns its keep rather than the place it is least needed.
|
|
2366
2366
|
</p>
|
|
2367
2367
|
|
|
2368
|
+
<h3 id="pushdown">Pushdown adapters: one query, many engines</h3>
|
|
2369
|
+
<p class="section-note">
|
|
2370
|
+
A remote source already receives a structured request: range, sort, filters, quick text,
|
|
2371
|
+
grouping, pivoting and totals. A <strong>pushdown adapter</strong> turns that request into
|
|
2372
|
+
whatever an engine speaks, so connecting a new back end is a translation layer rather than a
|
|
2373
|
+
new source.
|
|
2374
|
+
</p>
|
|
2375
|
+
<pre><code>import { createPushdownSource, odataAdapter } from 'lattice-grid';
|
|
2376
|
+
|
|
2377
|
+
const source = createPushdownSource({
|
|
2378
|
+
adapter: odataAdapter({ url: 'https://api.example.com/Orders' }),
|
|
2379
|
+
compute,
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
createGrid(host, { source, columns: [...] });</code></pre>
|
|
2383
|
+
|
|
2384
|
+
<p class="section-note">
|
|
2385
|
+
<strong>An adapter never carries an engine.</strong> Each one takes what it needs as a
|
|
2386
|
+
parameter: <code>restAdapter</code> takes a <code>fetch</code> and bundles no HTTP library,
|
|
2387
|
+
<code>dfqlAdapter</code> takes a token, and <code>duckdbAdapter</code> takes a connection you
|
|
2388
|
+
have already made. So a grid can drive a full analytical engine without this package carrying
|
|
2389
|
+
one, and installing Lattice never installs anything else.
|
|
2390
|
+
</p>
|
|
2391
|
+
<p class="section-note">
|
|
2392
|
+
<strong>An adapter declares what it can answer.</strong> No engine speaks the whole query.
|
|
2393
|
+
OData takes a condition tree but only some operators; a single-term API takes one field and one
|
|
2394
|
+
value; a hand-written endpoint may take nothing but a page number. The adapter states its
|
|
2395
|
+
capabilities, the SDK divides the request accordingly, and the grid finishes whatever is left.
|
|
2396
|
+
</p>
|
|
2397
|
+
<div class="table-wrap">
|
|
2398
|
+
<table>
|
|
2399
|
+
<thead><tr><th>Capability</th><th>Values</th><th>Meaning</th></tr></thead>
|
|
2400
|
+
<tbody>
|
|
2401
|
+
<tr><td class="name">filter</td><td class="type">false | 'term' | 'flat' | 'tree'</td><td class="desc">Nothing, a single field and term, a flat conjunction, or a full condition tree.</td></tr>
|
|
2402
|
+
<tr><td class="name">operators</td><td class="type">string[]</td><td class="desc">Which comparisons the engine understands. A condition using anything else stays with the grid.</td></tr>
|
|
2403
|
+
<tr><td class="name">sort</td><td class="type">false | 'single' | 'multi'</td><td class="desc">How many columns it can order by.</td></tr>
|
|
2404
|
+
<tr><td class="name">quick</td><td class="type">boolean</td><td class="desc">Whether free-text search across columns can be pushed.</td></tr>
|
|
2405
|
+
<tr><td class="name">range</td><td class="type">boolean</td><td class="desc">Whether it can return a window rather than the whole result.</td></tr>
|
|
2406
|
+
<tr><td class="name">total</td><td class="type">boolean</td><td class="desc">Whether it can report how many rows match.</td></tr>
|
|
2407
|
+
</tbody>
|
|
2408
|
+
</table>
|
|
2409
|
+
</div>
|
|
2410
|
+
|
|
2411
|
+
<p class="section-note">
|
|
2412
|
+
<strong>Anything left over means the whole result is fetched.</strong> Filtering a <em>window</em>
|
|
2413
|
+
of rows in the browser is not a slower way to get the right answer, it is a fast way to get a
|
|
2414
|
+
wrong one: the rows that belong on the first page may be on the ninth, and the count is whatever
|
|
2415
|
+
the engine happened to return. So when the grid has work left to do it asks the engine for the
|
|
2416
|
+
complete result, applies the remainder, and pages from what it holds. It says so once, naming
|
|
2417
|
+
the part that could not be pushed, because the fix is usually a wider adapter rather than a
|
|
2418
|
+
bigger machine. <code>source.lastPlan()</code> reports the division for any request.
|
|
2419
|
+
</p>
|
|
2420
|
+
|
|
2421
|
+
<p class="section-note">
|
|
2422
|
+
<strong>A conjunction splits; a disjunction does not.</strong> An <code>and</code> group narrows
|
|
2423
|
+
with each condition, so the engine can apply the conditions it understands and the grid narrows
|
|
2424
|
+
what comes back. An <code>or</code> group widens with each branch, so pushing only the supported
|
|
2425
|
+
branches returns fewer rows than the filter allows, and the grid cannot recover rows that were
|
|
2426
|
+
never fetched. A disjunction the engine cannot fully answer therefore stays with the grid whole.
|
|
2427
|
+
The same asymmetry governs <a href="#facets">faceting</a>.
|
|
2428
|
+
</p>
|
|
2429
|
+
|
|
2430
|
+
<p class="section-note">
|
|
2431
|
+
<strong>A sort is pushed whole or not at all.</strong> Ordering by the first column and fixing
|
|
2432
|
+
the rest in the browser needs every row anyway, so a partial sort buys nothing and returns rows
|
|
2433
|
+
in an order that is wrong until the grid corrects it.
|
|
2434
|
+
</p>
|
|
2435
|
+
|
|
2436
|
+
<div class="table-wrap">
|
|
2437
|
+
<table>
|
|
2438
|
+
<thead><tr><th>Adapter</th><th>For</th><th>Notes</th></tr></thead>
|
|
2439
|
+
<tbody>
|
|
2440
|
+
<tr><td class="name">odataAdapter</td><td class="desc">Any OData v4 endpoint</td><td class="desc">Writes <code>$filter</code>, <code>$orderby</code>, <code>$top</code>, <code>$skip</code> and <code>$count</code>. System options keep their <code>$</code> unencoded, which several servers require.</td></tr>
|
|
2441
|
+
<tr><td class="name">restAdapter</td><td class="desc">The API you already have</td><td class="desc">Parameter names are yours to choose. Paging and sorting are assumed; filtering is assumed absent until you declare <code>operators</code>, because an adapter that claims to filter when the endpoint ignores it returns the wrong rows silently.</td></tr>
|
|
2442
|
+
<tr><td class="name">duckdbAdapter</td><td class="desc">A DuckDB connection</td><td class="desc">Writes SQL and takes the whole query: filter tree, multi-column sort and paging. <code>from</code> is any FROM expression, so <code>read_parquet('s3://bucket/*.parquet')</code> is as valid as a table name. The engine is yours to create and install; this imports nothing, so the bundle is unchanged whether you use it or not.</td></tr>
|
|
2443
|
+
<tr><td class="name">dfqlAdapter</td><td class="desc">DemandFlow entities</td><td class="desc">Speaks <code>POST /v1/query</code>. Sends the entity, the key attribute and the prefix to match, a field projection and one field-and-term filter, matched as a case-insensitive substring. It cannot sort or page, so the grid does both, and every request carries a <code>countOnly</code> line because <code>limit</code> caps rows <em>scanned</em> rather than matched: a filtered query returns an arbitrary subset, and the count is the only thing that reveals it.</td></tr>
|
|
2444
|
+
</tbody>
|
|
2445
|
+
</table>
|
|
2446
|
+
</div>
|
|
2447
|
+
|
|
2448
|
+
<h4 id="pushdown-cookbook">Wiring it to the API you already have</h4>
|
|
2449
|
+
<p class="section-note">
|
|
2450
|
+
Most data sits behind a service someone on your team wrote. The adapter below sends four
|
|
2451
|
+
parameters and expects <code>{ rows, total }</code> back. Start by declaring only what the
|
|
2452
|
+
endpoint genuinely does, and widen it as you teach the endpoint more.
|
|
2453
|
+
</p>
|
|
2454
|
+
<pre><code>const source = createPushdownSource({
|
|
2455
|
+
compute,
|
|
2456
|
+
adapter: restAdapter({
|
|
2457
|
+
url: '/api/orders',
|
|
2458
|
+
// Only the comparisons the endpoint really applies. Claiming more here
|
|
2459
|
+
// returns the wrong rows rather than merely running slowly.
|
|
2460
|
+
operators: ['eq', 'gt', 'lt', 'contains'],
|
|
2461
|
+
params: { offset: 'skip', limit: 'take' },
|
|
2462
|
+
}),
|
|
2463
|
+
});</code></pre>
|
|
2464
|
+
<p class="section-note">The request that reaches your service, and the answer it owes:</p>
|
|
2465
|
+
<div class="table-wrap">
|
|
2466
|
+
<table>
|
|
2467
|
+
<thead><tr><th>Parameter</th><th>Example</th><th>Meaning</th></tr></thead>
|
|
2468
|
+
<tbody>
|
|
2469
|
+
<tr><td class="name">skip / take</td><td class="type">40, 20</td><td class="desc">The window. Return exactly that slice.</td></tr>
|
|
2470
|
+
<tr><td class="name">sort / order</td><td class="type">amount,name / desc,asc</td><td class="desc">Columns in priority order, and a direction for each.</td></tr>
|
|
2471
|
+
<tr><td class="name">filter</td><td class="type">JSON condition tree</td><td class="desc">Only the conditions your declared operators cover. Everything else the grid keeps.</td></tr>
|
|
2472
|
+
<tr><td class="name">q</td><td class="type">free text</td><td class="desc">Present only when you declare <code>quick: true</code>.</td></tr>
|
|
2473
|
+
</tbody>
|
|
2474
|
+
</table>
|
|
2475
|
+
</div>
|
|
2476
|
+
<pre><code>// Express. FastAPI and ASP.NET differ only in how the query string is read.
|
|
2477
|
+
app.get('/api/orders', async (req, res) => {
|
|
2478
|
+
const { skip = 0, take = 100, sort, order, filter } = req.query;
|
|
2479
|
+
|
|
2480
|
+
let q = db('orders');
|
|
2481
|
+
if (filter) q = applyConditions(q, JSON.parse(filter)); // your translation
|
|
2482
|
+
if (sort) {
|
|
2483
|
+
sort.split(',').forEach((col, i) => {
|
|
2484
|
+
q = q.orderBy(col, (order || '').split(',')[i] === 'desc' ? 'desc' : 'asc');
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// The count is of everything matching, not of the page. A grid scrollbar is
|
|
2489
|
+
// sized from it, so a page-sized total makes the grid look empty below.
|
|
2490
|
+
const [{ count }] = await q.clone().clearOrder().count({ count: '*' });
|
|
2491
|
+
const rows = await q.offset(Number(skip)).limit(Number(take));
|
|
2492
|
+
|
|
2493
|
+
res.json({ rows, total: Number(count) });
|
|
2494
|
+
});</code></pre>
|
|
2495
|
+
<p class="section-note">
|
|
2496
|
+
<strong>The total is the commonest mistake.</strong> It is the number of rows matching the
|
|
2497
|
+
filter, not the number returned in this page. The grid sizes its scrollbar from it and requests
|
|
2498
|
+
windows against it, so returning the page length makes a large result look like one page.
|
|
2499
|
+
</p>
|
|
2500
|
+
|
|
2501
|
+
<h4 id="pushdown-sdk">Building an adapter from the parts</h4>
|
|
2502
|
+
<p class="section-note">
|
|
2503
|
+
<code>createPushdownSource</code> is the whole story for most callers. When an engine needs a
|
|
2504
|
+
source of its own, the four pieces it is assembled from are exported separately, so a custom
|
|
2505
|
+
source can plan and finish work the same way rather than reimplementing the split.
|
|
2506
|
+
</p>
|
|
2507
|
+
<div class="table-wrap">
|
|
2508
|
+
<table>
|
|
2509
|
+
<thead><tr><th>Export</th><th>Signature</th><th>Description</th></tr></thead>
|
|
2510
|
+
<tbody>
|
|
2511
|
+
<tr><td class="name">capabilitiesOf</td><td class="type">(declared?) => Required<PushdownCapabilities></td><td class="desc">Resolves what an adapter declared against the defaults, giving a complete set with no absent keys to test for.</td></tr>
|
|
2512
|
+
<tr><td class="name">splitFilters</td><td class="type">(filters, caps) => { pushed, residual }</td><td class="desc">Divides a condition tree into the half the engine takes and the half left over. A conjunction splits; a disjunction that is not fully supported stays whole on the client, because pushing part of an <code>or</code> returns fewer rows than the filter allows and the grid cannot recover what was never fetched.</td></tr>
|
|
2513
|
+
<tr><td class="name">planQuery</td><td class="type">(request, caps) => PushdownPlan</td><td class="desc">Plans one request: the query to send, the work to finish afterwards, whether the whole result is needed, and which parts stayed behind.</td></tr>
|
|
2514
|
+
<tr><td class="name">applyResidual</td><td class="type">(rows, residual, compute) => unknown[]</td><td class="desc">Applies whatever the engine could not, through the grid's own filter and sort kernels rather than a second implementation, so a residual predicate means exactly what the same predicate means anywhere else.</td></tr>
|
|
2515
|
+
<tr><td class="name">NO_CAPABILITIES</td><td class="type">Readonly<Required<PushdownCapabilities>></td><td class="desc">The set an adapter that declares nothing is treated as having: everything off. Such an adapter still works; the grid simply does all the work.</td></tr>
|
|
2516
|
+
</tbody>
|
|
2517
|
+
</table>
|
|
2518
|
+
</div>
|
|
2519
|
+
<p class="section-note">
|
|
2520
|
+
<strong>Residual work needs the complete result.</strong> <code>applyResidual</code> expects
|
|
2521
|
+
every matching row, not a window. Filtering a window is not a slower route to the right answer,
|
|
2522
|
+
it is a fast route to a wrong one: the rows that belong on page one may sit on page nine.
|
|
2523
|
+
<code>planQuery</code> sets <code>needsAll</code> whenever that applies, and
|
|
2524
|
+
<code>createPushdownSource</code> switches to fetching everything and paging from what it holds.
|
|
2525
|
+
</p>
|
|
2526
|
+
|
|
2368
2527
|
<h3 id="derived-join">Joining two grids</h3>
|
|
2369
2528
|
<p class="section-note">
|
|
2370
2529
|
Two grids each holding their own data, and a third showing where they meet. Orders against
|
|
@@ -3049,7 +3208,7 @@ createGrid(el, {
|
|
|
3049
3208
|
Sorting, filtering, selection, grouping, expanding, paging, undo, paste and a refused edit are all
|
|
3050
3209
|
announced. In Windows High Contrast Mode state is translated into borders and system colours
|
|
3051
3210
|
instead of tints. No information is carried by hue alone.</p>
|
|
3052
|
-
<p>The full keyboard map, the screen reader support statement and the known limits
|
|
3211
|
+
<p>The full keyboard map, the screen reader support statement and the known limits,
|
|
3053
3212
|
including the drag-only pivot zones, are in
|
|
3054
3213
|
<a href="api-detail.html#accessibility-guide">the guide</a>.</p>
|
|
3055
3214
|
|
|
@@ -3582,7 +3741,7 @@ createGrid(el, {
|
|
|
3582
3741
|
<table>
|
|
3583
3742
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
3584
3743
|
<tbody>
|
|
3585
|
-
<tr><td class="name">width</td><td class="type">number | string</td><td class="desc">A pixel width, or a percentage of the grid's inner width as a string
|
|
3744
|
+
<tr><td class="name">width</td><td class="type">number | string</td><td class="desc">A pixel width, or a percentage of the grid's inner width as a string, `'25%'`. A percentage is a share of the *whole* grid. `flex` divides only the space left over after fixed columns, so the two are not interchangeable: `flex: 25` on four columns is a quarter of the remainder, which is a quarter of the grid only when nothing else is fixed. <small>(optional)</small></td></tr>
|
|
3586
3745
|
<tr><td class="name">min</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
3587
3746
|
<tr><td class="name">max</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
3588
3747
|
<tr><td class="name">flex</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -4470,7 +4629,7 @@ createGrid(el, {
|
|
|
4470
4629
|
<tr><td class="name">showHeader</td><td class="type">boolean</td><td class="desc">Draw the column headings at all. `true` by default. `false` removes the row, and removes it from the accessibility tree rather than only from view, a heading a screen reader still announces is invisible, not hidden. What a small dashboard tile wants when its `title` already says what the panel is. Distinct from `showColumnFunctions`, which keeps the headings and drops only the sort, filter and menu controls inside them. <small>(optional)</small></td></tr>
|
|
4471
4630
|
<tr><td class="name">headerHeight</td><td class="type">number</td><td class="desc">Header height in pixels. <small>(optional)</small></td></tr>
|
|
4472
4631
|
<tr><td class="name">overscan</td><td class="type">number</td><td class="desc">How many rows to render beyond the viewport. More costs memory and smooths fast scrolling; fewer is lighter and can show a gap. <small>(optional)</small></td></tr>
|
|
4473
|
-
<tr><td class="name">autoHeight</td><td class="type">boolean | 'visible'</td><td class="desc">Size rows to their content rather than to the density token. Only rows that are actually rendered are ever measured, in both settings
|
|
4632
|
+
<tr><td class="name">autoHeight</td><td class="type">boolean | 'visible'</td><td class="desc">Size rows to their content rather than to the density token. Only rows that are actually rendered are ever measured, in both settings: the grid does not lay out rows you cannot see. The difference is what happens on a large grid: `true` gives up above ten thousand rows and falls back to fixed heights, because a cumulative offset array being patched as you scroll a million rows is not worth the result. `'visible'` keeps measuring at any size, accepting that the scrollbar shifts as rows are measured on the way past. The name is historical and reads as though it were about which rows are measured; it is about whether the ceiling applies. <small>(optional)</small></td></tr>
|
|
4474
4633
|
<tr><td class="name">state</td><td class="type">GridState</td><td class="desc">Sort, filters, grouping, widths and the rest, restored at construction. <small>(optional)</small></td></tr>
|
|
4475
4634
|
<tr><td class="name">licence</td><td class="type">string</td><td class="desc">Your licence key. Without one the grid renders in full and watermarks off localhost. <small>(optional)</small></td></tr>
|
|
4476
4635
|
<tr><td class="name">maximise</td><td class="type">boolean</td><td class="desc">Offer a full-screen control. <small>(optional)</small></td></tr>
|
|
@@ -4979,6 +5138,58 @@ createGrid(el, {
|
|
|
4979
5138
|
</tbody>
|
|
4980
5139
|
</table>
|
|
4981
5140
|
</div>
|
|
5141
|
+
<h3 id="type-PushdownAdapter">PushdownAdapter</h3>
|
|
5142
|
+
<p class="section-note">An engine the grid can query, and what it is able to answer.</p>
|
|
5143
|
+
<div class="table-wrap">
|
|
5144
|
+
<table>
|
|
5145
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5146
|
+
<tbody>
|
|
5147
|
+
<tr><td class="name">name</td><td class="type">string</td><td class="desc">Used in diagnostics and in the message when work cannot be pushed. <small>(optional)</small></td></tr>
|
|
5148
|
+
<tr><td class="name">capabilities</td><td class="type">PushdownCapabilities</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5149
|
+
<tr><td class="name">execute</td><td class="type">(query: RemoteRequest, request?: RemoteRequest):</td><td class="desc">Run the part of the query the adapter declared it could handle.</td></tr>
|
|
5150
|
+
</tbody>
|
|
5151
|
+
</table>
|
|
5152
|
+
</div>
|
|
5153
|
+
<h3 id="type-PushdownCapabilities">PushdownCapabilities</h3>
|
|
5154
|
+
<p class="section-note">What a pushdown adapter can answer. Everything is off unless declared.</p>
|
|
5155
|
+
<div class="table-wrap">
|
|
5156
|
+
<table>
|
|
5157
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5158
|
+
<tbody>
|
|
5159
|
+
<tr><td class="name">filter</td><td class="type">false | 'term' | 'flat' | 'tree'</td><td class="desc">`false`, a single field and term, a flat conjunction, or a full tree. <small>(optional)</small></td></tr>
|
|
5160
|
+
<tr><td class="name">operators</td><td class="type">string[]</td><td class="desc">Which comparison operators the engine understands. <small>(optional)</small></td></tr>
|
|
5161
|
+
<tr><td class="name">sort</td><td class="type">false | 'single' | 'multi'</td><td class="desc">`false`, one column only, or many. <small>(optional)</small></td></tr>
|
|
5162
|
+
<tr><td class="name">quick</td><td class="type">boolean</td><td class="desc">Whether a free-text search across columns can be pushed. <small>(optional)</small></td></tr>
|
|
5163
|
+
<tr><td class="name">range</td><td class="type">boolean</td><td class="desc">Whether the engine can return a window rather than the whole result. <small>(optional)</small></td></tr>
|
|
5164
|
+
<tr><td class="name">total</td><td class="type">boolean</td><td class="desc">Whether it can report the count of matching rows. <small>(optional)</small></td></tr>
|
|
5165
|
+
<tr><td class="name">group</td><td class="type">boolean</td><td class="desc">Whether it can group and aggregate. <small>(optional)</small></td></tr>
|
|
5166
|
+
</tbody>
|
|
5167
|
+
</table>
|
|
5168
|
+
</div>
|
|
5169
|
+
<h3 id="type-PushdownPlan">PushdownPlan</h3>
|
|
5170
|
+
<p class="section-note">How one request was divided between the engine and the grid.</p>
|
|
5171
|
+
<div class="table-wrap">
|
|
5172
|
+
<table>
|
|
5173
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5174
|
+
<tbody>
|
|
5175
|
+
<tr><td class="name">pushed</td><td class="type">RemoteRequest</td><td class="desc">The query the adapter was given.</td></tr>
|
|
5176
|
+
<tr><td class="name">residual</td><td class="type">{ filters: object | null; sort: SortEntry[] | null; quick: string }</td><td class="desc">What the grid applied afterwards.</td></tr>
|
|
5177
|
+
<tr><td class="name">needsAll</td><td class="type">boolean</td><td class="desc">Whether the whole result had to be fetched rather than a window.</td></tr>
|
|
5178
|
+
<tr><td class="name">unpushed</td><td class="type">string[]</td><td class="desc">Which parts could not be pushed: `filter`, `sort`, `quick`.</td></tr>
|
|
5179
|
+
</tbody>
|
|
5180
|
+
</table>
|
|
5181
|
+
</div>
|
|
5182
|
+
<h3 id="type-PushdownSourceConfig">PushdownSourceConfig</h3>
|
|
5183
|
+
<div class="table-wrap">
|
|
5184
|
+
<table>
|
|
5185
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5186
|
+
<tbody>
|
|
5187
|
+
<tr><td class="name">adapter</td><td class="type">PushdownAdapter</td><td class="desc"></td></tr>
|
|
5188
|
+
<tr><td class="name">compute</td><td class="type">object</td><td class="desc">The compute barrel, for applying whatever the engine could not. <small>(optional)</small></td></tr>
|
|
5189
|
+
<tr><td class="name">pageSize</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5190
|
+
</tbody>
|
|
5191
|
+
</table>
|
|
5192
|
+
</div>
|
|
4982
5193
|
<h3 id="type-RailAction">RailAction</h3>
|
|
4983
5194
|
<div class="table-wrap">
|
|
4984
5195
|
<table>
|
|
@@ -5461,7 +5672,7 @@ createGrid(el, {
|
|
|
5461
5672
|
<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>
|
|
5462
5673
|
<tr><td class="name">capability</td><td class="type">(colId: string, opts?: {</td><td class="desc">Process capability against the column's `spec`, with control limits and the Western Electric rule breaks. `baseline` fixes the limits over the first N readings, which is how a shift is found rather than hidden by the limits it widened.</td></tr>
|
|
5463
5674
|
<tr><td class="name">interval</td><td class="type">(colId: string, opts?: {</td><td class="desc">A confidence interval for what a column measures, the range the estimate pins the figure down to, not a verdict about it. Reads the rows the filters left, so an interval narrows as the grid does: it describes the filtered population, not the whole table.</td></tr>
|
|
5464
|
-
<tr><td class="name">series</td><td class="type">(colId: string, opts: { by: string; periodsPerYear?: number }): SeriesStats | null</td><td class="desc">How a column varies along an ordering. `by` is required and never guessed
|
|
5675
|
+
<tr><td class="name">series</td><td class="type">(colId: string, opts: { by: string; periodsPerYear?: number }): SeriesStats | null</td><td class="desc">How a column varies along an ordering. `by` is required and never guessed: kernels see rows in the order they arrived, which is not the grid's sort.</td></tr>
|
|
5465
5676
|
<tr><td class="name">weightedAverage</td><td class="type">(colId: string, weightId: string): number | null</td><td class="desc">A weighted average of one column by another.</td></tr>
|
|
5466
5677
|
<tr><td class="name">keyOf</td><td class="type">(data: unknown): string | null</td><td class="desc">The key a row's data resolves to.</td></tr>
|
|
5467
5678
|
<tr><td class="name">maintenance</td><td class="type">Readonly<Record<string, 'maintained' | 'rescan'>></td><td class="desc">Which reductions can be maintained against a change, and which rescan. <small>(read-only)</small></td></tr>
|
|
@@ -5660,7 +5871,7 @@ createGrid(el, {
|
|
|
5660
5871
|
<!-- END GENERATED TYPE REFERENCE -->
|
|
5661
5872
|
|
|
5662
5873
|
<footer>
|
|
5663
|
-
Lattice Grid 1.12.
|
|
5874
|
+
Lattice Grid 1.12.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
5664
5875
|
This document describes the behaviour of the shipped library. Where this guide and the code
|
|
5665
5876
|
disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
5666
5877
|
</footer>
|
package/docs/CHART-CODES.md
CHANGED
|
@@ -105,7 +105,7 @@ wide panel, letterboxed in a tall one: rather than being stretched to fill.
|
|
|
105
105
|
## Colour
|
|
106
106
|
|
|
107
107
|
Values are shaded with a sequential ramp: monotone lightness, so darker always
|
|
108
|
-
means larger. Pass `diverging: true` for a measure with a meaningful midpoint
|
|
108
|
+
means larger. Pass `diverging: true` for a measure with a meaningful midpoint,
|
|
109
109
|
profit and loss, change against a baseline, which centres the ramp on **zero**
|
|
110
110
|
rather than on the middle of the data, because a neutral colour at the mean
|
|
111
111
|
claims the mean is neutral.
|