ixmaps-gl 0.2.14 → 0.2.16
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 +38 -3
- package/ixmaps-gl.js +418 -202
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,41 @@ an id wins, a polygon's position is flat's shape center (the vertex mean in Merc
|
|
|
101
101
|
`chartlower` (else `layerupper`/`layerlower`) hide a theme outside their scales; `.filter()` takes
|
|
102
102
|
flat's grammar (`WHERE a > 5 AND b NOT x`, `LIKE`, `IN`, `BETWEEN`, `$field$`, or a plain regex).
|
|
103
103
|
|
|
104
|
+
### Handle API
|
|
105
|
+
|
|
106
|
+
The main interface is flat's handle chain: `ixmaps.map()` — or `ixmaps.map("name")` for the map
|
|
107
|
+
with that `name` in its `Map()` options, so several maps on one page can be addressed separately
|
|
108
|
+
(no name: the last built map, as in flat). From the map handle, `map("name").theme("themename")`
|
|
109
|
+
returns the theme handle (flat's `themeApi`):
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
ixmaps.map("map1").theme("mytheme").changeStyle("scale:1.2", "factor");
|
|
113
|
+
ixmaps.map().theme("mytheme").show(); // also hide(), toggle(), remove(),
|
|
114
|
+
// replace(theme), markClass(n), unmarkClass(n)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Projection changes never need the project machinery: `map().setProjection("globe")` (or `"mercator"`)
|
|
118
|
+
swaps the MapLibre projection and rebuilds the projection-dependent layers — themes, data and the
|
|
119
|
+
current view are never touched (unlike `setProjectJSON`, which re-defines every theme to change
|
|
120
|
+
the projection and, without `"keepview"`, resets the view).
|
|
121
|
+
|
|
122
|
+
`CHART|USER` themes (a page's own `userdraw` chart function) render as live SVG in a pane over
|
|
123
|
+
the map, as flat renders them — vector-crisp at every zoom, labels as real text, following every
|
|
124
|
+
pan via transform-only updates (≈3ms for 8,000 charts). Picking/tooltips run on a transparent
|
|
125
|
+
deck layer at the same positions.
|
|
126
|
+
|
|
127
|
+
The historic global forms remain as compatibility wrappers and dispatch on arity: the canonical
|
|
128
|
+
theme-first `ixmaps.changeThemeStyle(themeId, "style:value", "set")` and `ixmaps.removeTheme(id)`
|
|
129
|
+
(htmlgui.js), the handle method `map().changeThemeStyle(theme, style, flag)`, and the older
|
|
130
|
+
map-first pages calling `ixmaps.changeThemeStyle(null|mapName, themeId, style, flag)` /
|
|
131
|
+
`ixmaps.removeTheme(null|mapName, id)` / `ixmaps.setBasemapOpacity(null|mapName, opacity, mode)`
|
|
132
|
+
— where a registered map name targets that map and a null or unknown name the last built one.
|
|
133
|
+
The `ixmaps.Map(div, opts, callback)` callback receives the builder for its own
|
|
134
|
+
`.options().view().layer()` chain, which also carries flat's mapApi handle methods
|
|
135
|
+
(`setView`, `replace`, `show`, `getThemes`, … — htmlgui_flat.js), each delegated to the handle
|
|
136
|
+
of that map's options name — so a page that keeps the callback object as its map handle,
|
|
137
|
+
as flat pages do, works unmodified.
|
|
138
|
+
|
|
104
139
|
### Zoom levels
|
|
105
140
|
|
|
106
141
|
Zoom numbers in the ixmaps API are ixmaps-flat's, i.e. Leaflet's (256px tiles):
|
|
@@ -208,7 +243,7 @@ of your own project files gl can render.
|
|
|
208
243
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
209
244
|
|
|
210
245
|
```html
|
|
211
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
246
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.16/ixmaps-gl.js"></script>
|
|
212
247
|
```
|
|
213
248
|
|
|
214
249
|
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
@@ -216,13 +251,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
|
|
|
216
251
|
browser script, which defines the global `ixmaps`):
|
|
217
252
|
|
|
218
253
|
```html
|
|
219
|
-
<script src="https://unpkg.com/ixmaps-gl@0.2.
|
|
254
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.16/ixmaps-gl.js"></script>
|
|
220
255
|
```
|
|
221
256
|
|
|
222
257
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
223
258
|
|
|
224
259
|
```html
|
|
225
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
260
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.16/ixmaps-gl.js"></script>
|
|
226
261
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
227
262
|
|
|
228
263
|
<script>
|
package/ixmaps-gl.js
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
// ---------------------------------------------------------------
|
|
83
83
|
// this engine's release (= package.json "version", checked by
|
|
84
84
|
// test/unit/version.test.mjs); ixmaps.glVersion, and logged once at start
|
|
85
|
-
const IXMAPS_GL_VERSION = '0.2.
|
|
85
|
+
const IXMAPS_GL_VERSION = '0.2.16';
|
|
86
86
|
|
|
87
87
|
const LIB_URLS = {
|
|
88
88
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
@@ -1411,7 +1411,7 @@
|
|
|
1411
1411
|
const key = String(id), area = geometryArea(f.geometry);
|
|
1412
1412
|
if (!flatShapeWins(best.get(key), area)) continue;
|
|
1413
1413
|
const pos = flatShapeCenter(f.geometry);
|
|
1414
|
-
if (pos) best.set(key, { area, pos });
|
|
1414
|
+
if (pos) best.set(key, { area, pos, areaKm2: flatShapeAreaKm2(rt, f) });
|
|
1415
1415
|
}
|
|
1416
1416
|
}
|
|
1417
1417
|
rt._positionIndex = best;
|
|
@@ -1445,7 +1445,11 @@
|
|
|
1445
1445
|
const v = row[lookupField];
|
|
1446
1446
|
if (v == null || v === '') continue;
|
|
1447
1447
|
const hit = index.get(flatLookupKey(v, spec.style));
|
|
1448
|
-
|
|
1448
|
+
// the joined feature is a POINT — DENSITY (value per km² of the
|
|
1449
|
+
// joined shape, as flat) needs the shape's km² area carried along
|
|
1450
|
+
// (featureAreaKm2 prefers this marker), exactly as the choropleth
|
|
1451
|
+
// join stamps it
|
|
1452
|
+
if (hit) features.push({ type: 'Feature', geometry: { type: 'Point', coordinates: hit.pos }, properties: row, _flatAreaKm2: hit.areaKm2 });
|
|
1449
1453
|
}
|
|
1450
1454
|
}
|
|
1451
1455
|
return { type: 'FeatureCollection', features };
|
|
@@ -1951,6 +1955,10 @@
|
|
|
1951
1955
|
// per-map handle — most real pages only ever build one map anyway. Set
|
|
1952
1956
|
// once per successful build(), just before it returns engineApi below.
|
|
1953
1957
|
let _lastMapApi = null;
|
|
1958
|
+
// flat's map instance registry (htmlgui_flat.js 2416): the built maps by
|
|
1959
|
+
// their Map() options name — ixmaps.map("name") is the handle of THAT
|
|
1960
|
+
// map, ixmaps.map() stays the last built one
|
|
1961
|
+
const _mapApis = Object.create(null);
|
|
1954
1962
|
// resolves with the first built map's API: ixmaps.map() calls a page
|
|
1955
1963
|
// makes while the map is still building wait for it
|
|
1956
1964
|
let _resolveMapReady;
|
|
@@ -2006,16 +2014,57 @@
|
|
|
2006
2014
|
// the user chart's size unit: flat's normalX(15) = 300 is the chart's
|
|
2007
2015
|
// normal radius (maxSize), shown at gl's normal bubble radius
|
|
2008
2016
|
const USER_CHART_MAX_SIZE = 300;
|
|
2009
|
-
//
|
|
2010
|
-
|
|
2017
|
+
// ---- the pane USER charts render into: one per map, live over the canvas ----
|
|
2018
|
+
// flat draws its user charts as LIVE SVG over the map — vector-crisp at
|
|
2019
|
+
// every zoom, labels as real text. The first port here rasterized every
|
|
2020
|
+
// chart into one texture atlas instead, and a page with thousands of
|
|
2021
|
+
// charts got ~50 px per chart (the atlas area is fixed) — icons scaled
|
|
2022
|
+
// up several times over: blur, mushy gradients, unreadable labels. In
|
|
2023
|
+
// this pane each chart is a <g> mounted once; a map 'move' only updates
|
|
2024
|
+
// its transform (translate + scale), which the browser composites —
|
|
2025
|
+
// the same thing flat's own engine does over Leaflet, at the same item
|
|
2026
|
+
// counts. Keyed by the map's options object, so runtimes defined later
|
|
2027
|
+
// (loadProject, post-build map.layer) find it too.
|
|
2028
|
+
const _chartHosts = new WeakMap();
|
|
2029
|
+
function chartHostFor(mapOptions, map, el) {
|
|
2030
|
+
let host = _chartHosts.get(mapOptions);
|
|
2031
|
+
if (host) return host;
|
|
2032
|
+
const pane = document.createElementNS(SVG_NS, 'svg');
|
|
2033
|
+
pane.setAttribute('class', 'ix-user-charts');
|
|
2034
|
+
pane.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:3;';
|
|
2035
|
+
const defs = document.createElementNS(SVG_NS, 'defs');
|
|
2036
|
+
pane.appendChild(defs);
|
|
2037
|
+
el.appendChild(pane);
|
|
2038
|
+
host = { pane, defs, map, el, runtimes: new Set(), raf: 0 };
|
|
2039
|
+
host.requestUpdate = () => {
|
|
2040
|
+
if (host.raf) return;
|
|
2041
|
+
host.raf = global.requestAnimationFrame(() => { host.raf = 0; host.update(); });
|
|
2042
|
+
};
|
|
2043
|
+
// transform-only per move: positions follow the map live; the charts'
|
|
2044
|
+
// own markup is redrawn on the settled-refresh cadence (freezeOnPan)
|
|
2045
|
+
host.update = () => {
|
|
2046
|
+
const w = el.clientWidth, h = el.clientHeight, margin = 256;
|
|
2047
|
+
for (const rt of host.runtimes) {
|
|
2048
|
+
const items = rt._chartItems;
|
|
2049
|
+
if (!items) continue;
|
|
2050
|
+
for (const it of items) {
|
|
2051
|
+
const p = map.project(it.coords);
|
|
2052
|
+
const off = p.x < -margin || p.y < -margin || p.x > w + margin || p.y > h + margin;
|
|
2053
|
+
if (off) { if (it.el.style.display !== 'none') it.el.style.display = 'none'; continue; }
|
|
2054
|
+
if (it.el.style.display === 'none') it.el.style.display = '';
|
|
2055
|
+
it.el.setAttribute('transform', `translate(${p.x} ${p.y}) scale(${it.s}) translate(${-it.ox} ${-it.oy})`);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
map.on('move', host.requestUpdate);
|
|
2060
|
+
map.on('resize', host.requestUpdate);
|
|
2061
|
+
_chartHosts.set(mapOptions, host);
|
|
2062
|
+
return host;
|
|
2063
|
+
}
|
|
2011
2064
|
// ?ixgl-debug in the page URL: ixmaps-gl logs what it draws (console.info)
|
|
2012
2065
|
function glDebug() {
|
|
2013
2066
|
try { return new URLSearchParams(global.location.search).has('ixgl-debug'); } catch (e) { return false; }
|
|
2014
2067
|
}
|
|
2015
|
-
// the pixel area all of a layer's chart images may take together: deck.gl
|
|
2016
|
-
// packs them into one texture (1024 px wide), which must stay well below
|
|
2017
|
-
// the GPU's texture size limit — see _userChartIcon
|
|
2018
|
-
const USER_CHART_ATLAS_AREA = 1024 * 8192;
|
|
2019
2068
|
class LayerBuilder {
|
|
2020
2069
|
constructor(name) {
|
|
2021
2070
|
this.name = name;
|
|
@@ -2110,11 +2159,24 @@
|
|
|
2110
2159
|
define() { return this; }
|
|
2111
2160
|
}
|
|
2112
2161
|
|
|
2162
|
+
// flat-era pages keep the layer construct and CALL it like a function
|
|
2163
|
+
// when handing it back to the map later (censimenti's sidebar:
|
|
2164
|
+
// map().add(__theme(),'clear')) — the call returns the builder; pages also
|
|
2165
|
+
// chain builder methods right on the factory result, so the construct
|
|
2166
|
+
// delegates every one of them (chaining stays on the construct). Every
|
|
2167
|
+
// consumption point unwraps __ixLayer, so the object form keeps working.
|
|
2113
2168
|
function layer(name, configFn) {
|
|
2114
2169
|
const builder = new LayerBuilder(name);
|
|
2115
2170
|
if (configFn) configFn(builder);
|
|
2116
|
-
return builder;
|
|
2171
|
+
const construct = function () { return builder; };
|
|
2172
|
+
construct.__ixLayer = builder;
|
|
2173
|
+
for (const k of Object.getOwnPropertyNames(LayerBuilder.prototype)) {
|
|
2174
|
+
if (k === 'constructor') continue;
|
|
2175
|
+
construct[k] = function (...args) { const r = builder[k](...args); return r === builder ? construct : r; };
|
|
2176
|
+
}
|
|
2177
|
+
return construct;
|
|
2117
2178
|
}
|
|
2179
|
+
const unwrapLayer = x => (typeof x === 'function' && x.__ixLayer) ? x.__ixLayer : x;
|
|
2118
2180
|
|
|
2119
2181
|
// ---------------------------------------------------------------
|
|
2120
2182
|
// Binding aliases. Real ixmaps-flat accepts ~50 .binding() keys and maps
|
|
@@ -2871,8 +2933,20 @@
|
|
|
2871
2933
|
if (__hoverTooltipWatch || !el.parentElement) return;
|
|
2872
2934
|
const host = el.parentElement;
|
|
2873
2935
|
const attach = (tipEl) => {
|
|
2936
|
+
// deck nests its tooltip inside its widget root (.fill, z-index 2),
|
|
2937
|
+
// a stacking context under the USER-chart pane (z 3) — the tooltip's
|
|
2938
|
+
// own z-index can never cross that. Moved out to the map container,
|
|
2939
|
+
// a sibling of the pane, where z-index 7 puts it above everything;
|
|
2940
|
+
// same coordinate origin, deck keeps updating the same node.
|
|
2941
|
+
// Re-asserted on every style write (deck sets z-index 1 itself).
|
|
2942
|
+
const liftTooltip = () => {
|
|
2943
|
+
if (tipEl.parentNode !== el) el.appendChild(tipEl);
|
|
2944
|
+
if (tipEl.style.zIndex !== '7') tipEl.style.zIndex = 7;
|
|
2945
|
+
};
|
|
2946
|
+
liftTooltip();
|
|
2874
2947
|
let lastSetTransform = null;
|
|
2875
2948
|
const mo = new MutationObserver(() => {
|
|
2949
|
+
liftTooltip();
|
|
2876
2950
|
if (tipEl.style.display === 'none' || !tipEl.textContent.trim()) return;
|
|
2877
2951
|
const t = tipEl.style.transform;
|
|
2878
2952
|
if (!t || t === lastSetTransform) return;
|
|
@@ -2972,7 +3046,10 @@
|
|
|
2972
3046
|
// touches the tooltip DOM in the same tick.
|
|
2973
3047
|
function hideDefaultHoverTooltip() {
|
|
2974
3048
|
requestAnimationFrame(() => {
|
|
2975
|
-
|
|
3049
|
+
// the hover tooltip is reparented into el (see watchHoverTooltip) —
|
|
3050
|
+
// look there first, then where deck originally nested it
|
|
3051
|
+
const defaultTooltipEl = (el && el.querySelector('.deck-tooltip')) ||
|
|
3052
|
+
(el.parentElement && el.parentElement.querySelector('.deck-tooltip'));
|
|
2976
3053
|
if (defaultTooltipEl) defaultTooltipEl.style.display = 'none';
|
|
2977
3054
|
});
|
|
2978
3055
|
}
|
|
@@ -3063,10 +3140,12 @@
|
|
|
3063
3140
|
function mount() {
|
|
3064
3141
|
map.on('move', updatePinnedTooltipPosition);
|
|
3065
3142
|
watchHoverTooltip();
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3143
|
+
// anchor inside the map container: map.project() gives container-
|
|
3144
|
+
// relative pixels, and styling the page's own body (relative) breaks
|
|
3145
|
+
// any page element absolutely positioned against it (AirBnB's
|
|
3146
|
+
// #onmap-buttons on a zero-height all-float body flew off-screen)
|
|
3147
|
+
el.style.position = el.style.position || 'relative';
|
|
3148
|
+
el.appendChild(pinnedTooltipEl);
|
|
3070
3149
|
}
|
|
3071
3150
|
return { getTooltip, onClick, mount };
|
|
3072
3151
|
}
|
|
@@ -3079,8 +3158,35 @@
|
|
|
3079
3158
|
// calls: addLegendPanel(rt), setLegendOption(value), updateSubTheme(rt),
|
|
3080
3159
|
// layoutLegends().
|
|
3081
3160
|
// ---------------------------------------------------------------
|
|
3161
|
+
// The map-level `align` option positions the legend — BOTH the native
|
|
3162
|
+
// panel stack (createLegend) and a page legend (map.legend(html), the
|
|
3163
|
+
// legendEl built in build()): one helper, so they can never disagree.
|
|
3164
|
+
// gl's four corners, else flat's own values (htmlgui_flat.js 421-470):
|
|
3165
|
+
// "…left" → left (the part before "left", else 55px), 12px from the top;
|
|
3166
|
+
// "…right" → right (else 25px); "center" / "top" → top center.
|
|
3167
|
+
// Default "top-right" per explicit request (real engine's own fallback is
|
|
3168
|
+
// right-anchored too, just without an opinion on vertical placement).
|
|
3169
|
+
function legendAlignCssFor(mapOptions) {
|
|
3170
|
+
const ALIGN_CSS = {
|
|
3171
|
+
'top-left': 'left:10px;top:10px;',
|
|
3172
|
+
'top-right': 'right:10px;top:10px;',
|
|
3173
|
+
'bottom-left': 'left:10px;bottom:10px;',
|
|
3174
|
+
'bottom-right': 'right:10px;bottom:10px;'
|
|
3175
|
+
};
|
|
3176
|
+
const flatAlignCss = align => {
|
|
3177
|
+
const a = String(align || '');
|
|
3178
|
+
// flat hands these to jQuery .css(), which adds px to a unitless
|
|
3179
|
+
// number — a raw cssText needs it explicit
|
|
3180
|
+
const px = v => /^\d+(\.\d+)?$/.test(v) ? v + 'px' : v;
|
|
3181
|
+
if (/left/.test(a)) return 'left:' + px(a.split('left')[0] || '55px') + ';top:12px;';
|
|
3182
|
+
if (/right/.test(a)) return 'right:' + px(a.split('right')[0] || '25px') + ';top:10px;';
|
|
3183
|
+
if (a === 'center' || a === 'top') return 'left:50%;transform:translateX(-50%);top:10px;';
|
|
3184
|
+
return null;
|
|
3185
|
+
};
|
|
3186
|
+
return ALIGN_CSS[mapOptions.align] || flatAlignCss(mapOptions.align) || ALIGN_CSS['top-right'];
|
|
3187
|
+
}
|
|
3082
3188
|
function createLegend(ctx) {
|
|
3083
|
-
const { builder, map, el, overlay, runtimes, engineApi, built, refresh, scheduleRefresh, notifyRedraw } = ctx;
|
|
3189
|
+
const { builder, map, el, overlay, runtimes, engineApi, built, refresh, scheduleRefresh, notifyRedraw, quietRefresh } = ctx;
|
|
3084
3190
|
let addLegendPanel = () => {};
|
|
3085
3191
|
let setLegendOption = () => {};
|
|
3086
3192
|
let updateSubTheme = () => {};
|
|
@@ -3122,6 +3228,10 @@
|
|
|
3122
3228
|
const legendOpt = builder.mapOptions.legend;
|
|
3123
3229
|
// let, not const: map.setLegend(value) switches them at runtime (see setLegendOption)
|
|
3124
3230
|
let legendOn = legendIsOn(legendOpt);
|
|
3231
|
+
// flat: a page legend (map.legend(html) → ixmaps.legend.url) REPLACES
|
|
3232
|
+
// the theme-generated legend — legend.js 2076 renders the layerlist
|
|
3233
|
+
// legend instead (useLayerListLegend) — so the native panels stay off
|
|
3234
|
+
if (builder._legendHtml) legendOn = false;
|
|
3125
3235
|
let legendFolded = legendOpt === 'closed';
|
|
3126
3236
|
{
|
|
3127
3237
|
// Map-level `align` option positions the legend panel — a map-
|
|
@@ -3140,24 +3250,7 @@
|
|
|
3140
3250
|
// shape choice earlier this session. Default "top-right" per
|
|
3141
3251
|
// explicit request (real engine's own fallback is right-anchored
|
|
3142
3252
|
// too, just without an opinion on vertical placement).
|
|
3143
|
-
const
|
|
3144
|
-
'top-left': 'left:10px;top:10px;',
|
|
3145
|
-
'top-right': 'right:10px;top:10px;',
|
|
3146
|
-
'bottom-left': 'left:10px;bottom:10px;',
|
|
3147
|
-
'bottom-right': 'right:10px;bottom:10px;'
|
|
3148
|
-
};
|
|
3149
|
-
// gl's four corners, else flat's own values (htmlgui_flat.js
|
|
3150
|
-
// 421-470): "…left" → left (the part before "left", else 55px), 12px
|
|
3151
|
-
// from the top; "…right" → right (else 25px); "center" / "top" → top
|
|
3152
|
-
// center
|
|
3153
|
-
const flatAlignCss = align => {
|
|
3154
|
-
const a = String(align || '');
|
|
3155
|
-
if (/left/.test(a)) return 'left:' + (a.split('left')[0] || '55px') + ';top:12px;';
|
|
3156
|
-
if (/right/.test(a)) return 'right:' + (a.split('right')[0] || '25px') + ';top:10px;';
|
|
3157
|
-
if (a === 'center' || a === 'top') return 'left:50%;transform:translateX(-50%);top:10px;';
|
|
3158
|
-
return null;
|
|
3159
|
-
};
|
|
3160
|
-
const legendAlignCss = ALIGN_CSS[builder.mapOptions.align] || flatAlignCss(builder.mapOptions.align) || ALIGN_CSS['top-right'];
|
|
3253
|
+
const legendAlignCss = legendAlignCssFor(builder.mapOptions);
|
|
3161
3254
|
// Real ixmaps-flat's SUB-THEME (MapTheme.createSubTheme,
|
|
3162
3255
|
// maptheme.js 14284-14292 / 15010-15160; map.Themes.enableSubThemes
|
|
3163
3256
|
// defaults to true): marking fields of a multi-field choropleth
|
|
@@ -3228,15 +3321,28 @@
|
|
|
3228
3321
|
legendStack = document.createElement('div');
|
|
3229
3322
|
legendStack.className = 'ix-legend-stack';
|
|
3230
3323
|
legendStack.style.cssText = 'position:absolute;' + legendAlignCss + 'z-index:6;width:280px;'
|
|
3231
|
-
+ 'display:flex;flex-direction:column;max-height:
|
|
3324
|
+
+ 'display:flex;flex-direction:column;max-height:66.6%;overflow-y:auto;'
|
|
3232
3325
|
+ 'border-radius:6px;box-shadow:' + shadow + ';pointer-events:auto;';
|
|
3233
3326
|
if (!document.getElementById('ix-legend-stack-css')) {
|
|
3234
3327
|
const css = document.createElement('style');
|
|
3235
3328
|
css.id = 'ix-legend-stack-css';
|
|
3236
|
-
|
|
3329
|
+
// the slider rules are flat's own legend.css .slider block
|
|
3330
|
+
// (355-411) — its two duplicate blocks resolved to what the
|
|
3331
|
+
// cascade actually shows: a 5px rounded grey track at 0.7
|
|
3332
|
+
// opacity (1 on hover) and a green thumb
|
|
3333
|
+
css.textContent = '.ix-legend-stack>.ix-native-legend:not([data-ixoff])~.ix-native-legend:not([data-ixoff]){border-top:1px solid rgba(128,128,128,.35)}'
|
|
3334
|
+
+ '.ix-legend-stack input[type=range]{-webkit-appearance:none;appearance:none;height:5px;border-radius:10px;background:rgba(155,155,155,0.3);border:solid #888 0px;outline:none;opacity:0.7;transition:opacity .2s;}'
|
|
3335
|
+
+ '.ix-legend-stack input[type=range]:hover{opacity:1;}'
|
|
3336
|
+
+ '.ix-legend-stack input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:0.5em;background:#4CAF50;cursor:pointer;}'
|
|
3337
|
+
+ '.ix-legend-stack input[type=range]::-moz-range-thumb{width:25px;height:25px;border-radius:50%;background:#4CAF50;cursor:pointer;}';
|
|
3237
3338
|
document.head.appendChild(css);
|
|
3238
3339
|
}
|
|
3239
|
-
|
|
3340
|
+
// flat anchors its legend inside the map div (#map-legend), so the
|
|
3341
|
+
// panel's max-height resolves against the MAP's height — the stack's
|
|
3342
|
+
// former parent anchor collapsed it to 0 on pages whose body has
|
|
3343
|
+
// only floated children (height 0)
|
|
3344
|
+
el.style.position = el.style.position || 'relative';
|
|
3345
|
+
el.appendChild(legendStack);
|
|
3240
3346
|
return legendStack;
|
|
3241
3347
|
};
|
|
3242
3348
|
// map.setLegend(value): the `legend` map option at runtime, same values as
|
|
@@ -3246,7 +3352,7 @@
|
|
|
3246
3352
|
setLegendOption = (opt) => {
|
|
3247
3353
|
builder.mapOptions.legend = opt;
|
|
3248
3354
|
legendFolded = opt === 'closed';
|
|
3249
|
-
legendOn = legendIsOn(opt);
|
|
3355
|
+
legendOn = builder._legendHtml ? false : legendIsOn(opt);
|
|
3250
3356
|
runtimes.forEach(rt => {
|
|
3251
3357
|
if (!legendOn) {
|
|
3252
3358
|
if (rt._legendPanel && rt._legendPanel.parentNode) rt._legendPanel.parentNode.removeChild(rt._legendPanel);
|
|
@@ -3281,7 +3387,7 @@
|
|
|
3281
3387
|
rt._triggerRedraw = () => { refresh(); };
|
|
3282
3388
|
if (subThemeCapable(rt)) rt._onMarksChanged = () => updateSubTheme(rt);
|
|
3283
3389
|
if (!legendOn || !el.parentElement || !legendApplies(rt)) return;
|
|
3284
|
-
el.
|
|
3390
|
+
el.style.position = el.style.position || 'relative';
|
|
3285
3391
|
{
|
|
3286
3392
|
// .type("...|NOLEGEND") — the fourth real legend-related type()
|
|
3287
3393
|
// token: opts a theme OUT of the legend entirely (real engine's
|
|
@@ -3328,23 +3434,22 @@
|
|
|
3328
3434
|
// would hand the dark look a light bg here: its dark branch never
|
|
3329
3435
|
// ran for this map, its light bg would clash with dark rows/text)
|
|
3330
3436
|
if (!rt.style.legendtheme) legendColors.bg = looksDark ? '#111' : flatLook.bg;
|
|
3331
|
-
// max-height:66% resolves against el
|
|
3332
|
-
//
|
|
3333
|
-
//
|
|
3334
|
-
//
|
|
3335
|
-
//
|
|
3336
|
-
//
|
|
3337
|
-
//
|
|
3338
|
-
//
|
|
3339
|
-
//
|
|
3340
|
-
//
|
|
3341
|
-
//
|
|
3342
|
-
// it just overflows its flex parent instead.
|
|
3437
|
+
// max-height:66.6% resolves against el's own height (the map
|
|
3438
|
+
// container, which always has a definite height for the map
|
|
3439
|
+
// itself to render into) since this panel sits in the stack,
|
|
3440
|
+
// absolutely positioned inside el. flex:0 1 auto + min-height:0
|
|
3441
|
+
// lets the panel SHRINK to the stack's 2/3 cap so the ROWS
|
|
3442
|
+
// wrapper (its own flex:1 1 auto + min-height:0 below) scrolls
|
|
3443
|
+
// internally while the header/description/slider stay fixed —
|
|
3444
|
+
// with flex:0 0 auto the panel grew past the cap and the whole
|
|
3445
|
+
// stack scrolled instead. text-align:left: the page body may
|
|
3446
|
+
// centre everything (the AirBnB port does), the legend is left
|
|
3447
|
+
// aligned chrome.
|
|
3343
3448
|
// one box for every theme, as flat's #map-legend (legend.js
|
|
3344
3449
|
// 2200-2460): the panels stack in the legend stack in theme
|
|
3345
3450
|
// order, a line between them (see layoutLegends)
|
|
3346
|
-
panel.style.cssText = 'position:relative;flex:0
|
|
3347
|
-
+ 'display:flex;flex-direction:column;'
|
|
3451
|
+
panel.style.cssText = 'position:relative;flex:0 1 auto;min-height:0;overflow:hidden;'
|
|
3452
|
+
+ 'display:flex;flex-direction:column;text-align:left;'
|
|
3348
3453
|
+ 'background:' + legendColors.bg + ';color:' + legendColors.fg + ';font:12px/1.4 -apple-system,Arial,sans-serif;'
|
|
3349
3454
|
+ 'padding:10px 12px 12px;pointer-events:auto;';
|
|
3350
3455
|
const stack = legendStackEl(legendColors.shadow);
|
|
@@ -3783,12 +3888,12 @@
|
|
|
3783
3888
|
slider.min = isOpacitySlider ? '0' : '25';
|
|
3784
3889
|
slider.max = isOpacitySlider ? '100' : '200';
|
|
3785
3890
|
slider.value = String(initialPct);
|
|
3786
|
-
slider.style.cssText = 'width:
|
|
3891
|
+
slider.style.cssText = 'width:50%;margin-top:0.4em;'; // flat's own inline slider style (legend.js 2392)
|
|
3787
3892
|
slider.addEventListener('input', () => {
|
|
3788
3893
|
const pct = parseInt(slider.value, 10);
|
|
3789
3894
|
sliderRow.querySelector('.ix-legend-scale-val').textContent = pct;
|
|
3790
3895
|
rt.setStyle(isOpacitySlider ? { fillopacity: pct / 100 } : { scale: pct / 100 });
|
|
3791
|
-
|
|
3896
|
+
quietRefresh();
|
|
3792
3897
|
});
|
|
3793
3898
|
bodyEl.appendChild(slider);
|
|
3794
3899
|
}
|
|
@@ -3855,7 +3960,7 @@
|
|
|
3855
3960
|
this._layerBuilders.push(lb);
|
|
3856
3961
|
return lb;
|
|
3857
3962
|
}
|
|
3858
|
-
this._layerBuilders.push(nameOrBuilder);
|
|
3963
|
+
this._layerBuilders.push(unwrapLayer(nameOrBuilder));
|
|
3859
3964
|
return this;
|
|
3860
3965
|
}
|
|
3861
3966
|
// .Layer() — alias of .layer()
|
|
@@ -4058,6 +4163,10 @@
|
|
|
4058
4163
|
// world once, at the cost of that zoom limit.
|
|
4059
4164
|
renderWorldCopies: this._engineOptions.worldcopies !== false
|
|
4060
4165
|
});
|
|
4166
|
+
// the live-SVG pane USER charts render into (see chartHostFor) —
|
|
4167
|
+
// registered here, keyed by this map's options object, so every
|
|
4168
|
+
// runtime (including later loadProject/map.layer ones) finds it
|
|
4169
|
+
chartHostFor(this._engineOptions, map, el);
|
|
4061
4170
|
// ixmaps.Map(id, {mapProjection}): flat's orthographic map is MapLibre's
|
|
4062
4171
|
// native globe here (same mapping as loadProject, applyProjectMap);
|
|
4063
4172
|
// "ortographic" is the spelling flat pages commonly carry. Any other
|
|
@@ -4179,12 +4288,16 @@
|
|
|
4179
4288
|
attributionBox.set(this._attributionText != null ? this._attributionText : this.mapOptions.attribution);
|
|
4180
4289
|
this._setAttribution = text => attributionBox.set(text);
|
|
4181
4290
|
|
|
4182
|
-
if (this._legendHtml
|
|
4291
|
+
if (this._legendHtml) {
|
|
4183
4292
|
const legendEl = document.createElement('div');
|
|
4184
|
-
|
|
4293
|
+
// a page legend (map.legend(html)) is positioned by the map's
|
|
4294
|
+
// own align option — the same rule that places the native stack
|
|
4295
|
+
legendEl.style.cssText = 'position:absolute;' + legendAlignCssFor(this.mapOptions) + 'z-index:5;pointer-events:none';
|
|
4185
4296
|
legendEl.innerHTML = this._legendHtml;
|
|
4186
|
-
|
|
4187
|
-
|
|
4297
|
+
// inside the map container, never the page's body (see the pinned
|
|
4298
|
+
// tooltip mount) — styling body breaks page-positioned elements
|
|
4299
|
+
el.style.position = el.style.position || 'relative';
|
|
4300
|
+
el.appendChild(legendEl);
|
|
4188
4301
|
}
|
|
4189
4302
|
|
|
4190
4303
|
// themes are addressed by either the layer()'s own name (e.g. "AREU")
|
|
@@ -4305,6 +4418,7 @@
|
|
|
4305
4418
|
// the layer name): add(theme, flag) — "replace" swaps a theme of the
|
|
4306
4419
|
// same id —, replace(id, theme, flag), remove(id)
|
|
4307
4420
|
add: (layerBuilder, flag) => {
|
|
4421
|
+
layerBuilder = unwrapLayer(layerBuilder);
|
|
4308
4422
|
_chainedLayers = _chainedLayers.then(() => {
|
|
4309
4423
|
if (/replace/i.test(flag || '')) {
|
|
4310
4424
|
const def = layerBuilder.definition();
|
|
@@ -4316,6 +4430,7 @@
|
|
|
4316
4430
|
return engineApi;
|
|
4317
4431
|
},
|
|
4318
4432
|
replace: (id, layerBuilder, flag) => {
|
|
4433
|
+
layerBuilder = unwrapLayer(layerBuilder);
|
|
4319
4434
|
_chainedLayers = _chainedLayers.then(() => { removeThemeById(id); return engineApi.defineLayer(layerBuilder); })
|
|
4320
4435
|
.catch(err => console.error('[ixmaps-gl] map.replace():', err));
|
|
4321
4436
|
return engineApi;
|
|
@@ -4347,6 +4462,23 @@
|
|
|
4347
4462
|
},
|
|
4348
4463
|
setMapTypeId: (id) => engineApi.setMapType(id),
|
|
4349
4464
|
mapType: (id) => engineApi.setMapType(id),
|
|
4465
|
+
// projection swap WITHOUT the project machinery: setProjectJSON
|
|
4466
|
+
// tears down and re-defines every theme to change the projection
|
|
4467
|
+
// (a projection toggle on a theme-heavy page paid a full data
|
|
4468
|
+
// re-aggregation — the power plants stage page). The projection
|
|
4469
|
+
// itself is one MapLibre call; the layers are projection-
|
|
4470
|
+
// dependent (globe far-hemisphere cull, chart placement) so they
|
|
4471
|
+
// are rebuilt — refreshLayers, once now and again when the new
|
|
4472
|
+
// projection has rendered — but THEMES and VIEW are never touched
|
|
4473
|
+
setProjection: (type) => {
|
|
4474
|
+
if (typeof map.setProjection !== 'function') { console.warn('[ixmaps-gl] map.setProjection not available on the loaded maplibre-gl build (needs >=5.0.1) — projection unchanged'); return engineApi; }
|
|
4475
|
+
const t = /globe/i.test(String(type || '')) ? 'globe' : 'mercator';
|
|
4476
|
+
try { map.setProjection({ type: t }); } catch (e) { console.warn('[ixmaps-gl] map.setProjection failed:', e); return engineApi; }
|
|
4477
|
+
refreshLayers();
|
|
4478
|
+
map.triggerRepaint();
|
|
4479
|
+
map.once('idle', refreshLayers);
|
|
4480
|
+
return engineApi;
|
|
4481
|
+
},
|
|
4350
4482
|
// flat's getMapTypeId(): the basemap type name in use
|
|
4351
4483
|
getMapTypeId: () => String(builder.mapOptions.mapType || ''),
|
|
4352
4484
|
// flat's attribution (htmlgui.js htmlgui_set/getAttributionString)
|
|
@@ -4495,7 +4627,7 @@
|
|
|
4495
4627
|
// exactly how this engine's own facet-filter/style setters below
|
|
4496
4628
|
// already push responsibility for "what should happen" to the
|
|
4497
4629
|
// caller rather than guessing.
|
|
4498
|
-
defineLayer: (layerBuilder) => defineFromDefinition(layerBuilder.definition()),
|
|
4630
|
+
defineLayer: (layerBuilder) => defineFromDefinition(unwrapLayer(layerBuilder).definition()),
|
|
4499
4631
|
// GL-PORT COMPAT: real ixmaps-flat's map handle, as a page gets it
|
|
4500
4632
|
// from `myMap.then(map => map.view(...).options(...).layer(a)
|
|
4501
4633
|
// .layer(b))` — the common multi-layer idiom — is chainable on the
|
|
@@ -4597,6 +4729,11 @@
|
|
|
4597
4729
|
if (rt._unlisten) rt._unlisten();
|
|
4598
4730
|
if (rt._cancelWait) rt._cancelWait();
|
|
4599
4731
|
clearTimeout(rt._dependentTimer);
|
|
4732
|
+
// a removed theme's live USER-chart SVG is not part of the deck
|
|
4733
|
+
// layers — leaving its groups mounted keeps the charts on the
|
|
4734
|
+
// map after map.remove()/replace() (censimenti's sidebar)
|
|
4735
|
+
if (rt._clearUserChartPane) rt._clearUserChartPane();
|
|
4736
|
+
if (rt._chartHost && rt._chartHost.runtimes) rt._chartHost.runtimes.delete(rt);
|
|
4600
4737
|
});
|
|
4601
4738
|
const removed = removedRuntimes.length > 0;
|
|
4602
4739
|
// stale-registry guard: _globalThemeRegistry is a bare name->
|
|
@@ -5228,9 +5365,18 @@
|
|
|
5228
5365
|
// the native legend: createLegend (its four hooks are what the rest
|
|
5229
5366
|
// of the map calls — panels, the legend option, sub-themes, layout)
|
|
5230
5367
|
({ addLegendPanel, setLegendOption, updateSubTheme, layoutLegends } =
|
|
5231
|
-
createLegend({ builder, map, el, overlay, runtimes, engineApi, built, refresh, scheduleRefresh, notifyRedraw
|
|
5368
|
+
createLegend({ builder, map, el, overlay, runtimes, engineApi, built, refresh, scheduleRefresh, notifyRedraw,
|
|
5369
|
+
// flat's legend slider (legend.js 2839-2854) patches the style and
|
|
5370
|
+
// calls redrawTheme — a per-theme redraw that does NOT fire
|
|
5371
|
+
// htmlgui_onDrawTheme, so a page's own onDrawTheme hook (this
|
|
5372
|
+
// port's hookStatistics re-applying its own slider's scale)
|
|
5373
|
+
// only reasserts on the next full redraw (pan/zoom). refresh()
|
|
5374
|
+
// here would notify synchronously and clobber the new scale in
|
|
5375
|
+
// the same tick — this quiet variant is that flat behavior.
|
|
5376
|
+
quietRefresh: () => { refreshLayers(); } }));
|
|
5232
5377
|
|
|
5233
5378
|
_lastMapApi = engineApi;
|
|
5379
|
+
if (this.mapOptions && this.mapOptions.name) _mapApis[this.mapOptions.name] = engineApi;
|
|
5234
5380
|
// ?ixgl-debug: ixmaps.__glDebug.check() reports, for the moment it is
|
|
5235
5381
|
// called, every theme's geometry problems (invalid or mixed-dimension
|
|
5236
5382
|
// coordinates, a shape far larger than the theme's others) and what
|
|
@@ -5296,6 +5442,27 @@
|
|
|
5296
5442
|
}
|
|
5297
5443
|
}
|
|
5298
5444
|
|
|
5445
|
+
// GL-PORT COMPAT: flat's ixmaps.Map(div, opts, cb) hands the callback a
|
|
5446
|
+
// mapApi HANDLE (htmlgui_flat.js 904-935) — a real page keeps it and
|
|
5447
|
+
// calls its methods on it (__map.setView(...), __map.replace(...) —
|
|
5448
|
+
// AirBnB's city selector). This engine hands the callback the BUILDER
|
|
5449
|
+
// for its own .options().view().layer() chain; the builder here also
|
|
5450
|
+
// carries flat's handle methods, delegated to the map handle of THIS
|
|
5451
|
+
// map's options name (calls before the map exists wait for it, exactly
|
|
5452
|
+
// as ixmaps.map() does). The builder's own methods with different
|
|
5453
|
+
// semantics — layer, view, options, require, attribution, on — keep
|
|
5454
|
+
// theirs and are not overridden.
|
|
5455
|
+
for (const m of ['replace', 'add', 'remove', 'removeTheme', 'replaceTheme', 'changeThemeStyle', 'setThemeStyle',
|
|
5456
|
+
'refreshTheme', 'redrawTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'setProjection', 'resize',
|
|
5457
|
+
'loadProject', 'project', 'setThemeVisible', 'setThemeTimeFrame', 'show', 'hide', 'getZoom', 'getThemeObj',
|
|
5458
|
+
'getThemes', 'theme', 'setView']) {
|
|
5459
|
+
if (!MapBuilder.prototype[m]) {
|
|
5460
|
+
MapBuilder.prototype[m] = function (...args) {
|
|
5461
|
+
return mapHandle(this.mapOptions && this.mapOptions.name)[m](...args);
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
|
|
5299
5466
|
// named createMap (not Map) so it doesn't shadow the built-in ES6 Map
|
|
5300
5467
|
// class used throughout this file — a real bug caught during testing
|
|
5301
5468
|
// (every `new Map()` below silently resolved to *this* function instead)
|
|
@@ -5362,6 +5529,9 @@
|
|
|
5362
5529
|
}
|
|
5363
5530
|
return promise;
|
|
5364
5531
|
};
|
|
5532
|
+
// flat's mapApi.setView(center, zoom) (htmlgui_flat.js 1327) — the
|
|
5533
|
+
// same live-or-build-time split as .view() above
|
|
5534
|
+
promise.setView = (...args) => promise.view(...args);
|
|
5365
5535
|
promise.options = (...args) => { builder.options(...args); return promise; };
|
|
5366
5536
|
promise.attribution = (...args) => { builder.attribution(...args); return promise; };
|
|
5367
5537
|
promise.legend = (...args) => { builder.legend(...args); return promise; };
|
|
@@ -9030,8 +9200,12 @@
|
|
|
9030
9200
|
buildDeckLayers(zoom, bbox, liveZoom = zoom, globeCenter = null) {
|
|
9031
9201
|
this._lastZoom = zoom;
|
|
9032
9202
|
this._lastBbox = bbox;
|
|
9033
|
-
|
|
9034
|
-
|
|
9203
|
+
// the live USER-chart SVG pane is not part of the deck layers — an
|
|
9204
|
+
// early return here must also unmount its groups, or the pane keeps
|
|
9205
|
+
// showing the last drawing after the theme went out of scale (or was
|
|
9206
|
+
// hidden via setThemeVisible)
|
|
9207
|
+
if (this._hidden) { this._clearUserChartPane(); return []; }
|
|
9208
|
+
if (!this.flags.has('FEATURE') && !this.flags.has('FEATURES') && chartHiddenByScale(this.style, liveZoom)) { this._clearUserChartPane(); return []; }
|
|
9035
9209
|
// DOT is checked FIRST, matching the real engine's own precedence
|
|
9036
9210
|
// (its DOT branch is a loop-level fast-path that bypasses the whole
|
|
9037
9211
|
// drawChart/modifier machinery entirely — a layer flagged DOT never
|
|
@@ -9902,6 +10076,11 @@
|
|
|
9902
10076
|
}
|
|
9903
10077
|
// USER charts (see resolveUserChartFunction): one icon per chart, drawn
|
|
9904
10078
|
// by the page's function; null → no such function (drawn as bubbles)
|
|
10079
|
+
_clearUserChartPane() {
|
|
10080
|
+
if (this._chartPaneGroup) this._chartPaneGroup.remove();
|
|
10081
|
+
this._chartPaneGroup = null;
|
|
10082
|
+
this._chartItems = null;
|
|
10083
|
+
}
|
|
9905
10084
|
_buildUserChartLayers(combined, liveZoom, sizeValueOf) {
|
|
9906
10085
|
const name = String(this.style.userdraw || '');
|
|
9907
10086
|
const draw = resolveUserChartFunction(name);
|
|
@@ -9937,9 +10116,17 @@
|
|
|
9937
10116
|
// radius of it (see USER_CHART_MAX_SIZE)
|
|
9938
10117
|
const pxPerUnit = NORMAL_RADIUS_PX * objectZoomFactor(liveZoom, this.mapOptions) * (styleNum(this.style.scale) || 1) / USER_CHART_MAX_SIZE;
|
|
9939
10118
|
const flag = theme.szFlag;
|
|
9940
|
-
const
|
|
9941
|
-
|
|
9942
|
-
|
|
10119
|
+
const chartHost = _chartHosts.get(this.mapOptions);
|
|
10120
|
+
if (!chartHost) {
|
|
10121
|
+
if (!this._userChartWarned) { this._userChartWarned = true; console.warn(`[ixmaps-gl] USER chart "${name}": no chart pane — drawn as bubbles`); }
|
|
10122
|
+
return null;
|
|
10123
|
+
}
|
|
10124
|
+
// this view's charts, mounted as live <g> elements in the pane (see
|
|
10125
|
+
// chartHostFor) — flat's own SVG rendering: no atlas, no raster, no
|
|
10126
|
+
// async image decode, the whole drawing is synchronous
|
|
10127
|
+
const items = [];
|
|
10128
|
+
const defsSeen = new Set();
|
|
10129
|
+
let html = '';
|
|
9943
10130
|
for (const d of combined) {
|
|
9944
10131
|
const value = valueOf(d);
|
|
9945
10132
|
// the item's class (range classes set it for groups too — a negative
|
|
@@ -9947,133 +10134,69 @@
|
|
|
9947
10134
|
const cat = d.properties.cat != null ? d.properties.cat : (d.properties.counts ? dominant(d.properties.counts) : 0);
|
|
9948
10135
|
const radiusPx = valueRadius(sizeValueOf(d), liveZoom, this.style, this.mapOptions, this.flags, this._maxSizeValue);
|
|
9949
10136
|
const size = radiusPx / pxPerUnit;
|
|
9950
|
-
const
|
|
9951
|
-
if (!
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
this.
|
|
10137
|
+
const chart = this._userChartSvg(draw, name, theme, flag, d, value, cat, size, sizeValueOf(d));
|
|
10138
|
+
if (!chart) continue;
|
|
10139
|
+
html += chart.markup;
|
|
10140
|
+
// the chart's own defs (gradients) go to the pane once per id —
|
|
10141
|
+
// several charts share one (pinnacle's per-color gradient)
|
|
10142
|
+
for (const id of chart.ids) {
|
|
10143
|
+
if (defsSeen.has(id)) continue;
|
|
10144
|
+
defsSeen.add(id);
|
|
10145
|
+
const def = this._userChartGroup.ownerDocument.getElementById(id);
|
|
10146
|
+
if (def) chartHost.defs.appendChild(def.cloneNode(true));
|
|
10147
|
+
}
|
|
10148
|
+
items.push({ d, coords: d.geometry.coordinates, ox: chart.ox, oy: chart.oy, s: pxPerUnit,
|
|
10149
|
+
r: Math.max(chart.vw, chart.vh) * pxPerUnit / 2 });
|
|
10150
|
+
}
|
|
10151
|
+
if (glDebug()) console.info(`[ixmaps-gl debug] ${themeIdOf(this)}: zoom ${mapLibreToFlatZoom(liveZoom).toFixed(2)}, ${combined.length} charts, ${items.length} drawn${this._viewMoving ? ' (moving)' : ''}`);
|
|
10152
|
+
if (!items.length) {
|
|
10153
|
+
if (this._chartPaneGroup) this._chartPaneGroup.remove();
|
|
10154
|
+
this._chartPaneGroup = null;
|
|
10155
|
+
this._chartItems = null;
|
|
10156
|
+
return (this._userChartLastLayers = []);
|
|
10157
|
+
}
|
|
10158
|
+
if (this._chartPaneGroup) this._chartPaneGroup.remove();
|
|
10159
|
+
this._chartPaneGroup = document.createElementNS(SVG_NS, 'g');
|
|
10160
|
+
chartHost.pane.appendChild(this._chartPaneGroup);
|
|
10161
|
+
this._chartPaneGroup.innerHTML = html;
|
|
10162
|
+
const els = this._chartPaneGroup.children;
|
|
10163
|
+
for (let i = 0; i < items.length; i++) items[i].el = els[i];
|
|
10164
|
+
this._chartItems = items;
|
|
10165
|
+
chartHost.runtimes.add(this);
|
|
10166
|
+
chartHost.update();
|
|
10167
|
+
// picking: a transparent circle per chart — the VISIBLE chart is the
|
|
10168
|
+
// live SVG in the pane; this deck layer only serves hover, click and
|
|
10169
|
+
// the tooltips (pickedFeature unwraps the {d, icon} entry shape)
|
|
10170
|
+
this._userChartLastLayers = [new ScatterplotLayer({
|
|
9973
10171
|
id: `ix-bubbles-${this.name}`,
|
|
9974
|
-
data,
|
|
9975
|
-
|
|
10172
|
+
data: items.map(it => ({ d: it.d, icon: true, r: it.r })),
|
|
10173
|
+
pickable: true,
|
|
9976
10174
|
getPosition: e => e.d.geometry.coordinates,
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
billboard: true,
|
|
9981
|
-
// no mipmaps: the charts are drawn at their size on screen
|
|
9982
|
-
textureParameters: { minFilter: 'linear', magFilter: 'linear' },
|
|
10175
|
+
getRadius: e => Math.max(2, e.r),
|
|
10176
|
+
radiusUnits: 'pixels', radiusMinPixels: 1,
|
|
10177
|
+
stroked: false, filled: true, getFillColor: [0, 0, 0, 0],
|
|
9983
10178
|
parameters: ICON_LAYER_GLOBE_PARAMETERS
|
|
9984
10179
|
})];
|
|
9985
10180
|
return this._userChartLastLayers;
|
|
9986
10181
|
}
|
|
9987
|
-
//
|
|
9988
|
-
//
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
if (seen.has(icon.id)) continue;
|
|
9997
|
-
seen.add(icon.id);
|
|
9998
|
-
if (x + icon.width + PAD > W) { x = 0; y += rowH + PAD; rowH = 0; }
|
|
9999
|
-
place.push([icon, x, y]);
|
|
10000
|
-
mapping[icon.id] = { x, y, width: icon.width, height: icon.height, anchorX: icon.anchorX, anchorY: icon.anchorY, mask: false };
|
|
10001
|
-
x += icon.width + PAD; rowH = Math.max(rowH, icon.height);
|
|
10002
|
-
}
|
|
10003
|
-
const canvas = document.createElement('canvas');
|
|
10004
|
-
canvas.width = W; canvas.height = Math.max(1, y + rowH);
|
|
10005
|
-
const ctx = canvas.getContext('2d');
|
|
10006
|
-
for (const [icon, px, py] of place) ctx.drawImage(icon.canvas, px, py);
|
|
10007
|
-
this._userChartAtlasCache = { sig, canvas, mapping };
|
|
10008
|
-
return this._userChartAtlasCache;
|
|
10009
|
-
}
|
|
10010
|
-
// user chart images become ready one by one: one redraw once they are
|
|
10011
|
-
_scheduleUserChartRedraw() {
|
|
10012
|
-
if (this._userChartRedrawTimer) return;
|
|
10013
|
-
this._userChartRedrawTimer = setTimeout(() => {
|
|
10014
|
-
this._userChartRedrawTimer = null;
|
|
10015
|
-
if (this._userChartWaiting && this._triggerRedraw) this._triggerRedraw();
|
|
10016
|
-
}, 30);
|
|
10017
|
-
}
|
|
10018
|
-
// The chart is drawn once (its SVG kept in _userChartSvgs) and rasterised
|
|
10019
|
-
// at its size on screen (× devicePixelRatio, in steps of USER_CHART_RASTER
|
|
10020
|
-
// px) onto a canvas (icon.canvas, once the SVG image has decoded): one
|
|
10021
|
-
// image per chart at a fixed 256 px made the texture of a few hundred
|
|
10022
|
-
// charts outgrow the GPU's texture size — WebGL errors, and deck.gl
|
|
10023
|
-
// frames that failed, leaving the map un-updated after a zoom.
|
|
10024
|
-
_userChartIcon(draw, name, theme, flag, d, value, cat, size, nSize, pxPerUnit, maxRasterPx = Infinity) {
|
|
10025
|
-
const base = `user|${name}|${cat}|${Math.round(value * 100) / 100}|${Math.round(size * 2) / 2}|${flag}`;
|
|
10182
|
+
// the chart's SVG, cached by its draw parameters — a failing draw
|
|
10183
|
+
// (undefined) is tried again on the next redraw (null: draws nothing)
|
|
10184
|
+
_userChartSvg(draw, name, theme, flag, d, value, cat, size, nSize) {
|
|
10185
|
+
// the title is part of the markup now (item.szTitle) — items sharing
|
|
10186
|
+
// value/size/class but not a title must not share a cache entry
|
|
10187
|
+
const raw = d.properties.raw || d.properties;
|
|
10188
|
+
const titleField = this.style.titlefield || this.binding.title;
|
|
10189
|
+
const title = titleField && raw && raw[titleField] != null ? String(raw[titleField]) : undefined;
|
|
10190
|
+
const base = `user|${name}|${cat}|${Math.round(value * 100) / 100}|${Math.round(size * 2) / 2}|${flag}|${title || ''}`;
|
|
10026
10191
|
if (!this._userChartSvgs) this._userChartSvgs = new Map();
|
|
10027
|
-
if (!this._userChartRasters) this._userChartRasters = new Map();
|
|
10028
10192
|
let chart = this._userChartSvgs.get(base);
|
|
10029
10193
|
if (chart === undefined) {
|
|
10030
10194
|
chart = this._drawUserChartSvg(draw, name, theme, flag, d, value, cat, size, nSize);
|
|
10031
|
-
if (chart === undefined) return null;
|
|
10195
|
+
if (chart === undefined) return null;
|
|
10032
10196
|
if (this._userChartSvgs.size >= ICON_CACHE_MAX) this._userChartSvgs.delete(this._userChartSvgs.keys().next().value);
|
|
10033
10197
|
this._userChartSvgs.set(base, chart);
|
|
10034
10198
|
}
|
|
10035
|
-
|
|
10036
|
-
const dpr = (global.devicePixelRatio || 1);
|
|
10037
|
-
const want = Math.max(chart.vw, chart.vh) * pxPerUnit * dpr;
|
|
10038
|
-
const fits = USER_CHART_RASTER.filter(r => r <= maxRasterPx);
|
|
10039
|
-
const steps = fits.length ? fits : USER_CHART_RASTER.slice(0, 1);
|
|
10040
|
-
const px = steps.find(r => r >= want) || steps[steps.length - 1];
|
|
10041
|
-
const key = base + '|' + px;
|
|
10042
|
-
const rasters = this._userChartRasters;
|
|
10043
|
-
if (rasters.has(key)) {
|
|
10044
|
-
// least recently used goes first
|
|
10045
|
-
const hit = rasters.get(key);
|
|
10046
|
-
rasters.delete(key); rasters.set(key, hit);
|
|
10047
|
-
return hit;
|
|
10048
|
-
}
|
|
10049
|
-
const s = px / Math.max(chart.vw, chart.vh);
|
|
10050
|
-
const w = Math.max(1, Math.round(chart.vw * s)), h = Math.max(1, Math.round(chart.vh * s));
|
|
10051
|
-
const icon = { canvas: null, width: w, height: h, anchorX: (chart.ox - chart.vx) * s, anchorY: (chart.oy - chart.vy) * s, id: key, unitHeight: chart.vh };
|
|
10052
|
-
// a chart whose image fails is left out (not waited for)
|
|
10053
|
-
const failed = e => {
|
|
10054
|
-
icon.failed = true;
|
|
10055
|
-
if (!this._userChartImageError) console.warn(`[ixmaps-gl] USER chart ${name}: chart image failed`, e);
|
|
10056
|
-
this._userChartImageError = e || true;
|
|
10057
|
-
this._scheduleUserChartRedraw();
|
|
10058
|
-
};
|
|
10059
|
-
// the SVG goes through a canvas: deck.gl blends an SVG image as decoded
|
|
10060
|
-
// (premultiplied), which darkened half-transparent fills (a
|
|
10061
|
-
// fadenegative arrow turned grey)
|
|
10062
|
-
const img = new Image();
|
|
10063
|
-
img.onerror = failed;
|
|
10064
|
-
img.onload = () => {
|
|
10065
|
-
try {
|
|
10066
|
-
const canvas = document.createElement('canvas');
|
|
10067
|
-
canvas.width = w; canvas.height = h;
|
|
10068
|
-
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
|
10069
|
-
icon.canvas = canvas;
|
|
10070
|
-
} catch (e) { failed(e); return; }
|
|
10071
|
-
this._scheduleUserChartRedraw();
|
|
10072
|
-
};
|
|
10073
|
-
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(chart.svg.replace('__W__', w).replace('__H__', h));
|
|
10074
|
-
if (rasters.size >= ICON_CACHE_MAX) rasters.delete(rasters.keys().next().value);
|
|
10075
|
-
rasters.set(key, icon);
|
|
10076
|
-
return icon;
|
|
10199
|
+
return chart || null;
|
|
10077
10200
|
}
|
|
10078
10201
|
// the page's chart function draws one chart into the hidden SVG: its
|
|
10079
10202
|
// markup and box (chart units), null when it draws nothing, undefined
|
|
@@ -10084,11 +10207,19 @@
|
|
|
10084
10207
|
const color = this._userChartColor(cat);
|
|
10085
10208
|
const raw = d.properties.raw || d.properties;
|
|
10086
10209
|
const label = this.binding.title && raw && raw[this.binding.title] != null ? String(raw[this.binding.title]) : undefined;
|
|
10210
|
+
// flat's chart title (style.titlefield, maptheme's item.szTitle) —
|
|
10211
|
+
// pinnacleChart draws it below the value text (chart.js 204-216)
|
|
10212
|
+
const titleField = this.style.titlefield || this.binding.title;
|
|
10213
|
+
const title = titleField && raw && raw[titleField] != null ? String(raw[titleField]) : undefined;
|
|
10087
10214
|
let chart = null;
|
|
10088
10215
|
try {
|
|
10089
10216
|
const ret = draw.call(global.ixmaps, document, {
|
|
10090
10217
|
target: g, theme, value, values: [value], size, maxSize: USER_CHART_MAX_SIZE, color, class: cat, flag,
|
|
10091
|
-
|
|
10218
|
+
// flat hands the chart the item's color CLASS object (maptheme's
|
|
10219
|
+
// ColorScheme entry) — pinnacleChart reads ccolor.lowColor for its
|
|
10220
|
+
// VALUES label fill (usercharts/d3/chart.js 199)
|
|
10221
|
+
ccolor: { szColor: color, lowColor: color, highColor: color },
|
|
10222
|
+
item: { szColor: color, szLabel: label, szTitle: title, nCount: d.properties.point_count || 1, nSize }, dbRecord: raw
|
|
10092
10223
|
});
|
|
10093
10224
|
const bb = ret ? g.getBBox() : null;
|
|
10094
10225
|
if (bb && bb.width > 0 && bb.height > 0) {
|
|
@@ -10100,7 +10231,7 @@
|
|
|
10100
10231
|
const vx = bb.x - pad, vy = bb.y - pad, vw = bb.width + 2 * pad, vh = bb.height + 2 * pad;
|
|
10101
10232
|
// the chart's origin (0,0 — or the point flat moves it to, ret) sits at the map position
|
|
10102
10233
|
const ox = (ret && ret.x) || 0, oy = (ret && ret.y) || 0;
|
|
10103
|
-
chart = { vx, vy, vw, vh, ox, oy,
|
|
10234
|
+
chart = { vx, vy, vw, vh, ox, oy, markup, ids: [...ids],
|
|
10104
10235
|
svg: `<svg xmlns="${SVG_NS}" width="__W__" height="__H__" viewBox="${vx} ${vy} ${vw} ${vh}"><defs>${defs}</defs>${markup}</svg>` };
|
|
10105
10236
|
}
|
|
10106
10237
|
} catch (e) {
|
|
@@ -11423,22 +11554,73 @@
|
|
|
11423
11554
|
try { hook.call(global.ixmaps, themeIdOf(rt)); } catch (e) { console.error('[ixmaps-gl] htmlgui_onNewTheme:', e); }
|
|
11424
11555
|
}
|
|
11425
11556
|
|
|
11426
|
-
// flat's ixmaps.
|
|
11427
|
-
//
|
|
11428
|
-
//
|
|
11557
|
+
// flat's ixmaps.themeApi (htmlgui_flat.js 1112-1196): the theme handle a
|
|
11558
|
+
// page gets from map().theme(name) — changeStyle is flat's canonical
|
|
11559
|
+
// global changeThemeStyle(szThemeName, szStyle, szFlag) (htmlgui.js 2050)
|
|
11560
|
+
// in handle form; show / hide / toggle are setThemeVisible, remove the map
|
|
11561
|
+
// handle's. flat's own markClass delegates 3-arg — a stale call against
|
|
11562
|
+
// htmlgui.js's 2-arg markThemeClass (2196) — the canonical form is used
|
|
11563
|
+
// here. Theme ids are global (the registry), so the map binding only
|
|
11564
|
+
// matters for replace.
|
|
11565
|
+
function themeApi(szMap, szTheme) {
|
|
11566
|
+
this.szMap = szMap || null;
|
|
11567
|
+
this.szTheme = szTheme || null;
|
|
11568
|
+
}
|
|
11569
|
+
themeApi.prototype = {
|
|
11570
|
+
changeStyle(szStyle, szFlag) { compatChangeThemeStyle(this.szTheme, szStyle, szFlag); return this; },
|
|
11571
|
+
markClass(nClass) { markThemeClass(this.szTheme, nClass); return this; },
|
|
11572
|
+
unmarkClass(nClass) { unmarkThemeClass(this.szTheme, nClass); return this; },
|
|
11573
|
+
show() { mapHandle(this.szMap).setThemeVisible(this.szTheme, true); return this; },
|
|
11574
|
+
hide() { mapHandle(this.szMap).setThemeVisible(this.szTheme, false); return this; },
|
|
11575
|
+
toggle() {
|
|
11576
|
+
const rt = _globalThemeRegistry.get(this.szTheme);
|
|
11577
|
+
mapHandle(this.szMap).setThemeVisible(this.szTheme, rt ? !!rt._hidden : false);
|
|
11578
|
+
return this;
|
|
11579
|
+
},
|
|
11580
|
+
remove() { mapHandle(this.szMap).remove(this.szTheme); return this; },
|
|
11581
|
+
replace(theme, flag) { mapHandle(this.szMap).replaceTheme(this.szTheme, theme, flag); return this; }
|
|
11582
|
+
};
|
|
11583
|
+
|
|
11584
|
+
// flat's ixmaps.map() / ixmaps.api(szMap) — the running map's handle
|
|
11585
|
+
// (htmlgui.js 173ff, htmlgui_flat.js mapApi 1283). Its methods act on the
|
|
11586
|
+
// map named by szMap (its Map() options name) or, without a name, the
|
|
11587
|
+
// last built map; called while the map is still building, they run once
|
|
11588
|
+
// it is ready.
|
|
11429
11589
|
const MAP_HANDLE_METHODS = ['replace', 'add', 'remove', 'removeTheme', 'replaceTheme', 'changeThemeStyle', 'setThemeStyle',
|
|
11430
|
-
'refreshTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible', 'setThemeTimeFrame', 'attribution'];
|
|
11431
|
-
const
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11590
|
+
'refreshTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'setProjection', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible', 'setThemeTimeFrame', 'attribution'];
|
|
11591
|
+
const _mapHandles = Object.create(null);
|
|
11592
|
+
function makeMapHandle(szMap) {
|
|
11593
|
+
const h = {};
|
|
11594
|
+
const api = () => (szMap && _mapApis[szMap]) || _lastMapApi;
|
|
11595
|
+
for (const m of MAP_HANDLE_METHODS) {
|
|
11596
|
+
h[m] = (...args) => {
|
|
11597
|
+
const a = api();
|
|
11598
|
+
if (a) { const r = a[m](...args); return r === a ? h : r; }
|
|
11599
|
+
_mapReady.then(ready => ready[m](...args));
|
|
11600
|
+
return h;
|
|
11601
|
+
};
|
|
11602
|
+
}
|
|
11603
|
+
h.getZoom = () => { const a = api(); return a ? a.getZoom() : undefined; };
|
|
11604
|
+
h.getThemeObj = id => getThemeObj(id);
|
|
11605
|
+
// flat's mapApi.theme(szTheme) (htmlgui_flat.js 1571): the theme handle
|
|
11606
|
+
h.theme = szTheme => new themeApi(szMap, szTheme);
|
|
11607
|
+
// flat's mapApi aliases (htmlgui_flat.js 1286+): show/hide a theme,
|
|
11608
|
+
// redrawTheme (refreshTheme here), project (loadProject), getThemes(),
|
|
11609
|
+
// setView (view, both flat's object form and (array, zoom))
|
|
11610
|
+
h.show = id => h.setThemeVisible(id, true);
|
|
11611
|
+
h.hide = id => h.setThemeVisible(id, false);
|
|
11612
|
+
h.redrawTheme = id => h.refreshTheme(id);
|
|
11613
|
+
h.project = (...a) => h.loadProject(...a);
|
|
11614
|
+
h.setView = (...a) => h.view(...a);
|
|
11615
|
+
h.getThemes = () => [...new Set(_globalThemeRegistry.values())].map(rt => getThemeObj(themeIdOf(rt))).filter(Boolean);
|
|
11616
|
+
return h;
|
|
11617
|
+
}
|
|
11618
|
+
const _mapHandle = makeMapHandle(null);
|
|
11619
|
+
function mapHandle(szMap) {
|
|
11620
|
+
if (!szMap) return _mapHandle;
|
|
11621
|
+
if (!_mapHandles[szMap]) _mapHandles[szMap] = makeMapHandle(szMap);
|
|
11622
|
+
return _mapHandles[szMap];
|
|
11438
11623
|
}
|
|
11439
|
-
_mapHandle.getZoom = () => (_lastMapApi ? _lastMapApi.getZoom() : undefined);
|
|
11440
|
-
_mapHandle.getThemeObj = id => getThemeObj(id);
|
|
11441
|
-
function mapHandle() { return _mapHandle; }
|
|
11442
11624
|
|
|
11443
11625
|
// flat's ixmaps.formatValue(value, precision, flag) (ui/js/tools/format.js),
|
|
11444
11626
|
// which user chart scripts call: thousands separated by "." (BLANK: a
|
|
@@ -12013,10 +12195,44 @@
|
|
|
12013
12195
|
return _lastMapApi.loadProject(src, flags);
|
|
12014
12196
|
}
|
|
12015
12197
|
|
|
12198
|
+
// flat's global compat forms come in two arities: the canonical
|
|
12199
|
+
// theme-first signatures — changeThemeStyle(szThemeName, szStyle, szFlag)
|
|
12200
|
+
// (htmlgui.js 2050, with its duplicate-leading shift, 2052),
|
|
12201
|
+
// removeTheme(szThemeId) (1884), setBasemapOpacity(nOpacity, szMode)
|
|
12202
|
+
// (1409) — and the older map-first pages calling
|
|
12203
|
+
// (null|szMap, szId, ...) — PNRR passes an explicit null. Arity tells
|
|
12204
|
+
// them apart; theme ids are global (the registry), so the leading map
|
|
12205
|
+
// argument only matters for setBasemapOpacity, where a registered map
|
|
12206
|
+
// name targets that map and null or an unknown name the last built one.
|
|
12207
|
+
function themeStyleArgs(a) {
|
|
12208
|
+
if (a.length >= 4) a = a.slice(1);
|
|
12209
|
+
else if (a[0] === a[1]) a = a.slice(1);
|
|
12210
|
+
return a;
|
|
12211
|
+
}
|
|
12212
|
+
function compatChangeThemeStyle(...a) {
|
|
12213
|
+
const [szId, szStyle, szFlag] = themeStyleArgs(a);
|
|
12214
|
+
return _mapHandle.changeThemeStyle(szId, szStyle, szFlag);
|
|
12215
|
+
}
|
|
12216
|
+
function compatRemoveTheme(...a) {
|
|
12217
|
+
return _mapHandle.remove(a.length >= 2 ? a[1] : a[0]);
|
|
12218
|
+
}
|
|
12219
|
+
function compatSetBasemapOpacity(...a) {
|
|
12220
|
+
if (a.length >= 3) {
|
|
12221
|
+
const api = (a[0] && _mapApis[a[0]]) || _lastMapApi;
|
|
12222
|
+
if (api) api.setBasemapOpacity(a[1], a[2]);
|
|
12223
|
+
return;
|
|
12224
|
+
}
|
|
12225
|
+
return _mapHandle.setBasemapOpacity(a[0], a[1]);
|
|
12226
|
+
}
|
|
12227
|
+
|
|
12016
12228
|
global.ixmaps = {
|
|
12017
12229
|
layer, Layer: layer, Map: createMap, setExternalData: setExternalDataBridge, getThemeObj, data: ixmapsData,
|
|
12018
12230
|
szResourceBase: ixmapsSzResourceBase, getProjectString, setProjectJSON, loadProject,
|
|
12019
12231
|
markThemeClass, unmarkThemeClass, getBoundingBox, setTitle, setTitleBox, refreshTheme, formatValue: flatFormatValue,
|
|
12232
|
+
// flat's ixmaps.__formatValue (ui/js/tools/legend.js 451-553) — pages call
|
|
12233
|
+
// it for statistics/legend values; legend's own variant differs from
|
|
12234
|
+
// format.js's only in the SPACE flag, which flatFormatValue covers too
|
|
12235
|
+
__formatValue: flatFormatValue,
|
|
12020
12236
|
map: mapHandle, getZoom, getThemeDefinitionObj, embeddedSVG,
|
|
12021
12237
|
// flat's theme list: the theme objects of every theme
|
|
12022
12238
|
getThemes: () => [...new Set(_globalThemeRegistry.values())].map(rt => getThemeObj(themeIdOf(rt))).filter(Boolean),
|
|
@@ -12033,11 +12249,11 @@
|
|
|
12033
12249
|
setAttribution: (text) => { if (_lastMapApi && _lastMapApi.setAttribution) _lastMapApi.setAttribution(text); },
|
|
12034
12250
|
htmlgui_setAttributionString: (text) => { if (_lastMapApi && _lastMapApi.setAttribution) _lastMapApi.setAttribution(text); },
|
|
12035
12251
|
htmlgui_getAttributionString: () => (_lastMapApi && _lastMapApi.getAttribution ? _lastMapApi.getAttribution() : ''),
|
|
12036
|
-
changeThemeStyle:
|
|
12037
|
-
removeTheme:
|
|
12252
|
+
changeThemeStyle: compatChangeThemeStyle,
|
|
12253
|
+
removeTheme: compatRemoveTheme,
|
|
12038
12254
|
// flat's time slider: show the records of a theme (null = all themes) whose timefield lies in [min, max)
|
|
12039
12255
|
setThemeTimeFrame: (szId, nMin, nMax) => _mapHandle.setThemeTimeFrame(szId, nMin, nMax),
|
|
12040
|
-
setBasemapOpacity:
|
|
12256
|
+
setBasemapOpacity: compatSetBasemapOpacity,
|
|
12041
12257
|
// this engine's version (flat's own ixmaps.version numbers flat)
|
|
12042
12258
|
glVersion: IXMAPS_GL_VERSION,
|
|
12043
12259
|
// flat always has this page hook (its layer legend tool defines it,
|
|
@@ -12062,7 +12278,7 @@
|
|
|
12062
12278
|
// engine runs in a Node vm there); deliberately NOT on the ixmaps object
|
|
12063
12279
|
global.__ixmapsGlInternals = {
|
|
12064
12280
|
IXMAPS_GL_VERSION, youtubeClickToPlay, symbolsFlagCompat, tooltipTable, legendRowLabels, scaleDenom, cssColorAlpha, aggregatedCategoricalClass, dominantDopacityAlpha, isAggregatedCategoricalChoropleth, computeBubblePackLayout, isolatedBubblePackLayout, sequenceLayout, multiQuadOffsets, pixelOffsetLngLat,
|
|
12065
|
-
normalizeTheme, projectThemeToDefinition, withoutProjectCode, groupRecordCount, resolveBasemapStyleUrl, resolveMapTypeColor, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
12281
|
+
normalizeTheme, projectThemeToDefinition, withoutProjectCode, groupRecordCount, resolveBasemapStyleUrl, resolveMapTypeColor, LayerBuilder, LayerRuntime, MapBuilder, typeStyleNumbers, styleNum,
|
|
12066
12282
|
resolveScriptUrl, isTrustedScriptUrl, loadProcessingScript, loadBrokerData, applyBrokerThemePatch, makeBrokerTheme,
|
|
12067
12283
|
equalIntervalBreaks, quantileBreaks, naturalBreaks, evenStrideSample, jenksBreakValues, partsFromBreakValues, resolvePartsClass,
|
|
12068
12284
|
computeAlphaStats, computeMultiFieldClasses, computeDominantStats, resolveDominantClass, computeComposeColorStats,
|
|
@@ -12071,7 +12287,7 @@
|
|
|
12071
12287
|
flatToMapLibreZoom, mapLibreToFlatZoom, scaleDenominatorAt, resolveZoomReference, resolveAggregationPx, valuesHiddenByScale,
|
|
12072
12288
|
fetchLayerData, parseCsvText, dataTableRows, geometryRowsToFeatureCollection, filterFlatValues, applyField100, field100Binding, rangeClassLegendTotals, resolveAggregateValue, classValueSeparate, cellAggregatedValues, oneHot, groupCoLocated, aggregateOnGrid, GridAggregateIndex, flatRangeParts, aggregateField100, valueRadius, itemPlotGeometry, itemAnchor, objectZoomFactor, resolveZoomReference, defaultNormalSizeValue,
|
|
12073
12289
|
applyWhereFilter, joinChartPositions, flatLookupKey, flatShapeCenter, chartHiddenByScale, glowHiddenByScale, isSymbolChart, flatChartAlignOffset, flatChartBranch, flatValueText, categoryValueRecord, flatDerivateRgb, flatChartTextRgb, flatGroupedValue, flatNoBreaks, featuresHiddenByScale, boxHiddenByScale, flatShadowOn, snapToAggregationGrid,
|
|
12074
|
-
flatFormatValue,
|
|
12290
|
+
flatFormatValue, themeStyleArgs,
|
|
12075
12291
|
dataCacheKey, dataCacheDisabled, cachedLayerData, featuresBounds, wantsZoomToExtent, legendIsOn,
|
|
12076
12292
|
};
|
|
12077
12293
|
global.__setFilter = __setFilter;
|