ixmaps-gl 0.2.4 → 0.2.5

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 CHANGED
@@ -193,7 +193,7 @@ of your own project files gl can render.
193
193
  Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
194
194
 
195
195
  ```html
196
- <script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.4/ixmaps-gl.js"></script>
196
+ <script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.5/ixmaps-gl.js"></script>
197
197
  ```
198
198
 
199
199
  or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
@@ -201,13 +201,13 @@ or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmap
201
201
  browser script, which defines the global `ixmaps`):
202
202
 
203
203
  ```html
204
- <script src="https://unpkg.com/ixmaps-gl@0.2.4/ixmaps-gl.js"></script>
204
+ <script src="https://unpkg.com/ixmaps-gl@0.2.5/ixmaps-gl.js"></script>
205
205
  ```
206
206
 
207
207
  or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
208
208
 
209
209
  ```html
210
- <script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.4/ixmaps-gl.js"></script>
210
+ <script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.5/ixmaps-gl.js"></script>
211
211
  <div id="map_div" style="position:absolute;inset:0;"></div>
212
212
 
213
213
  <script>
package/ixmaps-gl.js CHANGED
@@ -80,7 +80,7 @@
80
80
  // ---------------------------------------------------------------
81
81
  // this engine's release (= package.json "version", checked by
82
82
  // test/unit/version.test.mjs); ixmaps.glVersion, and logged once at start
83
- const IXMAPS_GL_VERSION = '0.2.4';
83
+ const IXMAPS_GL_VERSION = '0.2.5';
84
84
 
85
85
  const LIB_URLS = {
86
86
  // Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
@@ -306,7 +306,7 @@
306
306
  // crafted link must not be able to load arbitrary code into the page.
307
307
  // ---------------------------------------------------------------
308
308
  // v0.1.6: knows this engine's aliases, lookupfield rule, meta↔style, builder methods, project loading
309
- const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.21/dist/validate.mjs';
309
+ const VALIDATOR_URL_DEFAULT = 'https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-grammar@v0.1.22/dist/validate.mjs';
310
310
  // a page may set ixmaps.validate BEFORE loading this script — the
311
311
  // `global.ixmaps = {...}` export below would otherwise overwrite it
312
312
  const _preloadValidate = global.ixmaps && global.ixmaps.validate;
@@ -2066,7 +2066,7 @@
2066
2066
  // edit it by hand; `npm run unit` fails when it drifts.
2067
2067
  // ---------------------------------------------------------------
2068
2068
  // <grammar:binding-aliases>
2069
- // generated from ixmaps-grammar 0.1.21 (ixmaps-flat 1.0.41, eaaf2b7 2026-09-20) — 52 aliases
2069
+ // generated from ixmaps-grammar 0.1.22 (ixmaps-flat 1.0.42, 2b978d5 2026-10-02) — 52 aliases
2070
2070
  const FLAT_BINDING_ALIASES = {
2071
2071
  "aggregation": "style.aggregationfield",
2072
2072
  "aggregationfield": "style.aggregationfield",
@@ -2128,7 +2128,7 @@
2128
2128
  // both — so .style({tooltip}) is a tooltip and .meta({fillopacity}) a style
2129
2129
  // property. GENERATED by test/sync-grammar.mjs — do not edit.
2130
2130
  // <grammar:meta-keys>
2131
- // generated from ixmaps-grammar 0.1.21 — 5 meta keys
2131
+ // generated from ixmaps-grammar 0.1.22 — 5 meta keys
2132
2132
  const FLAT_META_KEYS = ["description","name","snippet","title","tooltip"];
2133
2133
  // </grammar:meta-keys>
2134
2134
 
@@ -7707,6 +7707,35 @@
7707
7707
  return this._glowIconCache.get(key);
7708
7708
  }
7709
7709
 
7710
+ // SHADOW on chart symbols — flat's chart drop shadow (maptheme.js 17097-17160, 18709-18749):
7711
+ // an SVG filter that blurs the symbol by nShadowBlur (3) REAL screen pixels, offsets it by
7712
+ // nShadowDx/Dy (1.5/2.5 px) and darkens it to 0.1 × its color at the source alpha. Here: a
7713
+ // blurred disc as a MASK icon (tinted per item via getColor), one per size step, so the blur
7714
+ // stays ~blurPx on screen whatever the symbol radius (a single scaled icon would blur big
7715
+ // bubbles more). The disc spans 2/3 of the canvas: the rest is room for the blur.
7716
+ // `shape`: the symbol's own (drawSymbolPath, as _buildSingleIcon) — flat's filter shadows
7717
+ // whatever the symbol is, e.g. a RECT square
7718
+ _getShadowIcon(radiusPx, blurPx, shape) {
7719
+ shape = shape || 'circle';
7720
+ const step = Math.max(0, Math.min(7, Math.round(Math.log2(Math.max(1, radiusPx)))));
7721
+ const key = `shadow-${shape}-${step}-${blurPx}`;
7722
+ if (!this._glowIconCache.has(key)) {
7723
+ const size = 96, disc = 32, c = size / 2;
7724
+ const sigma = Math.max(0.5, Math.min(12, blurPx * disc / Math.pow(2, step)));
7725
+ const canvas = document.createElement('canvas');
7726
+ canvas.width = size; canvas.height = size;
7727
+ const ctx = canvas.getContext('2d');
7728
+ // the disc itself is drawn off-canvas; only its blurred shadow lands at the center
7729
+ ctx.shadowColor = 'rgba(255,255,255,1)';
7730
+ ctx.shadowBlur = sigma * 2;
7731
+ ctx.shadowOffsetX = size * 4;
7732
+ ctx.fillStyle = '#fff';
7733
+ ctx.beginPath(); drawSymbolPath(ctx, shape, c - size * 4, c, disc); ctx.closePath(); ctx.fill();
7734
+ this._glowIconCache.set(key, { url: canvas.toDataURL(), width: size, height: size, anchorX: c, anchorY: c, mask: true, id: key });
7735
+ }
7736
+ return this._glowIconCache.get(key);
7737
+ }
7738
+
7710
7739
  _buildBubbleIcon(counts, colors) {
7711
7740
  // The cache key quantizes each count's RATIO to the group's total,
7712
7741
  // not the raw count — computeBubblePackLayout's radii are purely
@@ -9506,6 +9535,34 @@
9506
9535
  const singleBorderColorRgb = this.style.linecolor && styleLineColor(this.style.linecolor) !== 'none' ? hexOrNamedToRgb(styleLineColor(this.style.linecolor)) : null;
9507
9536
  const singleBorderWidthPx = styleNum(this.style.linewidth) || 0;
9508
9537
 
9538
+ const itemRgb = d => this.categoryColorsRgb[d.properties.counts ? dominant(d.properties.counts) : d.properties.cat] || [128, 128, 128];
9539
+ const radiusOf = d => valueRadius(sizeValueOf(d), liveZoom, this.style, this.mapOptions, this.flags, this._maxSizeValue);
9540
+
9541
+ // SHADOW (style.shadow, flat's gate: maxshadow, shadowupper/lower — flatShadowOn): a soft
9542
+ // drop shadow under each symbol, see _getShadowIcon
9543
+ if (flatShadowOn(this.style, combined.length, liveZoom)) {
9544
+ const blurPx = styleNum(this.style.shadowblur) || 3;
9545
+ const num = (v, d) => (isNaN(styleNum(v)) ? d : styleNum(v));
9546
+ const dx = num(this.style.shadowdx, 1.5), dy = num(this.style.shadowdy, 2.5);
9547
+ layers.push(new IconLayer({
9548
+ id: `ix-symbolshadow-${this.name}-g${this._iconGeneration}`,
9549
+ data: combined, pickable: false,
9550
+ getPosition: d => d.geometry.coordinates,
9551
+ getIcon: d => this._getShadowIcon(radiusOf(d), blurPx, d.properties.counts ? 'circle' : this._resolveSymbolShape(d.properties)),
9552
+ getSize: d => radiusOf(d) * 3, // disc = 2/3 of the icon → its diameter is 2r
9553
+ getPixelOffset: [dx, dy],
9554
+ getColor: d => {
9555
+ const rgb = itemRgb(d);
9556
+ const gray = Math.round(Math.min(255, 0.1 * (rgb[0] + rgb[1] + rgb[2])));
9557
+ return [gray, gray, gray, Math.round(this._iconAlpha(d) * fillOpacity)];
9558
+ },
9559
+ sizeUnits: 'pixels',
9560
+ billboard: true,
9561
+ parameters: ICON_LAYER_GLOBE_PARAMETERS,
9562
+ updateTriggers: { getIcon: [liveZoom, blurPx], getSize: liveZoom }
9563
+ }));
9564
+ }
9565
+
9509
9566
  // GLOW: gradient-texture halo (see _getGlowIcon for why this diverges
9510
9567
  // from the real engine's literal flat-circle formula). Individual
9511
9568
  // and cluster glows keep their own size multiplier (11 vs 9 — a lone
@@ -9523,6 +9580,20 @@
9523
9580
  billboard: true,
9524
9581
  parameters: ICON_LAYER_GLOBE_PARAMETERS
9525
9582
  }));
9583
+ } else if (this.flags.has('AURA') && !this.flags.has('ZOOM')) {
9584
+ // AURA (flat maptheme.js 20924-20930, the GLOW branch's `else`): a circle at 1.3 × the
9585
+ // radius behind the symbol, in the line color (else the symbol's), at 0.3 opacity
9586
+ layers.push(new IconLayer({
9587
+ id: `ix-aura-${this.name}-g${this._iconGeneration}`,
9588
+ data: combined, pickable: false,
9589
+ getPosition: d => d.geometry.coordinates,
9590
+ getIcon: d => this._buildSingleIcon(singleBorderColorRgb || itemRgb(d), 0.3, 'circle', null, 0),
9591
+ getSize: d => radiusOf(d) * 2 * 1.3,
9592
+ getColor: d => [255, 255, 255, this._iconAlpha(d)],
9593
+ sizeUnits: 'pixels',
9594
+ billboard: true,
9595
+ parameters: ICON_LAYER_GLOBE_PARAMETERS
9596
+ }));
9526
9597
  }
9527
9598
 
9528
9599
  // BOX / CIRCULARBOX with TITLE / BOTTOMTITLE — flat's BOX block
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ixmaps-gl",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "MapLibre GL + deck.gl reimplementation of the ixmaps-flat declarative mapping engine",
5
5
  "main": "ixmaps-gl.js",
6
6
  "browser": "ixmaps-gl.js",