@bicharts/chart-host 0.6.113 → 0.6.116

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.
Files changed (40) hide show
  1. package/README.md +94 -5
  2. package/dist/adapters.mjs +3 -3
  3. package/dist/card.mjs +2 -2
  4. package/dist/category-colours.mjs +275 -0
  5. package/dist/{chunk-7XT5OJ6M.mjs → chunk-63IUMWY2.mjs} +40 -3
  6. package/dist/{chunk-AEO5SFEQ.mjs → chunk-EFKGSDFF.mjs} +1 -1
  7. package/dist/{chunk-TE54AFRN.mjs → chunk-GZSYTDFN.mjs} +531 -393
  8. package/dist/{chunk-PELRAFYZ.mjs → chunk-IW6BKFF5.mjs} +1 -1
  9. package/dist/{chunk-OLCUKXKL.mjs → chunk-JMLSNV5Q.mjs} +1 -1
  10. package/dist/chunk-JTZTTJJK.mjs +244 -0
  11. package/dist/{chunk-FIG5DIW7.mjs → chunk-K2TH7D5V.mjs} +335 -335
  12. package/dist/{chunk-KY4FI2HA.mjs → chunk-QG5TENI3.mjs} +5 -1
  13. package/dist/{chunk-KGVNZR23.mjs → chunk-RLOIA7QK.mjs} +1 -1
  14. package/dist/{chunk-IL5M7K6Z.mjs → chunk-TUZCM7SX.mjs} +2 -2
  15. package/dist/{chunk-GTJG3SMD.mjs → chunk-VGJH63LZ.mjs} +1 -1
  16. package/dist/{chunk-2KLFIGTQ.mjs → chunk-ZZBX5ZJ6.mjs} +145 -18
  17. package/dist/dax.mjs +23 -1
  18. package/dist/index.mjs +97 -16
  19. package/dist/legend-fit.mjs +3 -3
  20. package/dist/page.mjs +7 -5
  21. package/dist/provinces.mjs +4 -4
  22. package/dist/react.mjs +18 -14
  23. package/dist/testing.mjs +1 -1
  24. package/dist/types/categoryColours.d.ts +71 -0
  25. package/dist/types/contract.d.ts +16 -3
  26. package/dist/types/dax.d.ts +26 -0
  27. package/dist/types/filterScope.d.ts +52 -4
  28. package/dist/types/fit.d.ts +6 -1
  29. package/dist/types/fitDom.d.ts +13 -0
  30. package/dist/types/host.d.ts +19 -5
  31. package/dist/types/index.d.ts +7 -4
  32. package/dist/types/keyboardMarks.d.ts +1 -1
  33. package/dist/types/markFollower.d.ts +21 -0
  34. package/dist/types/page.d.ts +1 -1
  35. package/dist/types/qualifyTable.d.ts +52 -0
  36. package/dist/types/react.d.ts +11 -3
  37. package/dist/types/selectionMarker.d.ts +92 -0
  38. package/dist/zoom-memory.mjs +1 -1
  39. package/package.json +6 -1
  40. /package/dist/{chunk-5YSZABCS.mjs → chunk-LXTW6YII.mjs} +0 -0
@@ -4,11 +4,11 @@ import {
4
4
  claimParkedLasso,
5
5
  installLasso,
6
6
  lassoEffect
7
- } from "./chunk-IL5M7K6Z.mjs";
7
+ } from "./chunk-TUZCM7SX.mjs";
8
8
  import {
9
9
  censusHitBands,
10
10
  ensureCrossfilterHitTargets
11
- } from "./chunk-AEO5SFEQ.mjs";
11
+ } from "./chunk-EFKGSDFF.mjs";
12
12
  import {
13
13
  clearPress,
14
14
  clearSelectionGlyphs,
@@ -24,26 +24,34 @@ import {
24
24
  selectionRuleOf,
25
25
  trackPress,
26
26
  validRowSet
27
- } from "./chunk-KGVNZR23.mjs";
27
+ } from "./chunk-RLOIA7QK.mjs";
28
28
  import {
29
29
  clipWindowOf,
30
30
  fitRenderedChart,
31
31
  rememberScroll,
32
+ restoreInnerScroll,
32
33
  takeScroll,
33
34
  unpinScrolledAxis
34
- } from "./chunk-7XT5OJ6M.mjs";
35
+ } from "./chunk-63IUMWY2.mjs";
35
36
  import {
36
37
  geoFromCache
37
- } from "./chunk-5YSZABCS.mjs";
38
+ } from "./chunk-LXTW6YII.mjs";
38
39
  import {
39
40
  Fn,
41
+ Lr,
42
+ Nt,
40
43
  Q,
41
- Rt,
42
44
  Yn,
43
- _r,
44
45
  tt,
45
46
  xt
46
- } from "./chunk-FIG5DIW7.mjs";
47
+ } from "./chunk-K2TH7D5V.mjs";
48
+ import {
49
+ SAME_SHADE_DELTA_E,
50
+ censusColourSpread,
51
+ deltaE2000,
52
+ hexToLab,
53
+ labToHex
54
+ } from "./chunk-JTZTTJJK.mjs";
47
55
  import {
48
56
  ACTIVE_TICK_CLASS,
49
57
  ANIM_LOOP_DELAY_DEFAULT,
@@ -88,7 +96,7 @@ import {
88
96
  XFILTER_REFRESH_EVENT,
89
97
  chartOwnsTimeline,
90
98
  periodTickSuppressesFeedback
91
- } from "./chunk-KY4FI2HA.mjs";
99
+ } from "./chunk-QG5TENI3.mjs";
92
100
 
93
101
  // src/labelContrast.ts
94
102
  function parseRGBA(c) {
@@ -249,237 +257,11 @@ function clamp01(v) {
249
257
  if (typeof v !== "number" || !isFinite(v)) return 1;
250
258
  return Math.max(0, Math.min(1, v));
251
259
  }
252
- function hexToRGB(hex2) {
253
- const p = toRGBA(hex2);
260
+ function hexToRGB(hex) {
261
+ const p = toRGBA(hex);
254
262
  return [p[0], p[1], p[2]];
255
263
  }
256
264
 
257
- // src/deltaE.ts
258
- function linearise(c) {
259
- const v = c / 255;
260
- return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
261
- }
262
- var Xn = 0.95047;
263
- var Yn2 = 1;
264
- var Zn = 1.08883;
265
- var EPS = 216 / 24389;
266
- var KAPPA = 24389 / 27;
267
- function f(t) {
268
- return t > EPS ? Math.cbrt(t) : (KAPPA * t + 16) / 116;
269
- }
270
- function rgbToLab(r, g, b) {
271
- const R = linearise(r), G = linearise(g), B = linearise(b);
272
- const X = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B;
273
- const Y = 0.2126729 * R + 0.7151522 * G + 0.072175 * B;
274
- const Z = 0.0193339 * R + 0.119192 * G + 0.9503041 * B;
275
- const fx = f(X / Xn), fy = f(Y / Yn2), fz = f(Z / Zn);
276
- return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
277
- }
278
- function hexToLab(hex2) {
279
- const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex2.trim());
280
- if (!m) return null;
281
- const h = m[1].length === 3 ? m[1][0] + m[1][0] + m[1][1] + m[1][1] + m[1][2] + m[1][2] : m[1];
282
- return rgbToLab(parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16));
283
- }
284
- var RAD = Math.PI / 180;
285
- var DEG = 180 / Math.PI;
286
- var NEUTRAL_CHROMA = 1e-4;
287
- function deltaE2000(c1, c2) {
288
- const C1 = Math.hypot(c1.a, c1.b), C2 = Math.hypot(c2.a, c2.b);
289
- const Cbar = (C1 + C2) / 2;
290
- const C7 = Math.pow(Cbar, 7);
291
- const G = 0.5 * (1 - Math.sqrt(C7 / (C7 + Math.pow(25, 7))));
292
- const a1p = (1 + G) * c1.a, a2p = (1 + G) * c2.a;
293
- const C1p = Math.hypot(a1p, c1.b), C2p = Math.hypot(a2p, c2.b);
294
- const neutral = C1p < NEUTRAL_CHROMA || C2p < NEUTRAL_CHROMA;
295
- const h1p = C1p < NEUTRAL_CHROMA ? 0 : (Math.atan2(c1.b, a1p) * DEG + 360) % 360;
296
- const h2p = C2p < NEUTRAL_CHROMA ? 0 : (Math.atan2(c2.b, a2p) * DEG + 360) % 360;
297
- const dLp = c2.L - c1.L;
298
- const dCp = C2p - C1p;
299
- let dhp;
300
- if (neutral) dhp = 0;
301
- else if (Math.abs(h2p - h1p) <= 180) dhp = h2p - h1p;
302
- else if (h2p - h1p > 180) dhp = h2p - h1p - 360;
303
- else dhp = h2p - h1p + 360;
304
- const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(dhp / 2 * RAD);
305
- const Lbarp = (c1.L + c2.L) / 2;
306
- const Cbarp = (C1p + C2p) / 2;
307
- let hbarp;
308
- if (neutral) hbarp = h1p + h2p;
309
- else if (Math.abs(h1p - h2p) <= 180) hbarp = (h1p + h2p) / 2;
310
- else if (h1p + h2p < 360) hbarp = (h1p + h2p + 360) / 2;
311
- else hbarp = (h1p + h2p - 360) / 2;
312
- const T = 1 - 0.17 * Math.cos((hbarp - 30) * RAD) + 0.24 * Math.cos(2 * hbarp * RAD) + 0.32 * Math.cos((3 * hbarp + 6) * RAD) - 0.2 * Math.cos((4 * hbarp - 63) * RAD);
313
- const dTheta = 30 * Math.exp(-Math.pow((hbarp - 275) / 25, 2));
314
- const Cbarp7 = Math.pow(Cbarp, 7);
315
- const RC = 2 * Math.sqrt(Cbarp7 / (Cbarp7 + Math.pow(25, 7)));
316
- const RT = -RC * Math.sin(2 * dTheta * RAD);
317
- const Lm50 = Math.pow(Lbarp - 50, 2);
318
- const SL = 1 + 0.015 * Lm50 / Math.sqrt(20 + Lm50);
319
- const SC = 1 + 0.045 * Cbarp;
320
- const SH = 1 + 0.015 * Cbarp * T;
321
- const tL = dLp / SL, tC = dCp / SC, tH = dHp / SH;
322
- return Math.sqrt(tL * tL + tC * tC + tH * tH + RT * tC * tH);
323
- }
324
- function labToHex(lab) {
325
- const finv = (t) => {
326
- const t3 = t * t * t;
327
- return t3 > EPS ? t3 : (116 * t - 16) / KAPPA;
328
- };
329
- const fy = (lab.L + 16) / 116, fx = fy + lab.a / 500, fz = fy - lab.b / 200;
330
- const X = Xn * finv(fx);
331
- const Y = Yn2 * (lab.L > KAPPA * EPS ? fy * fy * fy : lab.L / KAPPA);
332
- const Z = Zn * finv(fz);
333
- const lin = [
334
- 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z,
335
- -0.969266 * X + 1.8760108 * Y + 0.041556 * Z,
336
- 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
337
- ];
338
- return "#" + lin.map((v) => {
339
- const c = Math.max(0, Math.min(1, v));
340
- const g = c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
341
- return Math.round(g * 255).toString(16).padStart(2, "0");
342
- }).join("");
343
- }
344
- function deltaEHex(a, b) {
345
- const la = hexToLab(a), lb = hexToLab(b);
346
- return la && lb ? deltaE2000(la, lb) : NaN;
347
- }
348
-
349
- // src/colourSpread.ts
350
- var SAME_SHADE_DELTA_E = 5;
351
- var MIN_RAMP_FILLS = 4;
352
- var RAMP_SLACK_MAX = 0.15;
353
- var MIN_MARKS = 24;
354
- var ELEMENT_CAP = 5e3;
355
- var MAX_CELLS = 48;
356
- var CELL_STEP = 32;
357
- var EMPTY = { marks: 0, distinctFills: 0, crowdShare: 0, spanDeltaE: 0 };
358
- function styleOf(el, doc) {
359
- try {
360
- const view = doc?.defaultView;
361
- return view?.getComputedStyle ? view.getComputedStyle(el) : null;
362
- } catch {
363
- return null;
364
- }
365
- }
366
- function fillOf(el, cs) {
367
- const a = el.getAttribute?.("fill");
368
- if (a !== null && a !== void 0 && String(a).trim() !== "") return String(a).trim();
369
- const c = cs?.fill;
370
- return c === null || c === void 0 ? "" : String(c).trim();
371
- }
372
- function toRgb(raw) {
373
- const s = raw.trim().toLowerCase();
374
- if (!s || s === "none" || s === "transparent") return null;
375
- const hex2 = /^#([0-9a-f]{3}|[0-9a-f]{6})$/.exec(s);
376
- if (hex2) {
377
- const h = hex2[1];
378
- const full = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
379
- return [
380
- parseInt(full.slice(0, 2), 16),
381
- parseInt(full.slice(2, 4), 16),
382
- parseInt(full.slice(4, 6), 16)
383
- ];
384
- }
385
- const m = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)\s*(?:[,/]\s*([\d.]+)\s*)?\)$/.exec(s);
386
- if (!m) return null;
387
- if (m[4] !== void 0 && parseFloat(m[4]) === 0) return null;
388
- return [Math.round(parseFloat(m[1])), Math.round(parseFloat(m[2])), Math.round(parseFloat(m[3]))];
389
- }
390
- function cellKey(rgb) {
391
- return [
392
- Math.round(rgb[0] / CELL_STEP),
393
- Math.round(rgb[1] / CELL_STEP),
394
- Math.round(rgb[2] / CELL_STEP)
395
- ].join(",");
396
- }
397
- function hex(rgb) {
398
- return "#" + rgb.map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
399
- }
400
- function censusColourSpread(container, doc) {
401
- if (!container || typeof container.querySelectorAll !== "function") return EMPTY;
402
- try {
403
- const els = Array.from(
404
- container.querySelectorAll(`.${MARK_CLASS}, [${ROW_IDX_ATTR}]`)
405
- );
406
- if (els.length === 0 || els.length > ELEMENT_CAP) return EMPTY;
407
- const ownerDoc = doc ?? container.ownerDocument;
408
- const painted = [];
409
- for (const el of els) {
410
- const rgb = toRgb(fillOf(el, styleOf(el, ownerDoc)));
411
- if (rgb) painted.push({ rgb, tag: String(el.tagName || "").toLowerCase() });
412
- }
413
- const byTag = /* @__PURE__ */ new Map();
414
- for (const p of painted) {
415
- const g = byTag.get(p.tag) ?? { fills: /* @__PURE__ */ new Set(), n: 0 };
416
- g.fills.add(p.rgb.join(","));
417
- g.n++;
418
- byTag.set(p.tag, g);
419
- }
420
- const overlays = new Set([...byTag].filter(([, g]) => g.fills.size === 1 && g.n >= MIN_MARKS).map(([t]) => t));
421
- const restFills = new Set([...byTag].filter(([t]) => !overlays.has(t)).flatMap(([, g]) => [...g.fills]));
422
- const counted = restFills.size > 1 ? painted.filter((p) => !overlays.has(p.tag)) : painted;
423
- const distinct = /* @__PURE__ */ new Set();
424
- const cells = /* @__PURE__ */ new Map();
425
- let marks = 0;
426
- for (const { rgb } of counted) {
427
- marks++;
428
- distinct.add(rgb.join(","));
429
- const k = cellKey(rgb);
430
- const cur = cells.get(k);
431
- if (cur) cur.n++;
432
- else cells.set(k, { rgb, n: 1 });
433
- }
434
- if (marks < MIN_MARKS || distinct.size < MIN_RAMP_FILLS) return EMPTY;
435
- const top = [...cells.values()].sort((a, b) => b.n - a.n).slice(0, MAX_CELLS);
436
- const cs = top.map((c) => hexToLab(hex(c.rgb))).filter((l) => l !== null);
437
- if (cs.length < MIN_RAMP_FILLS) return EMPTY;
438
- const d = cs.map(() => new Array(cs.length).fill(0));
439
- let span = 0, lo = 0, hi = 1;
440
- for (let i = 0; i < cs.length; i++) {
441
- for (let j = i + 1; j < cs.length; j++) {
442
- const v = deltaE2000(cs[i], cs[j]);
443
- d[i][j] = v;
444
- d[j][i] = v;
445
- if (v > span) {
446
- span = v;
447
- lo = i;
448
- hi = j;
449
- }
450
- }
451
- }
452
- if (span <= 0) return EMPTY;
453
- let slack = 0, interior = 0;
454
- for (let i = 0; i < cs.length; i++) {
455
- if (i === lo || i === hi) continue;
456
- slack += (d[lo][i] + d[i][hi]) / span - 1;
457
- interior++;
458
- }
459
- if (interior < 2 || slack / interior > RAMP_SLACK_MAX) return EMPTY;
460
- let crowd = 0;
461
- for (let i = 0; i < cs.length; i++) {
462
- let n = 0;
463
- for (let j = 0; j < cs.length; j++) {
464
- if (i === j || d[i][j] <= SAME_SHADE_DELTA_E) n += top[j].n;
465
- }
466
- if (n > crowd) crowd = n;
467
- }
468
- return {
469
- marks,
470
- distinctFills: distinct.size,
471
- crowdShare: Math.round(crowd / marks * 100),
472
- spanDeltaE: Math.round(span)
473
- };
474
- } catch {
475
- return EMPTY;
476
- }
477
- }
478
- function colourSpreadFlag(c) {
479
- if (c.marks === 0) return "";
480
- return "ramp:d3:crowd" + Math.min(80, Math.floor(c.crowdShare / 20) * 20);
481
- }
482
-
483
265
  // src/autoRamp.ts
484
266
  var AUTO_RAMP_CLASSES = 7;
485
267
  var AUTO_RAMP_DRAWN_OPACITY = 0.5;
@@ -498,9 +280,9 @@ function mixLab(a, b, t) {
498
280
  return { L: a.L + (b.L - a.L) * t, a: a.a + (b.a - a.a) * t, b: a.b + (b.b - a.b) * t };
499
281
  }
500
282
  function paintedOver(fg, canvas, alpha) {
501
- const f2 = [1, 3, 5].map((i) => parseInt(fg.slice(i, i + 2), 16));
283
+ const f = [1, 3, 5].map((i) => parseInt(fg.slice(i, i + 2), 16));
502
284
  const c = [1, 3, 5].map((i) => parseInt(canvas.slice(i, i + 2), 16));
503
- const mixed = f2.map((v, i) => Math.round(v * alpha + c[i] * (1 - alpha))).map((v) => v.toString(16).padStart(2, "0"));
285
+ const mixed = f.map((v, i) => Math.round(v * alpha + c[i] * (1 - alpha))).map((v) => v.toString(16).padStart(2, "0"));
504
286
  return hexToLab("#" + mixed.join(""));
505
287
  }
506
288
  function autoRampMinStep(low, high, canvas, classes = AUTO_RAMP_CLASSES, opacity = AUTO_RAMP_DRAWN_OPACITY) {
@@ -527,8 +309,8 @@ function resolveAutoColorScale(p) {
527
309
  if (key === memoKey) return memoValue;
528
310
  const bg = hexToLab(canvas);
529
311
  const darkCanvas = bg.L < 50;
530
- const usable = (hex2) => {
531
- const lab = hex2 ? hexToLab(hex2) : null;
312
+ const usable = (hex) => {
313
+ const lab = hex ? hexToLab(hex) : null;
532
314
  return !!lab && deltaE2000(lab, bg) >= MIN_ACCENT_VS_CANVAS;
533
315
  };
534
316
  const candidates = p.isHighContrast ? [normHex(p.themeAccent), normHex(p.themeFg)] : [...palette.map(normHex), normHex(p.themeAccent), normHex(p.themeFg)];
@@ -554,6 +336,252 @@ function resolveAutoColorScale(p) {
554
336
  return memoValue;
555
337
  }
556
338
 
339
+ // src/selectionMarker.ts
340
+ var SELECTION_MARKER_DEFAULT = "auto";
341
+ var MAP_SELECTION_MARKER_DEFAULT = "outline";
342
+ function readSelectionMarker(raw) {
343
+ const v = typeof raw === "string" ? raw.trim().toLowerCase() : "";
344
+ return v === "none" || v === "dot" || v === "check" || v === "outline" || v === "auto" ? v : SELECTION_MARKER_DEFAULT;
345
+ }
346
+ function readMapSelectionMarker(raw) {
347
+ const v = typeof raw === "string" ? raw.trim().toLowerCase() : "";
348
+ return v === "none" || v === "dot" || v === "check" || v === "outline" ? v : MAP_SELECTION_MARKER_DEFAULT;
349
+ }
350
+ var REGION_JOIN_COLUMN = "__geoIso__";
351
+ function isRegionMapData(data) {
352
+ const cols = data?.columns;
353
+ if (!Array.isArray(cols)) return false;
354
+ return cols.some((c) => !!c && c.name === REGION_JOIN_COLUMN);
355
+ }
356
+ var NEUTRAL_CENTRE_RAMP_SELECTOR = '.llm-value-ramp[data-kind="diverging"]';
357
+ function hasNeutralCentreRamp(container) {
358
+ try {
359
+ return !!container?.querySelector?.(NEUTRAL_CENTRE_RAMP_SELECTOR);
360
+ } catch {
361
+ return false;
362
+ }
363
+ }
364
+ function selectionMarkerFor(facts) {
365
+ if (facts.regionMap) return readMapSelectionMarker(facts.mapChoice);
366
+ const c = readSelectionMarker(facts.choice);
367
+ if (c !== "auto") return c;
368
+ return facts.neutralCentreRamp ? "outline" : "none";
369
+ }
370
+ var SELECTION_OUTLINE_CLASS = "lch-sel-outline";
371
+ var SVG_NS = "http://www.w3.org/2000/svg";
372
+ var AREA_TAGS = /* @__PURE__ */ new Set(["path", "rect", "polygon"]);
373
+ var UNFILLED = /^(none|transparent|rgba\([^)]*,\s*0(\.0+)?\s*\))$/i;
374
+ function isAreaMark(el) {
375
+ const tag = (el.localName || el.tagName || "").toLowerCase();
376
+ if (!AREA_TAGS.has(tag)) return false;
377
+ let fill = "", fillOpacity = "";
378
+ try {
379
+ const cs = el.ownerDocument?.defaultView?.getComputedStyle?.(el);
380
+ fill = (cs?.fill || "").trim();
381
+ fillOpacity = (cs?.fillOpacity || "").trim();
382
+ } catch {
383
+ }
384
+ const style = el.style;
385
+ if (!fill) fill = (style?.fill || el.getAttribute("fill") || "").trim();
386
+ if (!fillOpacity) fillOpacity = (style?.fillOpacity || el.getAttribute("fill-opacity") || "").trim();
387
+ if (fill && UNFILLED.test(fill)) return false;
388
+ if (fillOpacity !== "" && parseFloat(fillOpacity) === 0) return false;
389
+ return true;
390
+ }
391
+ function collectOutlineMarks(container, filter) {
392
+ const picked = [];
393
+ const seen = /* @__PURE__ */ new Set();
394
+ const add = (el) => {
395
+ if (seen.has(el)) return;
396
+ seen.add(el);
397
+ if (isAreaMark(el)) picked.push(el);
398
+ };
399
+ for (const m of Array.from(container.querySelectorAll(`.${MARK_CLASS}.${MARK_SELECTED_CLASS}`))) {
400
+ if (filter?.shown && !filter.shown(m)) continue;
401
+ if ((m.localName || "").toLowerCase() === "g") {
402
+ for (const s of Array.from(m.querySelectorAll("path, rect, polygon"))) add(s);
403
+ } else add(m);
404
+ }
405
+ if (picked.length < 2 || picked.length > 300) return picked;
406
+ const EPS = 1;
407
+ const rects = picked.map((m) => {
408
+ try {
409
+ return m.getBoundingClientRect();
410
+ } catch {
411
+ return null;
412
+ }
413
+ });
414
+ return picked.filter((_, i) => {
415
+ const c = rects[i];
416
+ if (!c) return true;
417
+ const cArea = c.width * c.height;
418
+ if (!(cArea > 0)) return true;
419
+ for (let j = 0; j < rects.length; j++) {
420
+ const r = rects[j];
421
+ if (j === i || !r) continue;
422
+ const a = r.width * r.height;
423
+ if (!(a > 0) || a >= cArea) continue;
424
+ if (r.left >= c.left - EPS && r.right <= c.right + EPS && r.top >= c.top - EPS && r.bottom <= c.bottom + EPS) return false;
425
+ }
426
+ return true;
427
+ });
428
+ }
429
+ function outlineWindowOf(mark, stop) {
430
+ let acc = null;
431
+ const narrow = (w) => {
432
+ if (!w) return;
433
+ acc = acc ? {
434
+ left: Math.max(acc.left, w.left),
435
+ top: Math.max(acc.top, w.top),
436
+ right: Math.min(acc.right, w.right),
437
+ bottom: Math.min(acc.bottom, w.bottom)
438
+ } : { ...w };
439
+ };
440
+ for (let el = mark; el && el !== stop; el = el.parentElement) {
441
+ try {
442
+ if ((el.localName || "").toLowerCase() === "svg") {
443
+ const r = el.getBoundingClientRect();
444
+ if (r.width > 0 && r.height > 0) narrow({ left: r.left, top: r.top, right: r.right, bottom: r.bottom });
445
+ } else if (el.hasAttribute?.("clip-path")) narrow(clipWindowOf(el));
446
+ } catch {
447
+ }
448
+ }
449
+ return acc;
450
+ }
451
+ function controlBoxesIn(root, opts = {}) {
452
+ const pad = opts.pad ?? 2, join = opts.join ?? 4;
453
+ let boxes = [];
454
+ try {
455
+ for (const el of Array.from(root?.querySelectorAll?.(`.${CONTROL_CLASS}`) ?? [])) {
456
+ const r = el.getBoundingClientRect();
457
+ if (!(r.width > 0) || !(r.height > 0)) continue;
458
+ boxes.push({ left: r.left - pad, top: r.top - pad, right: r.right + pad, bottom: r.bottom + pad });
459
+ }
460
+ } catch {
461
+ return [];
462
+ }
463
+ const near = (a, b) => a.left - join <= b.right && b.left - join <= a.right && a.top - join <= b.bottom && b.top - join <= a.bottom;
464
+ let merged = true;
465
+ while (merged) {
466
+ merged = false;
467
+ const out = [];
468
+ for (const b of boxes) {
469
+ const hit = out.find((o) => near(o, b));
470
+ if (hit) {
471
+ hit.left = Math.min(hit.left, b.left);
472
+ hit.top = Math.min(hit.top, b.top);
473
+ hit.right = Math.max(hit.right, b.right);
474
+ hit.bottom = Math.max(hit.bottom, b.bottom);
475
+ merged = true;
476
+ } else out.push({ ...b });
477
+ }
478
+ boxes = out;
479
+ }
480
+ return boxes;
481
+ }
482
+ function copyGeometry(doc, el) {
483
+ const tag = (el.localName || "").toLowerCase();
484
+ const keep = tag === "path" ? ["d"] : tag === "rect" ? ["x", "y", "width", "height", "rx", "ry"] : tag === "polygon" ? ["points"] : null;
485
+ if (!keep) return null;
486
+ const out = doc.createElementNS(SVG_NS, tag);
487
+ let any = false;
488
+ for (const a of keep) {
489
+ const v = el.getAttribute(a);
490
+ if (v != null && v !== "") {
491
+ out.setAttribute(a, v);
492
+ any = true;
493
+ }
494
+ }
495
+ return any ? out : null;
496
+ }
497
+ var num = (n) => String(Math.round(n * 1e3) / 1e3);
498
+ var maskSeq = 0;
499
+ function paintSelectionOutlines(overlay, marks, style) {
500
+ const doc = overlay.ownerDocument;
501
+ const base = overlay.getBoundingClientRect();
502
+ const width = style.width ?? 2;
503
+ const windowOf = style.windowOf ?? ((m) => outlineWindowOf(m));
504
+ const root = doc.createElementNS(SVG_NS, "svg");
505
+ root.setAttribute("class", SELECTION_OUTLINE_CLASS);
506
+ root.setAttribute("aria-hidden", "true");
507
+ root.setAttribute("focusable", "false");
508
+ root.setAttribute("style", "position:absolute;left:0;top:0;width:1px;height:1px;overflow:visible;pointer-events:none");
509
+ let host = root;
510
+ const holes = (style.avoid ?? []).filter((b) => b.right > b.left && b.bottom > b.top);
511
+ if (holes.length) {
512
+ const id = `${SELECTION_OUTLINE_CLASS}-mask-${++maskSeq}`;
513
+ const defs = doc.createElementNS(SVG_NS, "defs");
514
+ const mask = doc.createElementNS(SVG_NS, "mask");
515
+ mask.setAttribute("id", id);
516
+ mask.setAttribute("maskUnits", "userSpaceOnUse");
517
+ const BIG = 1e5;
518
+ for (const [k, v] of [["x", -BIG], ["y", -BIG], ["width", 2 * BIG], ["height", 2 * BIG]]) mask.setAttribute(k, String(v));
519
+ const open = doc.createElementNS(SVG_NS, "rect");
520
+ for (const [k, v] of [["x", -BIG], ["y", -BIG], ["width", 2 * BIG], ["height", 2 * BIG]]) open.setAttribute(k, String(v));
521
+ open.setAttribute("fill", "#fff");
522
+ mask.appendChild(open);
523
+ for (const b of holes) {
524
+ const r = doc.createElementNS(SVG_NS, "rect");
525
+ r.setAttribute("class", `${SELECTION_OUTLINE_CLASS}-hole`);
526
+ r.setAttribute("x", num(b.left - base.left));
527
+ r.setAttribute("y", num(b.top - base.top));
528
+ r.setAttribute("width", num(b.right - b.left));
529
+ r.setAttribute("height", num(b.bottom - b.top));
530
+ r.setAttribute("fill", "#000");
531
+ mask.appendChild(r);
532
+ }
533
+ defs.appendChild(mask);
534
+ root.appendChild(defs);
535
+ host = doc.createElementNS(SVG_NS, "g");
536
+ host.setAttribute("mask", `url(#${id})`);
537
+ root.appendChild(host);
538
+ }
539
+ let drawn = 0;
540
+ const max = style.max ?? 600;
541
+ for (const m of marks) {
542
+ if (drawn >= max) break;
543
+ let ctm = null;
544
+ try {
545
+ ctm = m.getScreenCTM?.() ?? null;
546
+ } catch {
547
+ ctm = null;
548
+ }
549
+ if (!ctm) continue;
550
+ const shape = copyGeometry(doc, m);
551
+ if (!shape) continue;
552
+ let parent = host;
553
+ let ox = base.left, oy = base.top;
554
+ const w = windowOf(m);
555
+ if (w) {
556
+ if (!(w.right > w.left) || !(w.bottom > w.top)) continue;
557
+ const v = doc.createElementNS(SVG_NS, "svg");
558
+ v.setAttribute("x", num(w.left - base.left));
559
+ v.setAttribute("y", num(w.top - base.top));
560
+ v.setAttribute("width", num(w.right - w.left));
561
+ v.setAttribute("height", num(w.bottom - w.top));
562
+ v.setAttribute("overflow", "hidden");
563
+ host.appendChild(v);
564
+ parent = v;
565
+ ox = w.left;
566
+ oy = w.top;
567
+ }
568
+ const transform = `matrix(${num(ctm.a)} ${num(ctm.b)} ${num(ctm.c)} ${num(ctm.d)} ${num(ctm.e - ox)} ${num(ctm.f - oy)})`;
569
+ for (const [stroke, sw] of [[style.halo, width + 2], [style.color, width]]) {
570
+ const p = shape.cloneNode(false);
571
+ p.setAttribute("transform", transform);
572
+ p.setAttribute("fill", "none");
573
+ p.setAttribute("stroke", stroke);
574
+ p.setAttribute("stroke-width", String(sw));
575
+ p.setAttribute("stroke-linejoin", "round");
576
+ p.setAttribute("vector-effect", "non-scaling-stroke");
577
+ parent.appendChild(p);
578
+ }
579
+ drawn++;
580
+ }
581
+ if (drawn > 0) overlay.appendChild(root);
582
+ return drawn;
583
+ }
584
+
557
585
  // src/exportTable.ts
558
586
  var EXPORT_FORMATS = Object.freeze(["xlsx", "csv"]);
559
587
  var XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
@@ -702,8 +730,8 @@ function excelNumberFormat(col, values = []) {
702
730
  if (!sh) return null;
703
731
  let frac = 0;
704
732
  for (const v of values) {
705
- const f2 = fractionDigits(sh.percent ? Math.round(v * 100 * 1e9) / 1e9 : v);
706
- if (f2 > frac) frac = f2;
733
+ const f = fractionDigits(sh.percent ? Math.round(v * 100 * 1e9) / 1e9 : v);
734
+ if (f > frac) frac = f;
707
735
  }
708
736
  return codeFromShape(sh, frac);
709
737
  }
@@ -999,7 +1027,7 @@ function bytesToBase64(bytes) {
999
1027
  }
1000
1028
  function exportFormatsFor(hostFormats) {
1001
1029
  if (!Array.isArray(hostFormats)) return EXPORT_FORMATS.slice();
1002
- return EXPORT_FORMATS.filter((f2) => hostFormats.indexOf(f2) >= 0);
1030
+ return EXPORT_FORMATS.filter((f) => hostFormats.indexOf(f) >= 0);
1003
1031
  }
1004
1032
  function exportTableService(save, formats = EXPORT_FORMATS) {
1005
1033
  const allowed = formats.slice();
@@ -1169,12 +1197,20 @@ function resolveOptions(p) {
1169
1197
  const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
1170
1198
  return Number.isFinite(n) ? clamp(n, DIM_OPACITY_MIN, 1) : void 0;
1171
1199
  })(),
1172
- // A recognised glyph or none: an unknown string must never reach the painter as a live mode, and "none" is
1173
- // undefined rather than a value, so a host that passes nothing and one that passes "none" resolve alike.
1200
+ // A recognised marker or nothing: an unknown string must never reach the painter as a live mode. "none" and
1201
+ // "auto" read undefined here, as a host that passes nothing does; what tells them apart is selectionMarker.
1174
1202
  selectionGlyph: (() => {
1175
1203
  const v = (p.selectionGlyph == null ? "" : String(p.selectionGlyph)).toLowerCase();
1176
- return v === "dot" || v === "check" ? v : void 0;
1204
+ return v === "dot" || v === "check" || v === "outline" ? v : void 0;
1177
1205
  })(),
1206
+ // THE WHOLE CHOICE: absent, unknown and "auto" are the default ("auto"), and "none" is a reader's None. The map's
1207
+ // choice beside it, its default "outline" when it made none.
1208
+ selectionMarker: readSelectionMarker(p.selectionGlyph),
1209
+ mapSelectionGlyph: (() => {
1210
+ const v = (p.mapSelectionGlyph == null ? "" : String(p.mapSelectionGlyph)).toLowerCase();
1211
+ return v === "none" || v === "dot" || v === "check" || v === "outline" ? v : void 0;
1212
+ })(),
1213
+ mapSelectionMarker: readMapSelectionMarker(p.mapSelectionGlyph),
1178
1214
  // A host FACT from the generate answer, true only for a literal true: silence never suppresses anything.
1179
1215
  suppressSelectionGlyph: p.suppressSelectionGlyph === true ? true : void 0,
1180
1216
  // A host FACT (1.22.0): the chart works from the keyboard. True only for a literal true, like the one above.
@@ -1319,7 +1355,7 @@ function resolvePointChannel(cols, rowObjs, point) {
1319
1355
  let labelCol;
1320
1356
  if (bind) {
1321
1357
  const dims = cols.filter((c) => !c.isMeasure).map((c) => c.name);
1322
- const res = _r(dims, rowObjs, bind, point?.mapKind, cols.map((c) => c.name));
1358
+ const res = Lr(dims, rowObjs, bind, point?.mapKind, cols.map((c) => c.name));
1323
1359
  bind = res.bind;
1324
1360
  rolesBackfilled = res.backfilled;
1325
1361
  rolesRefused = res.refused;
@@ -1496,7 +1532,7 @@ function buildRenderPayload(cols, rowObjs, geo, point, destination, opts) {
1496
1532
  }
1497
1533
  function longitudeBeside(names) {
1498
1534
  const words = (n) => n.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-zÀ-ɏ]+/);
1499
- return names.find((n) => words(n).some((w) => w === "lon" || w === "lng" || w === "long" || w === "longitude")) ?? names.find((n) => Rt(words(n)) === "lon") ?? null;
1535
+ return names.find((n) => words(n).some((w) => w === "lon" || w === "lng" || w === "long" || w === "longitude")) ?? names.find((n) => Nt(words(n)) === "lon") ?? null;
1500
1536
  }
1501
1537
 
1502
1538
  // src/codeComments.ts
@@ -1634,10 +1670,97 @@ function stripJsComments(code) {
1634
1670
  return out;
1635
1671
  }
1636
1672
 
1673
+ // src/markFollower.ts
1674
+ var MARK_MOVE_ATTRIBUTES = Object.freeze([
1675
+ "transform",
1676
+ "d",
1677
+ "x",
1678
+ "y",
1679
+ "cx",
1680
+ "cy",
1681
+ "points",
1682
+ "width",
1683
+ "height",
1684
+ "visibility",
1685
+ "opacity",
1686
+ "style",
1687
+ "class"
1688
+ ]);
1689
+ function followMarks(container, opts) {
1690
+ const win = opts.win ?? container.ownerDocument?.defaultView ?? null;
1691
+ let observer = null;
1692
+ let resize = null;
1693
+ let size = "";
1694
+ let queued = false;
1695
+ let queuedResize = false;
1696
+ const sizeOf = () => `${container.offsetWidth ?? 0}x${container.offsetHeight ?? 0}`;
1697
+ const movesAMark = (r) => {
1698
+ const t = r.target;
1699
+ if (!t || opts.blind && t.closest?.(opts.blind)) return false;
1700
+ return !!(t.classList?.contains(MARK_CLASS) || t.querySelector?.(`.${MARK_CLASS}`));
1701
+ };
1702
+ const queue = (resized) => {
1703
+ if (resized) queuedResize = true;
1704
+ if (queued) return;
1705
+ queued = true;
1706
+ const run = () => {
1707
+ const r = queuedResize;
1708
+ queued = false;
1709
+ queuedResize = false;
1710
+ if (observer || resize) {
1711
+ try {
1712
+ opts.repaint(r);
1713
+ } catch {
1714
+ }
1715
+ }
1716
+ };
1717
+ if (win?.requestAnimationFrame) win.requestAnimationFrame(run);
1718
+ else setTimeout(run, 16);
1719
+ };
1720
+ return {
1721
+ follow(on) {
1722
+ if (!on) {
1723
+ if (observer) {
1724
+ observer.disconnect();
1725
+ observer = null;
1726
+ }
1727
+ if (resize) {
1728
+ resize.disconnect();
1729
+ resize = null;
1730
+ }
1731
+ return;
1732
+ }
1733
+ const MO = win?.MutationObserver;
1734
+ if (!observer && MO) {
1735
+ observer = new MO((records) => {
1736
+ if (!queued && records.some(movesAMark)) queue(false);
1737
+ });
1738
+ observer.observe(container, { subtree: true, attributes: true, attributeFilter: [...MARK_MOVE_ATTRIBUTES] });
1739
+ }
1740
+ const RO = win?.ResizeObserver;
1741
+ if (!resize && RO) {
1742
+ resize = new RO(() => {
1743
+ if (sizeOf() !== size) queue(true);
1744
+ });
1745
+ resize.observe(container);
1746
+ }
1747
+ },
1748
+ painted() {
1749
+ size = sizeOf();
1750
+ },
1751
+ takeRecords() {
1752
+ observer?.takeRecords();
1753
+ },
1754
+ get following() {
1755
+ return !!(observer || resize);
1756
+ }
1757
+ };
1758
+ }
1759
+
1637
1760
  // src/blankRender.ts
1638
- var EMPTY2 = { markCount: 0, legendMarkCount: 0, containerKind: "empty", declaresNoRowMarks: false };
1761
+ var EMPTY = { markCount: 0, legendMarkCount: 0, containerKind: "empty", declaresNoRowMarks: false };
1639
1762
  function censusMarks(container) {
1640
- if (!container || typeof container.querySelectorAll !== "function") return EMPTY2;
1763
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY;
1641
1764
  try {
1642
1765
  const marks = /* @__PURE__ */ new Set();
1643
1766
  for (const el of Array.from(container.querySelectorAll(`.${MARK_CLASS}`))) marks.add(el);
@@ -1656,7 +1779,7 @@ function censusMarks(container) {
1656
1779
  declaresNoRowMarks: !!container.querySelector(`[${NO_ROW_MARKS_ATTR}]`)
1657
1780
  };
1658
1781
  } catch {
1659
- return EMPTY2;
1782
+ return EMPTY;
1660
1783
  }
1661
1784
  }
1662
1785
  function isBlankRender(i) {
@@ -1680,8 +1803,8 @@ function censusClippedText(container) {
1680
1803
  try {
1681
1804
  const svg = container?.querySelector?.("svg");
1682
1805
  if (!svg) return none;
1683
- const f2 = svg.getBoundingClientRect();
1684
- if (!f2 || !(f2.width > 0) || !(f2.height > 0)) return none;
1806
+ const f = svg.getBoundingClientRect();
1807
+ if (!f || !(f.width > 0) || !(f.height > 0)) return none;
1685
1808
  const view = svg.ownerDocument?.defaultView;
1686
1809
  const clipped = [];
1687
1810
  let measured = 0;
@@ -1691,7 +1814,7 @@ function censusClippedText(container) {
1691
1814
  const s = view?.getComputedStyle?.(t);
1692
1815
  if (s && (s.display === "none" || s.visibility === "hidden" || s.opacity === "0")) continue;
1693
1816
  measured++;
1694
- const top = f2.top - r.top, left = f2.left - r.left, bottom = r.bottom - f2.bottom, right = r.right - f2.right;
1817
+ const top = f.top - r.top, left = f.left - r.left, bottom = r.bottom - f.bottom, right = r.right - f.right;
1695
1818
  if (top > CLIP_TOLERANCE_PX || left > CLIP_TOLERANCE_PX || bottom > CLIP_TOLERANCE_PX || right > CLIP_TOLERANCE_PX) {
1696
1819
  clipped.push({
1697
1820
  text: String(t.textContent ?? "").slice(0, 40),
@@ -1716,13 +1839,13 @@ function clippedTextFlag(c) {
1716
1839
  }
1717
1840
 
1718
1841
  // src/valuePlacement.ts
1719
- var MIN_MARKS2 = 24;
1720
- var ELEMENT_CAP2 = 5e3;
1842
+ var MIN_MARKS = 24;
1843
+ var ELEMENT_CAP = 5e3;
1721
1844
  var FIT_SAMPLE = 90;
1722
1845
  var VALUE_AXIS_MIN_FIT = 0.8;
1723
1846
  var MIN_AXIS_SPAN_PX = 40;
1724
- var EMPTY3 = { marks: 0, axis: "", column: "", fit: 0, offShare: 0, medianOffRadii: 0 };
1725
- function num(el, name) {
1847
+ var EMPTY2 = { marks: 0, axis: "", column: "", fit: 0, offShare: 0, medianOffRadii: 0 };
1848
+ function num2(el, name) {
1726
1849
  const v = el.getAttribute?.(name);
1727
1850
  if (v === null || v === void 0 || String(v).trim() === "") return NaN;
1728
1851
  return parseFloat(String(v));
@@ -1754,12 +1877,12 @@ function robustLine(v, pos) {
1754
1877
  }
1755
1878
  function censusValuePlacement(container, data) {
1756
1879
  try {
1757
- if (!container || typeof container.querySelectorAll !== "function") return EMPTY3;
1880
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY2;
1758
1881
  const rows = data?.rows;
1759
1882
  const columns = data?.columns ?? [];
1760
- if (!Array.isArray(rows) || !rows.length || !columns.length) return EMPTY3;
1883
+ if (!Array.isArray(rows) || !rows.length || !columns.length) return EMPTY2;
1761
1884
  const els = Array.from(container.querySelectorAll(`circle[${ROW_IDX_ATTR}]`));
1762
- if (els.length < MIN_MARKS2 || els.length > ELEMENT_CAP2) return EMPTY3;
1885
+ if (els.length < MIN_MARKS || els.length > ELEMENT_CAP) return EMPTY2;
1763
1886
  const dots = [];
1764
1887
  const seen = /* @__PURE__ */ new Set();
1765
1888
  for (const el of els) {
@@ -1767,12 +1890,12 @@ function censusValuePlacement(container, data) {
1767
1890
  if (!/^\d+$/.test(raw)) continue;
1768
1891
  const i = +raw;
1769
1892
  const row = rows[i];
1770
- const x = num(el, "cx"), y = num(el, "cy"), r = num(el, "r");
1893
+ const x = num2(el, "cx"), y = num2(el, "cy"), r = num2(el, "r");
1771
1894
  if (!row || !Number.isFinite(x) || !Number.isFinite(y) || !(r > 0)) continue;
1772
1895
  seen.add(i);
1773
1896
  dots.push({ row, x, y, r });
1774
1897
  }
1775
- if (dots.length < MIN_MARKS2 || seen.size < dots.length * 0.9) return EMPTY3;
1898
+ if (dots.length < MIN_MARKS || seen.size < dots.length * 0.9) return EMPTY2;
1776
1899
  let best = null;
1777
1900
  for (let c = 0; c < columns.length; c++) {
1778
1901
  if (/^__.*__$/.test(String(columns[c]?.name ?? ""))) continue;
@@ -1817,7 +1940,7 @@ function censusValuePlacement(container, data) {
1817
1940
  }
1818
1941
  }
1819
1942
  }
1820
- if (!best) return EMPTY3;
1943
+ if (!best) return EMPTY2;
1821
1944
  let off = 0, measured = 0;
1822
1945
  const radii = [];
1823
1946
  dots.forEach((d, k) => {
@@ -1828,7 +1951,7 @@ function censusValuePlacement(container, data) {
1828
1951
  radii.push(inRadii);
1829
1952
  if (inRadii > 1) off++;
1830
1953
  });
1831
- if (measured < MIN_MARKS2) return EMPTY3;
1954
+ if (measured < MIN_MARKS) return EMPTY2;
1832
1955
  return {
1833
1956
  marks: measured,
1834
1957
  axis: best.axis,
@@ -1838,7 +1961,7 @@ function censusValuePlacement(container, data) {
1838
1961
  medianOffRadii: Math.round(median(radii) * 100) / 100
1839
1962
  };
1840
1963
  } catch {
1841
- return EMPTY3;
1964
+ return EMPTY2;
1842
1965
  }
1843
1966
  }
1844
1967
  function valuePlacementFlag(c) {
@@ -1851,7 +1974,7 @@ function valuePlacementFlag(c) {
1851
1974
  // src/labelContrastDom.ts
1852
1975
  var LABEL_CONTRAST_DONE_ATTR = "data-lch-contrast";
1853
1976
  var LABEL_CONTRAST_CAP = 2e3;
1854
- var EMPTY4 = {
1977
+ var EMPTY3 = {
1855
1978
  rects: 0,
1856
1979
  scanned: 0,
1857
1980
  fixed: 0,
@@ -1929,11 +2052,11 @@ function extendPillOver(pill, tr) {
1929
2052
  const inv = m.inverse();
1930
2053
  const toUser = (x2, y2) => ({ x: inv.a * x2 + inv.c * y2 + inv.e, y: inv.b * x2 + inv.d * y2 + inv.f });
1931
2054
  const a = toUser(tr.left, tr.top), b = toUser(tr.right, tr.bottom);
1932
- const num2 = (k) => {
2055
+ const num3 = (k) => {
1933
2056
  const v = parseFloat(pill.getAttribute(k) || "");
1934
2057
  return isFinite(v) ? v : NaN;
1935
2058
  };
1936
- const x = num2("x"), y = num2("y"), w = num2("width"), h = num2("height");
2059
+ const x = num3("x"), y = num3("y"), w = num3("width"), h = num3("height");
1937
2060
  if (![x, y, w, h].every((v) => isFinite(v)) || !(w > 0) || !(h > 0)) return false;
1938
2061
  const pad = 1 / m.a;
1939
2062
  const x0 = Math.min(x, Math.min(a.x, b.x) - pad), x1 = Math.max(x + w, Math.max(a.x, b.x) + pad);
@@ -2002,7 +2125,7 @@ function addPill(tx, fill) {
2002
2125
  }
2003
2126
  }
2004
2127
  function applyLabelContrast(container, opts = {}) {
2005
- const report = { ...EMPTY4 };
2128
+ const report = { ...EMPTY3 };
2006
2129
  if (!container) {
2007
2130
  report.skipped = "no-container";
2008
2131
  return report;
@@ -2340,10 +2463,10 @@ function seenRight(el, right, stop) {
2340
2463
  }
2341
2464
  return right;
2342
2465
  }
2343
- var SVG_NS = "http://www.w3.org/2000/svg";
2466
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2344
2467
  function overflowWindowOf(el) {
2345
2468
  const win = el.ownerDocument.defaultView;
2346
- if (!win || el.namespaceURI === SVG_NS) return null;
2469
+ if (!win || el.namespaceURI === SVG_NS2) return null;
2347
2470
  try {
2348
2471
  const o = win.getComputedStyle(el);
2349
2472
  const clips = (v) => !!v && v !== "visible";
@@ -2426,17 +2549,22 @@ function createAnnotationLayer(container, onClick, opts) {
2426
2549
  const names = (data.columns || []).map((c) => typeof c === "string" ? c : c?.name);
2427
2550
  const marks = Array.from(container.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}]`)).filter((m) => !m.closest(`.${ANNOTATION_LAYER_CLASS}`));
2428
2551
  const marksByRow = /* @__PURE__ */ new Map();
2429
- for (const m of marks) for (const r of parseIdxs(m.getAttribute(ROW_IDX_ATTR))) {
2430
- const at = marksByRow.get(r);
2431
- if (at) at.push(m);
2432
- else marksByRow.set(r, [m]);
2552
+ const rowsOf = /* @__PURE__ */ new Map();
2553
+ for (const m of marks) {
2554
+ const own = new Set(parseIdxs(m.getAttribute(ROW_IDX_ATTR)));
2555
+ rowsOf.set(m, own);
2556
+ for (const r of own) {
2557
+ const at = marksByRow.get(r);
2558
+ if (at) at.push(m);
2559
+ else marksByRow.set(r, [m]);
2560
+ }
2433
2561
  }
2434
2562
  const origin = container.getBoundingClientRect();
2435
2563
  const open = origin.width > 0 && origin.height > 0 ? box(origin) : FREE;
2436
2564
  const clipOf = /* @__PURE__ */ new Map();
2437
2565
  const windowOf = (el) => {
2438
2566
  if (!clipOf.has(el)) {
2439
- if (el.namespaceURI === SVG_NS) {
2567
+ if (el.namespaceURI === SVG_NS2) {
2440
2568
  const w = clipWindowOf(el);
2441
2569
  clipOf.set(el, w && box(w));
2442
2570
  } else clipOf.set(el, overflowWindowOf(el));
@@ -2451,7 +2579,7 @@ function createAnnotationLayer(container, onClick, opts) {
2451
2579
  const viewOf = (m) => {
2452
2580
  let v = inView;
2453
2581
  for (let el = m.parentElement; v && el && el !== container; el = el.parentElement) {
2454
- if (el.namespaceURI === SVG_NS && el.localName === "svg") {
2582
+ if (el.namespaceURI === SVG_NS2 && el.localName === "svg") {
2455
2583
  const s = el.getBoundingClientRect();
2456
2584
  if (s.width > 0 && s.height > 0) v = cut(v, box(s));
2457
2585
  }
@@ -2546,16 +2674,31 @@ function createAnnotationLayer(container, onClick, opts) {
2546
2674
  for (const a of list) {
2547
2675
  const pairs = (a.where ? Object.entries(a.where) : a.column != null ? [[a.column, a.value]] : []).map(([c, v]) => [names.indexOf(c), text(v)]);
2548
2676
  const want = pairs.map((p) => p[1]).join(" / ");
2549
- const hits2 = /* @__PURE__ */ new Set();
2677
+ const hits2 = /* @__PURE__ */ new Set(), keyRows = /* @__PURE__ */ new Set();
2550
2678
  if (pairs.length && pairs.every((p) => p[0] >= 0)) (data.rows || []).forEach((row, r) => {
2551
- if (pairs.every(([ci, v]) => text(row?.[ci]) === v)) for (const m of marksByRow.get(r) ?? []) hits2.add(m);
2679
+ if (!pairs.every(([ci, v]) => text(row?.[ci]) === v)) return;
2680
+ keyRows.add(r);
2681
+ for (const m of marksByRow.get(r) ?? []) hits2.add(m);
2552
2682
  });
2553
2683
  if (!hits2.size) continue;
2554
- let best = null, bestWhole = null, bestMark = null, laidOut = false;
2684
+ const rank = (m) => {
2685
+ const own = rowsOf.get(m);
2686
+ let out = 0;
2687
+ for (const r of own) if (!keyRows.has(r)) out++;
2688
+ const level = parseInt(m.getAttribute("aria-level") || "", 10);
2689
+ return [out, out === 0 && own.size === keyRows.size ? 0 : 1, own.size, Number.isFinite(level) ? -level : 0];
2690
+ };
2691
+ const before = (p, q) => p[0] - q[0] || p[1] - q[1] || p[2] - q[2] || p[3] - q[3];
2692
+ const drawn = [];
2555
2693
  for (const m of hits2) {
2556
2694
  const b = m.getBoundingClientRect();
2557
- if (!(b.width > 0 && b.height > 0)) continue;
2558
- laidOut = true;
2695
+ if (b.width > 0 && b.height > 0) drawn.push({ m, b, k: rank(m) });
2696
+ }
2697
+ const laidOut = drawn.length > 0;
2698
+ const most = drawn.reduce((k, d) => !k || before(d.k, k) < 0 ? d.k : k, null);
2699
+ let best = null, bestWhole = null, bestMark = null;
2700
+ for (const { m, b, k } of drawn) {
2701
+ if (before(k, most) !== 0) continue;
2559
2702
  const v = cut(box(b), viewOf(m));
2560
2703
  if (v && (!best || area(v) > area(best))) {
2561
2704
  best = v;
@@ -2815,16 +2958,17 @@ function installKeyboard(env) {
2815
2958
  const { container } = env;
2816
2959
  let rovingKey = null;
2817
2960
  let destroyed = false;
2818
- const marksNow = () => {
2819
- if (env.inert()) return [];
2820
- return Array.from(container.querySelectorAll(env.markSelector)).filter((m) => !m.closest(env.skipSelector) && (m.getAttribute("data-row-idx") ?? "") !== "");
2821
- };
2961
+ const ownRole = /* @__PURE__ */ new WeakSet();
2962
+ const marksNow = () => Array.from(container.querySelectorAll(env.markSelector)).filter((m) => !m.closest(env.skipSelector) && (m.getAttribute("data-row-idx") ?? "") !== "");
2822
2963
  const wired = (el) => el.hasAttribute(WIRED_ATTR);
2823
2964
  const wire = (el, tabindex, role) => {
2824
2965
  if (el.hasAttribute("tabindex") && !wired(el)) return false;
2825
2966
  el.setAttribute(WIRED_ATTR, "");
2826
2967
  el.setAttribute("tabindex", tabindex);
2827
- if (!el.hasAttribute("role")) el.setAttribute("role", role);
2968
+ if (!el.hasAttribute("role") || ownRole.has(el)) {
2969
+ el.setAttribute("role", role);
2970
+ ownRole.add(el);
2971
+ }
2828
2972
  return true;
2829
2973
  };
2830
2974
  const setRoving = (list, to) => {
@@ -2835,22 +2979,17 @@ function installKeyboard(env) {
2835
2979
  if (destroyed) return;
2836
2980
  try {
2837
2981
  container.classList?.add(KEYBOARD_CLASS);
2982
+ const inert = env.inert();
2838
2983
  const list = marksNow();
2839
2984
  for (const m of list) {
2840
- if (!wire(m, "-1", "button")) continue;
2841
- m.setAttribute("aria-pressed", env.isSelected(m) ? "true" : "false");
2985
+ if (!wire(m, "-1", inert ? "img" : "button")) continue;
2986
+ if (inert) m.removeAttribute("aria-pressed");
2987
+ else m.setAttribute("aria-pressed", env.isSelected(m) ? "true" : "false");
2842
2988
  if (!m.hasAttribute("aria-label") && !m.querySelector(":scope > title")) {
2843
2989
  const name = env.labelOf(m);
2844
2990
  if (name) m.setAttribute("aria-label", name);
2845
2991
  }
2846
2992
  }
2847
- if (!list.length) {
2848
- for (const m of Array.from(container.querySelectorAll(`[${WIRED_ATTR}]`)))
2849
- if (m.matches(env.markSelector)) {
2850
- m.removeAttribute("tabindex");
2851
- m.removeAttribute(WIRED_ATTR);
2852
- }
2853
- }
2854
2993
  const own = list.filter(wired);
2855
2994
  if (own.length) {
2856
2995
  const keep = own.find((m) => env.keyOf(m) === rovingKey) ?? own.find((m) => env.isSelected(m)) ?? own[0];
@@ -2879,7 +3018,7 @@ function installKeyboard(env) {
2879
3018
  e.preventDefault();
2880
3019
  const ME = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
2881
3020
  press.dispatchEvent(new ME("click", { bubbles: true, cancelable: true }));
2882
- } else if (k === "Escape" && env.escapeClears !== false) env.clear();
3021
+ } else if (k === "Escape" && env.escapeClears !== false && !env.inert()) env.clear();
2883
3022
  return;
2884
3023
  }
2885
3024
  const mark = t.closest?.(env.markSelector);
@@ -2921,9 +3060,10 @@ function installKeyboard(env) {
2921
3060
  rovingKey = env.keyOf(mark);
2922
3061
  env.select(mark, { ctrl: !!(e.ctrlKey || e.metaKey), shift: !!e.shiftKey });
2923
3062
  break;
3063
+ // An inert chart's click on empty canvas clears nothing, so neither does its Escape.
2924
3064
  case "Escape":
2925
3065
  hideTip();
2926
- if (env.escapeClears !== false) env.clear();
3066
+ if (env.escapeClears !== false && !env.inert()) env.clear();
2927
3067
  break;
2928
3068
  }
2929
3069
  };
@@ -3399,77 +3539,13 @@ function createChartHost(container, config) {
3399
3539
  };
3400
3540
  const GLYPH_LAYER_CLASS = "bic-selection-glyphs";
3401
3541
  let glyphPositionWas = null;
3402
- let glyphObserver = null;
3403
- let glyphResize = null;
3404
- let glyphSize = "";
3405
- let glyphQueued = false;
3406
- let glyphQueuedReport = false;
3542
+ const glyphFollower = followMarks(container, {
3543
+ win,
3544
+ blind: `.${GLYPH_LAYER_CLASS}, .${LASSO_OUTLINE_CLASS}`,
3545
+ repaint: (resized) => paintGlyphLayer(resized ? "if-changed" : false)
3546
+ });
3407
3547
  let glyphReported = "";
3408
- const sizeOfContainer = () => `${container.offsetWidth ?? 0}x${container.offsetHeight ?? 0}`;
3409
- const glyphBlind = `.${GLYPH_LAYER_CLASS}, .${LASSO_OUTLINE_CLASS}`;
3410
- const movesAMark = (r) => {
3411
- const t = r.target;
3412
- if (!t || t.closest?.(glyphBlind)) return false;
3413
- return !!(t.classList?.contains(MARK_CLASS) || t.querySelector?.(`.${MARK_CLASS}`));
3414
- };
3415
- const queueGlyphs = (reportIfChanged) => {
3416
- if (reportIfChanged) glyphQueuedReport = true;
3417
- if (glyphQueued) return;
3418
- glyphQueued = true;
3419
- const run = () => {
3420
- const r = glyphQueuedReport;
3421
- glyphQueued = false;
3422
- glyphQueuedReport = false;
3423
- if (glyphObserver || glyphResize) paintGlyphLayer(r ? "if-changed" : false);
3424
- };
3425
- if (win?.requestAnimationFrame) win.requestAnimationFrame(run);
3426
- else setTimeout(run, 16);
3427
- };
3428
- const followGlyphs = (on) => {
3429
- if (!on) {
3430
- if (glyphObserver) {
3431
- glyphObserver.disconnect();
3432
- glyphObserver = null;
3433
- }
3434
- if (glyphResize) {
3435
- glyphResize.disconnect();
3436
- glyphResize = null;
3437
- }
3438
- return;
3439
- }
3440
- const MO = win?.MutationObserver;
3441
- if (!glyphObserver && MO) {
3442
- glyphObserver = new MO((records) => {
3443
- if (!glyphQueued && records.some(movesAMark)) queueGlyphs(false);
3444
- });
3445
- glyphObserver.observe(container, {
3446
- subtree: true,
3447
- attributes: true,
3448
- attributeFilter: [
3449
- "transform",
3450
- "d",
3451
- "x",
3452
- "y",
3453
- "cx",
3454
- "cy",
3455
- "points",
3456
- "width",
3457
- "height",
3458
- "visibility",
3459
- "opacity",
3460
- "style",
3461
- "class"
3462
- ]
3463
- });
3464
- }
3465
- const RO = win?.ResizeObserver;
3466
- if (!glyphResize && RO) {
3467
- glyphResize = new RO(() => {
3468
- if (sizeOfContainer() !== glyphSize) queueGlyphs(true);
3469
- });
3470
- glyphResize.observe(container);
3471
- }
3472
- };
3548
+ const followGlyphs = (on) => glyphFollower.follow(on);
3473
3549
  const markShown = (m) => {
3474
3550
  try {
3475
3551
  const cv = m.checkVisibility;
@@ -3503,11 +3579,11 @@ function createChartHost(container, config) {
3503
3579
  }
3504
3580
  return windowOf.get(el) ?? null;
3505
3581
  };
3506
- const EPS2 = 0.5;
3582
+ const EPS = 0.5;
3507
3583
  return (m, x, y) => {
3508
3584
  for (let el = m; el && el !== container; el = el.parentElement) {
3509
3585
  const w = read(el);
3510
- if (w && (x < w.left - EPS2 || x > w.right + EPS2 || y < w.top - EPS2 || y > w.bottom + EPS2)) return false;
3586
+ if (w && (x < w.left - EPS || x > w.right + EPS || y < w.top - EPS || y > w.bottom + EPS)) return false;
3511
3587
  }
3512
3588
  return true;
3513
3589
  };
@@ -3518,8 +3594,13 @@ function createChartHost(container, config) {
3518
3594
  try {
3519
3595
  let layer = container.querySelector(`:scope > .${GLYPH_LAYER_CLASS}`);
3520
3596
  clearSelectionGlyphs(layer);
3521
- const mode = resolved.selectionGlyph;
3522
- if (mode !== "dot" && mode !== "check") {
3597
+ const mode = selectionMarkerFor({
3598
+ choice: resolved.selectionMarker,
3599
+ mapChoice: resolved.mapSelectionMarker,
3600
+ regionMap: isRegionMapData(data),
3601
+ neutralCentreRamp: hasNeutralCentreRamp(container)
3602
+ });
3603
+ if (mode !== "dot" && mode !== "check" && mode !== "outline") {
3523
3604
  glyphReported = "";
3524
3605
  return;
3525
3606
  }
@@ -3567,20 +3648,41 @@ function createChartHost(container, config) {
3567
3648
  const sw = container.scrollWidth || 0, sh = container.scrollHeight || 0;
3568
3649
  layer.style.width = `${sw}px`;
3569
3650
  layer.style.height = `${sh}px`;
3570
- glyphSize = sizeOfContainer();
3571
- const centers = [];
3572
- collectGlyphCentersWhere(container, layer.getBoundingClientRect(), centers, { shown: markShown, at: glyphWindows() });
3651
+ glyphFollower.painted();
3573
3652
  const hc = !!resolved.isHighContrast;
3574
- const drawn = centers.length ? paintSelectionGlyphs(layer, centers, {
3575
- symbol: mode === "check" ? "\u2713" : "\u25CF",
3576
- color: hc ? resolved.themeFg || "#111" : "#111",
3577
- halo: hc ? resolved.themeBg || "#fff" : "#fff"
3578
- }) : 0;
3579
- const said = `${mode}:${drawn}:${centers.length}`;
3653
+ let drawn = 0, total = 0;
3654
+ if (mode === "outline") {
3655
+ const marks = collectOutlineMarks(container, { shown: markShown });
3656
+ total = marks.length;
3657
+ let ink = resolved.themeFg || "";
3658
+ if (!ink) {
3659
+ try {
3660
+ ink = win?.getComputedStyle?.(container)?.color || "";
3661
+ } catch {
3662
+ ink = "";
3663
+ }
3664
+ }
3665
+ drawn = total ? paintSelectionOutlines(layer, marks, {
3666
+ color: ink || "#111",
3667
+ halo: resolved.themeBg || (hc ? "#000" : "#fff"),
3668
+ windowOf: (m) => outlineWindowOf(m, container),
3669
+ avoid: controlBoxesIn(container)
3670
+ }) : 0;
3671
+ } else {
3672
+ const centers = [];
3673
+ collectGlyphCentersWhere(container, layer.getBoundingClientRect(), centers, { shown: markShown, at: glyphWindows() });
3674
+ total = centers.length;
3675
+ drawn = centers.length ? paintSelectionGlyphs(layer, centers, {
3676
+ symbol: mode === "check" ? "\u2713" : "\u25CF",
3677
+ color: hc ? resolved.themeFg || "#111" : "#111",
3678
+ halo: hc ? resolved.themeBg || "#fff" : "#fff"
3679
+ }) : 0;
3680
+ }
3681
+ const said = `${mode}:${drawn}:${total}`;
3580
3682
  if (report === true || report === "if-changed" && said !== glyphReported) {
3581
3683
  glyphReported = said;
3582
3684
  try {
3583
- config.onSelectionGlyphs?.({ mode, drawn, total: centers.length, suppressed: false });
3685
+ config.onSelectionGlyphs?.({ mode, drawn, total, suppressed: false });
3584
3686
  } catch {
3585
3687
  }
3586
3688
  }
@@ -3588,7 +3690,7 @@ function createChartHost(container, config) {
3588
3690
  } finally {
3589
3691
  try {
3590
3692
  followGlyphs(follow);
3591
- glyphObserver?.takeRecords();
3693
+ glyphFollower.takeRecords();
3592
3694
  } catch {
3593
3695
  }
3594
3696
  }
@@ -3716,7 +3818,15 @@ function createChartHost(container, config) {
3716
3818
  { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey }
3717
3819
  ));
3718
3820
  const fromLabel = !!tick && !multi && !toggledOff && !chartOwnsTimeline(container);
3719
- notify(next, "user", toggledOff ? null : tick, false, true, fromLabel ? { fromLabel: true } : void 0);
3821
+ const fromMark = !tick && !multi && !toggledOff;
3822
+ notify(
3823
+ next,
3824
+ "user",
3825
+ toggledOff ? null : tick,
3826
+ false,
3827
+ true,
3828
+ fromLabel ? { fromLabel: true } : fromMark ? { fromMark: true } : void 0
3829
+ );
3720
3830
  };
3721
3831
  container.addEventListener(XFILTER_REFRESH_EVENT, onXf);
3722
3832
  const untrackPress = trackPress(container);
@@ -3877,6 +3987,21 @@ function createChartHost(container, config) {
3877
3987
  } catch {
3878
3988
  }
3879
3989
  }
3990
+ if (config.onCategoryColourCensus) {
3991
+ const report = config.onCategoryColourCensus;
3992
+ const bg = resolved.backgroundColor ?? resolved.themeBg;
3993
+ const opts = {
3994
+ pageBg: typeof bg === "string" && bg ? bg : void 0,
3995
+ dimOpacity: typeof resolved.dimOpacity === "number" ? resolved.dimOpacity : void 0
3996
+ };
3997
+ import("./category-colours.mjs").then((m) => {
3998
+ try {
3999
+ report(m.censusCategoryColours(container, doc, opts));
4000
+ } catch {
4001
+ }
4002
+ }).catch(() => {
4003
+ });
4004
+ }
3880
4005
  if (config.onClippedTextCensus) {
3881
4006
  try {
3882
4007
  config.onClippedTextCensus(censusClippedText(container));
@@ -3890,6 +4015,7 @@ function createChartHost(container, config) {
3890
4015
  }
3891
4016
  }
3892
4017
  const scrollMem = takeScroll(container);
4018
+ if (keepScroll) restoreInnerScroll(container, scrollMem, scrollKey());
3893
4019
  if (fitOpts !== false) {
3894
4020
  try {
3895
4021
  const base = fitOpts === true ? {} : fitOpts;
@@ -4208,7 +4334,7 @@ function createChartHost(container, config) {
4208
4334
  }
4209
4335
 
4210
4336
  // src/group.ts
4211
- var EMPTY5 = Object.freeze({ sourceId: null, rows: Object.freeze([]) });
4337
+ var EMPTY4 = Object.freeze({ sourceId: null, rows: Object.freeze([]) });
4212
4338
  function toSourceRows(rowMap, payloadRowIdxs) {
4213
4339
  if (!rowMap) return payloadRowIdxs.slice();
4214
4340
  return payloadRowIdxs.map((i) => rowMap[i]).filter((i) => i !== void 0);
@@ -4246,7 +4372,7 @@ function createChartGroup(columns, rows, opts = {}) {
4246
4372
  let geo = opts.geo ?? null;
4247
4373
  let point = opts.point ?? null;
4248
4374
  let destination = opts.destination ?? null;
4249
- let selection = EMPTY5;
4375
+ let selection = EMPTY4;
4250
4376
  const subs = /* @__PURE__ */ new Set();
4251
4377
  const members = /* @__PURE__ */ new Set();
4252
4378
  const notify = (change) => {
@@ -4379,17 +4505,27 @@ export {
4379
4505
  straddles,
4380
4506
  minContrastOver,
4381
4507
  pickColorOver,
4382
- deltaEHex,
4383
- SAME_SHADE_DELTA_E,
4384
- MIN_RAMP_FILLS,
4385
- censusColourSpread,
4386
- colourSpreadFlag,
4387
4508
  AUTO_RAMP_CLASSES,
4388
4509
  AUTO_RAMP_DRAWN_OPACITY,
4389
4510
  AUTO_RAMP_MIN_STEP_DELTA_E,
4390
4511
  autoRampMinStep,
4391
4512
  autoRampCanvas,
4392
4513
  resolveAutoColorScale,
4514
+ SELECTION_MARKER_DEFAULT,
4515
+ MAP_SELECTION_MARKER_DEFAULT,
4516
+ readSelectionMarker,
4517
+ readMapSelectionMarker,
4518
+ REGION_JOIN_COLUMN,
4519
+ isRegionMapData,
4520
+ NEUTRAL_CENTRE_RAMP_SELECTOR,
4521
+ hasNeutralCentreRamp,
4522
+ selectionMarkerFor,
4523
+ SELECTION_OUTLINE_CLASS,
4524
+ isAreaMark,
4525
+ collectOutlineMarks,
4526
+ outlineWindowOf,
4527
+ controlBoxesIn,
4528
+ paintSelectionOutlines,
4393
4529
  EXPORT_FORMATS,
4394
4530
  XLSX_MIME,
4395
4531
  CSV_MIME,
@@ -4409,6 +4545,8 @@ export {
4409
4545
  resolveOptions,
4410
4546
  buildRenderPayload,
4411
4547
  stripJsComments,
4548
+ MARK_MOVE_ATTRIBUTES,
4549
+ followMarks,
4412
4550
  censusMarks,
4413
4551
  isBlankRender,
4414
4552
  blankRenderFlag,