ixmaps-gl 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/ixmaps-gl.js +146 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ of your own project files gl can render.
|
|
|
193
193
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
194
194
|
|
|
195
195
|
```html
|
|
196
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
196
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.4/ixmaps-gl.js"></script>
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
@@ -201,13 +201,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
|
|
|
201
201
|
browser script, which defines the global `ixmaps`):
|
|
202
202
|
|
|
203
203
|
```html
|
|
204
|
-
<script src="https://unpkg.com/ixmaps-gl@0.2.
|
|
204
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.4/ixmaps-gl.js"></script>
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
208
208
|
|
|
209
209
|
```html
|
|
210
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
210
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.4/ixmaps-gl.js"></script>
|
|
211
211
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
212
212
|
|
|
213
213
|
<script>
|
|
@@ -273,7 +273,7 @@ For checking page files before they run, use the static checker in ixmaps-gramma
|
|
|
273
273
|
| [`mappa_stranieri_30.html`](./stage/mappa_stranieri_30.html) | Symbol shapes, city picker, runtime `changeThemeStyle` filtering, light legend theme |
|
|
274
274
|
| [`roma_incidenti_pericolosita_sidebar_gl.html`](./stage/roma_incidenti_pericolosita_sidebar_gl.html) | AGGREGATE on a gridwidth grid, facets sidebar |
|
|
275
275
|
| [`roma_incidenti_pericolosita_sidebar.html`](./stage/roma_incidenti_pericolosita_sidebar.html) | The same page on the **real** ixmaps-flat engine, kept for side-by-side reference |
|
|
276
|
-
| [`uk_collisions_2023.html`](./stage/uk_collisions_2023.html), [`ixmaps-loader_70.html`](./
|
|
276
|
+
| [`uk_collisions_2023.html`](./stage/uk_collisions_2023.html), [`app/ixmaps-loader_70.html`](./app/ixmaps-loader_70.html) | Additional real-page compatibility tests |
|
|
277
277
|
| [`examples/`](./examples) | Smaller feature-by-feature test pages (choropleth classing modes, bubble ranges, etc.) |
|
|
278
278
|
|
|
279
279
|
Serve any of these with a static file server (they fetch remote data over HTTPS, so
|
package/ixmaps-gl.js
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
// ---------------------------------------------------------------
|
|
81
81
|
// this engine's release (= package.json "version", checked by
|
|
82
82
|
// test/unit/version.test.mjs); ixmaps.glVersion, and logged once at start
|
|
83
|
-
const IXMAPS_GL_VERSION = '0.2.
|
|
83
|
+
const IXMAPS_GL_VERSION = '0.2.4';
|
|
84
84
|
|
|
85
85
|
const LIB_URLS = {
|
|
86
86
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
@@ -306,7 +306,7 @@
|
|
|
306
306
|
// crafted link must not be able to load arbitrary code into the page.
|
|
307
307
|
// ---------------------------------------------------------------
|
|
308
308
|
// v0.1.6: knows this engine's aliases, lookupfield rule, meta↔style, builder methods, project loading
|
|
309
|
-
const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.
|
|
309
|
+
const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.21/dist/validate.mjs';
|
|
310
310
|
// a page may set ixmaps.validate BEFORE loading this script — the
|
|
311
311
|
// `global.ixmaps = {...}` export below would otherwise overwrite it
|
|
312
312
|
const _preloadValidate = global.ixmaps && global.ixmaps.validate;
|
|
@@ -587,6 +587,78 @@
|
|
|
587
587
|
}
|
|
588
588
|
return null;
|
|
589
589
|
}
|
|
590
|
+
// ---------------------------------------------------------------
|
|
591
|
+
// flat's data cache (maptheme.js 2457-2500, themeDataCacheA): data once
|
|
592
|
+
// loaded from a source is reused by every later theme of the map with the
|
|
593
|
+
// same source — data.cache (also style.datacache, see the dbtable* table)
|
|
594
|
+
// defaults to true (fDataCache, maptheme.js 7736); cache: false reloads.
|
|
595
|
+
// The key is the fetch identity PLUS the shape the binding asks for:
|
|
596
|
+
// rowsResult() turns the same CSV rows into a Table (lookup only) or a
|
|
597
|
+
// FeatureCollection (position), so two themes reading one URL differently
|
|
598
|
+
// must not share one result.
|
|
599
|
+
// ---------------------------------------------------------------
|
|
600
|
+
// the data source itself (no shape) — also rt._dataSourceKey, which tells the
|
|
601
|
+
// facet filters which themes share one dataset (setFacetFilter)
|
|
602
|
+
function dataSourceKey(data) {
|
|
603
|
+
const d = data || {};
|
|
604
|
+
return JSON.stringify({ url: d.url, urls: d.urls, type: d.type, query: d.query,
|
|
605
|
+
process: d.process && String(d.process), ext: d.ext, name: d.ext ? d.name : undefined });
|
|
606
|
+
}
|
|
607
|
+
function dataCacheKey(data, binding) {
|
|
608
|
+
const b = binding || {};
|
|
609
|
+
return dataSourceKey(data) + '|' + (b.lookup && !b.position ? 'table' : 'pos:' + (b.position || ''));
|
|
610
|
+
}
|
|
611
|
+
function dataCacheDisabled(dataConfig) {
|
|
612
|
+
const c = dataConfig && dataConfig.cache;
|
|
613
|
+
return c === false || (typeof c === 'string' && c.trim().toLowerCase() === 'false');
|
|
614
|
+
}
|
|
615
|
+
// cache: Map key → Promise; a failed load is not kept (the next theme tries again)
|
|
616
|
+
function cachedLayerData(cache, data, binding, engineOptions, { reload = false } = {}) {
|
|
617
|
+
const key = dataCacheKey(data, binding);
|
|
618
|
+
if (reload || dataCacheDisabled(data) || !cache.has(key)) {
|
|
619
|
+
const p = fetchLayerData(data, binding, engineOptions);
|
|
620
|
+
cache.set(key, p);
|
|
621
|
+
p.catch(() => { if (cache.get(key) === p) cache.delete(key); });
|
|
622
|
+
}
|
|
623
|
+
return { key, sourceKey: dataSourceKey(data), promise: cache.get(key) };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ---------------------------------------------------------------
|
|
627
|
+
// SHOW / ZOOMTO (flat maptheme.js 8714-8740, MapTheme.zoomTo 15664): once
|
|
628
|
+
// a theme is drawn, the map zooms to the extent of its items, then the
|
|
629
|
+
// flag is dropped — once per theme definition, not on every redraw.
|
|
630
|
+
// featuresBounds: [[west, south], [east, north]] of every coordinate, or
|
|
631
|
+
// null when there is none.
|
|
632
|
+
// ---------------------------------------------------------------
|
|
633
|
+
function featuresBounds(features) {
|
|
634
|
+
let w = Infinity, s = Infinity, e = -Infinity, n = -Infinity;
|
|
635
|
+
const visit = c => {
|
|
636
|
+
if (!Array.isArray(c)) return;
|
|
637
|
+
if (typeof c[0] === 'number' && typeof c[1] === 'number') {
|
|
638
|
+
if (!isFinite(c[0]) || !isFinite(c[1]) || Math.abs(c[1]) > 90) return;
|
|
639
|
+
if (c[0] < w) w = c[0]; if (c[0] > e) e = c[0];
|
|
640
|
+
if (c[1] < s) s = c[1]; if (c[1] > n) n = c[1];
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
for (const x of c) visit(x);
|
|
644
|
+
};
|
|
645
|
+
const geom = g => {
|
|
646
|
+
if (!g) return;
|
|
647
|
+
if (g.type === 'GeometryCollection') (g.geometries || []).forEach(geom);
|
|
648
|
+
else visit(g.coordinates);
|
|
649
|
+
};
|
|
650
|
+
for (const f of features || []) geom(f && f.geometry);
|
|
651
|
+
return w === Infinity ? null : [[w, s], [e, n]];
|
|
652
|
+
}
|
|
653
|
+
// the `legend` map option as flat reads it (htmlgui_flat.js 748-794): on whenever given
|
|
654
|
+
// and not false/"false"/0
|
|
655
|
+
function legendIsOn(opt) {
|
|
656
|
+
return opt !== undefined && opt !== null && opt !== false && opt !== 'false' && opt !== 0 && opt !== '0';
|
|
657
|
+
}
|
|
658
|
+
function wantsZoomToExtent(flags) {
|
|
659
|
+
return !!flags && (flags.has('SHOW') || flags.has('ZOOMTO'));
|
|
660
|
+
}
|
|
661
|
+
|
|
590
662
|
function rowsResult(rows, binding) {
|
|
591
663
|
if (binding && binding.lookup && !binding.position) return { type: 'Table', rows };
|
|
592
664
|
const position = (binding && binding.position) || geometryField(rows);
|
|
@@ -1994,7 +2066,7 @@
|
|
|
1994
2066
|
// edit it by hand; `npm run unit` fails when it drifts.
|
|
1995
2067
|
// ---------------------------------------------------------------
|
|
1996
2068
|
// <grammar:binding-aliases>
|
|
1997
|
-
// generated from ixmaps-grammar 0.1.
|
|
2069
|
+
// generated from ixmaps-grammar 0.1.21 (ixmaps-flat 1.0.41, eaaf2b7 2026-09-20) — 52 aliases
|
|
1998
2070
|
const FLAT_BINDING_ALIASES = {
|
|
1999
2071
|
"aggregation": "style.aggregationfield",
|
|
2000
2072
|
"aggregationfield": "style.aggregationfield",
|
|
@@ -2056,7 +2128,7 @@
|
|
|
2056
2128
|
// both — so .style({tooltip}) is a tooltip and .meta({fillopacity}) a style
|
|
2057
2129
|
// property. GENERATED by test/sync-grammar.mjs — do not edit.
|
|
2058
2130
|
// <grammar:meta-keys>
|
|
2059
|
-
// generated from ixmaps-grammar 0.1.
|
|
2131
|
+
// generated from ixmaps-grammar 0.1.21 — 5 meta keys
|
|
2060
2132
|
const FLAT_META_KEYS = ["description","name","snippet","title","tooltip"];
|
|
2061
2133
|
// </grammar:meta-keys>
|
|
2062
2134
|
|
|
@@ -2653,7 +2725,9 @@
|
|
|
2653
2725
|
// ones. Confirmed live as a real, not just theoretical, problem:
|
|
2654
2726
|
// without this, a 3-layer page sharing one >1.2M-row multi-file CSV
|
|
2655
2727
|
// source fetched all 5 files 3 SEPARATE times.
|
|
2656
|
-
|
|
2728
|
+
// one data cache for the map's whole lifetime (flat's themeDataCacheA):
|
|
2729
|
+
// build() and every later defineLayer()/refreshTheme() share it
|
|
2730
|
+
const dataCache = this._dataCache = this._dataCache || new Map();
|
|
2657
2731
|
const runtimes = [];
|
|
2658
2732
|
// themes whose data comes by name (see namedDataTheme) start empty and
|
|
2659
2733
|
// load on their own once the map is built, as in flat, where every
|
|
@@ -2693,9 +2767,9 @@
|
|
|
2693
2767
|
raw = await fetchLayerData(spec.data, spec.binding, this._engineOptions);
|
|
2694
2768
|
cacheKey = 'obj:' + spec.name;
|
|
2695
2769
|
} else {
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
raw = await
|
|
2770
|
+
const cached = cachedLayerData(dataCache, spec.data, spec.binding, this._engineOptions);
|
|
2771
|
+
cacheKey = cached.sourceKey;
|
|
2772
|
+
raw = await cached.promise;
|
|
2699
2773
|
}
|
|
2700
2774
|
const filtered = applyWhereFilter(raw, spec.filter);
|
|
2701
2775
|
const fc = filtered.type === 'Table' ? joinTableFeatures(spec, filtered, runtimes) : filtered;
|
|
@@ -2709,6 +2783,7 @@
|
|
|
2709
2783
|
rt._definition = def; // for refreshTheme
|
|
2710
2784
|
rt._deferredLoad = deferred;
|
|
2711
2785
|
rt._named = named;
|
|
2786
|
+
rt._zoomToPending = wantsZoomToExtent(spec.flags);
|
|
2712
2787
|
runtimes.push(rt);
|
|
2713
2788
|
notifyNewTheme(rt);
|
|
2714
2789
|
_globalThemeRegistry.set(rt.name, rt);
|
|
@@ -2765,6 +2840,10 @@
|
|
|
2765
2840
|
const b = map.getBounds();
|
|
2766
2841
|
return [{ lat: b.getSouth(), lng: b.getWest() }, { lat: b.getNorth(), lng: b.getEast() }];
|
|
2767
2842
|
};
|
|
2843
|
+
// set once by the 'load' event — NOT map.loaded(), which MapLibre turns false again
|
|
2844
|
+
// whenever tiles are loading (after every pan/zoom), so a theme defined then would
|
|
2845
|
+
// wait for a 'load' that never fires again (SHOW / ZOOMTO, see maybeZoomToTheme)
|
|
2846
|
+
let mapHasLoaded = false;
|
|
2768
2847
|
|
|
2769
2848
|
// flat's page hook ixmaps.htmlgui_onZoomAndPan(nZoom), called after
|
|
2770
2849
|
// a zoom or a pan of more than 10 px (mapscript2.js 6445-6454) — a
|
|
@@ -3147,6 +3226,7 @@
|
|
|
3147
3226
|
else map.once('idle', () => map.fire('move'));
|
|
3148
3227
|
};
|
|
3149
3228
|
let addLegendPanel = () => {}; // set up with the legend, below
|
|
3229
|
+
let setLegendOption = () => {}; // map.setLegend(value) — set up with the legend, below
|
|
3150
3230
|
let updateSubTheme = () => {};
|
|
3151
3231
|
let layoutLegends = () => {}; // set up with the legend, below
|
|
3152
3232
|
let setDataLoading = () => {}; // set up with the loads, below
|
|
@@ -3358,10 +3438,14 @@
|
|
|
3358
3438
|
options: (o) => {
|
|
3359
3439
|
if (o && typeof o === 'object') {
|
|
3360
3440
|
Object.assign(builder._engineOptions, o);
|
|
3441
|
+
// read only when the MapLibre map is created — switched here at runtime
|
|
3442
|
+
if ('worldcopies' in o) map.setRenderWorldCopies(o.worldcopies !== false && o.worldcopies !== 'false');
|
|
3361
3443
|
refresh();
|
|
3362
3444
|
}
|
|
3363
3445
|
return engineApi;
|
|
3364
3446
|
},
|
|
3447
|
+
// the `legend` map option at runtime (see setLegendOption)
|
|
3448
|
+
setLegend: (opt) => { setLegendOption(opt); return engineApi; },
|
|
3365
3449
|
layer: (nameOrBuilder) => {
|
|
3366
3450
|
if (typeof nameOrBuilder !== 'string') {
|
|
3367
3451
|
const lb = nameOrBuilder;
|
|
@@ -3413,9 +3497,14 @@
|
|
|
3413
3497
|
for (const rt of targets) {
|
|
3414
3498
|
if (rt._named) { await loadNamedTheme(rt, true); continue; }
|
|
3415
3499
|
const spec = normalizeTheme(rt._definition);
|
|
3416
|
-
|
|
3500
|
+
// flat's refresh reloads, cache or not (maptheme.js 2366: datacache = false);
|
|
3501
|
+
// the fresh result also replaces the cached one for later themes
|
|
3502
|
+
const raw = spec.data && spec.data.obj
|
|
3503
|
+
? await fetchLayerData(spec.data, rt._specBinding, builder._engineOptions)
|
|
3504
|
+
: await cachedLayerData(builder._dataCache, spec.data, rt._specBinding, builder._engineOptions, { reload: true }).promise;
|
|
3417
3505
|
const filtered = applyWhereFilter(raw, spec.filter);
|
|
3418
3506
|
rt.replaceFeatures(filtered.type === 'Table' ? joinTableFeatures(spec, filtered, runtimes) : filtered);
|
|
3507
|
+
maybeZoomToTheme(rt); // a scale-deferred theme gets its first data here
|
|
3419
3508
|
}
|
|
3420
3509
|
if (targets.length) { refresh(); notifyRedraw(); }
|
|
3421
3510
|
}
|
|
@@ -3506,6 +3595,19 @@
|
|
|
3506
3595
|
loadingEl.style.display = 'none';
|
|
3507
3596
|
}
|
|
3508
3597
|
};
|
|
3598
|
+
// SHOW / ZOOMTO: zoom to the theme's extent the first time it has
|
|
3599
|
+
// features on a loaded map (see featuresBounds), then never again for
|
|
3600
|
+
// this definition — as flat drops the flag after its zoomTo()
|
|
3601
|
+
function maybeZoomToTheme(rt) {
|
|
3602
|
+
if (!rt || !rt._zoomToPending || !mapHasLoaded || !runtimes.includes(rt)) return;
|
|
3603
|
+
const bounds = featuresBounds(rt.features);
|
|
3604
|
+
if (!bounds) return; // no data yet (named / deferred): wait for it
|
|
3605
|
+
rt._zoomToPending = false;
|
|
3606
|
+
const [[w, s], [e, n]] = bounds;
|
|
3607
|
+
if (w === e && s === n) map.jumpTo({ center: [w, s], zoom: Math.max(map.getZoom(), 12) });
|
|
3608
|
+
else map.fitBounds(bounds, { padding: 30, animate: false, maxZoom: 16 });
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3509
3611
|
async function loadNamedTheme(rt, force) {
|
|
3510
3612
|
const spec = normalizeTheme(rt._definition);
|
|
3511
3613
|
const name = String(spec.data.name);
|
|
@@ -3523,7 +3625,7 @@
|
|
|
3523
3625
|
table = await w.promise;
|
|
3524
3626
|
}
|
|
3525
3627
|
await built;
|
|
3526
|
-
if (runtimes.includes(rt)) fillNamedTheme(rt, spec, table, patch);
|
|
3628
|
+
if (runtimes.includes(rt)) { fillNamedTheme(rt, spec, table, patch); maybeZoomToTheme(rt); }
|
|
3527
3629
|
} catch (err) {
|
|
3528
3630
|
// a broker that hands nothing over yet is normal in flat (it may
|
|
3529
3631
|
// wait for other data); the theme stays empty until a refresh
|
|
@@ -3604,10 +3706,16 @@
|
|
|
3604
3706
|
let raw;
|
|
3605
3707
|
const deferred = deferredFeatureLoad(spec, map.getZoom());
|
|
3606
3708
|
const named = namedDataTheme(spec);
|
|
3709
|
+
let sourceKey = null;
|
|
3607
3710
|
if (deferred || named) {
|
|
3608
3711
|
raw = { type: 'FeatureCollection', features: [] };
|
|
3609
|
-
} else {
|
|
3712
|
+
} else if (spec.data && spec.data.obj) {
|
|
3713
|
+
// in memory already — nothing to cache (see build())
|
|
3610
3714
|
raw = await fetchLayerData(spec.data, spec.binding, builder._engineOptions);
|
|
3715
|
+
} else {
|
|
3716
|
+
const cached = cachedLayerData(builder._dataCache, spec.data, spec.binding, builder._engineOptions);
|
|
3717
|
+
sourceKey = cached.sourceKey;
|
|
3718
|
+
raw = await cached.promise;
|
|
3611
3719
|
}
|
|
3612
3720
|
const filtered = applyWhereFilter(raw, spec.filter);
|
|
3613
3721
|
const fc = filtered.type === 'Table' ? joinTableFeatures(spec, filtered, runtimes) : filtered;
|
|
@@ -3615,8 +3723,9 @@
|
|
|
3615
3723
|
rt._definition = pdef; // for refreshTheme
|
|
3616
3724
|
rt._deferredLoad = deferred;
|
|
3617
3725
|
rt._named = named;
|
|
3726
|
+
rt._zoomToPending = wantsZoomToExtent(spec.flags);
|
|
3618
3727
|
rt._dataSourceKey = named ? 'name:' + spec.data.name
|
|
3619
|
-
: JSON.stringify({ url: spec.data && spec.data.url, urls: spec.data && spec.data.urls, type: spec.data && spec.data.type, query: spec.data && spec.data.query, obj: !!(spec.data && spec.data.obj) });
|
|
3728
|
+
: sourceKey || JSON.stringify({ url: spec.data && spec.data.url, urls: spec.data && spec.data.urls, type: spec.data && spec.data.type, query: spec.data && spec.data.query, obj: !!(spec.data && spec.data.obj) });
|
|
3620
3729
|
runtimes.push(rt);
|
|
3621
3730
|
_globalThemeRegistry.set(rt.name, rt);
|
|
3622
3731
|
if (rt.meta && rt.meta.name && rt.meta.name !== rt.name) _globalThemeRegistry.set(rt.meta.name, rt);
|
|
@@ -3624,6 +3733,7 @@
|
|
|
3624
3733
|
notifyNewTheme(rt);
|
|
3625
3734
|
refresh();
|
|
3626
3735
|
notifyRedraw();
|
|
3736
|
+
maybeZoomToTheme(rt); // before the map's 'load': its handler zooms pending themes
|
|
3627
3737
|
if (named) { listenNamedData(rt); if (!deferred) loadNamedTheme(rt, false); }
|
|
3628
3738
|
return rt.name;
|
|
3629
3739
|
}
|
|
@@ -3930,8 +4040,10 @@
|
|
|
3930
4040
|
map.on('moveend', loadDeferredThemes);
|
|
3931
4041
|
|
|
3932
4042
|
map.on('load', () => {
|
|
4043
|
+
mapHasLoaded = true;
|
|
3933
4044
|
loadDeferredThemes();
|
|
3934
4045
|
refresh();
|
|
4046
|
+
runtimes.forEach(maybeZoomToTheme);
|
|
3935
4047
|
hideSplash(splash);
|
|
3936
4048
|
const initialOpacity = parseFloat(this._engineOptions.basemapopacity);
|
|
3937
4049
|
if (!isNaN(initialOpacity)) { _basemapOpacity = Math.max(0, Math.min(1, initialOpacity)); applyBasemapOpacity(); }
|
|
@@ -4041,8 +4153,9 @@
|
|
|
4041
4153
|
// isn't false/"false" ("true", "open", "", 1, …), folded for
|
|
4042
4154
|
// "closed", off when not given at all.
|
|
4043
4155
|
const legendOpt = this.mapOptions.legend;
|
|
4044
|
-
|
|
4045
|
-
|
|
4156
|
+
// let, not const: map.setLegend(value) switches them at runtime (see setLegendOption)
|
|
4157
|
+
let legendOn = legendIsOn(legendOpt);
|
|
4158
|
+
let legendFolded = legendOpt === 'closed';
|
|
4046
4159
|
{
|
|
4047
4160
|
// Map-level `align` option positions the legend panel — a map-
|
|
4048
4161
|
// wide placement choice, not per-theme (unlike legendtheme/
|
|
@@ -4159,6 +4272,23 @@
|
|
|
4159
4272
|
el.parentElement.appendChild(legendStack);
|
|
4160
4273
|
return legendStack;
|
|
4161
4274
|
};
|
|
4275
|
+
// map.setLegend(value): the `legend` map option at runtime, same values as
|
|
4276
|
+
// ixmaps.Map(id, {legend}) — on ("true", "open", …), folded ("closed"), off
|
|
4277
|
+
// (false/"false"/0/undefined). Panels are made for themes that lack one,
|
|
4278
|
+
// removed when off, folded/unfolded otherwise.
|
|
4279
|
+
setLegendOption = (opt) => {
|
|
4280
|
+
builder.mapOptions.legend = opt;
|
|
4281
|
+
legendFolded = opt === 'closed';
|
|
4282
|
+
legendOn = legendIsOn(opt);
|
|
4283
|
+
runtimes.forEach(rt => {
|
|
4284
|
+
if (!legendOn) {
|
|
4285
|
+
if (rt._legendPanel && rt._legendPanel.parentNode) rt._legendPanel.parentNode.removeChild(rt._legendPanel);
|
|
4286
|
+
rt._legendPanel = null;
|
|
4287
|
+
} else if (!rt._legendPanel) addLegendPanel(rt);
|
|
4288
|
+
else if (rt._setLegendCollapsed) rt._setLegendCollapsed(legendFolded);
|
|
4289
|
+
});
|
|
4290
|
+
layoutLegends();
|
|
4291
|
+
};
|
|
4162
4292
|
layoutLegends = () => {
|
|
4163
4293
|
if (!legendStack) return;
|
|
4164
4294
|
const z = map.getZoom();
|
|
@@ -4553,6 +4683,7 @@
|
|
|
4553
4683
|
}
|
|
4554
4684
|
collapseBtn.addEventListener('click', () => { collapsed = !collapsed; applyCollapsed(); });
|
|
4555
4685
|
applyCollapsed();
|
|
4686
|
+
rt._setLegendCollapsed = c => { collapsed = !!c; applyCollapsed(); }; // setLegendOption
|
|
4556
4687
|
|
|
4557
4688
|
// Selection/filter by field — opt-in via .style({legendfilter:
|
|
4558
4689
|
// "<field>"}), e.g. "country_long" on the power-plants sample.
|
|
@@ -10062,6 +10193,7 @@
|
|
|
10062
10193
|
fetchLayerData, parseCsvText, dataTableRows, geometryRowsToFeatureCollection, filterFlatValues, applyField100, field100Binding, rangeClassLegendTotals, resolveAggregateValue, classValueSeparate, cellAggregatedValues, oneHot, groupCoLocated, aggregateOnGrid, GridAggregateIndex, flatRangeParts, aggregateField100, formatBubbleValue, valueRadius, itemPlotGeometry, itemAnchor, objectZoomFactor, resolveZoomReference, defaultNormalSizeValue,
|
|
10063
10194
|
applyWhereFilter, joinChartPositions, flatLookupKey, flatShapeCenter, chartHiddenByScale, featuresHiddenByScale, boxHiddenByScale, flatShadowOn, snapToAggregationGrid,
|
|
10064
10195
|
flatFormatValue,
|
|
10196
|
+
dataCacheKey, dataCacheDisabled, cachedLayerData, featuresBounds, wantsZoomToExtent, legendIsOn,
|
|
10065
10197
|
};
|
|
10066
10198
|
global.__setFilter = __setFilter;
|
|
10067
10199
|
global.__removeFacets = __removeFacets;
|