@toclocoinc/lattice-grid 1.62.1 → 1.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/API.html +526 -16
- package/docs/CHART-CODES.md +141 -2
- package/docs/api-detail.html +194 -5
- package/lattice-grid.d.ts +235 -5
- package/lattice-grid.esm.min.js +870 -73
- package/lattice-grid.min.cjs +870 -73
- package/lattice-grid.min.js +870 -73
- package/modules/ai.d.ts +1 -1
- package/modules/ai.esm.min.js +13 -16
- package/modules/ai.min.cjs +13 -16
- package/modules/ai.min.js +13 -16
- package/modules/angular.d.ts +1 -1
- package/modules/angular.esm.min.js +2 -2
- package/modules/angular.min.cjs +2 -2
- package/modules/angular.min.js +2 -2
- package/modules/chart-alluvial.d.ts +1 -1
- package/modules/chart-alluvial.esm.min.js +1 -1
- package/modules/chart-alluvial.min.cjs +1 -1
- package/modules/chart-alluvial.min.js +1 -1
- package/modules/chart-arc.d.ts +1 -1
- package/modules/chart-arc.esm.min.js +1 -1
- package/modules/chart-arc.min.cjs +1 -1
- package/modules/chart-arc.min.js +1 -1
- package/modules/chart-bubblemap.d.ts +1 -1
- package/modules/chart-bubblemap.esm.min.js +42 -6
- package/modules/chart-bubblemap.min.cjs +42 -6
- package/modules/chart-bubblemap.min.js +42 -6
- package/modules/chart-bump.d.ts +1 -1
- package/modules/chart-bump.esm.min.js +1 -1
- package/modules/chart-bump.min.cjs +1 -1
- package/modules/chart-bump.min.js +1 -1
- package/modules/chart-calendar.d.ts +1 -1
- package/modules/chart-calendar.esm.min.js +1 -1
- package/modules/chart-calendar.min.cjs +1 -1
- package/modules/chart-calendar.min.js +1 -1
- package/modules/chart-decomposition.d.ts +1 -1
- package/modules/chart-decomposition.esm.min.js +1 -1
- package/modules/chart-decomposition.min.cjs +1 -1
- package/modules/chart-decomposition.min.js +1 -1
- package/modules/chart-diverging.d.ts +1 -1
- package/modules/chart-diverging.esm.min.js +1 -1
- package/modules/chart-diverging.min.cjs +1 -1
- package/modules/chart-diverging.min.js +1 -1
- package/modules/chart-dumbbell.d.ts +1 -1
- package/modules/chart-dumbbell.esm.min.js +1 -1
- package/modules/chart-dumbbell.min.cjs +1 -1
- package/modules/chart-dumbbell.min.js +1 -1
- package/modules/chart-fan.d.ts +1 -1
- package/modules/chart-fan.esm.min.js +1 -1
- package/modules/chart-fan.min.cjs +1 -1
- package/modules/chart-fan.min.js +1 -1
- package/modules/chart-hexbin.d.ts +1 -1
- package/modules/chart-hexbin.esm.min.js +1 -1
- package/modules/chart-hexbin.min.cjs +1 -1
- package/modules/chart-hexbin.min.js +1 -1
- package/modules/chart-hexmap.d.ts +1 -1
- package/modules/chart-hexmap.esm.min.js +46 -6
- package/modules/chart-hexmap.min.cjs +46 -6
- package/modules/chart-hexmap.min.js +46 -6
- package/modules/chart-icicle.d.ts +1 -1
- package/modules/chart-icicle.esm.min.js +1 -1
- package/modules/chart-icicle.min.cjs +1 -1
- package/modules/chart-icicle.min.js +1 -1
- package/modules/chart-parallel.d.ts +1 -1
- package/modules/chart-parallel.esm.min.js +1 -1
- package/modules/chart-parallel.min.cjs +1 -1
- package/modules/chart-parallel.min.js +1 -1
- package/modules/chart-ridgeline.d.ts +1 -1
- package/modules/chart-ridgeline.esm.min.js +1 -1
- package/modules/chart-ridgeline.min.cjs +1 -1
- package/modules/chart-ridgeline.min.js +1 -1
- package/modules/chart-roc.d.ts +1 -1
- package/modules/chart-roc.esm.min.js +1 -1
- package/modules/chart-roc.min.cjs +1 -1
- package/modules/chart-roc.min.js +1 -1
- package/modules/chart-slope.d.ts +1 -1
- package/modules/chart-slope.esm.min.js +1 -1
- package/modules/chart-slope.min.cjs +1 -1
- package/modules/chart-slope.min.js +1 -1
- package/modules/chart-splom.d.ts +1 -1
- package/modules/chart-splom.esm.min.js +1 -1
- package/modules/chart-splom.min.cjs +1 -1
- package/modules/chart-splom.min.js +1 -1
- package/modules/chart-waffle.d.ts +1 -1
- package/modules/chart-waffle.esm.min.js +1 -1
- package/modules/chart-waffle.min.cjs +1 -1
- package/modules/chart-waffle.min.js +1 -1
- package/modules/charts.d.ts +1 -1
- package/modules/charts.esm.min.js +1917 -662
- package/modules/charts.min.cjs +1917 -662
- package/modules/charts.min.js +1917 -662
- package/modules/data-router.d.ts +1 -1
- package/modules/data-router.esm.min.js +129 -20
- package/modules/data-router.min.cjs +129 -20
- package/modules/data-router.min.js +129 -20
- package/modules/devtools.d.ts +1 -1
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.d.ts +1 -1
- package/modules/dhtmlx-compat.esm.min.js +4 -16
- package/modules/dhtmlx-compat.min.cjs +4 -16
- package/modules/dhtmlx-compat.min.js +4 -16
- package/modules/gantt.d.ts +1 -1
- package/modules/gantt.esm.min.js +187 -82
- package/modules/gantt.min.cjs +187 -82
- package/modules/gantt.min.js +187 -82
- package/modules/geo-europe-nuts.d.ts +16 -0
- package/modules/geo-europe-nuts.esm.min.js +29 -0
- package/modules/geo-uk.d.ts +18 -0
- package/modules/geo-uk.esm.min.js +29 -0
- package/modules/geo-us-states.d.ts +16 -0
- package/modules/geo-us-states.esm.min.js +29 -0
- package/modules/geo-world-110m.d.ts +17 -0
- package/modules/geo-world-110m.esm.min.js +29 -0
- package/modules/geo-world-50m.d.ts +16 -0
- package/modules/geo-world-50m.esm.min.js +29 -0
- package/modules/htmx.d.ts +1 -1
- package/modules/htmx.esm.min.js +870 -73
- package/modules/htmx.min.cjs +870 -73
- package/modules/htmx.min.js +870 -73
- package/modules/kanban.d.ts +1 -1
- package/modules/kanban.esm.min.js +4 -16
- package/modules/kanban.min.cjs +4 -16
- package/modules/kanban.min.js +4 -16
- package/modules/kpi.d.ts +1 -1
- package/modules/kpi.esm.min.js +76 -28
- package/modules/kpi.min.cjs +76 -28
- package/modules/kpi.min.js +76 -28
- package/modules/layout.d.ts +1 -1
- package/modules/layout.esm.min.js +4 -16
- package/modules/layout.min.cjs +4 -16
- package/modules/layout.min.js +4 -16
- package/modules/mock-socket.d.ts +1 -1
- package/modules/mock-socket.esm.min.js +2 -2
- package/modules/mock-socket.min.cjs +2 -2
- package/modules/mock-socket.min.js +2 -2
- package/modules/react.d.ts +308 -3
- package/modules/react.esm.min.js +1072 -22
- package/modules/react.min.cjs +1056 -21
- package/modules/react.min.js +1056 -21
- package/modules/svelte.d.ts +1 -1
- package/modules/svelte.esm.min.js +2 -2
- package/modules/svelte.min.cjs +2 -2
- package/modules/svelte.min.js +2 -2
- package/modules/tabs.d.ts +1 -1
- package/modules/tabs.esm.min.js +4 -16
- package/modules/tabs.min.cjs +4 -16
- package/modules/tabs.min.js +4 -16
- package/modules/vue.d.ts +1 -1
- package/modules/vue.esm.min.js +2 -2
- package/modules/vue.min.cjs +2 -2
- package/modules/vue.min.js +2 -2
- package/modules/webcomponent.d.ts +1 -1
- package/modules/webcomponent.esm.min.js +870 -73
- package/modules/webcomponent.min.cjs +870 -73
- package/modules/webcomponent.min.js +870 -73
- package/package.json +1 -1
package/docs/CHART-CODES.md
CHANGED
|
@@ -63,7 +63,42 @@ hundred bytes. They are *schematic, not cartographic*: recognisable, the right
|
|
|
63
63
|
shape for "which continent is biggest", and not a basemap. Nothing should be
|
|
64
64
|
measured off them.
|
|
65
65
|
|
|
66
|
-
**Country outlines
|
|
66
|
+
**Country outlines ship as optional packs** (1.63). A geometry pack is a module a
|
|
67
|
+
page imports only if it draws that map, so the charts bundle carries none of
|
|
68
|
+
them and the grid still fetches nothing at runtime. Each is TopoJSON — quantised
|
|
69
|
+
and delta-encoded — generated from a published public source by
|
|
70
|
+
`tools/build-geo-packs.mjs`, and each carries its own source URL, version,
|
|
71
|
+
retrieval date, licence and the attribution line that licence requires.
|
|
72
|
+
|
|
73
|
+
| Module | Regions | Source | Licence | Size (gz) |
|
|
74
|
+
|---|---|---|---|---|
|
|
75
|
+
| `modules/geo-world-110m` | 177 countries | Natural Earth 1:110m Admin 0, via world-atlas | Public domain | 39 KB |
|
|
76
|
+
| `modules/geo-world-50m` | 241 countries | Natural Earth 1:50m Admin 0, via world-atlas | Public domain | 225 KB |
|
|
77
|
+
| `modules/geo-us-states` | 50 states + DC | US Census, via us-atlas | Public domain | 36 KB |
|
|
78
|
+
| `modules/geo-europe-nuts` | NUTS levels 0–2 | Eurostat GISCO NUTS 2021 1:20m | Free re-use **with attribution** | 95 KB |
|
|
79
|
+
| `modules/geo-uk` | 9 regions, 361 local authorities, 650 constituencies | ONS Open Geography (BGC) | **OGL v3.0** | 292 KB |
|
|
80
|
+
|
|
81
|
+
Two of the five require an attribution line, which the pack carries and the map
|
|
82
|
+
renders: GISCO asks for `© EuroGeographics for the administrative boundaries`,
|
|
83
|
+
and the ONS for `Contains OS data © Crown copyright and database right 2026;
|
|
84
|
+
Source: Office for National Statistics licensed under the Open Government
|
|
85
|
+
Licence v.3.0`. The other three are public domain and ask for nothing.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
import { pack } from '@toclocoinc/lattice-grid/modules/geo-world-110m';
|
|
89
|
+
|
|
90
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'iso', y: 'revenue',
|
|
91
|
+
shapes: pack });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Two things the data does rather than we do: Natural Earth at 1:110m omits the
|
|
95
|
+
micro-states (Singapore, Malta, Monaco have no outline at that scale), so bind
|
|
96
|
+
country-level data to the 50m pack where those matter; and `geo-us-states`
|
|
97
|
+
places Alaska and Hawaii at their true longitudes rather than in an Albers-USA
|
|
98
|
+
composite's insets, so a map of all 51 spans the Pacific. The five US
|
|
99
|
+
territories are left out of that pack for the same reason.
|
|
100
|
+
|
|
101
|
+
**A host's own shapes still work.** That is a size decision too: a usable world at
|
|
67
102
|
country resolution is several hundred kilobytes of path data, larger than the
|
|
68
103
|
whole charts module, and it would be paid for by every page that draws a bar
|
|
69
104
|
chart. A country map is drawn from shapes the host supplies, at whatever
|
|
@@ -93,6 +128,28 @@ reported as unplaced rather than rolled up.
|
|
|
93
128
|
|
|
94
129
|
## The projection
|
|
95
130
|
|
|
131
|
+
A map names its projection, and a pack declares the right one for itself, so
|
|
132
|
+
`shapes: pack` alone draws a sensible map. `projection` overrides it and
|
|
133
|
+
`projectionOptions` carries `parallels` and `centre` to the two that take
|
|
134
|
+
them. The drawn geometry is **fitted** to the panel, so a map of one country
|
|
135
|
+
fills its box rather than sitting inside the whole globe's.
|
|
136
|
+
|
|
137
|
+
| Name | What it is | Use it for |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `equalEarth` | Equal-area (Šavrič, Patterson & Jenny 2018) | A world map — the default |
|
|
140
|
+
| `robinson` | Compromise, tabulated | A world map where the poles matter more than area |
|
|
141
|
+
| `mercator` | Conformal, cut at ±85.05° | Matching a web-map basemap |
|
|
142
|
+
| `albers` | Conic equal-area, two standard parallels | A country or continent in the mid-latitudes |
|
|
143
|
+
| `transverseMercator` | Conformal about a central meridian | A tall, narrow country: the UK through 2°W |
|
|
144
|
+
| `equirectangular` | Longitude and latitude onto x and y | Back-compatibility with maps drawn before 1.63 |
|
|
145
|
+
|
|
146
|
+
A ring that crosses the antimeridian — Russia, Fiji, the Chathams — is split
|
|
147
|
+
there before it is projected, so it draws as the parts it is rather than as a
|
|
148
|
+
band across the map. Antarctica is cropped until it carries a value, by the
|
|
149
|
+
country code `AQ` as well as the continent code `AN`.
|
|
150
|
+
|
|
151
|
+
### Without a pack
|
|
152
|
+
|
|
96
153
|
Equirectangular: longitude and latitude map linearly onto x and y. The least
|
|
97
154
|
arithmetic and the most distortion at the poles, which is the right trade for
|
|
98
155
|
shading regions by value: nothing here measures area, and a reader comparing
|
|
@@ -112,7 +169,89 @@ claims the mean is neutral.
|
|
|
112
169
|
|
|
113
170
|
A region with no data is left in the empty colour rather than the ramp's low
|
|
114
171
|
end: "no reading" and "the smallest reading" are different facts and must not
|
|
115
|
-
share a shade.
|
|
172
|
+
share a shade. Over a fitted geometry pack the ramp also stays clear of its own
|
|
173
|
+
palest sixth, so the *lowest real reading* does not fall close enough to that
|
|
174
|
+
empty tone to read as the same thing — a heatmap's ramp is unaffected; its pale
|
|
175
|
+
end is the intended bottom of its own scale.
|
|
176
|
+
|
|
177
|
+
## Rendering and interaction (1.63)
|
|
178
|
+
|
|
179
|
+
A region's stroke comes from `--lattice-chart-map-stroke` (falling back to the
|
|
180
|
+
grid-line token), drawn with `vector-effect: non-scaling-stroke` so zooming in
|
|
181
|
+
does not fatten it. `--lattice-chart-empty` is the fill a region with no
|
|
182
|
+
reading takes.
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
.my-app { --lattice-chart-map-stroke: #b0b8bf; }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`graticule`** draws a lon/lat reference grid under the regions, only over a
|
|
189
|
+
fitted geometry pack:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'iso', y: 'sales',
|
|
193
|
+
shapes: pack, graticule: true }); // every 30°, or { step: 10 }
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**`labels: true`** names the regions that carry a reading — largest value
|
|
197
|
+
first, so on a crowded map the labels that survive a collision are the ones
|
|
198
|
+
worth having. Off by default, like every other chart's data labels, and only
|
|
199
|
+
over a fitted pack: the schematic continents already name themselves in the
|
|
200
|
+
legend and the tooltip.
|
|
201
|
+
|
|
202
|
+
**A pack's attribution** — the licence line `geo-europe-nuts` and `geo-uk`
|
|
203
|
+
require — is drawn under the map automatically; the three public-domain packs
|
|
204
|
+
draw none.
|
|
205
|
+
|
|
206
|
+
**The panel's height follows its width** when a host does not fix one: the
|
|
207
|
+
plot reserves an `aspect-ratio` from the geometry's own natural shape, so a
|
|
208
|
+
`width: 480px` container with no height gets a box shaped like the map rather
|
|
209
|
+
than however tall the surrounding layout happened to leave it.
|
|
210
|
+
|
|
211
|
+
**Pan, wheel-zoom and reset** work over a fitted pack, by pointer and by
|
|
212
|
+
keyboard: drag to pan, wheel to zoom, Shift+arrow to pan from the keyboard,
|
|
213
|
+
`+`/`-` to zoom, `0` to reset. Plain arrow keys are left alone — they move
|
|
214
|
+
keyboard focus between regions, the same roving-tabindex behaviour every
|
|
215
|
+
Lattice chart's marks have. Zoom is centred on the map's own fitted view
|
|
216
|
+
rather than on the pointer: panning and zooming need no projection's inverse
|
|
217
|
+
this way, and none of the six projections below has one written.
|
|
218
|
+
|
|
219
|
+
### One example per pack
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
import { pack as world } from '@toclocoinc/lattice-grid/modules/geo-world-110m';
|
|
223
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'iso', y: 'sales',
|
|
224
|
+
shapes: world }); // Equal Earth, the pack's own default
|
|
225
|
+
|
|
226
|
+
import { pack as world50 } from '@toclocoinc/lattice-grid/modules/geo-world-50m';
|
|
227
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'iso', y: 'sales',
|
|
228
|
+
shapes: world50 }); // adds the micro-states 110m omits
|
|
229
|
+
|
|
230
|
+
import { pack as us } from '@toclocoinc/lattice-grid/modules/geo-us-states';
|
|
231
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'state', y: 'sales',
|
|
232
|
+
shapes: us }); // Albers; joins FIPS, USPS or ISO
|
|
233
|
+
|
|
234
|
+
import { pack as nuts } from '@toclocoinc/lattice-grid/modules/geo-europe-nuts';
|
|
235
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'region', y: 'sales',
|
|
236
|
+
shapes: nuts }); // NUTS 0-2 in one pack; joins by NUTS id
|
|
237
|
+
|
|
238
|
+
import { pack as uk } from '@toclocoinc/lattice-grid/modules/geo-uk';
|
|
239
|
+
createChart({ grid, container: '#map', type: 'geomap', code: 'authority', y: 'sales',
|
|
240
|
+
shapes: uk, layer: 'local-authorities' }); // regions | local-authorities | constituencies
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Against a bubble map or a hexbin map
|
|
244
|
+
|
|
245
|
+
`chart-bubblemap` and `chart-hexmap` take `shapes` too (1.63): the same pack, the
|
|
246
|
+
same projection, the outlines drawn faintly underneath for context. Without
|
|
247
|
+
`shapes`, both are unchanged — the points' own bounding box, scaled to the plot.
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
import '@toclocoinc/lattice-grid/modules/chart-bubblemap';
|
|
251
|
+
import { pack as us } from '@toclocoinc/lattice-grid/modules/geo-us-states';
|
|
252
|
+
createChart({ grid, container: '#map', type: 'bubblemap', lon: 'lng', lat: 'lat',
|
|
253
|
+
size: 'sales', shapes: us });
|
|
254
|
+
```
|
|
116
255
|
|
|
117
256
|
---
|
|
118
257
|
|
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.63.1</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -554,7 +554,7 @@
|
|
|
554
554
|
<a href="API.html">reference tables</a> are the shorter version for when you already know.
|
|
555
555
|
</p>
|
|
556
556
|
<p class="chips">
|
|
557
|
-
<span class="chip">Version 1.
|
|
557
|
+
<span class="chip">Version 1.63.1</span>
|
|
558
558
|
<span class="chip">Zero dependencies</span>
|
|
559
559
|
<span class="chip">No build step</span>
|
|
560
560
|
</p>
|
|
@@ -813,6 +813,96 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
813
813
|
nothing leaks.</p>
|
|
814
814
|
</div>
|
|
815
815
|
|
|
816
|
+
<div class="example">
|
|
817
|
+
<p class="example__label">React: every viewer, not only the grid (1.63)</p>
|
|
818
|
+
<pre><code><span class="kw">import</span> React <span class="kw">from</span> 'react';
|
|
819
|
+
<span class="kw">import</span> ReactDOM <span class="kw">from</span> 'react-dom';
|
|
820
|
+
<span class="kw">import</span> { createGrid } <span class="kw">from</span> '@toclocoinc/lattice-grid';
|
|
821
|
+
<span class="kw">import</span> { createKPI } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/kpi';
|
|
822
|
+
<span class="kw">import</span> { createChart } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/charts';
|
|
823
|
+
<span class="kw">import</span> { createTabs } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/tabs';
|
|
824
|
+
<span class="kw">import</span> { createDataRouter } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/data-router';
|
|
825
|
+
<span class="kw">import</span> { createLatticeReact } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/react';
|
|
826
|
+
|
|
827
|
+
<span class="cmt">// createLatticeReact builds whichever components the factories you pass support.</span>
|
|
828
|
+
<span class="cmt">// The individual factories are there too, if you want only one:</span>
|
|
829
|
+
<span class="cmt">// createLatticeGrid, createLatticeKPI, createLatticeChart, createLatticeKanban,</span>
|
|
830
|
+
<span class="cmt">// createLatticeGantt, createLatticeLayout, createLatticeTabs,</span>
|
|
831
|
+
<span class="cmt">// createLatticeGridContext, createLatticeRouter, createLatticeViewer.</span>
|
|
832
|
+
<span class="kw">const</span> L = createLatticeReact({
|
|
833
|
+
React, ReactDOM, createGrid, createKPI, createChart, createTabs, createDataRouter,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
<span class="kw">function</span> Dashboard({ rows, updates }) {
|
|
837
|
+
<span class="kw">const</span> router = L.useLatticeRouter(ROUTER_CONFIG);
|
|
838
|
+
<span class="kw">const</span> [tab, setTab] = React.useState('all');
|
|
839
|
+
<span class="kw">return</span> (
|
|
840
|
+
<L.LatticeRouterProvider router={router}>
|
|
841
|
+
<L.LatticeGridProvider>
|
|
842
|
+
<L.LatticeTabs
|
|
843
|
+
active={tab}
|
|
844
|
+
onTabChanged={(e) => setTab(e.id)}
|
|
845
|
+
tabs={[
|
|
846
|
+
{ id: 'all', label: 'All',
|
|
847
|
+
content: <L.LatticeGrid name="all" route="all" {...CONFIG} rowUpdates={updates} /> },
|
|
848
|
+
{ id: 'big', label: 'Significant',
|
|
849
|
+
content: () => <L.LatticeGrid name="big" route="significant" {...CONFIG} /> },
|
|
850
|
+
]}
|
|
851
|
+
/>
|
|
852
|
+
<L.LatticeKPI gridName="all" tiles={TILES} columns={5} />
|
|
853
|
+
<L.LatticeChart gridName="all" type="bar" x="region" y="count" onClick={pick} />
|
|
854
|
+
</L.LatticeGridProvider>
|
|
855
|
+
</L.LatticeRouterProvider>
|
|
856
|
+
);
|
|
857
|
+
}</code></pre>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="why">
|
|
860
|
+
<p><strong>Every viewer is a component now.</strong> Before 1.63 the adapter wrapped
|
|
861
|
+
<code>createGrid</code> and nothing else, so a React application wrote its own
|
|
862
|
+
<code>useEffect</code> for the KPI panel, for each chart, for the board, for the Gantt — and
|
|
863
|
+
its own tab strip, because the tabs module builds its tabs' grids itself and a grid built that
|
|
864
|
+
way is not a component. <code>createLatticeKPI</code>, <code>createLatticeChart</code>,
|
|
865
|
+
<code>createLatticeKanban</code>, <code>createLatticeGantt</code>,
|
|
866
|
+
<code>createLatticeLayout</code> and <code>createLatticeTabs</code> each return one component;
|
|
867
|
+
<code>createLatticeViewer</code> is the generic behind them, for anything not yet named, and
|
|
868
|
+
<code>createViewerController</code> is the framework-free lifecycle underneath.</p>
|
|
869
|
+
<p><strong>A viewer finds its grid through context, not a ref.</strong> A KPI panel and a chart
|
|
870
|
+
are built <em>against</em> a grid instance, and that instance does not exist until after the
|
|
871
|
+
first render. Writing to a ref re-renders nobody, so a sibling never learns the grid arrived.
|
|
872
|
+
<code>createLatticeGridContext</code> returns
|
|
873
|
+
<code>{ LatticeGridProvider, useLatticeGrid }</code>: a grid publishes itself under its
|
|
874
|
+
<code>name</code>, a viewer takes it by <code>gridName</code>, and the viewer is built the
|
|
875
|
+
moment it appears. A page with one grid names nothing.</p>
|
|
876
|
+
<p><strong>A tab's content is yours.</strong> <code>createLatticeTabs</code> keeps the module's
|
|
877
|
+
tablist semantics, keyboard handling, lazy first mount and live badges, and renders each tab's
|
|
878
|
+
React <code>content</code> into the module's own panel element through a portal — so a tab's
|
|
879
|
+
grid is a real <code><LatticeGrid></code> with props, a ref and the surrounding context.
|
|
880
|
+
A tab with no <code>content</code> is left to the module, so both kinds mix on one strip.</p>
|
|
881
|
+
<p><strong>The router is owned by a hook.</strong> <code>createLatticeRouter</code> returns
|
|
882
|
+
<code>useLatticeRouter</code>, which creates the router in an effect and destroys it in that
|
|
883
|
+
effect's cleanup — never in a <code>useState</code> initialiser, which React calls more than
|
|
884
|
+
once by design and whose discarded copy is never destroyed. It returns <code>null</code> on
|
|
885
|
+
the first render and the router on the second. <code><LatticeGrid route="…"></code>
|
|
886
|
+
attaches to whichever router <code>LatticeRouterProvider</code> published, and detaches
|
|
887
|
+
<em>before</em> the grid is destroyed so the router never holds a dead grid.</p>
|
|
888
|
+
<p><strong>A live feed needs no ref.</strong> <code>rowUpdates</code> is a keyed diff handed to
|
|
889
|
+
<code>grid.rows.apply()</code> whenever the object's identity changes.
|
|
890
|
+
<code>predicates</code> maps <code>{ name: fn }</code> to
|
|
891
|
+
<code>grid.filters.where(name, fn)</code>, which composes with whatever filter the reader set
|
|
892
|
+
— unlike the <code>filters</code> prop, which replaces the whole tree.</p>
|
|
893
|
+
<p><strong>What is a live prop is declared, not guessed.</strong> The grid takes any changed
|
|
894
|
+
configuration key through one call; no other viewer does. So each viewer lists the props it
|
|
895
|
+
can take while running (<code>VIEWER_APPLY</code>) and everything else is mount-time
|
|
896
|
+
configuration. A mount-time prop that changes is neither ignored nor silently remounted: it is
|
|
897
|
+
named once, with the remedy — a <code>key</code> that changes when you want the rebuild, or
|
|
898
|
+
the ref. <code>VIEWER_EVENTS</code> and <code>viewerHandlerName</code> are the matching event
|
|
899
|
+
surface (<code>card:move</code> becomes <code>onCardMove</code>), and
|
|
900
|
+
<code>DEFAULT_GRID_NAME</code> is the name a grid publishes under when you choose none.</p>
|
|
901
|
+
<p><strong>React 18 and 19, client only.</strong> Nothing uses an API added in 19 or removed in
|
|
902
|
+
19. Every component owns a real DOM element, so there is no SSR and no React Server Component
|
|
903
|
+
support.</p>
|
|
904
|
+
</div>
|
|
905
|
+
|
|
816
906
|
<div class="example">
|
|
817
907
|
<p class="example__label">Vue 3</p>
|
|
818
908
|
<pre><code><span class="kw">import</span> * <span class="kw">as</span> vue <span class="kw">from</span> 'vue';
|
|
@@ -1279,7 +1369,7 @@ off(); <span class="cmt">// every subscrip
|
|
|
1279
1369
|
</p>
|
|
1280
1370
|
<div class="example">
|
|
1281
1371
|
<p class="example__label">Which version am I running?</p>
|
|
1282
|
-
<pre><code>grid.getVersion(); <span class="cmt">// '1.
|
|
1372
|
+
<pre><code>grid.getVersion(); <span class="cmt">// '1.63.1'</span>
|
|
1283
1373
|
LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
|
|
1284
1374
|
</div>
|
|
1285
1375
|
<p class="lead-in">
|
|
@@ -1704,8 +1794,23 @@ format: { <span class="cmt">// when the short
|
|
|
1704
1794
|
negative: 'parentheses', <span class="cmt">// (£1,234.50)</span>
|
|
1705
1795
|
negativeClass: 'is-loss',
|
|
1706
1796
|
nullDisplay: ', ',
|
|
1707
|
-
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
format: { decimals: 0, signed: true } <span class="cmt">// +5, 0, -5</span></code></pre>
|
|
1708
1800
|
</div>
|
|
1801
|
+
<p class="lead-in">
|
|
1802
|
+
<code>signed</code> puts a leading <code>+</code> on a positive number, currency or percent
|
|
1803
|
+
value. Zero gets no sign either way — it is neither positive nor negative — and a negative
|
|
1804
|
+
value is entirely unaffected: it still renders however <code>negative</code> says
|
|
1805
|
+
(<code>'minus'</code> by default). Off by default, so an existing column's negatives-only
|
|
1806
|
+
look never changes underneath it.
|
|
1807
|
+
</p>
|
|
1808
|
+
<p class="lead-in">
|
|
1809
|
+
A key in <code>format</code> that the resolved type does not read — a typo, or a key that
|
|
1810
|
+
belongs to a different <code>format</code> shape, such as <code>signed</code> written on a
|
|
1811
|
+
text column — is never silently dropped. It warns once, by column and key name, instead of
|
|
1812
|
+
doing nothing.
|
|
1813
|
+
</p>
|
|
1709
1814
|
|
|
1710
1815
|
<h3>Lookups</h3>
|
|
1711
1816
|
<p class="lead-in">
|
|
@@ -2256,6 +2361,19 @@ grid.rows.expandAll();
|
|
|
2256
2361
|
grid.rows.collapse('EMEA');</code></pre>
|
|
2257
2362
|
</div>
|
|
2258
2363
|
|
|
2364
|
+
<p class="lead-in">
|
|
2365
|
+
<strong>Expand all</strong> and <strong>Collapse all</strong> are on the menu too, once the
|
|
2366
|
+
grid is grouped: the data area's own right-click menu and every column's header menu (the
|
|
2367
|
+
3-dot button and a right-click on the heading alike) carry both, driving
|
|
2368
|
+
<code>grid.rows.expandAll()</code>/<code>collapseAll()</code> — the same public API a host
|
|
2369
|
+
calls directly (BACKLOG-0001305). They are hidden, not disabled, on an ungrouped grid: there
|
|
2370
|
+
is nothing for either to act on. Both flow through the same <code>contextMenu</code>/
|
|
2371
|
+
<code>columnMenu</code> customisation chain as every other built-in item, so a host filtering
|
|
2372
|
+
or extending the menu sees them as ordinary items — matched, like every other built-in, by
|
|
2373
|
+
their translated <code>name</code> (catalogue keys <code>menu.expandAll</code> and
|
|
2374
|
+
<code>menu.collapseAll</code>, the same ones the generated group column's own header menu
|
|
2375
|
+
already used — see <a href="#custom-menu">your own menu items</a>).
|
|
2376
|
+
</p>
|
|
2259
2377
|
<p class="lead-in">
|
|
2260
2378
|
<code>groupPanel: true</code> adds a drag-and-drop strip above the column header — the
|
|
2261
2379
|
row-group panel. A user drags a heading into it to group by that column; the active
|
|
@@ -3550,6 +3668,52 @@ createGrid(el, {
|
|
|
3550
3668
|
with no server involved, including a time-window filter on its <code>TIMESTAMP</code> column.</p>
|
|
3551
3669
|
</div>
|
|
3552
3670
|
|
|
3671
|
+
<h3 id="duckdb-range-reads-guide">Whether a Parquet file streams or downloads whole</h3>
|
|
3672
|
+
<p class="lead-in">
|
|
3673
|
+
<code>duckdbAdapter</code> writes SQL and hands it to the connection you built; it never opens
|
|
3674
|
+
the file itself, so it has no say in whether <code>read_parquet(...)</code> reads the whole
|
|
3675
|
+
thing or only the bytes a query needs. That is decided by DuckDB's own configuration and by the
|
|
3676
|
+
HTTP server the file is served from.
|
|
3677
|
+
</p>
|
|
3678
|
+
<div class="example">
|
|
3679
|
+
<p class="example__label">Enable range reads before the first query</p>
|
|
3680
|
+
<pre><code><span class="kw">const</span> db = <span class="kw">await</span> makeDuckDB(); <span class="cmt">// yours: @duckdb/duckdb-wasm</span>
|
|
3681
|
+
<span class="kw">const</span> conn = <span class="kw">await</span> db.connect();
|
|
3682
|
+
|
|
3683
|
+
<span class="cmt">// Before the first read_parquet(...) call, on DuckDB-Wasm 1.32 and later:</span>
|
|
3684
|
+
<span class="kw">await</span> conn.query(<span class="str">'LOAD httpfs;'</span>);
|
|
3685
|
+
|
|
3686
|
+
createGrid(el, {
|
|
3687
|
+
columns,
|
|
3688
|
+
source: createPushdownSource({
|
|
3689
|
+
adapter: duckdbAdapter({
|
|
3690
|
+
connection: conn,
|
|
3691
|
+
from: <span class="str">"read_parquet('https://example.com/readings.parquet')"</span>,
|
|
3692
|
+
}),
|
|
3693
|
+
}),
|
|
3694
|
+
});</code></pre>
|
|
3695
|
+
</div>
|
|
3696
|
+
<div class="why">
|
|
3697
|
+
<p>Measured against a 1.5 MB Parquet file on GitHub Pages (BACKLOG-0001324): without
|
|
3698
|
+
<code>LOAD httpfs;</code>, DuckDB-Wasm 1.32.0's default HTTP path issued zero Range requests
|
|
3699
|
+
and read the whole file — <strong>100%</strong> — for every query, chip filters included. With
|
|
3700
|
+
it loaded on the connection first, the same query issued 25 Range requests and read
|
|
3701
|
+
<strong>30%</strong> of the file. DuckDB-Wasm 1.29.0 read 119% of the file on the same test
|
|
3702
|
+
(its ranges overlapped), so treat the exact percentages as a version's behaviour, not a fixed
|
|
3703
|
+
promise — re-measure against the DuckDB build you ship.</p>
|
|
3704
|
+
<p>The file host has to cooperate too: it must answer <code>HEAD</code>, advertise
|
|
3705
|
+
<code>Accept-Ranges: bytes</code>, and answer a range request with <code>206 Partial
|
|
3706
|
+
Content</code> and a <code>Content-Range</code> header (GitHub Pages does all three). Cross-
|
|
3707
|
+
origin, its CORS policy must also expose <code>Content-Range</code> and
|
|
3708
|
+
<code>Content-Length</code>, or the browser cannot read them back. Any of that missing, and
|
|
3709
|
+
DuckDB falls back to a full read with nothing said.</p>
|
|
3710
|
+
<p>A file registered with <code>db.registerFileBuffer(...)</code> is always downloaded whole,
|
|
3711
|
+
whatever version is loaded or however the host answers: a buffer has already been fetched in
|
|
3712
|
+
full before DuckDB ever opens it, so there is nothing left to range-read. There is no
|
|
3713
|
+
<code>force_download</code> option — it does not exist in DuckDB-Wasm; registering a buffer
|
|
3714
|
+
is the way to force a whole download.</p>
|
|
3715
|
+
</div>
|
|
3716
|
+
|
|
3553
3717
|
<h3 id="fulldataset">Whole-dataset statistics over a remote source</h3>
|
|
3554
3718
|
<p class="lead-in">
|
|
3555
3719
|
A windowed source reduces a total or statistic over the rows it has loaded, not the whole
|
|
@@ -3910,6 +4074,31 @@ detail.destroy();
|
|
|
3910
4074
|
} }</code> works today, and so do <code>median</code>, <code>stddev</code>, <code>gini</code>,
|
|
3911
4075
|
<code>iqr</code>, <code>entropy</code>, <code>trimmedMean</code> and the rest.
|
|
3912
4076
|
<code>statistics</code> is for what <code>select</code> structurally cannot reach.</p>
|
|
4077
|
+
<p id="statistics-coverage"><strong>Say whether the figure is approximate.</strong> Every
|
|
4078
|
+
statistics row carries <code>n</code>, the rows the figure was computed over — but
|
|
4079
|
+
<code>n</code> alone cannot tell you whether that was <em>all</em> of them. Over a windowed
|
|
4080
|
+
parent it quietly is not: a stream with <code>maxRows: 200</code> that has had 2,000 rows
|
|
4081
|
+
through it computes over the 200 it is holding, and the parent's
|
|
4082
|
+
<code>rows.count()</code>, <code>rows.matchCount()</code> and <code>rows.totalCount()</code>
|
|
4083
|
+
all report 200 as well, so nothing in those numbers says a thing has been left out. Ask the
|
|
4084
|
+
parent grid instead: <code>parent.rows.coverage()</code> returns
|
|
4085
|
+
<code>{ covered, total, windowed }</code>, and <strong><code>covered < total</code>, or
|
|
4086
|
+
<code>total === null</code>, means the figure is approximate</strong>. For that stream it
|
|
4087
|
+
reads <code>{ covered: 200, total: 2000, windowed: true }</code> — the 1,800 rows that
|
|
4088
|
+
have aged out are visible there and nowhere else. <code>total</code> is <code>null</code>,
|
|
4089
|
+
never a guess, when the source genuinely cannot know: a stream still open that has evicted
|
|
4090
|
+
nothing has no idea how many rows are coming. A memory parent reads
|
|
4091
|
+
<code>{ covered: n, total: n, windowed: false }</code>, so the check costs nothing to leave
|
|
4092
|
+
in and stays quiet when there is nothing to disclose.</p>
|
|
4093
|
+
<pre data-run="js" data-expect="true" data-covers="method:rows"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
4094
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
4095
|
+
rowKey: <span class="str">'id'</span>,
|
|
4096
|
+
columns: [{ field: <span class="str">'id'</span> }, { field: <span class="str">'amount'</span>, type: <span class="str">'number'</span> }],
|
|
4097
|
+
rows: [{ id: 1, amount: 10 }, { id: 2, amount: 20 }],
|
|
4098
|
+
});
|
|
4099
|
+
<span class="kw">const</span> { covered, total, windowed } = grid.rows.coverage();
|
|
4100
|
+
<span class="cmt">// A memory grid is exact: the figure covers everything there is.</span>
|
|
4101
|
+
<span class="kw">return</span> covered === total && windowed === <span class="kw">false</span>;</code></pre>
|
|
3913
4102
|
<div class="example">
|
|
3914
4103
|
<p class="example__label">Which columns move together</p>
|
|
3915
4104
|
<pre><code>source: {
|
|
@@ -9188,7 +9377,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
|
|
|
9188
9377
|
|
|
9189
9378
|
<footer>
|
|
9190
9379
|
<p>
|
|
9191
|
-
Lattice Grid 1.
|
|
9380
|
+
Lattice Grid 1.63.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
9192
9381
|
Written against the shipped source. Where this guide and the code disagree, the code wins,
|
|
9193
9382
|
please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
9194
9383
|
</p>
|