ixmaps-gl 0.2.10 → 0.2.11
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 +5 -4
- package/ixmaps-gl.js +136 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,7 +86,8 @@ circle/square/diamond/triangle. See the top-of-file comment in
|
|
|
86
86
|
A page's `.require(url)` scripts (page code, e.g. a user chart function) load in order before the
|
|
87
87
|
layers are built. `ixmaps.map()` is the map handle as in flat — its calls wait until the map is ready —
|
|
88
88
|
with `add(theme, flags)` / `replace` / `replaceTheme` / `remove`, `changeThemeStyle` for any style key,
|
|
89
|
-
`getZoom()` (flat's zoom), `resize()`, `
|
|
89
|
+
`getZoom()` (flat's zoom), `resize()`, `attribution(text)` (the page's attribution, bottom left on the map as in flat, in the style of the basemap credit and on its line — also
|
|
90
|
+
`.attribution()` on the builder, the `attribution` map option and `ixmaps.setAttribution(text)`; the basemap's credit stays bottom right), `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
|
|
90
91
|
`htmlgui_onZoomAndPan()` (also once on load) are called (as in flat, a theme swapped in with `replace` comes last
|
|
91
92
|
in the legend, while FEATURE and CHOROPLETH shapes always draw under the charts), and `map`, `getZoom` and flat's
|
|
92
93
|
`formatValue` exist as globals. `.data({name})` without a URL waits for data of that name: from
|
|
@@ -207,7 +208,7 @@ of your own project files gl can render.
|
|
|
207
208
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
208
209
|
|
|
209
210
|
```html
|
|
210
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
211
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.11/ixmaps-gl.js"></script>
|
|
211
212
|
```
|
|
212
213
|
|
|
213
214
|
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
@@ -215,13 +216,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
|
|
|
215
216
|
browser script, which defines the global `ixmaps`):
|
|
216
217
|
|
|
217
218
|
```html
|
|
218
|
-
<script src="https://unpkg.com/ixmaps-gl@0.2.
|
|
219
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.11/ixmaps-gl.js"></script>
|
|
219
220
|
```
|
|
220
221
|
|
|
221
222
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
222
223
|
|
|
223
224
|
```html
|
|
224
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
225
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.11/ixmaps-gl.js"></script>
|
|
225
226
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
226
227
|
|
|
227
228
|
<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.11';
|
|
86
86
|
|
|
87
87
|
const LIB_URLS = {
|
|
88
88
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
// crafted link must not be able to load arbitrary code into the page.
|
|
309
309
|
// ---------------------------------------------------------------
|
|
310
310
|
// v0.1.6: knows this engine's aliases, lookupfield rule, meta↔style, builder methods, project loading
|
|
311
|
-
const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.
|
|
311
|
+
const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.25/dist/validate.mjs';
|
|
312
312
|
// a page may set ixmaps.validate BEFORE loading this script — the
|
|
313
313
|
// `global.ixmaps = {...}` export below would otherwise overwrite it
|
|
314
314
|
const _preloadValidate = global.ixmaps && global.ixmaps.validate;
|
|
@@ -2125,7 +2125,7 @@
|
|
|
2125
2125
|
// edit it by hand; `npm run unit` fails when it drifts.
|
|
2126
2126
|
// ---------------------------------------------------------------
|
|
2127
2127
|
// <grammar:binding-aliases>
|
|
2128
|
-
// generated from ixmaps-grammar 0.1.
|
|
2128
|
+
// generated from ixmaps-grammar 0.1.25 (ixmaps-flat 1.0.42, 2b978d5 2026-10-02) — 52 aliases
|
|
2129
2129
|
const FLAT_BINDING_ALIASES = {
|
|
2130
2130
|
"aggregation": "style.aggregationfield",
|
|
2131
2131
|
"aggregationfield": "style.aggregationfield",
|
|
@@ -2187,7 +2187,7 @@
|
|
|
2187
2187
|
// both — so .style({tooltip}) is a tooltip and .meta({fillopacity}) a style
|
|
2188
2188
|
// property. GENERATED by test/sync-grammar.mjs — do not edit.
|
|
2189
2189
|
// <grammar:meta-keys>
|
|
2190
|
-
// generated from ixmaps-grammar 0.1.
|
|
2190
|
+
// generated from ixmaps-grammar 0.1.25 — 5 meta keys
|
|
2191
2191
|
const FLAT_META_KEYS = ["description","name","snippet","title","tooltip"];
|
|
2192
2192
|
// </grammar:meta-keys>
|
|
2193
2193
|
|
|
@@ -2643,6 +2643,39 @@
|
|
|
2643
2643
|
};
|
|
2644
2644
|
}
|
|
2645
2645
|
|
|
2646
|
+
// ---------------------------------------------------------------
|
|
2647
|
+
// flat's map attribution (ui/html/mappage.html #attribution-div): the
|
|
2648
|
+
// page's text bottom left on the map; hidden while empty ("null" counts
|
|
2649
|
+
// as empty, htmlgui_flat.js 522-526). Here a MapLibre control in the
|
|
2650
|
+
// bottom-left corner, styled like MapLibre's own attribution (the
|
|
2651
|
+
// basemap credit, bottom right — same line, same pill: white, 12px
|
|
2652
|
+
// radius, 12px/20px Helvetica Neue, links rgba(0,0,0,0.75)).
|
|
2653
|
+
// ---------------------------------------------------------------
|
|
2654
|
+
let _attributionCss = false;
|
|
2655
|
+
function createAttribution(map) {
|
|
2656
|
+
if (!_attributionCss && typeof document !== 'undefined') {
|
|
2657
|
+
_attributionCss = true;
|
|
2658
|
+
const css = document.createElement('style');
|
|
2659
|
+
css.textContent = '.ixmaps-gl-attribution a{color:rgba(0,0,0,0.75);text-decoration:none}'
|
|
2660
|
+
+ '.ixmaps-gl-attribution a:hover{color:inherit;text-decoration:underline}';
|
|
2661
|
+
document.head.appendChild(css);
|
|
2662
|
+
}
|
|
2663
|
+
const box = document.createElement('div');
|
|
2664
|
+
box.className = 'maplibregl-ctrl ixmaps-gl-attribution';
|
|
2665
|
+
box.style.cssText = 'background:#fff;border-radius:12px;padding:2px 8px;min-height:20px;box-sizing:content-box;'
|
|
2666
|
+
+ 'font:12px/20px "Helvetica Neue",Arial,Helvetica,sans-serif;color:#000;display:none;';
|
|
2667
|
+
map.addControl({ onAdd: () => box, onRemove: () => box.remove() }, 'bottom-left');
|
|
2668
|
+
let text = '';
|
|
2669
|
+
return {
|
|
2670
|
+
set(t) {
|
|
2671
|
+
text = t == null || t === 'null' ? '' : String(t);
|
|
2672
|
+
box.innerHTML = text;
|
|
2673
|
+
box.style.display = text ? 'block' : 'none';
|
|
2674
|
+
},
|
|
2675
|
+
get() { return text; }
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2646
2679
|
// ---------------------------------------------------------------
|
|
2647
2680
|
// Hover tooltips and the click-to-pin tooltip of a map, built once per map
|
|
2648
2681
|
// by MapBuilder.build(). ctx: the builder (its map options, for the
|
|
@@ -2677,8 +2710,8 @@
|
|
|
2677
2710
|
};
|
|
2678
2711
|
const pinnedTooltipEl = document.createElement('div');
|
|
2679
2712
|
pinnedTooltipEl.style.cssText = 'position:absolute;top:0;left:0;z-index:6;display:none;' +
|
|
2680
|
-
'pointer-events:auto;max-width:
|
|
2681
|
-
'padding:0.6em 1.6em 0.6em 0.7em;font-size:
|
|
2713
|
+
'pointer-events:auto;max-width:440px;max-height:360px;overflow:auto;' +
|
|
2714
|
+
'padding:0.6em 1.6em 0.6em 0.7em;font-size:1em;';
|
|
2682
2715
|
|
|
2683
2716
|
function updatePinnedTooltipPosition() {
|
|
2684
2717
|
if (!pinned) return;
|
|
@@ -2775,7 +2808,7 @@
|
|
|
2775
2808
|
const rt = findRuntimeForLayerId(layer.id);
|
|
2776
2809
|
if (!rt) return null;
|
|
2777
2810
|
const html = rt.buildTooltipHtml(object);
|
|
2778
|
-
return html ? { html, style: Object.assign({ fontSize: '
|
|
2811
|
+
return html ? { html, style: Object.assign({ fontSize: '1em', padding: '0.5em 0.7em', maxWidth: '440px' }, tooltipLook()) } : null;
|
|
2779
2812
|
}
|
|
2780
2813
|
function clickFor(picked) {
|
|
2781
2814
|
const info = picked && Object.assign({}, picked, { object: pickedFeature(picked.object) });
|
|
@@ -3587,7 +3620,13 @@
|
|
|
3587
3620
|
// needs (a chart library, a user chart) — page code, like a <script>
|
|
3588
3621
|
// tag; loaded in order before the layers (see loadRequiredScripts)
|
|
3589
3622
|
require(url) { this._required = (this._required || []).concat(url); return this; }
|
|
3590
|
-
attribution(
|
|
3623
|
+
// flat's .attribution(text): the page's attribution, bottom left on the
|
|
3624
|
+
// map (createAttribution) — also after the map is built
|
|
3625
|
+
attribution(a) {
|
|
3626
|
+
this._attributionText = a;
|
|
3627
|
+
if (this._setAttribution) this._setAttribution(a);
|
|
3628
|
+
return this;
|
|
3629
|
+
}
|
|
3591
3630
|
legend(html) { this._legendHtml = html; return this; }
|
|
3592
3631
|
// GL-PORT COMPAT: real ixmaps-flat's map.layer(name) returns a NEW
|
|
3593
3632
|
// per-layer builder for a .data()...define() chain on the LAYER
|
|
@@ -3781,13 +3820,10 @@
|
|
|
3781
3820
|
: resolveBasemapStyleUrl(this.mapOptions.mapType),
|
|
3782
3821
|
center: [lon, lat],
|
|
3783
3822
|
zoom: this._viewZoom != null && this._viewZoom !== '' ? flatToMapLibreZoom(Number(this._viewZoom)) : 8,
|
|
3784
|
-
//
|
|
3785
|
-
//
|
|
3786
|
-
//
|
|
3787
|
-
//
|
|
3788
|
-
// accepts extra text via customAttribution, appended alongside the
|
|
3789
|
-
// basemap's own required CARTO/OpenStreetMap credit rather than
|
|
3790
|
-
// replacing it.
|
|
3823
|
+
// MapLibre's own AttributionControl (bottom right) keeps the
|
|
3824
|
+
// basemap's required CARTO/OpenStreetMap credit; the page's own
|
|
3825
|
+
// .attribution() goes bottom left, as flat shows it
|
|
3826
|
+
// (createAttribution).
|
|
3791
3827
|
//
|
|
3792
3828
|
// A color `mapType` (mapTypeColor truthy — see
|
|
3793
3829
|
// buildBlankBackgroundStyle above) has no real basemap at all, so
|
|
@@ -3798,7 +3834,6 @@
|
|
|
3798
3834
|
// customAttribution's own required-credit behavior for a REAL
|
|
3799
3835
|
// basemap is left untouched.
|
|
3800
3836
|
...(mapTypeColor ? { attributionControl: false } : {}),
|
|
3801
|
-
...(this._attributionText ? { customAttribution: this._attributionText } : {}),
|
|
3802
3837
|
// MapLibre's default (true) is kept: with false it also stops the
|
|
3803
3838
|
// zoom-out where the world gets narrower than the window, so a
|
|
3804
3839
|
// world map can't open fully zoomed out. The deck.gl overlay takes
|
|
@@ -3912,6 +3947,10 @@
|
|
|
3912
3947
|
map.addControl(overlay);
|
|
3913
3948
|
map.addControl(new maplibregl.NavigationControl(), 'top-left');
|
|
3914
3949
|
tooltips.mount();
|
|
3950
|
+
// the page's attribution (.attribution(), Map option attribution)
|
|
3951
|
+
const attributionBox = createAttribution(map);
|
|
3952
|
+
attributionBox.set(this._attributionText != null ? this._attributionText : this.mapOptions.attribution);
|
|
3953
|
+
this._setAttribution = text => attributionBox.set(text);
|
|
3915
3954
|
|
|
3916
3955
|
if (this._legendHtml && el.parentElement) {
|
|
3917
3956
|
const legendEl = document.createElement('div');
|
|
@@ -4083,6 +4122,11 @@
|
|
|
4083
4122
|
mapType: (id) => engineApi.setMapType(id),
|
|
4084
4123
|
// flat's getMapTypeId(): the basemap type name in use
|
|
4085
4124
|
getMapTypeId: () => String(builder.mapOptions.mapType || ''),
|
|
4125
|
+
// flat's attribution (htmlgui.js htmlgui_set/getAttributionString)
|
|
4126
|
+
setAttribution: (text) => { builder.attribution(text); return engineApi; },
|
|
4127
|
+
// flat's map handle ixmaps.map().attribution(text) (htmlgui_flat.js 1381)
|
|
4128
|
+
attribution: (text) => engineApi.setAttribution(text),
|
|
4129
|
+
getAttribution: () => attributionBox.get(),
|
|
4086
4130
|
setBasemapOpacity: (delta, mode) => {
|
|
4087
4131
|
_basemapOpacity = mode === 'relative'
|
|
4088
4132
|
? Math.max(0, Math.min(1, _basemapOpacity + (parseFloat(delta) || 0)))
|
|
@@ -7269,7 +7313,10 @@
|
|
|
7269
7313
|
const geometry = { type: 'Point', coordinates: [ll.lng, ll.lat] };
|
|
7270
7314
|
const hasClass = cell.first.properties.classValue !== undefined;
|
|
7271
7315
|
let props = cell.n > 1
|
|
7272
|
-
|
|
7316
|
+
// titleRaw: the cell's first record — flat titles an aggregated
|
|
7317
|
+
// item by it (itemTitle)
|
|
7318
|
+
? { cluster: true, point_count: cell.n, value: cell.value, ...(hasClass ? { classValue: cell.classValue } : {}),
|
|
7319
|
+
titleRaw: cell.first.properties.raw || cell.first.properties }
|
|
7273
7320
|
: cell.first.properties;
|
|
7274
7321
|
if (cell.series && cell.n > 1) props = Object.assign({}, props, { series: cell.series });
|
|
7275
7322
|
// a field aggregation's cell key (the field value): the categories of
|
|
@@ -7335,6 +7382,7 @@
|
|
|
7335
7382
|
let cell = cells.get(key);
|
|
7336
7383
|
if (!cell) { cell = { sumX: 0, sumY: 0, n: 0, cx: snapped.x, cy: snapped.y, counts: new Array(n).fill(0), recordCounts: new Array(n).fill(0), classTotal: undefined }; cells.set(key, cell); }
|
|
7337
7384
|
cell.sumX += p.x; cell.sumY += p.y; cell.n++;
|
|
7385
|
+
if (!cell.titleRaw) cell.titleRaw = f.properties.titleRaw || f.properties.raw || null;
|
|
7338
7386
|
cell.counts[f.properties.cat] += f.properties.value;
|
|
7339
7387
|
if (f.properties.classValue !== undefined) cell.classTotal = (cell.classTotal || 0) + f.properties.classValue;
|
|
7340
7388
|
// point_count is the aggregated-record count of a multi-record
|
|
@@ -7348,7 +7396,8 @@
|
|
|
7348
7396
|
const ll = atCellCenter && cellPx ? worldPixelToLngLat(cell.cx, cell.cy, zoom) : worldPixelToLngLat(cell.sumX / cell.n, cell.sumY / cell.n, zoom);
|
|
7349
7397
|
return {
|
|
7350
7398
|
geometry: { type: 'Point', coordinates: [ll.lng, ll.lat] },
|
|
7351
|
-
properties: { counts: cell.counts, total: cell.counts.reduce((a, c) => a + c, 0), recordCounts: cell.recordCounts, ...(cell.classTotal !== undefined ? { classTotal: cell.classTotal } : {})
|
|
7399
|
+
properties: { counts: cell.counts, total: cell.counts.reduce((a, c) => a + c, 0), recordCounts: cell.recordCounts, ...(cell.classTotal !== undefined ? { classTotal: cell.classTotal } : {}),
|
|
7400
|
+
...(cell.titleRaw ? { titleRaw: cell.titleRaw } : {}) }
|
|
7352
7401
|
};
|
|
7353
7402
|
});
|
|
7354
7403
|
}
|
|
@@ -8253,14 +8302,31 @@
|
|
|
8253
8302
|
// only available for an individual, unaggregated point — an
|
|
8254
8303
|
// aggregated/grouped bubble has no single underlying record to
|
|
8255
8304
|
// tabulate, so it resolves to an empty string there.
|
|
8305
|
+
// an item's title as flat gives it (maptheme.js 11838-11886): the title
|
|
8306
|
+
// field of its record — of an aggregated item its first record; an
|
|
8307
|
+
// aggregated grid cell (no aggregation field) of more than 3 records,
|
|
8308
|
+
// or without a title, is titled "(n)"
|
|
8309
|
+
_itemTitle(props) {
|
|
8310
|
+
const raw = props.raw || props.titleRaw;
|
|
8311
|
+
const title = this.binding.title && raw && raw[this.binding.title] != null ? String(raw[this.binding.title]) : '';
|
|
8312
|
+
const aggregated = !!(props.counts || props.cluster);
|
|
8313
|
+
if (aggregated && this._clusterRadiusPx && !this._clusterField) {
|
|
8314
|
+
const n = groupRecordCount(props);
|
|
8315
|
+
if (!title || n > 3) return '(' + n + ')';
|
|
8316
|
+
}
|
|
8317
|
+
return title;
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8256
8320
|
buildTooltipHtml(object) {
|
|
8257
|
-
|
|
8321
|
+
// no .meta({tooltip}): flat's default template (tooltip_mustache.js 394)
|
|
8322
|
+
// — the theme title, the item's title and its chart
|
|
8323
|
+
const template = this.meta.tooltip || FLAT_DEFAULT_TOOLTIP;
|
|
8258
8324
|
if (!global.Mustache) {
|
|
8259
|
-
console.warn('[ixmaps-gl]
|
|
8325
|
+
console.warn('[ixmaps-gl] tooltips need Mustache.js, which is not loaded — ' +
|
|
8260
8326
|
'include https://unpkg.com/mustache@4.2.0/mustache.min.js before ixmaps-gl.js.');
|
|
8261
8327
|
return null;
|
|
8262
8328
|
}
|
|
8263
|
-
return global.Mustache.render(
|
|
8329
|
+
return global.Mustache.render(template, this._buildTooltipContext(object));
|
|
8264
8330
|
}
|
|
8265
8331
|
|
|
8266
8332
|
_buildTooltipContext(object) {
|
|
@@ -8281,9 +8347,7 @@
|
|
|
8281
8347
|
label: isGroup ? '' : (this.categoryDisplayLabels ? (this.categoryDisplayLabels[props.cat] || '') : ''),
|
|
8282
8348
|
// flat: the item's title is its titlefield value (binding title/
|
|
8283
8349
|
// titlefield, e.g. the comune name); else the class label
|
|
8284
|
-
title: isGroup ? '' : (this.
|
|
8285
|
-
? String(props.raw[this.binding.title])
|
|
8286
|
-
: (this.categoryDisplayLabels ? (this.categoryDisplayLabels[props.cat] || '') : '')),
|
|
8350
|
+
title: this._itemTitle(props) || (isGroup ? '' : (this.categoryDisplayLabels ? (this.categoryDisplayLabels[props.cat] || '') : '')),
|
|
8287
8351
|
count: isGroup ? (props.recordCounts ? props.recordCounts.reduce((a, c) => a + c, 0) : '') : 1,
|
|
8288
8352
|
class: isGroup ? '' : props.cat
|
|
8289
8353
|
}
|
|
@@ -8316,11 +8380,16 @@
|
|
|
8316
8380
|
// breakdown, one line per present category, each with its own legend
|
|
8317
8381
|
// color swatch and its OWN value (never summed across categories).
|
|
8318
8382
|
_renderItemChartHtml(props) {
|
|
8383
|
+
const unitOf = this.style.units ? ' ' + this.style.units : '';
|
|
8384
|
+
// an AGGREGATE CATEGORICAL choropleth polygon: its sum per category
|
|
8385
|
+
if (props.parts) {
|
|
8386
|
+
const labels = this.categoryDisplayLabels || this.categoryLabels || [];
|
|
8387
|
+
return tooltipTable(props.parts.map((v, i) => (v > 0 ? { rgb: this.categoryColorsRgb[i], label: labels[i] || '(n/d)', value: this._formatTooltipValue(v) + unitOf } : null)).filter(Boolean));
|
|
8388
|
+
}
|
|
8319
8389
|
if (this.flags.has('CHOROPLETH') && (this.flags.has('DOMINANT') || this.flags.has('COMPOSECOLOR')) && props.raw) {
|
|
8320
8390
|
return this._renderMultiFieldBarsHtml(props.raw);
|
|
8321
8391
|
}
|
|
8322
|
-
const
|
|
8323
|
-
const unit = this.style.units ? ' ' + this.style.units : '';
|
|
8392
|
+
const unit = unitOf;
|
|
8324
8393
|
|
|
8325
8394
|
// PLOT|GRIDSIZE curve cells: properties.values is an array parallel
|
|
8326
8395
|
// to _plotCategories() (e.g. one FERITI sum per year), not a single
|
|
@@ -8330,10 +8399,10 @@
|
|
|
8330
8399
|
if (Array.isArray(props.values)) {
|
|
8331
8400
|
const categories = this._isItemPlot() ? this._itemPlotLabels() : this._plotCategories();
|
|
8332
8401
|
const labels = this._isItemPlot() ? categories : Array.isArray(this.style.label) ? this.style.label.map(String) : categories;
|
|
8333
|
-
return props.values.map((v, i) => {
|
|
8402
|
+
return tooltipTable(props.values.map((v, i) => {
|
|
8334
8403
|
if (v == null || isNaN(v)) return null;
|
|
8335
|
-
return
|
|
8336
|
-
}).filter(Boolean)
|
|
8404
|
+
return { rgb: null, label: labels[i] || categories[i] || i, value: this._formatTooltipValue(v) + unit };
|
|
8405
|
+
}).filter(Boolean));
|
|
8337
8406
|
}
|
|
8338
8407
|
|
|
8339
8408
|
if (props.counts) {
|
|
@@ -8352,19 +8421,17 @@
|
|
|
8352
8421
|
// zoom until something resets it. Root cause of the "trackpad
|
|
8353
8422
|
// zoom sometimes just stops updating" reports.
|
|
8354
8423
|
const labels = this.categoryDisplayLabels || this.categoryLabels || [];
|
|
8355
|
-
return labels.map((label, i) => {
|
|
8424
|
+
return tooltipTable(labels.map((label, i) => {
|
|
8356
8425
|
if (!(props.counts[i] > 0)) return null;
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
}).filter(Boolean).join('');
|
|
8426
|
+
return { rgb: this.categoryColorsRgb[i], label: label || '(n/d)', value: this._formatTooltipValue(props.counts[i]) + unit };
|
|
8427
|
+
}).filter(Boolean));
|
|
8360
8428
|
}
|
|
8361
8429
|
if (props.cat != null) {
|
|
8362
8430
|
const label = this.categoryDisplayLabels ? (this.categoryDisplayLabels[props.cat] || '') : '';
|
|
8363
8431
|
const rgb = this.categoryColorsRgb ? this.categoryColorsRgb[props.cat] : null;
|
|
8364
8432
|
// no bound size field (e.g. a plain DOT|CATEGORICAL layer) -> just
|
|
8365
8433
|
// the category label, no ": undefined" value suffix
|
|
8366
|
-
|
|
8367
|
-
return `<div>${rgb ? swatch(rgb) : ''}${label}${valueSuffix}</div>`;
|
|
8434
|
+
return tooltipTable([{ rgb, label, value: this._hasValue(props.value) ? this._formatTooltipValue(props.value) + unit : '' }]);
|
|
8368
8435
|
}
|
|
8369
8436
|
return '';
|
|
8370
8437
|
}
|
|
@@ -8425,7 +8492,7 @@
|
|
|
8425
8492
|
.filter(k => k !== 'geometry')
|
|
8426
8493
|
.map(k => `<tr><td style="padding:0 0.6em 0 0;color:#888">${k}</td><td>${this._isNumericValue(raw[k]) ? this._formatTooltipValue(raw[k]) : raw[k]}</td></tr>`)
|
|
8427
8494
|
.join('');
|
|
8428
|
-
return `<table style="font-size:0.
|
|
8495
|
+
return `<table style="font-size:0.95em;border-collapse:collapse">${rows}</table>`;
|
|
8429
8496
|
}
|
|
8430
8497
|
|
|
8431
8498
|
_isNumericValue(v) {
|
|
@@ -8691,7 +8758,7 @@
|
|
|
8691
8758
|
geometry: f.geometry,
|
|
8692
8759
|
properties: {
|
|
8693
8760
|
// no part above 0: no item in flat, not painted
|
|
8694
|
-
value: agg ? agg.value : undefined, raw: agg ? f.properties : {}, cat: agg ? agg.index : null,
|
|
8761
|
+
value: agg ? agg.value : undefined, raw: agg ? f.properties : {}, cat: agg ? agg.index : null, parts: agg ? agg.parts : undefined,
|
|
8695
8762
|
dopacityAlpha: agg && dopacityActive ? dominantDopacityAlpha(this.style, this.flags, agg.value, aggMaxA[agg.index]) : null
|
|
8696
8763
|
}
|
|
8697
8764
|
};
|
|
@@ -9720,7 +9787,8 @@
|
|
|
9720
9787
|
return {
|
|
9721
9788
|
geometry: f.geometry,
|
|
9722
9789
|
properties: { counts: this._oneHot(f.properties.cat, f.properties.point_count), total: f.properties.value,
|
|
9723
|
-
...(f.properties.classValue !== undefined ? { classTotal: f.properties.classValue } : {})
|
|
9790
|
+
...(f.properties.classValue !== undefined ? { classTotal: f.properties.classValue } : {}),
|
|
9791
|
+
...(f.properties.titleRaw ? { titleRaw: f.properties.titleRaw } : {}) }
|
|
9724
9792
|
};
|
|
9725
9793
|
});
|
|
9726
9794
|
return { individual, groups };
|
|
@@ -10091,9 +10159,7 @@
|
|
|
10091
10159
|
const boxR = circular ? r * 1.1 + margin : r;
|
|
10092
10160
|
let title = null;
|
|
10093
10161
|
if (titleShown) {
|
|
10094
|
-
const
|
|
10095
|
-
const n = groupRecordCount(d.properties);
|
|
10096
|
-
const text = n > 3 ? '(' + n + ')' : (raw && raw[this.binding.title] != null ? String(raw[this.binding.title]) : '');
|
|
10162
|
+
const text = this._itemTitle(d.properties);
|
|
10097
10163
|
if (text) {
|
|
10098
10164
|
const f = titleFontPx;
|
|
10099
10165
|
const lines = wrapTitleLines(text, f, boxR * 2).split('\n');
|
|
@@ -10761,6 +10827,29 @@
|
|
|
10761
10827
|
|
|
10762
10828
|
// flat's page hook ixmaps.htmlgui_onNewTheme(szId), called when a theme
|
|
10763
10829
|
// is created (maptheme.js 927) — with the theme's id (its name)
|
|
10830
|
+
// flat's tooltip for a theme without its own template (ui/js/tools/
|
|
10831
|
+
// tooltip_mustache.js 394)
|
|
10832
|
+
// tooltip_mustache.js 394) — the same parts, structured: the theme title
|
|
10833
|
+
// as h2, the item title as h3, the chart (tooltipTable) below
|
|
10834
|
+
// (inline styles throughout: a page's own h2 / table / td rules must not
|
|
10835
|
+
// restyle the tooltip)
|
|
10836
|
+
const FLAT_DEFAULT_TOOLTIP = "<h2 style='margin:0 0 0.15em;padding:0;font-size:1.25em;font-weight:600;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis'>{{theme.title}}</h2>"
|
|
10837
|
+
+ "{{#theme.item.title}}<h3 style='margin:0 0 0.4em;padding:0;font-size:1.1em;font-weight:600;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis'>{{theme.item.title}}</h3>{{/theme.item.title}}"
|
|
10838
|
+
+ "{{theme.item.chart}}";
|
|
10839
|
+
// {{theme.item.chart}} as a table: per row a color swatch (or none), the
|
|
10840
|
+
// label (left, one line) and the value (right-aligned)
|
|
10841
|
+
function tooltipTable(rows) {
|
|
10842
|
+
if (!rows.length) return '';
|
|
10843
|
+
const swatch = rgb => (rgb ? `<span style="display:inline-block;width:0.75em;height:0.75em;border-radius:50%;background:rgb(${rgb.slice(0, 3).join(',')})"></span>` : '');
|
|
10844
|
+
const td = 'border:none;background:transparent;font:inherit;color:inherit;line-height:1.15;';
|
|
10845
|
+
return '<table style="border-collapse:collapse;border:none;background:transparent;margin:0.2em 0;font-size:1em;line-height:1.15">'
|
|
10846
|
+
+ rows.map(r => '<tr>'
|
|
10847
|
+
+ `<td style="${td}padding:0.05em 0.4em 0.05em 0;vertical-align:middle">${swatch(r.rgb)}</td>`
|
|
10848
|
+
// one line: a long label ends in an ellipsis (full text as its title)
|
|
10849
|
+
+ `<td style="${td}padding:0.05em 0.8em 0.05em 0;text-align:left"><span title="${escapeHtml(String(r.label).replace(/<[^>]*>/g, ''))}" style="display:inline-block;max-width:13em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom">${r.label}</span></td>`
|
|
10850
|
+
+ `<td style="${td}padding:0.05em 0;text-align:right;white-space:nowrap">${r.value}</td></tr>`).join('')
|
|
10851
|
+
+ '</table>';
|
|
10852
|
+
}
|
|
10764
10853
|
function themeIdOf(rt) {
|
|
10765
10854
|
return (rt.style && rt.style.name) || (rt.meta && rt.meta.name) || rt.name;
|
|
10766
10855
|
}
|
|
@@ -10774,7 +10863,7 @@
|
|
|
10774
10863
|
// htmlgui_flat.js). Its methods act on the last built map; called while
|
|
10775
10864
|
// the map is still building, they run once it is ready.
|
|
10776
10865
|
const MAP_HANDLE_METHODS = ['replace', 'add', 'remove', 'removeTheme', 'replaceTheme', 'changeThemeStyle', 'setThemeStyle',
|
|
10777
|
-
'refreshTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible'];
|
|
10866
|
+
'refreshTheme', 'setBasemapOpacity', 'setMapType', 'setMapTypeId', 'mapType', 'resize', 'view', 'options', 'layer', 'loadProject', 'require', 'setThemeVisible', 'attribution'];
|
|
10778
10867
|
const _mapHandle = {};
|
|
10779
10868
|
for (const m of MAP_HANDLE_METHODS) {
|
|
10780
10869
|
_mapHandle[m] = (...args) => {
|
|
@@ -11375,6 +11464,10 @@
|
|
|
11375
11464
|
// handle's methods on the last built map
|
|
11376
11465
|
getCenter: () => (_lastMapApi && _lastMapApi.map ? _lastMapApi.map.getCenter() : null),
|
|
11377
11466
|
getMapTypeId: () => (_lastMapApi && _lastMapApi.getMapTypeId ? _lastMapApi.getMapTypeId() : ''),
|
|
11467
|
+
// flat's attribution: ixmaps.setAttribution(text) and its htmlgui_ pair
|
|
11468
|
+
setAttribution: (text) => { if (_lastMapApi && _lastMapApi.setAttribution) _lastMapApi.setAttribution(text); },
|
|
11469
|
+
htmlgui_setAttributionString: (text) => { if (_lastMapApi && _lastMapApi.setAttribution) _lastMapApi.setAttribution(text); },
|
|
11470
|
+
htmlgui_getAttributionString: () => (_lastMapApi && _lastMapApi.getAttribution ? _lastMapApi.getAttribution() : ''),
|
|
11378
11471
|
changeThemeStyle: (szMap, szId, szStyle, szFlag) => _mapHandle.changeThemeStyle(szId, szStyle, szFlag),
|
|
11379
11472
|
removeTheme: (szMap, szId) => _mapHandle.remove(szId),
|
|
11380
11473
|
setBasemapOpacity: (szMap, nOpacity, szMode) => _mapHandle.setBasemapOpacity(nOpacity, szMode),
|
|
@@ -11401,7 +11494,7 @@
|
|
|
11401
11494
|
// test-only: lets test/unit/*.test.mjs call pure internals directly (the
|
|
11402
11495
|
// engine runs in a Node vm there); deliberately NOT on the ixmaps object
|
|
11403
11496
|
global.__ixmapsGlInternals = {
|
|
11404
|
-
IXMAPS_GL_VERSION, legendRowLabels, scaleDenom, cssColorAlpha, aggregatedCategoricalClass, dominantDopacityAlpha, isAggregatedCategoricalChoropleth, computeBubblePackLayout, isolatedBubblePackLayout, sequenceLayout,
|
|
11497
|
+
IXMAPS_GL_VERSION, tooltipTable, legendRowLabels, scaleDenom, cssColorAlpha, aggregatedCategoricalClass, dominantDopacityAlpha, isAggregatedCategoricalChoropleth, computeBubblePackLayout, isolatedBubblePackLayout, sequenceLayout,
|
|
11405
11498
|
normalizeTheme, projectThemeToDefinition, withoutProjectCode, groupRecordCount, resolveBasemapStyleUrl, resolveMapTypeColor, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
11406
11499
|
resolveScriptUrl, isTrustedScriptUrl, loadProcessingScript, loadBrokerData, applyBrokerThemePatch, makeBrokerTheme,
|
|
11407
11500
|
equalIntervalBreaks, quantileBreaks, naturalBreaks, evenStrideSample, jenksBreakValues, partsFromBreakValues, resolvePartsClass,
|