ixmaps-gl 0.2.0 → 0.2.3
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 +10 -7
- package/ixmaps-gl.js +103 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ circle/square/diamond/triangle. See the top-of-file comment in
|
|
|
72
72
|
A page's `.require(url)` scripts (page code, e.g. a user chart function) load in order before the
|
|
73
73
|
layers are built. `ixmaps.map()` is the map handle as in flat — its calls wait until the map is ready —
|
|
74
74
|
with `add(theme, flags)` / `replace` / `replaceTheme` / `remove`, `changeThemeStyle` for any style key,
|
|
75
|
-
`getZoom()` (flat's zoom), `resize()` and `
|
|
75
|
+
`getZoom()` (flat's zoom), `resize()`, `setBasemapOpacity` and `setMapType(id)` (also `setMapTypeId`, `mapType`: a basemap name reading dark/black/night/matter loads CARTO Dark Matter, any other name CARTO Positron, a color a plain background — the same rule the `mapType` option follows at start); the page's `htmlgui_onNewTheme(id)` and
|
|
76
76
|
`htmlgui_onZoomAndPan()` (also once on load) are called (as in flat, a theme swapped in with `replace` comes last
|
|
77
77
|
in the legend, while FEATURE and CHOROPLETH shapes always draw under the charts), and `map`, `getZoom` and flat's
|
|
78
78
|
`formatValue` exist as globals. `.data({name})` without a URL waits for data of that name: from
|
|
@@ -155,8 +155,9 @@ ones, `replace` swaps themes by `style.name`/`meta.name`, `themeonly`/`maponly`/
|
|
|
155
155
|
work as in flat. Themes in flat's older shape (data source in `style.dbtable*`, value field at
|
|
156
156
|
the top level) are translated first. It resolves to `{ themes, skipped, notes }`: a theme
|
|
157
157
|
that can't load is skipped with a warning, the rest still load. **Code a project names is not
|
|
158
|
-
run by default** — `required` scripts, `ext` data scripts and a project's `process`
|
|
159
|
-
reported, not executed (only a page's own
|
|
158
|
+
run by default** — `required` scripts, `ext` data scripts and a project's `process` and `query`
|
|
159
|
+
functions and function-string `colorscheme`s are reported, not executed (only a page's own
|
|
160
|
+
`.data({process, query})` and colorscheme functions run) — and the basemap isn't switched.
|
|
160
161
|
|
|
161
162
|
A page can opt in to a project's **scripts** (`data.ext`) for script URLs under prefixes it lists —
|
|
162
163
|
no built-in prefixes, and a project file's own `options.trustedscripts` is ignored. Both of flat's
|
|
@@ -181,7 +182,9 @@ script. For brokers that query by view, like flat's bbox data providers, ixmaps-
|
|
|
181
182
|
`ixmaps.getBoundingBox()`, `ixmaps.refreshTheme(id)` (loads the theme's data again, in place),
|
|
182
183
|
`ixmaps.setTitle(html)`/`setTitleBox(text, color)`, `ixmaps.getThemeObj(id).fVisible` (false for a
|
|
183
184
|
FEATURE theme out of scale) and calls a page's `ixmaps.htmlgui_onZoomAndPan(zoom)` after each zoom
|
|
184
|
-
or pan
|
|
185
|
+
or pan — and also during a zoom, at the pace of the layer refresh (about every 150 ms, zoom changes only),
|
|
186
|
+
so zoom-dependent page state (e.g. a basemap opacity ramp) changes in step with the scale gates
|
|
187
|
+
(`featurelower`, `chartupper`, …) instead of after the gesture has ended. Scripts that reach further into flat's internals (`ixmaps.parentApi`, other `htmlgui_*`
|
|
185
188
|
hooks) aren't supported. `test/project-report.mjs` shows which themes
|
|
186
189
|
of your own project files gl can render.
|
|
187
190
|
|
|
@@ -190,7 +193,7 @@ of your own project files gl can render.
|
|
|
190
193
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
191
194
|
|
|
192
195
|
```html
|
|
193
|
-
<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.3/ixmaps-gl.js"></script>
|
|
194
197
|
```
|
|
195
198
|
|
|
196
199
|
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
@@ -198,13 +201,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
|
|
|
198
201
|
browser script, which defines the global `ixmaps`):
|
|
199
202
|
|
|
200
203
|
```html
|
|
201
|
-
<script src="https://unpkg.com/ixmaps-gl@0.2.
|
|
204
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.3/ixmaps-gl.js"></script>
|
|
202
205
|
```
|
|
203
206
|
|
|
204
207
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
205
208
|
|
|
206
209
|
```html
|
|
207
|
-
<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.3/ixmaps-gl.js"></script>
|
|
208
211
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
209
212
|
|
|
210
213
|
<script>
|
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.3';
|
|
84
84
|
|
|
85
85
|
const LIB_URLS = {
|
|
86
86
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
@@ -1720,7 +1720,7 @@
|
|
|
1720
1720
|
// htmlgui_flat.js). Its methods act on the last built map; called while
|
|
1721
1721
|
// the map is still building, they run once it is ready.
|
|
1722
1722
|
const MAP_HANDLE_METHODS = ['replace', 'add', 'remove', 'removeTheme', 'replaceTheme', 'changeThemeStyle', 'setThemeStyle',
|
|
1723
|
-
'refreshTheme', 'setBasemapOpacity', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible'];
|
|
1723
|
+
'refreshTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible'];
|
|
1724
1724
|
const _mapHandle = {};
|
|
1725
1725
|
for (const m of MAP_HANDLE_METHODS) {
|
|
1726
1726
|
_mapHandle[m] = (...args) => {
|
|
@@ -2148,6 +2148,36 @@
|
|
|
2148
2148
|
dbtable: 'name', dbtableUrl: 'url', dbtableType: 'type', dbtableExt: 'ext',
|
|
2149
2149
|
dbtableProcess: 'process', dbtableQuery: 'query', dbtableObj: 'obj', datacache: 'cache',
|
|
2150
2150
|
};
|
|
2151
|
+
// Every project key the engine would turn into code (new Function)
|
|
2152
|
+
// is kept out, never run — a page's own .data({process, query}) and
|
|
2153
|
+
// .style({colorscheme: fn.toString()}) still run. Project keys of
|
|
2154
|
+
// that kind: the data processing function (data.process /
|
|
2155
|
+
// style.dbtableProcess), the broker query (data.query /
|
|
2156
|
+
// style.dbtableQuery — without it a data.name still reaches a
|
|
2157
|
+
// provider the PAGE defined as ixmaps[name]) and a colorscheme given
|
|
2158
|
+
// as a string, which resolveColorScheme evaluates as a function
|
|
2159
|
+
// (an array colorscheme is data and stays).
|
|
2160
|
+
const PROJECT_CODE_KEYS = [
|
|
2161
|
+
['data', 'process', 'data.process function'],
|
|
2162
|
+
['style', 'dbtableProcess', 'data.process function'],
|
|
2163
|
+
['data', 'query', 'data.query function'],
|
|
2164
|
+
['style', 'dbtableQuery', 'data.query function'],
|
|
2165
|
+
['style', 'colorscheme', 'colorscheme function', v => typeof v === 'string'],
|
|
2166
|
+
];
|
|
2167
|
+
function withoutProjectCode(t, report) {
|
|
2168
|
+
const found = PROJECT_CODE_KEYS.filter(([part, key, , isCode]) =>
|
|
2169
|
+
t[part] && t[part][key] != null && (!isCode || isCode(t[part][key])));
|
|
2170
|
+
if (!found.length) return t;
|
|
2171
|
+
const copy = Object.assign({}, t);
|
|
2172
|
+
found.forEach(([part, key]) => {
|
|
2173
|
+
if (copy[part] === t[part]) copy[part] = Object.assign({}, t[part]);
|
|
2174
|
+
delete copy[part][key];
|
|
2175
|
+
});
|
|
2176
|
+
const what = [...new Set(found.map(f => f[2]))].join(', ');
|
|
2177
|
+
report.notes.push(`theme "${t.layer}": the project's ${what} is never run by ixmaps-gl`);
|
|
2178
|
+
return copy;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2151
2181
|
function projectThemeToDefinition(theme) {
|
|
2152
2182
|
const t = theme || {};
|
|
2153
2183
|
const style = Object.assign({}, t.style);
|
|
@@ -2418,6 +2448,19 @@
|
|
|
2418
2448
|
const named = NAMED_MAPTYPE_COLORS[t.toLowerCase()];
|
|
2419
2449
|
return named || null;
|
|
2420
2450
|
}
|
|
2451
|
+
// A basemap NAME (flat's mapType: "VT_TONER_LITE", "VT_DATAVIZ_DARK",
|
|
2452
|
+
// "CartoDB - Dark matter", ...) picks one of two keyless CARTO vector
|
|
2453
|
+
// styles: dark for a name reading dark/black/night/matter, otherwise
|
|
2454
|
+
// light (Positron, gl's default). A color mapType (resolveMapTypeColor)
|
|
2455
|
+
// is a plain background instead and wins, so "dark" alone stays the
|
|
2456
|
+
// #1a1a1a background it always was.
|
|
2457
|
+
const BASEMAP_STYLE_URLS = {
|
|
2458
|
+
light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
|
|
2459
|
+
dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
|
|
2460
|
+
};
|
|
2461
|
+
function resolveBasemapStyleUrl(mapType) {
|
|
2462
|
+
return typeof mapType === 'string' && /dark|black|night|matter/i.test(mapType) ? BASEMAP_STYLE_URLS.dark : BASEMAP_STYLE_URLS.light;
|
|
2463
|
+
}
|
|
2421
2464
|
// Real ixmaps-flat's legend look (tools/legend.js 3773-3845, via
|
|
2422
2465
|
// tools/background_theme.js): DARK when the basemap is more than half
|
|
2423
2466
|
// opaque and the map background reads as dark — a map type naming
|
|
@@ -2688,7 +2731,7 @@
|
|
|
2688
2731
|
const map = new maplibregl.Map({
|
|
2689
2732
|
container: this.containerId,
|
|
2690
2733
|
style: mapTypeColor ? buildBlankBackgroundStyle(mapTypeColor)
|
|
2691
|
-
:
|
|
2734
|
+
: resolveBasemapStyleUrl(this.mapOptions.mapType),
|
|
2692
2735
|
center: [lon, lat],
|
|
2693
2736
|
zoom: this._viewZoom != null && this._viewZoom !== '' ? flatToMapLibreZoom(Number(this._viewZoom)) : 8,
|
|
2694
2737
|
// .attribution(a) (MapBuilder, above) was stored but never read —
|
|
@@ -2728,6 +2771,16 @@
|
|
|
2728
2771
|
// broker re-queries its data from it. Read at call time: pages wrap
|
|
2729
2772
|
// it after loading. flat passes its SVG zoom scale; this passes the
|
|
2730
2773
|
// flat zoom level, the one value gl has.
|
|
2774
|
+
//
|
|
2775
|
+
// gl also calls it DURING a zoom (notifyZoomAndPanMidGesture, wired
|
|
2776
|
+
// into the throttled refresh below), zoom changes only: the scale
|
|
2777
|
+
// gates (featurelower/chartupper ...) switch on the live zoom while
|
|
2778
|
+
// the gesture runs, so a page that ties something zoom-dependent to
|
|
2779
|
+
// this hook (e.g. a basemap opacity ramp) must be told in the same
|
|
2780
|
+
// tick — from 'moveend' alone it arrived after the gate had already
|
|
2781
|
+
// flipped (FEATURE gone, basemap still at the old, faint opacity: a
|
|
2782
|
+
// blank background until the zoom ended).
|
|
2783
|
+
let notifyZoomAndPanMidGesture = () => {};
|
|
2731
2784
|
{
|
|
2732
2785
|
let last = null;
|
|
2733
2786
|
map.on('moveend', () => {
|
|
@@ -2739,6 +2792,14 @@
|
|
|
2739
2792
|
if (z === prev.z && Math.abs(p.x - q.x) <= 10 && Math.abs(p.y - q.y) <= 10) return;
|
|
2740
2793
|
try { hook.call(global.ixmaps, mapLibreToFlatZoom(z)); } catch (e) { console.error('[ixmaps-gl] htmlgui_onZoomAndPan:', e); }
|
|
2741
2794
|
});
|
|
2795
|
+
notifyZoomAndPanMidGesture = () => {
|
|
2796
|
+
const hook = pageIxmaps() && pageIxmaps().htmlgui_onZoomAndPan;
|
|
2797
|
+
if (typeof hook !== 'function' || !last) return;
|
|
2798
|
+
const z = map.getZoom();
|
|
2799
|
+
if (z === last.z) return; // a pan alone waits for 'moveend' (and its 10 px rule)
|
|
2800
|
+
last = { z, c: map.getCenter() };
|
|
2801
|
+
try { hook.call(global.ixmaps, mapLibreToFlatZoom(z)); } catch (e) { console.error('[ixmaps-gl] htmlgui_onZoomAndPan:', e); }
|
|
2802
|
+
};
|
|
2742
2803
|
// flat calls it on the first draw too (its old zoom is unset then)
|
|
2743
2804
|
map.once('load', () => {
|
|
2744
2805
|
last = { z: map.getZoom(), c: map.getCenter() };
|
|
@@ -3122,6 +3183,31 @@
|
|
|
3122
3183
|
},
|
|
3123
3184
|
replaceTheme: (id, layerBuilder, flag) => engineApi.replace(id, layerBuilder, flag),
|
|
3124
3185
|
remove: (id) => { removeThemeById(id); return engineApi; },
|
|
3186
|
+
// flat's map.setMapType(id) / setMapTypeId / mapType (htmlgui_flat.js
|
|
3187
|
+
// mapApi): swap the basemap at runtime. MapLibre's setStyle replaces
|
|
3188
|
+
// the whole style; deck.gl's interleaved overlay re-adds its own
|
|
3189
|
+
// layers on 'styledata' by itself. What the new style resets is put
|
|
3190
|
+
// back once it has loaded: the projection (globe), the basemap
|
|
3191
|
+
// opacity (it is written into the style's paint properties).
|
|
3192
|
+
setMapType: (id) => {
|
|
3193
|
+
const color = resolveMapTypeColor(id);
|
|
3194
|
+
const style = color ? buildBlankBackgroundStyle(color) : resolveBasemapStyleUrl(id);
|
|
3195
|
+
const current = color ? null : resolveBasemapStyleUrl(builder.mapOptions.mapType);
|
|
3196
|
+
const wasColor = !!resolveMapTypeColor(builder.mapOptions.mapType);
|
|
3197
|
+
builder.mapOptions.mapType = id;
|
|
3198
|
+
if (!color && !wasColor && style === current) return engineApi; // same style: nothing to reload
|
|
3199
|
+
const projection = typeof map.getProjection === 'function' ? map.getProjection() : null;
|
|
3200
|
+
map.once('style.load', () => {
|
|
3201
|
+
if (projection && projection.type && typeof map.setProjection === 'function') {
|
|
3202
|
+
try { map.setProjection({ type: projection.type }); } catch (e) { /* projection unsupported by this build */ }
|
|
3203
|
+
}
|
|
3204
|
+
applyBasemapOpacity();
|
|
3205
|
+
});
|
|
3206
|
+
map.setStyle(style);
|
|
3207
|
+
return engineApi;
|
|
3208
|
+
},
|
|
3209
|
+
setMapTypeId: (id) => engineApi.setMapType(id),
|
|
3210
|
+
mapType: (id) => engineApi.setMapType(id),
|
|
3125
3211
|
setBasemapOpacity: (delta, mode) => {
|
|
3126
3212
|
_basemapOpacity = mode === 'relative'
|
|
3127
3213
|
? Math.max(0, Math.min(1, _basemapOpacity + (parseFloat(delta) || 0)))
|
|
@@ -3593,19 +3679,6 @@
|
|
|
3593
3679
|
return report;
|
|
3594
3680
|
}
|
|
3595
3681
|
|
|
3596
|
-
// a project file's data processing function is code the project
|
|
3597
|
-
// names — kept out, never run (a page's own .data({process}) runs)
|
|
3598
|
-
function withoutProjectCode(t, report) {
|
|
3599
|
-
const hasData = t.data && t.data.process != null;
|
|
3600
|
-
const hasStyle = t.style && t.style.dbtableProcess != null;
|
|
3601
|
-
if (!hasData && !hasStyle) return t;
|
|
3602
|
-
report.notes.push(`theme "${t.layer}": the project's data.process function is never run by ixmaps-gl`);
|
|
3603
|
-
const copy = Object.assign({}, t);
|
|
3604
|
-
if (hasData) { copy.data = Object.assign({}, t.data); delete copy.data.process; }
|
|
3605
|
-
if (hasStyle) { copy.style = Object.assign({}, t.style); delete copy.style.dbtableProcess; }
|
|
3606
|
-
return copy;
|
|
3607
|
-
}
|
|
3608
|
-
|
|
3609
3682
|
function applyProjectMap(m, f, report) {
|
|
3610
3683
|
const svg = String(m.map || '');
|
|
3611
3684
|
if (svg && !/generic\/(mercator|orthographic)\.svg$/i.test(svg)) report.notes.push(`flat SVG map "${svg.split('/').pop()}" is not available in ixmaps-gl — mercator used`);
|
|
@@ -3878,10 +3951,11 @@
|
|
|
3878
3951
|
const elapsed = now - lastRefreshAt;
|
|
3879
3952
|
if (elapsed >= REFRESH_INTERVAL_MS) {
|
|
3880
3953
|
lastRefreshAt = now;
|
|
3954
|
+
notifyZoomAndPanMidGesture();
|
|
3881
3955
|
refreshLayers();
|
|
3882
3956
|
} else {
|
|
3883
3957
|
clearTimeout(refreshTimer);
|
|
3884
|
-
refreshTimer = setTimeout(() => { lastRefreshAt = Date.now(); refreshLayers(); }, REFRESH_INTERVAL_MS - elapsed);
|
|
3958
|
+
refreshTimer = setTimeout(() => { lastRefreshAt = Date.now(); notifyZoomAndPanMidGesture(); refreshLayers(); }, REFRESH_INTERVAL_MS - elapsed);
|
|
3885
3959
|
}
|
|
3886
3960
|
scheduleNotifyRedraw();
|
|
3887
3961
|
}
|
|
@@ -5247,6 +5321,16 @@
|
|
|
5247
5321
|
return text;
|
|
5248
5322
|
}
|
|
5249
5323
|
|
|
5324
|
+
// Records behind one drawn chart item: a group's per-category record
|
|
5325
|
+
// counts summed, else a Supercluster cluster's point_count, else 1.
|
|
5326
|
+
// recordCounts only exists on RELOCATE groups (groupCoLocated); a plain
|
|
5327
|
+
// grid group (no RELOCATE) carries counts only — read unguarded, BOX|TITLE
|
|
5328
|
+
// without RELOCATE threw on the first cluster and the theme stopped drawing.
|
|
5329
|
+
function groupRecordCount(props) {
|
|
5330
|
+
const perCat = props.recordCounts || props.counts;
|
|
5331
|
+
return perCat ? perCat.reduce((x, c) => x + c, 0) : (props.point_count || 1);
|
|
5332
|
+
}
|
|
5333
|
+
|
|
5250
5334
|
function valuesFontSizePx(radiusPx, text, valueScale) {
|
|
5251
5335
|
return Math.min(radiusPx * 0.8, radiusPx * (3.3 / Math.max(1, text.length))) * (valueScale || 1);
|
|
5252
5336
|
}
|
|
@@ -9345,7 +9429,7 @@
|
|
|
9345
9429
|
let title = null;
|
|
9346
9430
|
if (titleShown) {
|
|
9347
9431
|
const raw = d.properties.raw;
|
|
9348
|
-
const n =
|
|
9432
|
+
const n = groupRecordCount(d.properties);
|
|
9349
9433
|
const text = n > 3 ? '(' + n + ')' : (raw && raw[this.binding.title] != null ? String(raw[this.binding.title]) : '');
|
|
9350
9434
|
if (text) {
|
|
9351
9435
|
const f = titleFontPx;
|
|
@@ -9968,7 +10052,7 @@
|
|
|
9968
10052
|
// engine runs in a Node vm there); deliberately NOT on the ixmaps object
|
|
9969
10053
|
global.__ixmapsGlInternals = {
|
|
9970
10054
|
IXMAPS_GL_VERSION,
|
|
9971
|
-
normalizeTheme, projectThemeToDefinition, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
10055
|
+
normalizeTheme, projectThemeToDefinition, withoutProjectCode, groupRecordCount, resolveBasemapStyleUrl, resolveMapTypeColor, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
9972
10056
|
resolveScriptUrl, isTrustedScriptUrl, loadProcessingScript, loadBrokerData, applyBrokerThemePatch, makeBrokerTheme,
|
|
9973
10057
|
equalIntervalBreaks, quantileBreaks, naturalBreaks, evenStrideSample, jenksBreakValues, partsFromBreakValues, resolvePartsClass,
|
|
9974
10058
|
computeAlphaStats, computeMultiFieldClasses, computeDominantStats, resolveDominantClass, computeComposeColorStats,
|