ixmaps-gl 0.2.7 → 0.2.8
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 +3 -3
- package/ixmaps-gl.js +42 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -203,7 +203,7 @@ of your own project files gl can render.
|
|
|
203
203
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
204
204
|
|
|
205
205
|
```html
|
|
206
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
206
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.8/ixmaps-gl.js"></script>
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
@@ -211,13 +211,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
|
|
|
211
211
|
browser script, which defines the global `ixmaps`):
|
|
212
212
|
|
|
213
213
|
```html
|
|
214
|
-
<script src="https://unpkg.com/ixmaps-gl@0.2.
|
|
214
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.8/ixmaps-gl.js"></script>
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
218
218
|
|
|
219
219
|
```html
|
|
220
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.
|
|
220
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.8/ixmaps-gl.js"></script>
|
|
221
221
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
222
222
|
|
|
223
223
|
<script>
|
package/ixmaps-gl.js
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
// ---------------------------------------------------------------
|
|
82
82
|
// this engine's release (= package.json "version", checked by
|
|
83
83
|
// test/unit/version.test.mjs); ixmaps.glVersion, and logged once at start
|
|
84
|
-
const IXMAPS_GL_VERSION = '0.2.
|
|
84
|
+
const IXMAPS_GL_VERSION = '0.2.8';
|
|
85
85
|
|
|
86
86
|
const LIB_URLS = {
|
|
87
87
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
@@ -6207,6 +6207,25 @@
|
|
|
6207
6207
|
return { present, radii, offsets, fitScale, maxR };
|
|
6208
6208
|
}
|
|
6209
6209
|
|
|
6210
|
+
// A group while legend classes are marked (flat's MapTheme.markClass,
|
|
6211
|
+
// maptheme.js 14339-14420, evidence "isolate" — the default): the parts
|
|
6212
|
+
// of the other categories are hidden, the marked ones keep their own
|
|
6213
|
+
// size and move to the group's center (the SEQUENCE transform reset to
|
|
6214
|
+
// its scale only). Biggest first, so a smaller marked part stays on top.
|
|
6215
|
+
// null when no marked category is in the group.
|
|
6216
|
+
function isolatedBubblePackLayout(counts, size, marked) {
|
|
6217
|
+
const full = computeBubblePackLayout(counts, size);
|
|
6218
|
+
const keep = full.present.map((p, k) => k).filter(k => marked.has(full.present[k].i))
|
|
6219
|
+
.sort((a, b) => full.radii[b] - full.radii[a]);
|
|
6220
|
+
if (!keep.length) return null;
|
|
6221
|
+
return {
|
|
6222
|
+
present: keep.map(k => full.present[k]),
|
|
6223
|
+
radii: keep.map(k => full.radii[k]),
|
|
6224
|
+
offsets: keep.map(() => ({ x: 0, y: 0 })),
|
|
6225
|
+
fitScale: full.fitScale, maxR: full.maxR
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6210
6229
|
// ---------------------------------------------------------------
|
|
6211
6230
|
// Classification — pure functions (no LayerRuntime state): class
|
|
6212
6231
|
// breaks for a numeric field, and which class a value falls in.
|
|
@@ -8011,7 +8030,7 @@
|
|
|
8011
8030
|
return this._glowIconCache.get(key);
|
|
8012
8031
|
}
|
|
8013
8032
|
|
|
8014
|
-
_buildBubbleIcon(counts, colors) {
|
|
8033
|
+
_buildBubbleIcon(counts, colors, marked = null) {
|
|
8015
8034
|
// The cache key quantizes each count's RATIO to the group's total,
|
|
8016
8035
|
// not the raw count — computeBubblePackLayout's radii are purely
|
|
8017
8036
|
// sqrt(c/total), so two clusters with wildly different absolute
|
|
@@ -8036,13 +8055,18 @@
|
|
|
8036
8055
|
// smooth.
|
|
8037
8056
|
const total = counts.reduce((a, b) => a + b, 0) || 1;
|
|
8038
8057
|
const RATIO_BUCKETS = 15;
|
|
8039
|
-
|
|
8058
|
+
// an isolated group shows only its marked parts, alone and at their
|
|
8059
|
+
// own size: their radius (√share, 1/100 steps) keys the icon, not the
|
|
8060
|
+
// coarse share buckets that can put a small marked part at 0
|
|
8061
|
+
const key = counts.map(c => Math.round((c / total) * RATIO_BUCKETS)).join('-') + (marked
|
|
8062
|
+
? '|iso:' + counts.map((c, i) => (c > 0 && marked.has(i) ? i + ':' + Math.round(Math.sqrt(c / total) * 100) : null)).filter(Boolean).join(',')
|
|
8063
|
+
: '');
|
|
8040
8064
|
if (this._iconCache.has(key)) return this._iconCache.get(key);
|
|
8041
8065
|
const size = BUBBLE_ICON_SIZE;
|
|
8042
8066
|
const canvas = document.createElement('canvas');
|
|
8043
8067
|
canvas.width = size; canvas.height = size;
|
|
8044
8068
|
const ctx = canvas.getContext('2d');
|
|
8045
|
-
const { present, radii, offsets, fitScale } = computeBubblePackLayout(counts, size);
|
|
8069
|
+
const { present, radii, offsets, fitScale } = (marked && isolatedBubblePackLayout(counts, size, marked)) || computeBubblePackLayout(counts, size);
|
|
8046
8070
|
const cx = size / 2, cy = size / 2;
|
|
8047
8071
|
present.forEach((p, i) => {
|
|
8048
8072
|
ctx.beginPath();
|
|
@@ -8341,6 +8365,15 @@
|
|
|
8341
8365
|
// counts as marked if ANY of its constituent categories is marked —
|
|
8342
8366
|
// a mixed cluster shouldn't dim just because one of several
|
|
8343
8367
|
// categories inside it happens to be unmarked.
|
|
8368
|
+
// the marked classes a group isolates to (isolatedBubblePackLayout),
|
|
8369
|
+
// or null: none marked, a page's own mark handler, or evidence
|
|
8370
|
+
// "isolate_gray" (keeps every part, dimmed via _iconAlpha)
|
|
8371
|
+
_groupIsolation(d) {
|
|
8372
|
+
if (!d.properties.counts || !this._markedClasses.size || this._onMarksChanged) return null;
|
|
8373
|
+
if (String(this.style.evidence || 'isolate') === 'isolate_gray') return null;
|
|
8374
|
+
return d.properties.counts.some((c, i) => c > 0 && this._markedClasses.has(i)) ? this._markedClasses : null;
|
|
8375
|
+
}
|
|
8376
|
+
|
|
8344
8377
|
_iconAlpha(d) {
|
|
8345
8378
|
if (!this._markedClasses.size) return 255;
|
|
8346
8379
|
if (d.properties.counts) {
|
|
@@ -9850,7 +9883,7 @@
|
|
|
9850
9883
|
id: `ix-glow-${this.name}-g${this._iconGeneration}`,
|
|
9851
9884
|
data: combined, pickable: false,
|
|
9852
9885
|
getPosition: d => d.geometry.coordinates,
|
|
9853
|
-
getIcon: d => this._getGlowIcon(this.categoryColorsRgb[d.properties.counts ? dominant(d.properties.counts) : d.properties.cat]),
|
|
9886
|
+
getIcon: d => this._getGlowIcon(this.categoryColorsRgb[d.properties.counts ? dominant(this._groupIsolation(d) ? d.properties.counts.map((c, i) => (this._markedClasses.has(i) ? c : 0)) : d.properties.counts) : d.properties.cat]),
|
|
9854
9887
|
getSize: d => valueRadius(sizeValueOf(d), liveZoom, this.style, this.mapOptions, this.flags, this._maxSizeValue) * (d.properties.counts ? 9 : 11),
|
|
9855
9888
|
getColor: d => [255, 255, 255, this._iconAlpha(d)],
|
|
9856
9889
|
...(alignOf.active ? { getPixelOffset: alignOf } : {}),
|
|
@@ -10000,7 +10033,7 @@
|
|
|
10000
10033
|
data: combined, pickable: true,
|
|
10001
10034
|
getPosition: d => d.geometry.coordinates,
|
|
10002
10035
|
getIcon: d => d.properties.counts
|
|
10003
|
-
? this._buildBubbleIcon(d.properties.counts, this.categoryColorsRgb)
|
|
10036
|
+
? this._buildBubbleIcon(d.properties.counts, this.categoryColorsRgb, this._groupIsolation(d))
|
|
10004
10037
|
: this._buildSingleIcon(this.categoryColorsRgb[d.properties.cat], fillOpacity, this._resolveSymbolShape(d.properties), singleBorderColorRgb, singleBorderWidthPx),
|
|
10005
10038
|
getSize: d => valueRadius(sizeValueOf(d), liveZoom, this.style, this.mapOptions, this.flags, this._maxSizeValue) * 2,
|
|
10006
10039
|
getColor: d => [255, 255, 255, this._iconAlpha(d)],
|
|
@@ -10073,7 +10106,8 @@
|
|
|
10073
10106
|
const outerRadiusPx = valueRadius(d.properties.total, liveZoom, this.style, this.mapOptions, this.flags, this._maxSizeValue);
|
|
10074
10107
|
const iconSizePx = outerRadiusPx * 2; // matches the cluster IconLayer's own getSize (*2) below
|
|
10075
10108
|
const pxPerCanvasUnit = iconSizePx / BUBBLE_ICON_SIZE;
|
|
10076
|
-
const
|
|
10109
|
+
const iso = this._groupIsolation(d);
|
|
10110
|
+
const { present, radii, offsets, fitScale } = (iso && isolatedBubblePackLayout(counts, BUBBLE_ICON_SIZE, iso)) || computeBubblePackLayout(counts, BUBBLE_ICON_SIZE);
|
|
10077
10111
|
|
|
10078
10112
|
present.forEach((p, i) => {
|
|
10079
10113
|
// one part for a range-classed cell: its class value, if separate
|
|
@@ -10746,7 +10780,7 @@
|
|
|
10746
10780
|
// test-only: lets test/unit/*.test.mjs call pure internals directly (the
|
|
10747
10781
|
// engine runs in a Node vm there); deliberately NOT on the ixmaps object
|
|
10748
10782
|
global.__ixmapsGlInternals = {
|
|
10749
|
-
IXMAPS_GL_VERSION,
|
|
10783
|
+
IXMAPS_GL_VERSION, computeBubblePackLayout, isolatedBubblePackLayout,
|
|
10750
10784
|
normalizeTheme, projectThemeToDefinition, withoutProjectCode, groupRecordCount, resolveBasemapStyleUrl, resolveMapTypeColor, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
10751
10785
|
resolveScriptUrl, isTrustedScriptUrl, loadProcessingScript, loadBrokerData, applyBrokerThemePatch, makeBrokerTheme,
|
|
10752
10786
|
equalIntervalBreaks, quantileBreaks, naturalBreaks, evenStrideSample, jenksBreakValues, partsFromBreakValues, resolvePartsClass,
|