@bicharts/chart-host 0.6.75 → 0.6.77

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.
@@ -0,0 +1,3806 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ Fn,
4
+ J3,
5
+ Xn,
6
+ Yn,
7
+ ge
8
+ } from "./chunk-H2P63UMG.mjs";
9
+ import {
10
+ NA_ISO3,
11
+ NA_LAT_MAX,
12
+ clipFeatureToLat
13
+ } from "./chunk-A2GMXZP7.mjs";
14
+ import {
15
+ ACTIVE_TICK_CLASS,
16
+ ANIM_LOOP_DELAY_DEFAULT,
17
+ ANIM_LOOP_DELAY_MIN,
18
+ ANIM_MAX_IDEAL_FRAMES_DEFAULT,
19
+ ANIM_MAX_IDEAL_FRAMES_MAX,
20
+ ANIM_MAX_IDEAL_FRAMES_MIN,
21
+ ANIM_PLAY_SPEED_DEFAULT,
22
+ ANIM_PLAY_SPEED_MAX,
23
+ ANIM_PLAY_SPEED_MIN,
24
+ APPROXIMATE_POSITIONS_DEFAULT,
25
+ AXIS_FILTER_CLASS,
26
+ COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
27
+ COLOR_SCALE_SELF_CLAMP_PCT_MAX,
28
+ COLOR_SCALE_SELF_CLAMP_PCT_MIN,
29
+ CONTAINER_SLOT_ANIM_STOP,
30
+ CONTAINER_SLOT_CLICKED_MARKS,
31
+ CONTAINER_SLOT_INITIAL_XF_MARK,
32
+ CONTAINER_SLOT_UI_STATE,
33
+ CONTAINER_SLOT_XF_CLEAR,
34
+ CONTROL_CLASS,
35
+ DIM_OPACITY_DEFAULT,
36
+ DIM_OPACITY_VAR,
37
+ FLIP_DIRECTION_DEFAULT,
38
+ FLIP_MODE_DEFAULT,
39
+ HOST_CONTAINER_CLASS,
40
+ HOST_CONTRACT_VERSION,
41
+ LEGEND_MARK_CLASS,
42
+ LIFT_SELECTED_CLASS,
43
+ MARK_CLASS,
44
+ MARK_KEY_ATTR,
45
+ MARK_SELECTED_CLASS,
46
+ MAX_MAP_POINTS_DEFAULT,
47
+ NO_ROW_MARKS_ATTR,
48
+ ROW_IDX_ATTR,
49
+ SEASONAL_MARKERS_DEFAULT,
50
+ SELECTION_ACTIVE_CLASS,
51
+ SELECTION_RULE_ATTR,
52
+ SELECTION_RULE_CLICKED_MARKS,
53
+ SNAPSHOT_SVG_ATTR,
54
+ VALUE_AXIS_BASELINE_DEFAULT,
55
+ XFILTER_REFRESH_EVENT,
56
+ chartOwnsTimeline,
57
+ periodTickSuppressesFeedback
58
+ } from "./chunk-7JWL6LYE.mjs";
59
+
60
+ // src/labelContrast.ts
61
+ function parseRGBA(c) {
62
+ if (typeof c !== "string") return null;
63
+ const s = c.trim();
64
+ const m = s.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i);
65
+ if (m) return [parseFloat(m[1]), parseFloat(m[2]), parseFloat(m[3]), m[4] !== void 0 ? parseFloat(m[4]) : 1];
66
+ let h = s.replace(/^#/, "");
67
+ if (h.length === 3) h = h.split("").map((ch) => ch + ch).join("");
68
+ if (h.length === 6 && /^[0-9a-fA-F]{6}$/.test(h)) {
69
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), 1];
70
+ }
71
+ return null;
72
+ }
73
+ var DARK_TEXT = "#111111";
74
+ var LIGHT_TEXT = "#ffffff";
75
+ var MIN_CONTRAST = 3;
76
+ var WHITE_TEXT_BG_LUM = 0.5;
77
+ var PILL_MIN_ALPHA = 0.05;
78
+ var PILL_OPAQUE_ALPHA = 0.85;
79
+ var PAGE_MATCH_TOLERANCE = 2;
80
+ function isPillBackdropAlpha(a) {
81
+ return a >= PILL_MIN_ALPHA && a < PILL_OPAQUE_ALPHA;
82
+ }
83
+ var PILL_MIN_COVERAGE = 0.6;
84
+ function pillBacksGlyph(overlapArea, glyphArea) {
85
+ if (!(glyphArea > 0) || !isFinite(overlapArea) || overlapArea <= 0) return false;
86
+ return overlapArea / glyphArea >= PILL_MIN_COVERAGE;
87
+ }
88
+ var BACKING_MAJORITY = 0.5;
89
+ function backingHoldsGlyph(backedArea, glyphArea) {
90
+ if (!(glyphArea > 0) || !isFinite(backedArea) || backedArea <= 0) return false;
91
+ return backedArea / glyphArea >= BACKING_MAJORITY;
92
+ }
93
+ function cellSuppressesNormalize(cellClass) {
94
+ return typeof cellClass === "string" && /(^|\s)lch-deck-panel(\s|$)/.test(cellClass);
95
+ }
96
+ var NAMED = {
97
+ white: [255, 255, 255, 1],
98
+ black: [0, 0, 0, 1],
99
+ none: [0, 0, 0, 0],
100
+ transparent: [0, 0, 0, 0]
101
+ };
102
+ function toRGBA(c) {
103
+ if (typeof c !== "string") return null;
104
+ const k = c.trim().toLowerCase();
105
+ if (k in NAMED) return NAMED[k];
106
+ return parseRGBA(c);
107
+ }
108
+ function compositeOver(fg, bg) {
109
+ const a = Math.max(0, Math.min(1, fg[3]));
110
+ return [
111
+ Math.round(fg[0] * a + bg[0] * (1 - a)),
112
+ Math.round(fg[1] * a + bg[1] * (1 - a)),
113
+ Math.round(fg[2] * a + bg[2] * (1 - a))
114
+ ];
115
+ }
116
+ function relativeLuminance(rgb) {
117
+ const lin = rgb.map((v) => {
118
+ const s = v / 255;
119
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
120
+ });
121
+ return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
122
+ }
123
+ function contrastRatio(l1, l2) {
124
+ const hi = Math.max(l1, l2);
125
+ const lo = Math.min(l1, l2);
126
+ return (hi + 0.05) / (lo + 0.05);
127
+ }
128
+ function decideLabelColor(textColor, fill, fillOpacity, pageBg, minContrast = MIN_CONTRAST, normalize = false) {
129
+ const fillRGBA = toRGBA(fill);
130
+ if (!fillRGBA) return { fix: false, color: "", reason: "fill-unparsed" };
131
+ const pageRGBA = toRGBA(pageBg) || [255, 255, 255, 1];
132
+ const pageRGB = [pageRGBA[0], pageRGBA[1], pageRGBA[2]];
133
+ const effFill = [fillRGBA[0], fillRGBA[1], fillRGBA[2], fillRGBA[3] * clamp01(fillOpacity)];
134
+ const bgRGB = compositeOver(effFill, pageRGB);
135
+ const bgLum = relativeLuminance(bgRGB);
136
+ const bgIsPage = Math.abs(bgRGB[0] - pageRGB[0]) <= PAGE_MATCH_TOLERANCE && Math.abs(bgRGB[1] - pageRGB[1]) <= PAGE_MATCH_TOLERANCE && Math.abs(bgRGB[2] - pageRGB[2]) <= PAGE_MATCH_TOLERANCE;
137
+ const effNormalize = normalize && !bgIsPage;
138
+ const textRGBA = toRGBA(textColor);
139
+ if (!effNormalize && textRGBA) {
140
+ const txtRGB = compositeOver(textRGBA, bgRGB);
141
+ const cur = contrastRatio(relativeLuminance(txtRGB), bgLum);
142
+ if (cur >= minContrast) return { fix: false, color: "", reason: "ok" };
143
+ }
144
+ const darkC = contrastRatio(relativeLuminance(hexToRGB(DARK_TEXT)), bgLum);
145
+ const lightC = contrastRatio(relativeLuminance(hexToRGB(LIGHT_TEXT)), bgLum);
146
+ let useLight = bgLum <= WHITE_TEXT_BG_LUM;
147
+ if (useLight && lightC < minContrast && darkC >= minContrast) useLight = false;
148
+ if (!useLight && darkC < minContrast && lightC >= minContrast) useLight = true;
149
+ return useLight ? { fix: true, color: LIGHT_TEXT, reason: "fixed-light" } : { fix: true, color: DARK_TEXT, reason: "fixed-dark" };
150
+ }
151
+ var GLYPH_SAMPLE_N = 3;
152
+ function glyphSampleGrid(box2, n = GLYPH_SAMPLE_N) {
153
+ const out = [];
154
+ if (!box2 || !(box2.width > 0) || !(box2.height > 0) || !(n >= 1)) return out;
155
+ const k = Math.floor(n);
156
+ for (let i = 0; i < k; i++) {
157
+ for (let j = 0; j < k; j++) {
158
+ out.push({
159
+ x: box2.left + box2.width * (i + 0.5) / k,
160
+ y: box2.top + box2.height * (j + 0.5) / k
161
+ });
162
+ }
163
+ }
164
+ return out;
165
+ }
166
+ var STRADDLE_COLS = 8;
167
+ var STRADDLE_ROWS = 2;
168
+ var STRADDLE_MIN_SHARE = 0.125;
169
+ var STRADDLE_TARGET_CONTRAST = 4.5;
170
+ var STRADDLE_PILL_ALPHA = 0.9;
171
+ function straddleSampleGrid(box2) {
172
+ const out = [];
173
+ if (!box2 || !(box2.width > 0) || !(box2.height > 0)) return out;
174
+ for (let i = 0; i < STRADDLE_COLS; i++) {
175
+ for (let j = 0; j < STRADDLE_ROWS; j++) {
176
+ out.push({
177
+ x: box2.left + box2.width * (i + 0.5) / STRADDLE_COLS,
178
+ y: box2.top + box2.height * (j + 0.5) / STRADDLE_ROWS
179
+ });
180
+ }
181
+ }
182
+ return out;
183
+ }
184
+ function backgroundGroups(bgs, tol = 8) {
185
+ const groups = [];
186
+ for (const b of bgs || []) {
187
+ const g = groups.find((x) => Math.abs(x.rgb[0] - b[0]) <= tol && Math.abs(x.rgb[1] - b[1]) <= tol && Math.abs(x.rgb[2] - b[2]) <= tol);
188
+ if (g) g.n++;
189
+ else groups.push({ rgb: [b[0], b[1], b[2]], n: 1 });
190
+ }
191
+ return groups.sort((a, b) => b.n - a.n);
192
+ }
193
+ function straddles(bgs) {
194
+ if (!bgs || bgs.length < 2) return false;
195
+ const g = backgroundGroups(bgs);
196
+ return g.length >= 2 && g[1].n / bgs.length >= STRADDLE_MIN_SHARE;
197
+ }
198
+ function minContrastOver(textColor, bgs) {
199
+ const t = toRGBA(textColor);
200
+ if (!t || !bgs || !bgs.length) return 0;
201
+ let worst = Infinity;
202
+ for (const b of bgs) {
203
+ const tx = compositeOver(t, b);
204
+ worst = Math.min(worst, contrastRatio(relativeLuminance(tx), relativeLuminance(b)));
205
+ }
206
+ return worst;
207
+ }
208
+ function pickColorOver(textColor, bgs, target = STRADDLE_TARGET_CONTRAST) {
209
+ const cur = minContrastOver(textColor, bgs);
210
+ if (cur >= target) return { color: String(textColor), contrast: cur, keep: true };
211
+ const d = minContrastOver(DARK_TEXT, bgs), l = minContrastOver(LIGHT_TEXT, bgs);
212
+ const best = d >= l ? { color: DARK_TEXT, contrast: d } : { color: LIGHT_TEXT, contrast: l };
213
+ return best.contrast > cur ? { ...best, keep: false } : { color: String(textColor), contrast: cur, keep: true };
214
+ }
215
+ function clamp01(v) {
216
+ if (typeof v !== "number" || !isFinite(v)) return 1;
217
+ return Math.max(0, Math.min(1, v));
218
+ }
219
+ function hexToRGB(hex2) {
220
+ const p = toRGBA(hex2);
221
+ return [p[0], p[1], p[2]];
222
+ }
223
+
224
+ // src/deltaE.ts
225
+ function linearise(c) {
226
+ const v = c / 255;
227
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
228
+ }
229
+ var Xn2 = 0.95047;
230
+ var Yn2 = 1;
231
+ var Zn = 1.08883;
232
+ var EPS = 216 / 24389;
233
+ var KAPPA = 24389 / 27;
234
+ function f(t) {
235
+ return t > EPS ? Math.cbrt(t) : (KAPPA * t + 16) / 116;
236
+ }
237
+ function rgbToLab(r, g, b) {
238
+ const R = linearise(r), G = linearise(g), B = linearise(b);
239
+ const X = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B;
240
+ const Y = 0.2126729 * R + 0.7151522 * G + 0.072175 * B;
241
+ const Z = 0.0193339 * R + 0.119192 * G + 0.9503041 * B;
242
+ const fx = f(X / Xn2), fy = f(Y / Yn2), fz = f(Z / Zn);
243
+ return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
244
+ }
245
+ function hexToLab(hex2) {
246
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex2.trim());
247
+ if (!m) return null;
248
+ 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];
249
+ return rgbToLab(parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16));
250
+ }
251
+ var RAD = Math.PI / 180;
252
+ var DEG = 180 / Math.PI;
253
+ var NEUTRAL_CHROMA = 1e-4;
254
+ function deltaE2000(c1, c2) {
255
+ const C1 = Math.hypot(c1.a, c1.b), C2 = Math.hypot(c2.a, c2.b);
256
+ const Cbar = (C1 + C2) / 2;
257
+ const C7 = Math.pow(Cbar, 7);
258
+ const G = 0.5 * (1 - Math.sqrt(C7 / (C7 + Math.pow(25, 7))));
259
+ const a1p = (1 + G) * c1.a, a2p = (1 + G) * c2.a;
260
+ const C1p = Math.hypot(a1p, c1.b), C2p = Math.hypot(a2p, c2.b);
261
+ const neutral = C1p < NEUTRAL_CHROMA || C2p < NEUTRAL_CHROMA;
262
+ const h1p = C1p < NEUTRAL_CHROMA ? 0 : (Math.atan2(c1.b, a1p) * DEG + 360) % 360;
263
+ const h2p = C2p < NEUTRAL_CHROMA ? 0 : (Math.atan2(c2.b, a2p) * DEG + 360) % 360;
264
+ const dLp = c2.L - c1.L;
265
+ const dCp = C2p - C1p;
266
+ let dhp;
267
+ if (neutral) dhp = 0;
268
+ else if (Math.abs(h2p - h1p) <= 180) dhp = h2p - h1p;
269
+ else if (h2p - h1p > 180) dhp = h2p - h1p - 360;
270
+ else dhp = h2p - h1p + 360;
271
+ const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(dhp / 2 * RAD);
272
+ const Lbarp = (c1.L + c2.L) / 2;
273
+ const Cbarp = (C1p + C2p) / 2;
274
+ let hbarp;
275
+ if (neutral) hbarp = h1p + h2p;
276
+ else if (Math.abs(h1p - h2p) <= 180) hbarp = (h1p + h2p) / 2;
277
+ else if (h1p + h2p < 360) hbarp = (h1p + h2p + 360) / 2;
278
+ else hbarp = (h1p + h2p - 360) / 2;
279
+ 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);
280
+ const dTheta = 30 * Math.exp(-Math.pow((hbarp - 275) / 25, 2));
281
+ const Cbarp7 = Math.pow(Cbarp, 7);
282
+ const RC = 2 * Math.sqrt(Cbarp7 / (Cbarp7 + Math.pow(25, 7)));
283
+ const RT = -RC * Math.sin(2 * dTheta * RAD);
284
+ const Lm50 = Math.pow(Lbarp - 50, 2);
285
+ const SL = 1 + 0.015 * Lm50 / Math.sqrt(20 + Lm50);
286
+ const SC = 1 + 0.045 * Cbarp;
287
+ const SH = 1 + 0.015 * Cbarp * T;
288
+ const tL = dLp / SL, tC = dCp / SC, tH = dHp / SH;
289
+ return Math.sqrt(tL * tL + tC * tC + tH * tH + RT * tC * tH);
290
+ }
291
+ function labToHex(lab) {
292
+ const finv = (t) => {
293
+ const t3 = t * t * t;
294
+ return t3 > EPS ? t3 : (116 * t - 16) / KAPPA;
295
+ };
296
+ const fy = (lab.L + 16) / 116, fx = fy + lab.a / 500, fz = fy - lab.b / 200;
297
+ const X = Xn2 * finv(fx);
298
+ const Y = Yn2 * (lab.L > KAPPA * EPS ? fy * fy * fy : lab.L / KAPPA);
299
+ const Z = Zn * finv(fz);
300
+ const lin = [
301
+ 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z,
302
+ -0.969266 * X + 1.8760108 * Y + 0.041556 * Z,
303
+ 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
304
+ ];
305
+ return "#" + lin.map((v) => {
306
+ const c = Math.max(0, Math.min(1, v));
307
+ const g = c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
308
+ return Math.round(g * 255).toString(16).padStart(2, "0");
309
+ }).join("");
310
+ }
311
+ function deltaEHex(a, b) {
312
+ const la = hexToLab(a), lb = hexToLab(b);
313
+ return la && lb ? deltaE2000(la, lb) : NaN;
314
+ }
315
+
316
+ // src/colourSpread.ts
317
+ var SAME_SHADE_DELTA_E = 5;
318
+ var MIN_RAMP_FILLS = 4;
319
+ var RAMP_SLACK_MAX = 0.15;
320
+ var MIN_MARKS = 24;
321
+ var ELEMENT_CAP = 5e3;
322
+ var MAX_CELLS = 48;
323
+ var CELL_STEP = 32;
324
+ var EMPTY = { marks: 0, distinctFills: 0, crowdShare: 0, spanDeltaE: 0 };
325
+ function styleOf(el, doc) {
326
+ try {
327
+ const view = doc?.defaultView;
328
+ return view?.getComputedStyle ? view.getComputedStyle(el) : null;
329
+ } catch {
330
+ return null;
331
+ }
332
+ }
333
+ function fillOf(el, cs) {
334
+ const a = el.getAttribute?.("fill");
335
+ if (a !== null && a !== void 0 && String(a).trim() !== "") return String(a).trim();
336
+ const c = cs?.fill;
337
+ return c === null || c === void 0 ? "" : String(c).trim();
338
+ }
339
+ function toRgb(raw) {
340
+ const s = raw.trim().toLowerCase();
341
+ if (!s || s === "none" || s === "transparent") return null;
342
+ const hex2 = /^#([0-9a-f]{3}|[0-9a-f]{6})$/.exec(s);
343
+ if (hex2) {
344
+ const h = hex2[1];
345
+ const full = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
346
+ return [
347
+ parseInt(full.slice(0, 2), 16),
348
+ parseInt(full.slice(2, 4), 16),
349
+ parseInt(full.slice(4, 6), 16)
350
+ ];
351
+ }
352
+ const m = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)\s*(?:[,/]\s*([\d.]+)\s*)?\)$/.exec(s);
353
+ if (!m) return null;
354
+ if (m[4] !== void 0 && parseFloat(m[4]) === 0) return null;
355
+ return [Math.round(parseFloat(m[1])), Math.round(parseFloat(m[2])), Math.round(parseFloat(m[3]))];
356
+ }
357
+ function cellKey(rgb) {
358
+ return [
359
+ Math.round(rgb[0] / CELL_STEP),
360
+ Math.round(rgb[1] / CELL_STEP),
361
+ Math.round(rgb[2] / CELL_STEP)
362
+ ].join(",");
363
+ }
364
+ function hex(rgb) {
365
+ return "#" + rgb.map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
366
+ }
367
+ function censusColourSpread(container, doc) {
368
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY;
369
+ try {
370
+ const els = Array.from(
371
+ container.querySelectorAll(`.${MARK_CLASS}, [${ROW_IDX_ATTR}]`)
372
+ );
373
+ if (els.length === 0 || els.length > ELEMENT_CAP) return EMPTY;
374
+ const ownerDoc = doc ?? container.ownerDocument;
375
+ const painted = [];
376
+ for (const el of els) {
377
+ const rgb = toRgb(fillOf(el, styleOf(el, ownerDoc)));
378
+ if (rgb) painted.push({ rgb, tag: String(el.tagName || "").toLowerCase() });
379
+ }
380
+ const byTag = /* @__PURE__ */ new Map();
381
+ for (const p of painted) {
382
+ const g = byTag.get(p.tag) ?? { fills: /* @__PURE__ */ new Set(), n: 0 };
383
+ g.fills.add(p.rgb.join(","));
384
+ g.n++;
385
+ byTag.set(p.tag, g);
386
+ }
387
+ const overlays = new Set([...byTag].filter(([, g]) => g.fills.size === 1 && g.n >= MIN_MARKS).map(([t]) => t));
388
+ const restFills = new Set([...byTag].filter(([t]) => !overlays.has(t)).flatMap(([, g]) => [...g.fills]));
389
+ const counted = restFills.size > 1 ? painted.filter((p) => !overlays.has(p.tag)) : painted;
390
+ const distinct = /* @__PURE__ */ new Set();
391
+ const cells = /* @__PURE__ */ new Map();
392
+ let marks = 0;
393
+ for (const { rgb } of counted) {
394
+ marks++;
395
+ distinct.add(rgb.join(","));
396
+ const k = cellKey(rgb);
397
+ const cur = cells.get(k);
398
+ if (cur) cur.n++;
399
+ else cells.set(k, { rgb, n: 1 });
400
+ }
401
+ if (marks < MIN_MARKS || distinct.size < MIN_RAMP_FILLS) return EMPTY;
402
+ const top = [...cells.values()].sort((a, b) => b.n - a.n).slice(0, MAX_CELLS);
403
+ const cs = top.map((c) => hexToLab(hex(c.rgb))).filter((l) => l !== null);
404
+ if (cs.length < MIN_RAMP_FILLS) return EMPTY;
405
+ const d = cs.map(() => new Array(cs.length).fill(0));
406
+ let span = 0, lo = 0, hi = 1;
407
+ for (let i = 0; i < cs.length; i++) {
408
+ for (let j = i + 1; j < cs.length; j++) {
409
+ const v = deltaE2000(cs[i], cs[j]);
410
+ d[i][j] = v;
411
+ d[j][i] = v;
412
+ if (v > span) {
413
+ span = v;
414
+ lo = i;
415
+ hi = j;
416
+ }
417
+ }
418
+ }
419
+ if (span <= 0) return EMPTY;
420
+ let slack = 0, interior = 0;
421
+ for (let i = 0; i < cs.length; i++) {
422
+ if (i === lo || i === hi) continue;
423
+ slack += (d[lo][i] + d[i][hi]) / span - 1;
424
+ interior++;
425
+ }
426
+ if (interior < 2 || slack / interior > RAMP_SLACK_MAX) return EMPTY;
427
+ let crowd = 0;
428
+ for (let i = 0; i < cs.length; i++) {
429
+ let n = 0;
430
+ for (let j = 0; j < cs.length; j++) {
431
+ if (i === j || d[i][j] <= SAME_SHADE_DELTA_E) n += top[j].n;
432
+ }
433
+ if (n > crowd) crowd = n;
434
+ }
435
+ return {
436
+ marks,
437
+ distinctFills: distinct.size,
438
+ crowdShare: Math.round(crowd / marks * 100),
439
+ spanDeltaE: Math.round(span)
440
+ };
441
+ } catch {
442
+ return EMPTY;
443
+ }
444
+ }
445
+ function colourSpreadFlag(c) {
446
+ if (c.marks === 0) return "";
447
+ return "ramp:d3:crowd" + Math.min(80, Math.floor(c.crowdShare / 20) * 20);
448
+ }
449
+
450
+ // src/autoRamp.ts
451
+ var AUTO_RAMP_CLASSES = 7;
452
+ var AUTO_RAMP_DRAWN_OPACITY = 0.5;
453
+ var AUTO_RAMP_MIN_STEP_DELTA_E = 2.3;
454
+ var FALLBACK_ACCENT = "#3182bd";
455
+ var MIN_ACCENT_VS_CANVAS = 20;
456
+ var HEX = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
457
+ function normHex(raw) {
458
+ if (typeof raw !== "string") return null;
459
+ const m = HEX.exec(raw.trim());
460
+ if (!m) return null;
461
+ const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
462
+ return "#" + h.toLowerCase();
463
+ }
464
+ function mixLab(a, b, t) {
465
+ 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 };
466
+ }
467
+ function paintedOver(fg, canvas, alpha) {
468
+ const f2 = [1, 3, 5].map((i) => parseInt(fg.slice(i, i + 2), 16));
469
+ const c = [1, 3, 5].map((i) => parseInt(canvas.slice(i, i + 2), 16));
470
+ const mixed = f2.map((v, i) => Math.round(v * alpha + c[i] * (1 - alpha))).map((v) => v.toString(16).padStart(2, "0"));
471
+ return hexToLab("#" + mixed.join(""));
472
+ }
473
+ function autoRampMinStep(low, high, canvas, classes = AUTO_RAMP_CLASSES, opacity = AUTO_RAMP_DRAWN_OPACITY) {
474
+ const lo = hexToLab(low), hi = hexToLab(high);
475
+ const bg = normHex(canvas);
476
+ if (!lo || !hi || !bg || classes < 2) return NaN;
477
+ let prev = null, min = Infinity;
478
+ for (let k = 0; k < classes; k++) {
479
+ const painted = paintedOver(labToHex(mixLab(lo, hi, k / (classes - 1))), bg, opacity);
480
+ if (prev) min = Math.min(min, deltaE2000(prev, painted));
481
+ prev = painted;
482
+ }
483
+ return min;
484
+ }
485
+ function autoRampCanvas(p) {
486
+ return normHex(p.backgroundColor) ?? normHex(p.themeBg) ?? "#ffffff";
487
+ }
488
+ var memoKey = "";
489
+ var memoValue = { low: "", high: "" };
490
+ function resolveAutoColorScale(p) {
491
+ const canvas = autoRampCanvas(p);
492
+ const palette = Array.isArray(p.palette) ? p.palette : [];
493
+ const key = [canvas, p.isHighContrast ? 1 : 0, p.themeAccent ?? "", p.themeFg ?? "", palette.join(",")].join("|");
494
+ if (key === memoKey) return memoValue;
495
+ const bg = hexToLab(canvas);
496
+ const darkCanvas = bg.L < 50;
497
+ const usable = (hex2) => {
498
+ const lab = hex2 ? hexToLab(hex2) : null;
499
+ return !!lab && deltaE2000(lab, bg) >= MIN_ACCENT_VS_CANVAS;
500
+ };
501
+ const candidates = p.isHighContrast ? [normHex(p.themeAccent), normHex(p.themeFg)] : [...palette.map(normHex), normHex(p.themeAccent), normHex(p.themeFg)];
502
+ const accent = candidates.find(usable) ?? FALLBACK_ACCENT;
503
+ const A = hexToLab(accent);
504
+ let t = 0.1;
505
+ let low = mixLab(bg, A, t);
506
+ while (t < 0.6 && deltaE2000(paintedOver(labToHex(low), canvas, AUTO_RAMP_DRAWN_OPACITY), bg) < SAME_SHADE_DELTA_E) {
507
+ t += 0.02;
508
+ low = mixLab(bg, A, t);
509
+ }
510
+ const lowHex = labToHex(low);
511
+ let high = { ...A };
512
+ let highHex = labToHex(high);
513
+ for (let step = 0; step < 80 && autoRampMinStep(lowHex, highHex, canvas) < AUTO_RAMP_MIN_STEP_DELTA_E; step++) {
514
+ const L = Math.max(4, Math.min(98, high.L + (darkCanvas ? 1 : -1)));
515
+ if (L === high.L) break;
516
+ high = { ...high, L };
517
+ highHex = labToHex(high);
518
+ }
519
+ memoKey = key;
520
+ memoValue = { low: lowHex, high: highHex };
521
+ return memoValue;
522
+ }
523
+
524
+ // src/defaults.ts
525
+ function themedLand(p) {
526
+ const bg = p.geoLandColor ? null : toRGBA(p.backgroundColor);
527
+ if (!bg || bg[3] < 1 || relativeLuminance([bg[0], bg[1], bg[2]]) > 0.2) return void 0;
528
+ const fg = toRGBA(p.themeFg) ?? [255, 255, 255, 1];
529
+ return "#" + compositeOver([fg[0], fg[1], fg[2], 0.18], [bg[0], bg[1], bg[2]]).map((v) => v.toString(16).padStart(2, "0")).join("");
530
+ }
531
+ var clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
532
+ var numberOr = (raw, dflt) => {
533
+ const n = raw == null || raw === "" ? NaN : Number(raw);
534
+ return Number.isFinite(n) ? n : dflt;
535
+ };
536
+ function resolveOptions(p) {
537
+ return {
538
+ // ---- host fields: pass through exactly as supplied ----
539
+ width: p.width,
540
+ height: p.height,
541
+ palette: p.palette,
542
+ geoUnmatched: p.geoUnmatched,
543
+ geo: p.geo,
544
+ geoPoint: p.geoPoint,
545
+ // THIRD AND FOURTH OCCURRENCES OF THE animLoopDelaySec OMISSION, found together by
546
+ // reading the contract against this list rather than by a bug report. geoPointDest is
547
+ // declared on RenderOptions, passed by the visual and read by the route archetypes, and
548
+ // was missing here - so a chart that claims "an arc is only as honest as its worse
549
+ // endpoint" never saw the destination end at all. The guard test at the bottom of
550
+ // tests/defaults.test.ts now compares the two lists, because four instances of one
551
+ // omission is a missing check, not four mistakes.
552
+ geoPointDest: p.geoPointDest,
553
+ // A host FACT about the data it serialised (contract 1.9.0), passed through untouched.
554
+ dateCellsAreUtcDays: p.dateCellsAreUtcDays,
555
+ // NO LONGER A PASS-THROUGH. A host without a Max Map Points setting used to leave this
556
+ // undefined, and every map chart then fell back to whatever number its own code carried -
557
+ // 1000 in every archetype written so far - while the offer gate used the server's. One
558
+ // default here makes the chart and the gate agree in every host. A positive value from
559
+ // the host wins; absent, blank, zero or junk means the shared default.
560
+ maxMapPoints: (() => {
561
+ const n = numberOr(p.maxMapPoints, 0);
562
+ return n > 0 ? Math.round(n) : MAX_MAP_POINTS_DEFAULT;
563
+ })(),
564
+ // FIFTH AND SIXTH OCCURRENCES, and the two that argue hardest for the guard test: both
565
+ // were passed by the visual AND read by the charts, and neither was ever declared here or
566
+ // in the contract - so the contract-vs-whitelist check could not see them either. What
567
+ // found them was scanning the OTHER direction, from the caller and the charts inward.
568
+ // 0 is MEANINGFUL for both ("let the chart choose", "no paging"), so numberOr rather than
569
+ // `|| DEFAULT`, which would turn an explicit 0 into something else. Clamping stays
570
+ // chart-side for the same reason it does for flipIntervalMs.
571
+ geoPointRadiusPx: Math.max(0, numberOr(p.geoPointRadiusPx, 0)),
572
+ pageSize: Math.max(0, numberOr(p.pageSize, 0)),
573
+ uiState: p.uiState,
574
+ setUiState: p.setUiState,
575
+ backgroundColor: p.backgroundColor,
576
+ themeBg: p.themeBg,
577
+ themeFg: p.themeFg,
578
+ isHighContrast: p.isHighContrast,
579
+ themeAccent: p.themeAccent,
580
+ cultureCode: p.cultureCode,
581
+ allowTooltips: p.allowTooltips,
582
+ noDataText: p.noDataText,
583
+ onSelect: p.onSelect,
584
+ // Control gestures (1.10.0). Passed straight through like onSelect: the host owns what a gesture MEANS,
585
+ // and a host that supplies nothing leaves every control behaving exactly as before.
586
+ onControlChange: p.onControlChange,
587
+ // ---- live-restyle knobs: `raw || undefined` / `.toString() || undefined` ----
588
+ colorScaleLow: p.colorScaleLow || void 0,
589
+ colorScaleHigh: p.colorScaleHigh || void 0,
590
+ // DERIVED, never passed: the automatic ends a blank picker stands for, resolved from the
591
+ // same palette and canvas the chart is handed on this render (autoRamp.ts).
592
+ colorScaleAutoLow: resolveAutoColorScale(p).low,
593
+ colorScaleAutoHigh: resolveAutoColorScale(p).high,
594
+ // "" (global, the default), "frame", or "self"; anything else fails open to global.
595
+ colorScaleScope: p.colorScaleScope === "frame" || p.colorScaleScope === "self" ? p.colorScaleScope : "",
596
+ // Math.max(50, Math.min(100, Number(raw) || 95)) — only meaningful under "self".
597
+ colorScaleSelfClampPct: clamp(Number(p.colorScaleSelfClampPct) || COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX),
598
+ // Map fills: `raw || undefined` like the colour-scale endpoints. The no-data ->
599
+ // land cascade is left to the chart (so the fallback chain is visible in the
600
+ // generated code); HC substitution has already happened in the caller.
601
+ geoLandColor: p.geoLandColor || themedLand(p) || void 0,
602
+ geoNoDataColor: p.geoNoDataColor || void 0,
603
+ aggregation: (p.aggregation == null ? "" : String(p.aggregation)) || void 0,
604
+ // A recognised value wins, anything else falls to the default: an unknown string must
605
+ // never reach a chart as a live mode. Both of these are read on EVERY render, so a
606
+ // reader can change their mind about a cached chart without regenerating it.
607
+ approximatePositions: (() => {
608
+ const v = (p.approximatePositions == null ? "" : String(p.approximatePositions)).toLowerCase();
609
+ return v === "mark" || v === "aggregate" || v === "skip" ? v : APPROXIMATE_POSITIONS_DEFAULT;
610
+ })(),
611
+ valueAxisBaseline: (() => {
612
+ const v = (p.valueAxisBaseline == null ? "" : String(p.valueAxisBaseline)).toLowerCase();
613
+ return v === "zero" || v === "fit" || v === "auto" ? v : VALUE_AXIS_BASELINE_DEFAULT;
614
+ })(),
615
+ // Same shape, and the same reason for it: an unrecognised string must never reach a chart
616
+ // as a live mode. Failing open to "auto" is the conservative direction here — "auto" is
617
+ // the setting that makes the chart PROVE the grid before it names a period, so a garbled
618
+ // value withdraws the claim rather than asserting one.
619
+ seasonalMarkers: (() => {
620
+ const v = (p.seasonalMarkers == null ? "" : String(p.seasonalMarkers)).toLowerCase();
621
+ return v === "auto" || v === "always" || v === "never" ? v : SEASONAL_MARKERS_DEFAULT;
622
+ })(),
623
+ // ---- animation: booleans, default+clamp numerics, "" default enum ----
624
+ animAutoPlay: !!p.animAutoPlay,
625
+ // Math.max(250, Math.min(5000, Number(raw) || 1000))
626
+ animPlaySpeedMs: clamp(Number(p.animPlaySpeedMs) || ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX),
627
+ // Loop-restart delay (seconds on the overview between auto-play passes).
628
+ // Floored at 0, deliberately NO upper clamp (huge = play once per viewing),
629
+ // and 0 must survive as 0 — hence numberOr, not `|| DEFAULT`. This field was
630
+ // MISSING from the resolve list when the assembly moved here, so callers
631
+ // that passed it saw it dropped: the scrubber read undefined -> 0 -> every
632
+ // pass restarted instantly and resumed-state auto-play lost its pacing.
633
+ animLoopDelaySec: Math.max(ANIM_LOOP_DELAY_MIN, numberOr(p.animLoopDelaySec, ANIM_LOOP_DELAY_DEFAULT)),
634
+ // Math.max(3, Math.min(500, Number(raw) || 60))
635
+ animMaxIdealFrames: clamp(Number(p.animMaxIdealFrames) || ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX),
636
+ animStopAtEnd: !!p.animStopAtEnd,
637
+ filtersDuringPlay: !!p.filtersDuringPlay,
638
+ animTimelineStyle: (p.animTimelineStyle || "").toString(),
639
+ // ---- card deck ----
640
+ // SECOND OCCURRENCE OF THE animLoopDelaySec BUG, six lines above (2026-08-13). The
641
+ // visual passed both of these, the generated code read both correctly, and this
642
+ // function — a whitelist that returns a NEW object — dropped them in between. Symptoms
643
+ // were exactly what the two defaults produce: flipSync undefined -> `!== false` -> true
644
+ // -> always synced, so "Flip Cards Together = Off" did nothing and the deck kept its
645
+ // shared strip; flipIntervalMs undefined -> `+(undefined || 0)` -> 0 -> the timer branch
646
+ // never ran, so a 1000 ms interval produced no flips. Two dead knobs, one omission.
647
+ //
648
+ // flipSync DEFAULTS TRUE, so it cannot use `!!p.flipSync` — that would read a missing
649
+ // value as "independent" and hand every deck twelve control strips. Absent means
650
+ // synced; only an explicit false switches it.
651
+ flipSync: p.flipSync === void 0 || p.flipSync === null ? true : !!p.flipSync,
652
+ // WHERE the controls live, which is a different question from whether the cards move
653
+ // together (2026-08-13: "the ability to flip individual cards shouldn't necessarily
654
+ // stop being able to flip all together too"). Resolution order matters and is the whole
655
+ // back-compat story:
656
+ // 1. an explicit, recognised flipMode wins;
657
+ // 2. otherwise DERIVE from flipSync, so a chart generated before this option existed
658
+ // behaves exactly as it did - true -> "all", false -> "single";
659
+ // 3. and a caller that sends neither gets the default.
660
+ // Deriving rather than defaulting is the point: a cached chart must not change behaviour
661
+ // because a newer client shipped, and "both" would have done exactly that to every deck
662
+ // already in a report.
663
+ flipMode: (() => {
664
+ const m = (p.flipMode == null ? "" : String(p.flipMode)).toLowerCase();
665
+ if (m === "single" || m === "all" || m === "both") return m;
666
+ if (p.flipSync === false) return "single";
667
+ if (p.flipSync === true) return "all";
668
+ return FLIP_MODE_DEFAULT;
669
+ })(),
670
+ // WHICH WAY A FACE TURNS. Anything unrecognised - including absent, which is every host
671
+ // that predates the option - is the default, side to side, which is what every chart that
672
+ // turns did before it existed.
673
+ flipDirection: (() => {
674
+ const d = (p.flipDirection == null ? "" : String(p.flipDirection)).toLowerCase();
675
+ return d === "horizontal" || d === "vertical" ? d : FLIP_DIRECTION_DEFAULT;
676
+ })(),
677
+ // 0 is MEANINGFUL here (manual only), so numberOr — not `|| DEFAULT` — for the same
678
+ // reason animLoopDelaySec uses it. UNSET STAYS UNSET (undefined), because the default is
679
+ // the CHART's, not this function's: the card deck reads `+(x || 0)` and is manual, a
680
+ // carousel reads `x == null ? 5000 : +x` and turns by itself. Resolving unset to 0 here
681
+ // made the carousel's default unreachable - every host handed it an explicit "manual".
682
+ // Clamping stays chart-side too: the archetypes clamp 500..120000, and two clamps in two
683
+ // repositories is how they end up disagreeing.
684
+ flipIntervalMs: (() => {
685
+ const n = numberOr(p.flipIntervalMs, NaN);
686
+ return Number.isFinite(n) ? Math.max(0, n) : void 0;
687
+ })(),
688
+ // `raw || undefined`, the same idiom as the colour-scale endpoints and the map fills:
689
+ // blank must arrive as ABSENT, not as an empty string, because the chart's fallback is
690
+ // `options.cardBackgroundColor || <its own choice>` and "" would satisfy a truthiness
691
+ // test in some hands while painting nothing in others.
692
+ cardBackgroundColor: p.cardBackgroundColor || void 0,
693
+ // A reader's on / off, or UNSET - never filled with false, for the flipIntervalMs reason: the
694
+ // default is the chart's (on only where its request asked for drop lines), and a host that
695
+ // turned unset into false would make that default unreachable. Pane choices arrive as text.
696
+ hoverDropLines: (() => {
697
+ const v = p.hoverDropLines;
698
+ if (typeof v === "boolean") return v;
699
+ const s = v == null ? "" : String(v).trim().toLowerCase();
700
+ return s === "on" || s === "true" || s === "1" ? true : s === "off" || s === "false" || s === "0" ? false : void 0;
701
+ })()
702
+ };
703
+ }
704
+
705
+ // src/geoLazy.ts
706
+ var byKind = /* @__PURE__ */ new Map();
707
+ var byAsset = /* @__PURE__ */ new Map();
708
+ var _na;
709
+ function geoAssetFor(geoKind) {
710
+ if (!geoKind) return null;
711
+ const k = geoKind.toLowerCase();
712
+ if (/^country/.test(k)) return "world";
713
+ if (k === "north-america") return "world";
714
+ if (k === "world") return "world";
715
+ if (k === "us-state-code" || k === "us-state-name") return "us-states";
716
+ if (k === "us-zip5") return "us-zip3";
717
+ return null;
718
+ }
719
+ var _worldBase;
720
+ function worldBasemap(world) {
721
+ if (!_worldBase) {
722
+ _worldBase = {
723
+ type: "FeatureCollection",
724
+ features: world.features.filter((f2) => f2?.geometry && f2.geometry.type !== "Point")
725
+ };
726
+ }
727
+ return _worldBase;
728
+ }
729
+ function northAmerica(world) {
730
+ if (!_na) {
731
+ _na = {
732
+ type: "FeatureCollection",
733
+ features: world.features.filter((f2) => NA_ISO3.has(f2?.id) && f2?.geometry && f2.geometry.type !== "Point").map((f2) => clipFeatureToLat(f2, NA_LAT_MAX)).filter((f2) => f2 !== null)
734
+ };
735
+ }
736
+ return _na;
737
+ }
738
+ function fromAssets(key) {
739
+ const asset = geoAssetFor(key);
740
+ if (!asset) return void 0;
741
+ const geo = byAsset.get(asset);
742
+ if (!geo) return void 0;
743
+ if (key === "north-america") return northAmerica(geo);
744
+ if (key === "world") return worldBasemap(geo);
745
+ return geo;
746
+ }
747
+ async function loadGeo(geoKind) {
748
+ if (!geoKind) return void 0;
749
+ const key = geoKind.toLowerCase();
750
+ const already = geoFromCache(key);
751
+ if (already) return already;
752
+ const asset = geoAssetFor(key);
753
+ if (!asset) return void 0;
754
+ if (asset === "us-states") {
755
+ byAsset.set(asset, (await import("./geoUsStates.generated-JEFM6OHO.mjs")).US_STATES);
756
+ } else if (asset === "us-zip3") {
757
+ byAsset.set(asset, (await import("./geoUsZip3.generated-VSWL7D6F.mjs")).US_ZIP3);
758
+ } else {
759
+ byAsset.set(asset, (await import("./geoWorld110m.generated-TD2TA7S7.mjs")).WORLD_110M);
760
+ }
761
+ return fromAssets(key);
762
+ }
763
+ function geoFromCache(geoKind) {
764
+ if (!geoKind) return void 0;
765
+ const key = geoKind.toLowerCase();
766
+ return byKind.get(key) ?? fromAssets(key);
767
+ }
768
+ function registerGeo(geoKind, geo) {
769
+ if (geoKind && geo) byKind.set(geoKind.toLowerCase(), geo);
770
+ }
771
+ function registerGeoAsset(asset, geo) {
772
+ if (!asset || !geo) return;
773
+ byAsset.set(asset, geo);
774
+ if (asset === "world") {
775
+ _na = void 0;
776
+ _worldBase = void 0;
777
+ }
778
+ }
779
+ function clearGeoCache() {
780
+ byKind.clear();
781
+ byAsset.clear();
782
+ _na = void 0;
783
+ _worldBase = void 0;
784
+ }
785
+
786
+ // src/payload.ts
787
+ var ROW_IDX_COLUMN = "__rowIdx__";
788
+ function resolvePointChannel(cols, rowObjs, point) {
789
+ let pLat = null;
790
+ let pLon = null;
791
+ let pPrec = null;
792
+ let geoPoint;
793
+ let bind = point ?? null;
794
+ let rolesBackfilled = [];
795
+ let rolesRefused = [];
796
+ let labelCol;
797
+ if (bind) {
798
+ const dims = cols.filter((c) => !c.isMeasure).map((c) => c.name);
799
+ const res = Xn(dims, rowObjs, bind, point?.mapKind, cols.map((c) => c.name));
800
+ bind = res.bind;
801
+ rolesBackfilled = res.backfilled;
802
+ rolesRefused = res.refused;
803
+ const lf = res.labelFallback;
804
+ labelCol = lf && (lf.country || lf.city || lf.state || lf.zip);
805
+ }
806
+ const hasPointBind = !!bind && !!(bind.city || bind.state || bind.zip || bind.country || bind.lat && bind.lon);
807
+ if (hasPointBind) {
808
+ const p = bind;
809
+ const built = J3(rowObjs.map((r) => ({
810
+ lat: p.lat ? r[p.lat] : null,
811
+ lon: p.lon ? r[p.lon] : null,
812
+ city: p.city ? r[p.city] : null,
813
+ state: p.state ? r[p.state] : null,
814
+ zip: p.zip ? r[p.zip] : null,
815
+ country: p.country ? r[p.country] : null,
816
+ label: labelCol ? r[labelCol] : null
817
+ })), point?.mapKind);
818
+ pLat = built.lat;
819
+ pLon = built.lon;
820
+ pPrec = built.precisions;
821
+ geoPoint = {
822
+ precision: built.precision,
823
+ precisionCounts: built.precisionCounts,
824
+ coarseExamples: built.coarseExamples,
825
+ // STRICTLY no-match. Ambiguous rows are deliberately NOT in here: new charts
826
+ // report them from ambiguousRows/ambiguousExamples, while OLD generated code
827
+ // still shows them — it computes its own off-map count from null coordinates
828
+ // and takes max(offMap, unplaced), so the not-shown total stays right without
829
+ // double-counting on either side of the upgrade.
830
+ unplaced: built.totalRows - built.matchedRows - built.ambiguousRows,
831
+ unplacedExamples: built.unmatched.slice(0, 8),
832
+ ambiguousRows: built.ambiguousRows,
833
+ ...built.ambiguousExamples.length ? { ambiguousExamples: built.ambiguousExamples } : {},
834
+ // Omitted entirely when nothing was adjusted, so the common case adds no bytes
835
+ // and a chart can treat presence as "something about this binding is worth
836
+ // saying".
837
+ ...rolesBackfilled.length ? { rolesBackfilled } : {},
838
+ ...rolesRefused.length ? { rolesRefused } : {}
839
+ };
840
+ }
841
+ return { lat: pLat, lon: pLon, prec: pPrec, geoPoint, rolesRefused };
842
+ }
843
+ function buildRenderPayload(cols, rowObjs, geo, point, destination, opts) {
844
+ cols = cols.filter((c) => c.name !== ROW_IDX_COLUMN);
845
+ let utcDayColumns = null;
846
+ if (opts?.utcDays) {
847
+ const norm = ge(cols, rowObjs);
848
+ rowObjs = norm.rows;
849
+ utcDayColumns = norm.columns;
850
+ }
851
+ const o = resolvePointChannel(cols, rowObjs, point);
852
+ const d = resolvePointChannel(cols, rowObjs, destination);
853
+ const pLat = o.lat, pLon = o.lon, pPrec = o.prec;
854
+ const dLat = d.lat, dLon = d.lon, dPrec = d.prec;
855
+ const geoPoint = o.geoPoint;
856
+ const geoPointDest = d.geoPoint;
857
+ const rolesRefused = [...o.rolesRefused, ...d.rolesRefused.map((s) => `destination: ${s}`)];
858
+ let geoIso = null;
859
+ let geoUnmatched;
860
+ let geoUnmatchedDistinct;
861
+ if (geo && geo.column && geo.kind && Yn(geo.kind)) {
862
+ const built = Fn(rowObjs.map((r) => r[geo.column]), geo.kind);
863
+ geoIso = built.iso;
864
+ geoUnmatched = { count: built.totalRows - built.matchedRows, examples: built.unmatched.slice(0, 8) };
865
+ geoUnmatchedDistinct = built.unmatched.length;
866
+ }
867
+ const hostNames = [ROW_IDX_COLUMN];
868
+ if (geoIso) hostNames.push("__geoIso__");
869
+ if (pLat) hostNames.push("__geoLat__", "__geoLon__", "__geoPrecision__");
870
+ if (dLat) hostNames.push("__geoLatD__", "__geoLonD__", "__geoPrecisionD__");
871
+ const owned = new Set(hostNames);
872
+ if (cols.some((c) => owned.has(c.name))) cols = cols.filter((c) => !owned.has(c.name));
873
+ const colNames = cols.map((c) => c.name);
874
+ const aliasCols = [];
875
+ if (opts?.aliases && opts.aliases.length > 0) {
876
+ const claimed = /* @__PURE__ */ new Set([...colNames, ...hostNames]);
877
+ for (const a of opts.aliases) {
878
+ const from = a ? colNames.indexOf(a.name) : -1;
879
+ if (from < 0 || !a.alias || claimed.has(a.alias)) continue;
880
+ claimed.add(a.alias);
881
+ aliasCols.push({ from, name: a.alias });
882
+ }
883
+ }
884
+ const extra = hostNames.length + aliasCols.length;
885
+ const out = new Array(rowObjs.length);
886
+ for (let r = 0; r < rowObjs.length; r++) {
887
+ const row = rowObjs[r];
888
+ const a = new Array(cols.length + extra);
889
+ for (let c = 0; c < cols.length; c++) {
890
+ const v = row[colNames[c]];
891
+ if (v instanceof Date) {
892
+ a[c] = v.toISOString();
893
+ } else {
894
+ a[c] = v ?? null;
895
+ }
896
+ }
897
+ let k = cols.length;
898
+ a[k++] = r;
899
+ if (geoIso) a[k++] = geoIso[r];
900
+ if (pLat && pLon) {
901
+ a[k++] = pLat[r];
902
+ a[k++] = pLon[r];
903
+ a[k++] = pPrec ? pPrec[r] : null;
904
+ }
905
+ if (dLat && dLon) {
906
+ a[k++] = dLat[r];
907
+ a[k++] = dLon[r];
908
+ a[k++] = dPrec ? dPrec[r] : null;
909
+ }
910
+ for (const ac of aliasCols) a[k++] = a[ac.from];
911
+ out[r] = a;
912
+ }
913
+ const colsOut = [...cols, { name: ROW_IDX_COLUMN, dataType: "Integer", isMeasure: false, modelDesc: "" }];
914
+ if (geoIso) colsOut.push({ name: "__geoIso__", dataType: "String", isMeasure: false, modelDesc: "" });
915
+ if (pLat) colsOut.push(
916
+ { name: "__geoLat__", dataType: "Double", isMeasure: false, modelDesc: "" },
917
+ { name: "__geoLon__", dataType: "Double", isMeasure: false, modelDesc: "" },
918
+ // Host metadata like the two above: which TIER placed this row ("latlon" | "city" |
919
+ // "zip3" | "state" | "country"), null when it was not placed. Never a dimension,
920
+ // never a label, never a colour grouping.
921
+ { name: "__geoPrecision__", dataType: "String", isMeasure: false, modelDesc: "" }
922
+ );
923
+ if (dLat) colsOut.push(
924
+ { name: "__geoLatD__", dataType: "Double", isMeasure: false, modelDesc: "" },
925
+ { name: "__geoLonD__", dataType: "Double", isMeasure: false, modelDesc: "" },
926
+ { name: "__geoPrecisionD__", dataType: "String", isMeasure: false, modelDesc: "" }
927
+ );
928
+ for (const ac of aliasCols) colsOut.push({ ...cols[ac.from], name: ac.name, aliasOf: cols[ac.from].name });
929
+ return {
930
+ columns: colsOut,
931
+ rows: out,
932
+ geoUnmatched,
933
+ geoUnmatchedDistinct,
934
+ geoPoint,
935
+ // Omitted when there is no second endpoint, so every existing chart's options object
936
+ // is unchanged rather than gaining an undefined key.
937
+ ...geoPointDest ? { geoPointDest } : {},
938
+ // Survives a refusal that placed nothing — see geoPointRefused. Omitted when empty so
939
+ // the ordinary case adds no bytes.
940
+ ...rolesRefused.length ? { geoPointRefused: rolesRefused } : {},
941
+ ...utcDayColumns ? { dateCellsAreUtcDays: true, utcDayColumns } : {}
942
+ };
943
+ }
944
+
945
+ // src/codeComments.ts
946
+ function stripJsComments(code) {
947
+ const src = String(code ?? "");
948
+ const n = src.length;
949
+ let out = "";
950
+ let i = 0;
951
+ const templates = [];
952
+ let prevSig = "";
953
+ let prevWord = "";
954
+ let wordOpen = false;
955
+ const regexMayFollow = () => {
956
+ if (prevSig === "") return true;
957
+ if ("(,=:[!&|?{};+-*%<>~^".includes(prevSig)) return true;
958
+ return /^(return|typeof|instanceof|in|of|new|delete|void|throw|case|do|else|yield|await)$/.test(prevWord);
959
+ };
960
+ while (i < n) {
961
+ const ch = src[i];
962
+ const next = i + 1 < n ? src[i + 1] : "";
963
+ const top = templates.length > 0 ? templates[templates.length - 1] : null;
964
+ if (top === -1) {
965
+ if (ch === "\\") {
966
+ out += src.slice(i, i + 2);
967
+ i += 2;
968
+ continue;
969
+ }
970
+ if (ch === "`") {
971
+ templates.pop();
972
+ out += ch;
973
+ i++;
974
+ prevSig = "`";
975
+ prevWord = "";
976
+ wordOpen = false;
977
+ continue;
978
+ }
979
+ if (ch === "$" && next === "{") {
980
+ templates[templates.length - 1] = 0;
981
+ out += "${";
982
+ i += 2;
983
+ prevSig = "{";
984
+ prevWord = "";
985
+ wordOpen = false;
986
+ continue;
987
+ }
988
+ out += ch;
989
+ i++;
990
+ continue;
991
+ }
992
+ if (ch === "/" && next === "/") {
993
+ let j = i + 2;
994
+ while (j < n && src[j] !== "\n" && src[j] !== "\r") j++;
995
+ out += " ";
996
+ i = j;
997
+ wordOpen = false;
998
+ continue;
999
+ }
1000
+ if (ch === "/" && next === "*") {
1001
+ const end = src.indexOf("*/", i + 2);
1002
+ const body = end < 0 ? src.slice(i + 2) : src.slice(i + 2, end);
1003
+ const newlines = body.match(/\r\n|\r|\n/g);
1004
+ out += newlines ? newlines.join("") : " ";
1005
+ i = end < 0 ? n : end + 2;
1006
+ wordOpen = false;
1007
+ continue;
1008
+ }
1009
+ if (ch === "'" || ch === '"') {
1010
+ let j = i + 1;
1011
+ while (j < n && src[j] !== ch && src[j] !== "\n") j += src[j] === "\\" ? 2 : 1;
1012
+ out += src.slice(i, Math.min(j + 1, n));
1013
+ i = j + 1;
1014
+ prevSig = ch;
1015
+ prevWord = "";
1016
+ wordOpen = false;
1017
+ continue;
1018
+ }
1019
+ if (ch === "`") {
1020
+ templates.push(-1);
1021
+ out += ch;
1022
+ i++;
1023
+ wordOpen = false;
1024
+ continue;
1025
+ }
1026
+ if (ch === "/" && regexMayFollow()) {
1027
+ let j = i + 1, inClass = false;
1028
+ while (j < n && src[j] !== "\n") {
1029
+ const c = src[j];
1030
+ if (c === "\\") {
1031
+ j += 2;
1032
+ continue;
1033
+ }
1034
+ if (c === "[") inClass = true;
1035
+ else if (c === "]") inClass = false;
1036
+ else if (c === "/" && !inClass) break;
1037
+ j++;
1038
+ }
1039
+ if (j < n && src[j] === "/") {
1040
+ j++;
1041
+ while (j < n && /[A-Za-z]/.test(src[j])) j++;
1042
+ out += src.slice(i, j);
1043
+ i = j;
1044
+ prevSig = "/";
1045
+ prevWord = "";
1046
+ wordOpen = false;
1047
+ continue;
1048
+ }
1049
+ }
1050
+ if (top !== null && top >= 0) {
1051
+ if (ch === "{") templates[templates.length - 1] = top + 1;
1052
+ else if (ch === "}") {
1053
+ if (top === 0) {
1054
+ templates[templates.length - 1] = -1;
1055
+ out += ch;
1056
+ i++;
1057
+ continue;
1058
+ }
1059
+ templates[templates.length - 1] = top - 1;
1060
+ }
1061
+ }
1062
+ out += ch;
1063
+ i++;
1064
+ if (/\s/.test(ch)) {
1065
+ wordOpen = false;
1066
+ } else {
1067
+ if (/[A-Za-z0-9_$]/.test(ch)) {
1068
+ prevWord = wordOpen ? prevWord + ch : ch;
1069
+ wordOpen = true;
1070
+ } else {
1071
+ prevWord = "";
1072
+ wordOpen = false;
1073
+ }
1074
+ prevSig = ch;
1075
+ }
1076
+ }
1077
+ return out;
1078
+ }
1079
+
1080
+ // src/selection.ts
1081
+ function rowIdxsFromMark(el) {
1082
+ if (!el) return [];
1083
+ const raw = el.getAttribute("data-row-idx");
1084
+ if (!raw) return [];
1085
+ const out = [];
1086
+ for (const tok of raw.split(",")) {
1087
+ const n = parseInt(tok.trim(), 10);
1088
+ if (!isNaN(n) && n >= 0) out.push(n);
1089
+ }
1090
+ return out;
1091
+ }
1092
+ function nextSelection(current, rowIdxs, mods = {}) {
1093
+ const cur = Array.from(current ?? []);
1094
+ if (mods.ctrl || mods.shift) {
1095
+ const next = new Set(cur);
1096
+ for (const r of rowIdxs) {
1097
+ if (next.has(r)) next.delete(r);
1098
+ else next.add(r);
1099
+ }
1100
+ return Array.from(next);
1101
+ }
1102
+ const same = cur.length === rowIdxs.length && rowIdxs.every((r) => cur.includes(r));
1103
+ return same ? [] : rowIdxs.slice();
1104
+ }
1105
+ function selectionRuleOf(container) {
1106
+ try {
1107
+ const el = container?.querySelector?.(`[${SELECTION_RULE_ATTR}]`);
1108
+ return el?.getAttribute(SELECTION_RULE_ATTR) === SELECTION_RULE_CLICKED_MARKS ? "clicked-marks" : "any-row";
1109
+ } catch {
1110
+ return "any-row";
1111
+ }
1112
+ }
1113
+ function markKeyOf(el) {
1114
+ if (!el) return "";
1115
+ const k = el.getAttribute(MARK_KEY_ATTR);
1116
+ if (k != null && k !== "") return k;
1117
+ return "rows:" + (el.getAttribute(ROW_IDX_ATTR) || "").replace(/\s+/g, "");
1118
+ }
1119
+ var toSet = (s) => new Set(Array.from(s ?? []));
1120
+ function sameRowSet(a, b) {
1121
+ const A = toSet(a), B = toSet(b);
1122
+ if (A.size !== B.size) return false;
1123
+ for (const r of A) if (!B.has(r)) return false;
1124
+ return true;
1125
+ }
1126
+ function planSelectionPaint(marks, selection, rule, clicked) {
1127
+ const sel = toSet(selection);
1128
+ if (sel.size === 0) return { on: marks.map(() => false), mode: "none", keys: [] };
1129
+ const anyRow = () => marks.map((m) => m.rows.some((r) => sel.has(r)));
1130
+ if (rule !== "clicked-marks") return { on: anyRow(), mode: "any-row", keys: [] };
1131
+ if (clicked && clicked.keys.length && sameRowSet(clicked.selection, sel)) {
1132
+ const want = new Set(clicked.keys);
1133
+ const on = marks.map((m) => m.rows.length > 0 && want.has(m.key));
1134
+ if (on.some(Boolean)) return { on, mode: "clicked", keys: marks.filter((_, i) => on[i]).map((m) => m.key) };
1135
+ }
1136
+ const exact = marks.map((m) => m.rows.length > 0 && sameRowSet(m.rows, sel));
1137
+ if (exact.some(Boolean)) return { on: exact, mode: "exact", keys: marks.filter((_, i) => exact[i]).map((m) => m.key) };
1138
+ return { on: anyRow(), mode: "fallback-any-row", keys: [] };
1139
+ }
1140
+ function nextClickedMarks(prev, current, key, next, mods = {}) {
1141
+ if (!next.length) return null;
1142
+ if (!(mods.ctrl || mods.shift)) return { keys: [key], selection: next.slice() };
1143
+ const held = prev && sameRowSet(prev.selection, current) ? prev.keys : [];
1144
+ const keys = held.includes(key) ? held.filter((k) => k !== key) : [...held, key];
1145
+ return { keys, selection: next.slice() };
1146
+ }
1147
+ function isInsideControl(target, container) {
1148
+ let el = target;
1149
+ if (el && el.nodeType !== 1) el = el.parentElement ?? el.parentNode ?? null;
1150
+ while (el && el !== container) {
1151
+ if (el.nodeType === 1 && el.classList?.contains?.(CONTROL_CLASS)) return true;
1152
+ el = el.parentElement ?? el.parentNode ?? null;
1153
+ }
1154
+ return false;
1155
+ }
1156
+ function createMarkResolver(env) {
1157
+ const { root, doc, log } = env;
1158
+ const isElement = (t) => {
1159
+ const E = doc?.defaultView?.Element ?? globalThis.Element;
1160
+ if (E && t instanceof E) return true;
1161
+ return !!t && typeof t.closest === "function" && typeof t.getAttribute === "function";
1162
+ };
1163
+ const viewStyle = (n) => {
1164
+ try {
1165
+ const gcs = doc?.defaultView?.getComputedStyle ?? (typeof getComputedStyle === "function" ? getComputedStyle : null);
1166
+ return gcs ? gcs(n).pointerEvents : "";
1167
+ } catch {
1168
+ return "";
1169
+ }
1170
+ };
1171
+ const isInvisibleStrokePath = (el) => {
1172
+ if (el.tagName.toLowerCase() !== "path") return false;
1173
+ const f2 = el.getAttribute("fill");
1174
+ if (f2 !== null && f2 !== "" && f2 !== "none" && f2 !== "transparent") return false;
1175
+ const s = (el.getAttribute("stroke") || "").trim().toLowerCase();
1176
+ const so = parseFloat(el.getAttribute("stroke-opacity") || "1");
1177
+ return s === "transparent" || s === "none" || so <= 0.01;
1178
+ };
1179
+ const distToPathCenterline = (el, x, y) => {
1180
+ const L = el.getTotalLength();
1181
+ if (!(L > 0)) return Number.MAX_VALUE;
1182
+ const m = el.getScreenCTM();
1183
+ if (!m) return Number.MAX_VALUE;
1184
+ let best = Number.MAX_VALUE;
1185
+ const N = 64;
1186
+ for (let i = 0; i <= N; i++) {
1187
+ const pt = el.getPointAtLength(L * i / N);
1188
+ const dx = m.a * pt.x + m.c * pt.y + m.e - x;
1189
+ const dy = m.b * pt.x + m.d * pt.y + m.f - y;
1190
+ const d = dx * dx + dy * dy;
1191
+ if (d < best) best = d;
1192
+ }
1193
+ return Math.sqrt(best);
1194
+ };
1195
+ const refineHitCorridor = (mark, x, y, sel) => {
1196
+ try {
1197
+ if (!mark || !isInvisibleStrokePath(mark) || !mark.getAttribute("data-row-idx")) return mark;
1198
+ const stack = doc.elementsFromPoint(x, y);
1199
+ const candidates = [];
1200
+ for (const el of stack) {
1201
+ if (!root.contains(el)) continue;
1202
+ if (!el.matches || !el.matches(sel)) continue;
1203
+ if (!el.getAttribute("data-row-idx")) continue;
1204
+ if (!isInvisibleStrokePath(el)) continue;
1205
+ candidates.push(el);
1206
+ }
1207
+ if (candidates.indexOf(mark) < 0) candidates.push(mark);
1208
+ if (candidates.length < 2) return mark;
1209
+ let best = mark;
1210
+ let bestD = Number.MAX_VALUE;
1211
+ for (const el of candidates) {
1212
+ const d = distToPathCenterline(el, x, y);
1213
+ if (d < bestD) {
1214
+ bestD = d;
1215
+ best = el;
1216
+ }
1217
+ }
1218
+ if (best !== mark) {
1219
+ log("d3-hit-corridor-refined", {
1220
+ candidates: candidates.length,
1221
+ distPx: Math.round(bestD),
1222
+ fromRows: (mark.getAttribute("data-row-idx") || "").slice(0, 24),
1223
+ toRows: (best.getAttribute("data-row-idx") || "").slice(0, 24)
1224
+ });
1225
+ }
1226
+ return best;
1227
+ } catch {
1228
+ return mark;
1229
+ }
1230
+ };
1231
+ const findMark = (target, sel = ".d3-mark", ev) => {
1232
+ if (!isElement(target)) return null;
1233
+ const el = target;
1234
+ const m = el.closest(sel);
1235
+ if (m && ev) return refineHitCorridor(m, ev.clientX, ev.clientY, sel);
1236
+ return m;
1237
+ };
1238
+ const penetrateOverlayAt = (x, y, sel) => {
1239
+ try {
1240
+ const stack = doc.elementsFromPoint(x, y);
1241
+ for (const el of stack) {
1242
+ if (!root.contains(el) || !isElement(el)) continue;
1243
+ const cand = el.matches(sel) ? el : el.closest(sel);
1244
+ if (!cand || !root.contains(cand) || !cand.getAttribute("data-row-idx")) continue;
1245
+ log("d3_click-overlay-penetrated", { tag: el.tagName, cls: el.getAttribute("class"), via: el === cand ? "self" : "closest" });
1246
+ return cand;
1247
+ }
1248
+ } catch {
1249
+ }
1250
+ return null;
1251
+ };
1252
+ const resolveByGeometry = (x, y, sel) => {
1253
+ try {
1254
+ const cands = root.querySelectorAll(sel);
1255
+ let best = null, bestArea = Number.MAX_VALUE;
1256
+ if (cands.length <= 600) {
1257
+ for (const el of Array.from(cands)) {
1258
+ if (!el.getAttribute("data-row-idx")) continue;
1259
+ const r = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
1260
+ if (!r || r.width <= 0 || r.height <= 0) continue;
1261
+ if (x < r.left || x > r.right || y < r.top || y > r.bottom) continue;
1262
+ const area2 = r.width * r.height;
1263
+ if (area2 < bestArea) {
1264
+ bestArea = area2;
1265
+ best = el;
1266
+ }
1267
+ }
1268
+ }
1269
+ const hasHittableShape = (node) => {
1270
+ const SHAPE = /^(rect|circle|ellipse|path|line|polygon|polyline|text|image|use)$/;
1271
+ const all = [node, ...Array.from(node.querySelectorAll("*"))];
1272
+ for (const n of all) {
1273
+ const tag = (n.tagName || "").toLowerCase();
1274
+ if (!SHAPE.test(tag)) continue;
1275
+ const pe = viewStyle(n);
1276
+ if (pe && pe !== "none") return true;
1277
+ }
1278
+ return false;
1279
+ };
1280
+ if (best && !hasHittableShape(best)) {
1281
+ log("d3_click-geom-resolved", { rows: (best.getAttribute("data-row-idx") || "").slice(0, 24) });
1282
+ return best;
1283
+ } else if (best) {
1284
+ log("d3_click-geom-skip-hittable", {});
1285
+ }
1286
+ } catch {
1287
+ }
1288
+ return null;
1289
+ };
1290
+ return { isInvisibleStrokePath, distToPathCenterline, refineHitCorridor, findMark, rowIdxsFromMark, penetrateOverlayAt, resolveByGeometry };
1291
+ }
1292
+ function collectD3GlyphCenters(container, baseRect, out) {
1293
+ const Point = container.ownerDocument?.defaultView?.DOMPoint ?? globalThis.DOMPoint;
1294
+ const marks = container.querySelectorAll(`.${MARK_CLASS}.${MARK_SELECTED_CLASS}`);
1295
+ const rects = [];
1296
+ for (let i = 0; i < marks.length; i++) {
1297
+ const r = marks[i].getBoundingClientRect();
1298
+ rects.push(r);
1299
+ }
1300
+ const CONTAINMENT_CAP = 300;
1301
+ const EPS2 = 1;
1302
+ const filterContainers = marks.length <= CONTAINMENT_CAP;
1303
+ const enclosesAnother = (ci) => {
1304
+ const c = rects[ci];
1305
+ const cArea = c.width * c.height;
1306
+ if (cArea <= 0) return false;
1307
+ for (let mj = 0; mj < rects.length; mj++) {
1308
+ if (mj === ci) continue;
1309
+ const m = rects[mj];
1310
+ const mArea = m.width * m.height;
1311
+ if (mArea <= 0 || mArea >= cArea) continue;
1312
+ if (m.left >= c.left - EPS2 && m.right <= c.right + EPS2 && m.top >= c.top - EPS2 && m.bottom <= c.bottom + EPS2) return true;
1313
+ }
1314
+ return false;
1315
+ };
1316
+ for (let i = 0; i < marks.length; i++) {
1317
+ const el = marks[i];
1318
+ const r = rects[i];
1319
+ if (r.width === 0 && r.height === 0) continue;
1320
+ if (filterContainers && enclosesAnother(i)) continue;
1321
+ const tag = el.tagName.toLowerCase();
1322
+ let sx;
1323
+ let sy;
1324
+ const cxA = el.getAttribute("data-cx"), cyA = el.getAttribute("data-cy");
1325
+ if (cxA != null && cyA != null && typeof el.getScreenCTM === "function") {
1326
+ const ctm = el.getScreenCTM();
1327
+ const cxN = parseFloat(cxA), cyN = parseFloat(cyA);
1328
+ if (ctm && isFinite(cxN) && isFinite(cyN)) {
1329
+ const p = new Point(cxN, cyN).matrixTransform(ctm);
1330
+ sx = p.x;
1331
+ sy = p.y;
1332
+ }
1333
+ }
1334
+ if (sx === void 0 && tag === "path" && typeof el.getPointAtLength === "function") {
1335
+ try {
1336
+ const len = el.getTotalLength();
1337
+ const ctm = el.getScreenCTM();
1338
+ if (len > 0 && ctm) {
1339
+ const dAttr = el.getAttribute("d") || "";
1340
+ const isArcSector = /[Aa]/.test(dAttr) && !/[CcSsQqTt]/.test(dAttr);
1341
+ let local;
1342
+ if (isArcSector) {
1343
+ const N = 24;
1344
+ let ax = 0, ay = 0;
1345
+ for (let k = 0; k < N; k++) {
1346
+ const sp = el.getPointAtLength(len * k / N);
1347
+ ax += sp.x;
1348
+ ay += sp.y;
1349
+ }
1350
+ const cand = new Point(ax / N, ay / N);
1351
+ let inside = true;
1352
+ try {
1353
+ if (typeof el.isPointInFill === "function") {
1354
+ inside = el.isPointInFill(cand);
1355
+ }
1356
+ } catch {
1357
+ inside = true;
1358
+ }
1359
+ if (inside) local = cand;
1360
+ }
1361
+ if (!local) local = el.getPointAtLength(len / 2);
1362
+ const p = local.matrixTransform(ctm);
1363
+ sx = p.x;
1364
+ sy = p.y;
1365
+ }
1366
+ } catch {
1367
+ }
1368
+ }
1369
+ if (sx === void 0 || sy === void 0) {
1370
+ const isRect = tag === "rect";
1371
+ sx = isRect ? r.left + r.width * 0.8 : r.left + r.width / 2;
1372
+ sy = isRect ? r.top + r.height * 0.2 : r.top + r.height / 2;
1373
+ }
1374
+ out.push({ x: sx - baseRect.left, y: sy - baseRect.top });
1375
+ }
1376
+ }
1377
+ var SELECTION_GLYPH_CLASS = "lch-sel-glyph";
1378
+ function clearSelectionGlyphs(overlay) {
1379
+ if (overlay) {
1380
+ while (overlay.firstChild) overlay.removeChild(overlay.firstChild);
1381
+ }
1382
+ }
1383
+ function paintSelectionGlyphs(overlay, centers, style) {
1384
+ const doc = overlay.ownerDocument;
1385
+ const frag = doc.createDocumentFragment();
1386
+ const count = Math.min(centers.length, style.max ?? 600);
1387
+ for (let i = 0; i < count; i++) {
1388
+ const c = centers[i];
1389
+ const g = doc.createElement("span");
1390
+ g.className = SELECTION_GLYPH_CLASS;
1391
+ g.textContent = style.symbol;
1392
+ g.style.position = "absolute";
1393
+ g.style.left = c.x + "px";
1394
+ g.style.top = c.y + "px";
1395
+ g.style.transform = "translate(-50%, -50%)";
1396
+ g.style.font = "700 11px/1 system-ui, -apple-system, Segoe UI, sans-serif";
1397
+ g.style.color = style.color;
1398
+ const halo = style.halo;
1399
+ g.style.textShadow = `-1px -1px 1px ${halo}, 1px -1px 1px ${halo}, -1px 1px 1px ${halo}, 1px 1px 1px ${halo}, 0 0 2px ${halo}`;
1400
+ g.style.pointerEvents = "none";
1401
+ frag.appendChild(g);
1402
+ }
1403
+ overlay.appendChild(frag);
1404
+ return count;
1405
+ }
1406
+
1407
+ // src/hitTargets.ts
1408
+ var SVG_NS = "http://www.w3.org/2000/svg";
1409
+ var HIT_ATTR = "data-lch-hit";
1410
+ var ELEMENT_CAP2 = 5e3;
1411
+ var mapsPresentationAttributes = /* @__PURE__ */ new WeakMap();
1412
+ function engineMapsPresentationAttributes(ownerDoc, view) {
1413
+ if (!ownerDoc || typeof ownerDoc !== "object") return false;
1414
+ const known = mapsPresentationAttributes.get(ownerDoc);
1415
+ if (known !== void 0) return known;
1416
+ let maps = false;
1417
+ try {
1418
+ const host = ownerDoc.documentElement;
1419
+ const probe = ownerDoc.createElementNS(SVG_NS, "svg");
1420
+ const shape = ownerDoc.createElementNS(SVG_NS, "rect");
1421
+ shape.setAttribute("fill", "none");
1422
+ probe.appendChild(shape);
1423
+ host.appendChild(probe);
1424
+ try {
1425
+ maps = String(view.getComputedStyle(shape).fill ?? "").trim().toLowerCase() === "none";
1426
+ } finally {
1427
+ host.removeChild(probe);
1428
+ }
1429
+ } catch {
1430
+ maps = false;
1431
+ }
1432
+ mapsPresentationAttributes.set(ownerDoc, maps);
1433
+ return maps;
1434
+ }
1435
+ function ensureCrossfilterHitTargets(container, doc) {
1436
+ const report = { tagged: 0, hitRects: 0, peFlips: 0, peStrokeOnly: 0 };
1437
+ try {
1438
+ if (!container || typeof container.querySelectorAll !== "function") return report;
1439
+ const els = container.querySelectorAll(
1440
+ `.${MARK_CLASS}, .${LEGEND_MARK_CLASS}, .${AXIS_FILTER_CLASS}`
1441
+ );
1442
+ report.tagged = els.length;
1443
+ if (els.length === 0 || els.length > ELEMENT_CAP2) return report;
1444
+ const ownerDoc = doc ?? container.ownerDocument;
1445
+ const view = ownerDoc?.defaultView;
1446
+ const computed = (el) => {
1447
+ try {
1448
+ return view?.getComputedStyle ? view.getComputedStyle(el) : null;
1449
+ } catch {
1450
+ return null;
1451
+ }
1452
+ };
1453
+ for (const el of Array.from(els)) {
1454
+ const tag = String(el.tagName || "").toLowerCase();
1455
+ if (tag === "g") {
1456
+ if (!el.hasAttribute?.(ROW_IDX_ATTR)) continue;
1457
+ if (el.querySelector?.(`rect[${HIT_ATTR}="1"]`)) continue;
1458
+ const cls = el.getAttribute?.("class") || "";
1459
+ const isFurniture = cls.indexOf(LEGEND_MARK_CLASS) >= 0 || cls.indexOf(AXIS_FILTER_CLASS) >= 0;
1460
+ if (!isFurniture) {
1461
+ let hasHittableChild = false;
1462
+ const kids = el.querySelectorAll?.("*") ?? [];
1463
+ for (const kid of Array.from(kids)) {
1464
+ const ktag = String(kid.tagName || "").toLowerCase();
1465
+ if (ktag === "g" || ktag === "tspan" || ktag === "title" || ktag === "defs") continue;
1466
+ const cs = computed(kid);
1467
+ if (!cs || cs.pointerEvents !== "none") {
1468
+ hasHittableChild = true;
1469
+ break;
1470
+ }
1471
+ }
1472
+ if (hasHittableChild) continue;
1473
+ }
1474
+ let bb = null;
1475
+ try {
1476
+ bb = el.getBBox?.();
1477
+ } catch {
1478
+ bb = null;
1479
+ }
1480
+ if (!bb || !(bb.width > 0) || !(bb.height > 0)) continue;
1481
+ const rect = ownerDoc.createElementNS(SVG_NS, "rect");
1482
+ rect.setAttribute("x", String(bb.x));
1483
+ rect.setAttribute("y", String(bb.y));
1484
+ rect.setAttribute("width", String(bb.width));
1485
+ rect.setAttribute("height", String(bb.height));
1486
+ rect.setAttribute("fill", "transparent");
1487
+ rect.setAttribute(HIT_ATTR, "1");
1488
+ if (rect.style) {
1489
+ rect.style.pointerEvents = "all";
1490
+ rect.style.cursor = "pointer";
1491
+ }
1492
+ el.insertBefore(rect, el.firstChild);
1493
+ report.hitRects++;
1494
+ } else {
1495
+ const cs = computed(el);
1496
+ if (cs && cs.pointerEvents === "none") {
1497
+ const fill = String((engineMapsPresentationAttributes(ownerDoc, view) ? cs.fill : el.getAttribute?.("fill") ?? cs.fill) ?? "").trim().toLowerCase();
1498
+ const unfilled = fill === "none" || fill === "transparent" || fill === "rgba(0, 0, 0, 0)";
1499
+ if (el.style) el.style.pointerEvents = unfilled ? "stroke" : "all";
1500
+ report.peFlips++;
1501
+ if (unfilled) report.peStrokeOnly++;
1502
+ }
1503
+ }
1504
+ }
1505
+ } catch {
1506
+ }
1507
+ return report;
1508
+ }
1509
+
1510
+ // src/blankRender.ts
1511
+ var EMPTY2 = { markCount: 0, legendMarkCount: 0, containerKind: "empty", declaresNoRowMarks: false };
1512
+ function censusMarks(container) {
1513
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY2;
1514
+ try {
1515
+ const marks = /* @__PURE__ */ new Set();
1516
+ for (const el of Array.from(container.querySelectorAll(`.${MARK_CLASS}`))) marks.add(el);
1517
+ for (const el of Array.from(container.querySelectorAll(`[${ROW_IDX_ATTR}]`))) marks.add(el);
1518
+ const legend = /* @__PURE__ */ new Set();
1519
+ for (const el of Array.from(container.querySelectorAll(`.${LEGEND_MARK_CLASS}`))) {
1520
+ legend.add(el);
1521
+ marks.delete(el);
1522
+ }
1523
+ const hasSvg = !!container.querySelector("svg");
1524
+ const hasAnything = hasSvg || (container.childElementCount ?? 0) > 0;
1525
+ return {
1526
+ markCount: marks.size,
1527
+ legendMarkCount: legend.size,
1528
+ containerKind: hasSvg ? "svg" : hasAnything ? "html" : "empty",
1529
+ declaresNoRowMarks: !!container.querySelector(`[${NO_ROW_MARKS_ATTR}]`)
1530
+ };
1531
+ } catch {
1532
+ return EMPTY2;
1533
+ }
1534
+ }
1535
+ function isBlankRender(i) {
1536
+ if (i.contractUntagged) return false;
1537
+ if (i.declaresNoRowMarks) return false;
1538
+ if (i.animated) return false;
1539
+ if (i.authoredNoDataText) return false;
1540
+ if (!(i.rows > 0)) return false;
1541
+ return i.markCount === 0;
1542
+ }
1543
+ function blankRenderFlag(lang, cause) {
1544
+ const clean = (s) => String(s || "unknown").toLowerCase().replace(/[^a-z0-9-]+/g, "-");
1545
+ return `blankrender:${clean(lang)}:${clean(cause)}`;
1546
+ }
1547
+
1548
+ // src/hitBands.ts
1549
+ var MIN_HIT_BAND_PX = 8;
1550
+ var ELEMENT_CAP3 = 5e3;
1551
+ var EMPTY3 = {
1552
+ openStrokes: 0,
1553
+ interactive: 0,
1554
+ hairline: 0,
1555
+ uncovered: 0,
1556
+ widestPx: 0
1557
+ };
1558
+ var OPEN_TAGS = /* @__PURE__ */ new Set(["path", "line", "polyline"]);
1559
+ function styleOf2(el, doc) {
1560
+ try {
1561
+ const view = doc?.defaultView;
1562
+ return view?.getComputedStyle ? view.getComputedStyle(el) : null;
1563
+ } catch {
1564
+ return null;
1565
+ }
1566
+ }
1567
+ function attrOrComputed(el, name, cs) {
1568
+ const a = el.getAttribute?.(name);
1569
+ if (a !== null && a !== void 0 && String(a).trim() !== "") return String(a).trim();
1570
+ const c = cs?.[name === "stroke-width" ? "strokeWidth" : name];
1571
+ return c === null || c === void 0 ? "" : String(c).trim();
1572
+ }
1573
+ function isUnfilled(fill) {
1574
+ const f2 = fill.toLowerCase();
1575
+ return f2 === "none" || f2 === "transparent" || f2 === "rgba(0, 0, 0, 0)" || f2 === "rgba(0,0,0,0)";
1576
+ }
1577
+ function widthPx(raw) {
1578
+ const m = /^(-?\d+(?:\.\d+)?)\s*(px)?$/i.exec(raw);
1579
+ if (!m) return NaN;
1580
+ const n = parseFloat(m[1]);
1581
+ return isFinite(n) ? n : NaN;
1582
+ }
1583
+ function censusHitBands(container, doc) {
1584
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY3;
1585
+ try {
1586
+ const els = Array.from(
1587
+ container.querySelectorAll(`.${MARK_CLASS}, [${ROW_IDX_ATTR}]`)
1588
+ );
1589
+ if (els.length === 0 || els.length > ELEMENT_CAP3) return EMPTY3;
1590
+ const ownerDoc = doc ?? container.ownerDocument;
1591
+ const widestByGeom = /* @__PURE__ */ new Map();
1592
+ const rows = [];
1593
+ for (const el of els) {
1594
+ const tag = String(el.tagName || "").toLowerCase();
1595
+ if (!OPEN_TAGS.has(tag)) continue;
1596
+ const cs = styleOf2(el, ownerDoc);
1597
+ const open = isUnfilled(attrOrComputed(el, "fill", cs));
1598
+ const w = widthPx(attrOrComputed(el, "stroke-width", cs) || "1");
1599
+ const geom = String(el.getAttribute?.("d") ?? "") || `${tag}#${String(el.getAttribute?.(ROW_IDX_ATTR) ?? "")}`;
1600
+ const inert = String(cs?.pointerEvents ?? el.style?.pointerEvents ?? "") === "none";
1601
+ rows.push({ el, geom, w, open, inert });
1602
+ if (isFinite(w)) {
1603
+ const prev = widestByGeom.get(geom);
1604
+ if (prev === void 0 || w > prev) widestByGeom.set(geom, w);
1605
+ }
1606
+ }
1607
+ const out = { ...EMPTY3 };
1608
+ for (const r of rows) {
1609
+ if (!r.open) continue;
1610
+ out.openStrokes++;
1611
+ if (r.inert) continue;
1612
+ out.interactive++;
1613
+ if (isFinite(r.w) && r.w > out.widestPx) out.widestPx = Math.round(r.w);
1614
+ if (!(r.w < MIN_HIT_BAND_PX)) continue;
1615
+ out.hairline++;
1616
+ const best = widestByGeom.get(r.geom);
1617
+ if (best === void 0 || best < MIN_HIT_BAND_PX) out.uncovered++;
1618
+ }
1619
+ return out;
1620
+ } catch {
1621
+ return EMPTY3;
1622
+ }
1623
+ }
1624
+ function hitBandFlag(c) {
1625
+ if (c.interactive === 0) return "";
1626
+ return c.uncovered > 0 ? "hitband:d3:thin" : "hitband:d3:ok";
1627
+ }
1628
+
1629
+ // src/clippedText.ts
1630
+ var CLIP_TOLERANCE_PX = 1;
1631
+ var MAX_REPORTED = 8;
1632
+ function censusClippedText(container) {
1633
+ const none = { measured: 0, clipped: [], clippedCount: 0, laidOut: false };
1634
+ try {
1635
+ const svg = container?.querySelector?.("svg");
1636
+ if (!svg) return none;
1637
+ const f2 = svg.getBoundingClientRect();
1638
+ if (!f2 || !(f2.width > 0) || !(f2.height > 0)) return none;
1639
+ const view = svg.ownerDocument?.defaultView;
1640
+ const clipped = [];
1641
+ let measured = 0;
1642
+ for (const t of Array.from(svg.querySelectorAll("text"))) {
1643
+ const r = t.getBoundingClientRect();
1644
+ if (!r || !(r.width > 0) || !(r.height > 0)) continue;
1645
+ const s = view?.getComputedStyle?.(t);
1646
+ if (s && (s.display === "none" || s.visibility === "hidden" || s.opacity === "0")) continue;
1647
+ measured++;
1648
+ const top = f2.top - r.top, left = f2.left - r.left, bottom = r.bottom - f2.bottom, right = r.right - f2.right;
1649
+ if (top > CLIP_TOLERANCE_PX || left > CLIP_TOLERANCE_PX || bottom > CLIP_TOLERANCE_PX || right > CLIP_TOLERANCE_PX) {
1650
+ clipped.push({
1651
+ text: String(t.textContent ?? "").slice(0, 40),
1652
+ top: Math.max(0, Math.round(top)),
1653
+ left: Math.max(0, Math.round(left)),
1654
+ bottom: Math.max(0, Math.round(bottom)),
1655
+ right: Math.max(0, Math.round(right))
1656
+ });
1657
+ }
1658
+ }
1659
+ clipped.sort((a, b) => Math.max(b.top, b.left, b.bottom, b.right) - Math.max(a.top, a.left, a.bottom, a.right));
1660
+ return { measured, clipped: clipped.slice(0, MAX_REPORTED), clippedCount: clipped.length, laidOut: true };
1661
+ } catch {
1662
+ return none;
1663
+ }
1664
+ }
1665
+ function clippedTextFlag(c) {
1666
+ if (!c.laidOut || c.clippedCount === 0) return "";
1667
+ const worst = c.clipped[0];
1668
+ const side = ["top", "left", "bottom", "right"].reduce((a, k) => worst[k] > worst[a] ? k : a, "top");
1669
+ return `${c.clippedCount} of ${c.measured} text label(s) are cut off by the frame; the worst, "${worst.text}", by ${worst[side]}px at the ${side}.`;
1670
+ }
1671
+
1672
+ // src/valuePlacement.ts
1673
+ var MIN_MARKS2 = 24;
1674
+ var ELEMENT_CAP4 = 5e3;
1675
+ var FIT_SAMPLE = 90;
1676
+ var VALUE_AXIS_MIN_FIT = 0.8;
1677
+ var MIN_AXIS_SPAN_PX = 40;
1678
+ var EMPTY4 = { marks: 0, axis: "", column: "", fit: 0, offShare: 0, medianOffRadii: 0 };
1679
+ function num(el, name) {
1680
+ const v = el.getAttribute?.(name);
1681
+ if (v === null || v === void 0 || String(v).trim() === "") return NaN;
1682
+ return parseFloat(String(v));
1683
+ }
1684
+ function median(xs) {
1685
+ if (!xs.length) return NaN;
1686
+ const s = [...xs].sort((a, b) => a - b);
1687
+ const m = s.length >> 1;
1688
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
1689
+ }
1690
+ function robustLine(v, pos) {
1691
+ const n = v.length;
1692
+ const step = Math.max(1, Math.floor(n / FIT_SAMPLE));
1693
+ const idx = [];
1694
+ for (let i = 0; i < n; i += step) idx.push(i);
1695
+ const slopes = [];
1696
+ for (let p = 0; p < idx.length; p++) {
1697
+ for (let q = p + 1; q < idx.length; q++) {
1698
+ const dv = v[idx[q]] - v[idx[p]];
1699
+ if (Math.abs(dv) < 1e-12) continue;
1700
+ slopes.push((pos[idx[q]] - pos[idx[p]]) / dv);
1701
+ }
1702
+ }
1703
+ if (slopes.length < 3) return null;
1704
+ const b = median(slopes);
1705
+ if (!Number.isFinite(b) || Math.abs(b) < 1e-12) return null;
1706
+ const a = median(pos.map((y, i) => y - b * v[i]));
1707
+ return Number.isFinite(a) ? { a, b } : null;
1708
+ }
1709
+ function censusValuePlacement(container, data) {
1710
+ try {
1711
+ if (!container || typeof container.querySelectorAll !== "function") return EMPTY4;
1712
+ const rows = data?.rows;
1713
+ const columns = data?.columns ?? [];
1714
+ if (!Array.isArray(rows) || !rows.length || !columns.length) return EMPTY4;
1715
+ const els = Array.from(container.querySelectorAll(`circle[${ROW_IDX_ATTR}]`));
1716
+ if (els.length < MIN_MARKS2 || els.length > ELEMENT_CAP4) return EMPTY4;
1717
+ const dots = [];
1718
+ const seen = /* @__PURE__ */ new Set();
1719
+ for (const el of els) {
1720
+ const raw = String(el.getAttribute(ROW_IDX_ATTR) ?? "").trim();
1721
+ if (!/^\d+$/.test(raw)) continue;
1722
+ const i = +raw;
1723
+ const row = rows[i];
1724
+ const x = num(el, "cx"), y = num(el, "cy"), r = num(el, "r");
1725
+ if (!row || !Number.isFinite(x) || !Number.isFinite(y) || !(r > 0)) continue;
1726
+ seen.add(i);
1727
+ dots.push({ row, x, y, r });
1728
+ }
1729
+ if (dots.length < MIN_MARKS2 || seen.size < dots.length * 0.9) return EMPTY4;
1730
+ let best = null;
1731
+ for (let c = 0; c < columns.length; c++) {
1732
+ if (/^__.*__$/.test(String(columns[c]?.name ?? ""))) continue;
1733
+ const vals = dots.map((d) => {
1734
+ const v = d.row[c];
1735
+ return v === null || v === void 0 || v === "" ? NaN : typeof v === "number" ? v : Number(v);
1736
+ });
1737
+ const finite = vals.filter(Number.isFinite);
1738
+ if (finite.length < dots.length * 0.9 || new Set(finite).size < 8) continue;
1739
+ const logs = finite.every((v) => v > 0) ? [false, true] : [false];
1740
+ for (const log of logs) {
1741
+ for (const axis of ["x", "y"]) {
1742
+ const keep = [];
1743
+ const v = [], pos = [];
1744
+ dots.forEach((d, k) => {
1745
+ if (!Number.isFinite(vals[k])) return;
1746
+ keep.push(k);
1747
+ v.push(log ? Math.log10(vals[k]) : vals[k]);
1748
+ pos.push(axis === "x" ? d.x : d.y);
1749
+ });
1750
+ const line = robustLine(v, pos);
1751
+ if (!line) continue;
1752
+ const lo = Math.min(...pos), hi = Math.max(...pos);
1753
+ if (hi - lo < MIN_AXIS_SPAN_PX) continue;
1754
+ const mean = pos.reduce((s, p) => s + p, 0) / pos.length;
1755
+ let ssRes = 0, ssTot = 0;
1756
+ const res = pos.map((p, k) => {
1757
+ const e = p - (line.a + line.b * v[k]);
1758
+ ssRes += e * e;
1759
+ ssTot += (p - mean) ** 2;
1760
+ return e;
1761
+ });
1762
+ if (ssTot <= 0) continue;
1763
+ const fit = 1 - ssRes / ssTot;
1764
+ if (fit >= VALUE_AXIS_MIN_FIT && (!best || fit > best.fit)) {
1765
+ const full = new Array(dots.length).fill(NaN);
1766
+ keep.forEach((k, j) => {
1767
+ full[k] = res[j];
1768
+ });
1769
+ best = { col: c, axis, fit, res: full, log };
1770
+ }
1771
+ }
1772
+ }
1773
+ }
1774
+ if (!best) return EMPTY4;
1775
+ let off = 0, measured = 0;
1776
+ const radii = [];
1777
+ dots.forEach((d, k) => {
1778
+ const e = best.res[k];
1779
+ if (!Number.isFinite(e)) return;
1780
+ measured++;
1781
+ const inRadii = Math.abs(e) / Math.max(d.r, 1);
1782
+ radii.push(inRadii);
1783
+ if (inRadii > 1) off++;
1784
+ });
1785
+ if (measured < MIN_MARKS2) return EMPTY4;
1786
+ return {
1787
+ marks: measured,
1788
+ axis: best.axis,
1789
+ column: String(columns[best.col]?.name ?? ""),
1790
+ fit: Math.round(best.fit * 1e3) / 1e3,
1791
+ offShare: Math.round(off / measured * 1e3) / 10,
1792
+ medianOffRadii: Math.round(median(radii) * 100) / 100
1793
+ };
1794
+ } catch {
1795
+ return EMPTY4;
1796
+ }
1797
+ }
1798
+ function valuePlacementFlag(c) {
1799
+ if (c.marks === 0) return "";
1800
+ const s = c.offShare;
1801
+ const bucket = s <= 0 ? 0 : s < 5 ? 1 : s < 20 ? 5 : s < 50 ? 20 : 50;
1802
+ return "place:d3:off" + bucket;
1803
+ }
1804
+
1805
+ // src/fit.ts
1806
+ var SCROLL_SLACK_PX = 8;
1807
+ function needsScroll(contentPx, viewportPx, slackPx = SCROLL_SLACK_PX) {
1808
+ if (!isFinite(contentPx) || !isFinite(viewportPx)) return false;
1809
+ if (contentPx <= 0 || viewportPx <= 0) return false;
1810
+ return contentPx - viewportPx > slackPx;
1811
+ }
1812
+ function contentExtentOf(boxes) {
1813
+ if (!boxes || boxes.length === 0) return { w: 0, h: 0, source: "none" };
1814
+ const usable = boxes.filter((b) => b && isFinite(b.right) && isFinite(b.bottom) && (b.right > 0 || b.bottom > 0));
1815
+ if (usable.length === 0) return { w: 0, h: 0, source: "none" };
1816
+ const inFlow = usable.filter((b) => !b.floating);
1817
+ const counted = inFlow.length > 0 ? inFlow : usable;
1818
+ let w = 0, h = 0;
1819
+ for (const b of counted) {
1820
+ if (b.right > w) w = b.right;
1821
+ if (b.bottom > h) h = b.bottom;
1822
+ }
1823
+ return { w, h, source: inFlow.length > 0 ? "in-flow" : "all-floating" };
1824
+ }
1825
+ function scrollFitFor(contentW, contentH, viewportW, viewportH, slackPx = SCROLL_SLACK_PX) {
1826
+ return {
1827
+ overflowX: needsScroll(contentW, viewportW, slackPx) ? "auto" : "hidden",
1828
+ overflowY: needsScroll(contentH, viewportH, slackPx) ? "auto" : "hidden"
1829
+ };
1830
+ }
1831
+ var FIT_CONTENT_SELECTOR = "text, .d3-mark, .d3-legend-mark";
1832
+ var PHANTOM_FRACTION = 0.95;
1833
+ function isPhantomBox(w, h, refW, refH) {
1834
+ return w > refW * PHANTOM_FRACTION || h > refH * PHANTOM_FRACTION;
1835
+ }
1836
+ var MAX_FRAME_GROW_FACTOR = 20;
1837
+ function planFrameGrow(inkBottomPx, elHeightPx, viewBoxH, ctmScale, slackPx = SCROLL_SLACK_PX, maxFactor = MAX_FRAME_GROW_FACTOR) {
1838
+ const none = (reason) => ({ grow: false, heightPx: elHeightPx, viewBoxH, reason });
1839
+ if (!isFinite(inkBottomPx) || !isFinite(elHeightPx) || elHeightPx <= 0) return none("unmeasurable");
1840
+ if (!needsScroll(inkBottomPx, elHeightPx, slackPx)) return none("fits");
1841
+ if (inkBottomPx > elHeightPx * maxFactor) return none("beyond-ceiling");
1842
+ const heightPx = Math.ceil(inkBottomPx);
1843
+ const delta = heightPx - elHeightPx;
1844
+ if (!(viewBoxH > 0)) return { grow: true, heightPx, viewBoxH, reason: "grow-no-viewbox" };
1845
+ if (!(ctmScale > 0) || !isFinite(ctmScale)) return none("no-scale");
1846
+ return { grow: true, heightPx, viewBoxH: viewBoxH + delta / ctmScale, reason: "grow" };
1847
+ }
1848
+ var AXIS_PIN_MIN_LABELS = 2;
1849
+ var AXIS_PIN_BAND_PAD_PX = 2;
1850
+ var AXIS_PIN_MAX_BAND_FRACTION = 0.4;
1851
+ var AXIS_PIN_TRACK_REACH_PX = 12;
1852
+ function isHorizontalLabelRow(boxes) {
1853
+ if (!boxes || boxes.length < 2) return false;
1854
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
1855
+ for (const b of boxes) {
1856
+ const cx = (b.left + b.right) / 2, cy = (b.top + b.bottom) / 2;
1857
+ if (!isFinite(cx) || !isFinite(cy)) return false;
1858
+ if (cx < minX) minX = cx;
1859
+ if (cx > maxX) maxX = cx;
1860
+ if (cy < minY) minY = cy;
1861
+ if (cy > maxY) maxY = cy;
1862
+ }
1863
+ return maxX - minX > maxY - minY;
1864
+ }
1865
+ function labelBand(boxes, tracks = [], pad = AXIS_PIN_BAND_PAD_PX, trackReach = AXIS_PIN_TRACK_REACH_PX) {
1866
+ if (!boxes || boxes.length === 0) return null;
1867
+ let top = Infinity, bottom = -Infinity;
1868
+ for (const b of boxes) {
1869
+ if (!isFinite(b.top) || !isFinite(b.bottom)) continue;
1870
+ if (b.top < top) top = b.top;
1871
+ if (b.bottom > bottom) bottom = b.bottom;
1872
+ }
1873
+ if (!isFinite(top) || !isFinite(bottom) || bottom <= top) return null;
1874
+ for (const t of tracks || []) {
1875
+ if (!isFinite(t.top) || !isFinite(t.bottom)) continue;
1876
+ const gap = t.bottom < top ? top - t.bottom : t.top > bottom ? t.top - bottom : 0;
1877
+ if (gap > trackReach) continue;
1878
+ if (t.top < top) top = t.top;
1879
+ if (t.bottom > bottom) bottom = t.bottom;
1880
+ }
1881
+ return { top: top - pad, bottom: bottom + pad };
1882
+ }
1883
+ function planAxisPin(candidates, viewportH, minLabels = AXIS_PIN_MIN_LABELS, maxFraction = AXIS_PIN_MAX_BAND_FRACTION) {
1884
+ const none = (reason) => ({ pin: false, index: -1, band: null, reason });
1885
+ if (!isFinite(viewportH) || viewportH <= 0) return none("unmeasurable");
1886
+ let sawAxis = false, best = null;
1887
+ for (const c of candidates || []) {
1888
+ if (!c || !c.labels || c.labels.length < minLabels) continue;
1889
+ sawAxis = true;
1890
+ if (!isHorizontalLabelRow(c.labels)) continue;
1891
+ if (!best || c.labels.length > best.labels.length) best = c;
1892
+ }
1893
+ if (!best) return none(sawAxis ? "vertical-only" : "no-axis");
1894
+ const band = labelBand(best.labels, best.tracks || []);
1895
+ if (!band) return none("unmeasurable");
1896
+ if (band.bottom - band.top > viewportH * maxFraction) return none("band-too-tall");
1897
+ return { pin: true, index: best.index, band, reason: "pinned" };
1898
+ }
1899
+ function axisPinPlacement(band, scrollTop, viewportH) {
1900
+ if (!band || !isFinite(band.top) || !isFinite(band.bottom)) return null;
1901
+ if (!isFinite(scrollTop) || !isFinite(viewportH) || viewportH <= 0) return null;
1902
+ if (band.top < scrollTop) return "top";
1903
+ if (band.bottom > scrollTop + viewportH) return "bottom";
1904
+ return null;
1905
+ }
1906
+
1907
+ // src/fitDom.ts
1908
+ var FIT_WALK_CAP = 8e3;
1909
+ function ctmScaleOf(m) {
1910
+ if (!m) return 0;
1911
+ const s = Math.hypot(m.a, m.b);
1912
+ return isFinite(s) && s > 0 ? s : 0;
1913
+ }
1914
+ function svgInkReach(svg) {
1915
+ try {
1916
+ const box2 = svg.getBoundingClientRect();
1917
+ if (!(box2.width > 0) || !(box2.height > 0)) return null;
1918
+ const els = svg.querySelectorAll(FIT_CONTENT_SELECTOR);
1919
+ const clipOf = clipWindowReader(svg);
1920
+ let right = -Infinity, bottom = -Infinity, seen = 0;
1921
+ for (let i = 0; i < els.length && seen < FIT_WALK_CAP; i++) {
1922
+ const r = els[i].getBoundingClientRect();
1923
+ if (r.width <= 0 && r.height <= 0) continue;
1924
+ if (isPhantomBox(r.width, r.height, box2.width, box2.height)) continue;
1925
+ let rr = r.right, rb = r.bottom;
1926
+ const clip = clipOf(els[i]);
1927
+ if (clip) {
1928
+ if (r.left >= clip.right || r.top >= clip.bottom) continue;
1929
+ rr = Math.min(rr, clip.right);
1930
+ rb = Math.min(rb, clip.bottom);
1931
+ }
1932
+ seen++;
1933
+ if (rr > right) right = rr;
1934
+ if (rb > bottom) bottom = rb;
1935
+ }
1936
+ if (seen === 0) return null;
1937
+ return { right: right - box2.left, bottom: bottom - box2.top };
1938
+ } catch {
1939
+ return null;
1940
+ }
1941
+ }
1942
+ function clipWindowOf(el) {
1943
+ try {
1944
+ const m = /url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(el.getAttribute("clip-path") || "");
1945
+ const cp = m ? (el.closest("svg") ?? el.ownerDocument).querySelector(`clipPath[id="${m[1].replace(/"/g, '\\"')}"]`) : null;
1946
+ const ctm = cp ? el.getScreenCTM?.() : null;
1947
+ if (!cp || !ctm || cp.getAttribute("clipPathUnits") === "objectBoundingBox") return null;
1948
+ let l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
1949
+ for (let i = 0; i < cp.children.length; i++) {
1950
+ const bb = cp.children[i].getBBox?.();
1951
+ if (!bb || !(bb.width > 0) || !(bb.height > 0)) continue;
1952
+ for (const [x, y] of [[bb.x, bb.y], [bb.x + bb.width, bb.y], [bb.x, bb.y + bb.height], [bb.x + bb.width, bb.y + bb.height]]) {
1953
+ const sx = ctm.a * x + ctm.c * y + ctm.e, sy = ctm.b * x + ctm.d * y + ctm.f;
1954
+ l = Math.min(l, sx);
1955
+ t = Math.min(t, sy);
1956
+ r = Math.max(r, sx);
1957
+ b = Math.max(b, sy);
1958
+ }
1959
+ }
1960
+ return isFinite(r) && isFinite(b) ? { left: l, top: t, right: r, bottom: b } : null;
1961
+ } catch {
1962
+ return null;
1963
+ }
1964
+ }
1965
+ function clipWindowReader(svg) {
1966
+ const own = /* @__PURE__ */ new Map();
1967
+ const windowOf = (el) => {
1968
+ if (!own.has(el)) own.set(el, clipWindowOf(el));
1969
+ return own.get(el);
1970
+ };
1971
+ return (el) => {
1972
+ let acc = null;
1973
+ for (let a = el; a && a !== svg; a = a.parentElement) {
1974
+ const w = windowOf(a);
1975
+ if (w) acc = acc ? { right: Math.min(acc.right, w.right), bottom: Math.min(acc.bottom, w.bottom) } : w;
1976
+ }
1977
+ return acc;
1978
+ };
1979
+ }
1980
+ function measureContainerBoxes(c, inkOf) {
1981
+ const boxes = [];
1982
+ if (!c || !c.children) return boxes;
1983
+ const view = c.ownerDocument && c.ownerDocument.defaultView || globalThis;
1984
+ const cRect = c.getBoundingClientRect();
1985
+ for (let i = 0; i < c.children.length; i++) {
1986
+ const el = c.children[i];
1987
+ let floating = false;
1988
+ try {
1989
+ const cs = view.getComputedStyle(el);
1990
+ if (cs.display === "none") continue;
1991
+ floating = cs.position === "absolute" || cs.position === "fixed";
1992
+ } catch {
1993
+ }
1994
+ const r = el.getBoundingClientRect();
1995
+ let right = r.right - cRect.left - c.clientLeft + c.scrollLeft;
1996
+ let bottom = r.bottom - cRect.top - c.clientTop + c.scrollTop;
1997
+ if (inkOf) {
1998
+ try {
1999
+ const ink = inkOf(el);
2000
+ if (ink) {
2001
+ const originX = r.left - cRect.left - c.clientLeft + c.scrollLeft;
2002
+ const originY = r.top - cRect.top - c.clientTop + c.scrollTop;
2003
+ if (isFinite(ink.right)) right = Math.max(right, originX + ink.right);
2004
+ if (isFinite(ink.bottom)) bottom = Math.max(bottom, originY + ink.bottom);
2005
+ }
2006
+ } catch {
2007
+ }
2008
+ }
2009
+ boxes.push({ right, bottom, floating });
2010
+ }
2011
+ return boxes;
2012
+ }
2013
+ function fitReadingFor(lane, c, slackPx = SCROLL_SLACK_PX, inkOf) {
2014
+ if (!c) return null;
2015
+ const viewW = c.clientWidth, viewH = c.clientHeight;
2016
+ if (!(viewW > 0) || !(viewH > 0)) return null;
2017
+ const boxes = measureContainerBoxes(c, inkOf);
2018
+ const extent = contentExtentOf(boxes);
2019
+ const contentW = extent.source === "none" ? c.scrollWidth : extent.w;
2020
+ const contentH = extent.source === "none" ? c.scrollHeight : extent.h;
2021
+ if (!isFinite(contentW) || !isFinite(contentH)) return null;
2022
+ return {
2023
+ lane,
2024
+ contentW: Math.round(contentW),
2025
+ contentH: Math.round(contentH),
2026
+ viewW,
2027
+ viewH,
2028
+ overflowsX: needsScroll(contentW, viewW, slackPx),
2029
+ overflowsY: needsScroll(contentH, viewH, slackPx),
2030
+ extentSource: extent.source,
2031
+ boxes: boxes.length,
2032
+ floating: boxes.filter((b) => b.floating).length,
2033
+ scrollW: c.scrollWidth,
2034
+ scrollH: c.scrollHeight
2035
+ };
2036
+ }
2037
+ var noPin = (reason) => ({ pinned: false, reason, bandPx: 0, axisAt: "", labels: 0, carried: 0, edge: null });
2038
+ var CONTAINER_SLOT_AXIS_PIN = "__bicAxisPin";
2039
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2040
+ var AXIS_PIN_MAX_CARRIED = 12;
2041
+ function unpinScrolledAxis(container) {
2042
+ if (!container) return;
2043
+ const st = container[CONTAINER_SLOT_AXIS_PIN];
2044
+ if (!st) return;
2045
+ try {
2046
+ container.removeEventListener("scroll", st.onScroll);
2047
+ } catch {
2048
+ }
2049
+ try {
2050
+ if (st.overlay.parentNode) st.overlay.parentNode.removeChild(st.overlay);
2051
+ } catch {
2052
+ }
2053
+ try {
2054
+ delete container[CONTAINER_SLOT_AXIS_PIN];
2055
+ } catch {
2056
+ }
2057
+ }
2058
+ function textHidden(t, view) {
2059
+ if (t.getAttribute("display") === "none" || t.getAttribute("visibility") === "hidden") return true;
2060
+ try {
2061
+ const cs = view.getComputedStyle(t);
2062
+ if (cs && (cs.display === "none" || cs.visibility === "hidden")) return true;
2063
+ } catch {
2064
+ }
2065
+ return false;
2066
+ }
2067
+ function backdropFill(svg, container, view, svgBox) {
2068
+ try {
2069
+ const rects = svg.querySelectorAll("rect");
2070
+ for (let i = 0; i < rects.length && i < 6; i++) {
2071
+ const r = rects[i];
2072
+ if (r.closest(".d3-mark")) continue;
2073
+ const b = r.getBoundingClientRect();
2074
+ if (b.width >= svgBox.width * 0.9 && b.height >= svgBox.height * 0.9) {
2075
+ let fill = "";
2076
+ try {
2077
+ fill = view.getComputedStyle(r).fill || "";
2078
+ } catch {
2079
+ }
2080
+ if (!fill || fill === "none") fill = r.getAttribute("fill") || "";
2081
+ if (fill && fill !== "none" && fill !== "transparent") return fill;
2082
+ break;
2083
+ }
2084
+ }
2085
+ } catch {
2086
+ }
2087
+ try {
2088
+ let el = container;
2089
+ for (let hops = 0; el && hops < 12; hops++, el = el.parentElement) {
2090
+ const bg = view.getComputedStyle(el).backgroundColor;
2091
+ if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
2092
+ }
2093
+ } catch {
2094
+ }
2095
+ return "#ffffff";
2096
+ }
2097
+ function cloneWithChain(el, svg, root) {
2098
+ const chain = [];
2099
+ let n = el.parentElement;
2100
+ while (n && n !== svg) {
2101
+ chain.unshift(n);
2102
+ n = n.parentElement;
2103
+ }
2104
+ let parent = root;
2105
+ for (const anc of chain) {
2106
+ const c = anc.cloneNode(false);
2107
+ parent.appendChild(c);
2108
+ parent = c;
2109
+ }
2110
+ parent.appendChild(el.cloneNode(true));
2111
+ const ids = root.querySelectorAll("[id]");
2112
+ for (let i = 0; i < ids.length; i++) ids[i].removeAttribute("id");
2113
+ }
2114
+ function pinScrolledAxis(container, svg) {
2115
+ unpinScrolledAxis(container);
2116
+ if (!container) return noPin("no-container");
2117
+ if (!svg) return noPin("no-svg");
2118
+ try {
2119
+ const doc = svg.ownerDocument;
2120
+ const view = doc && doc.defaultView || globalThis;
2121
+ const svgBox = svg.getBoundingClientRect();
2122
+ if (!(svgBox.width > 0) || !(svgBox.height > 0)) return noPin("unmeasurable");
2123
+ const m = typeof svg.getScreenCTM === "function" ? svg.getScreenCTM() : null;
2124
+ if (!m || !(m.a > 0) || !(m.d > 0) || !isFinite(m.e) || !isFinite(m.f)) return noPin("no-ctm");
2125
+ const viewportH = container.clientHeight;
2126
+ if (!(viewportH > 0)) return noPin("unmeasurable");
2127
+ const rel = (r) => ({ left: r.left - svgBox.left, top: r.top - svgBox.top, right: r.right - svgBox.left, bottom: r.bottom - svgBox.top });
2128
+ const ticks = svg.querySelectorAll(".tick");
2129
+ const byAxis = /* @__PURE__ */ new Map();
2130
+ for (let i = 0; i < ticks.length; i++) {
2131
+ const t = ticks[i];
2132
+ if (t.closest("svg") !== svg) continue;
2133
+ const p = t.parentElement;
2134
+ if (!p) continue;
2135
+ const list = byAxis.get(p);
2136
+ if (list) list.push(t);
2137
+ else byAxis.set(p, [t]);
2138
+ }
2139
+ const axes = [];
2140
+ const cands = [];
2141
+ byAxis.forEach((group, axis2) => {
2142
+ const labels = [];
2143
+ for (const tick of group) {
2144
+ const txt = tick.querySelector("text");
2145
+ if (!txt || textHidden(txt, view)) continue;
2146
+ const r = txt.getBoundingClientRect();
2147
+ if (!(r.width > 0) || !(r.height > 0)) continue;
2148
+ labels.push(rel(r));
2149
+ }
2150
+ const tracks = [];
2151
+ for (let i = 0; i < axis2.children.length; i++) {
2152
+ const ch = axis2.children[i];
2153
+ if (ch.tagName.toLowerCase() !== "path" || !(ch.getAttribute("class") || "").split(/\s+/).includes("domain")) continue;
2154
+ const r = ch.getBoundingClientRect();
2155
+ if (r.width > 0 && r.height <= 4) tracks.push(rel(r));
2156
+ }
2157
+ cands.push({ index: axes.length, labels, tracks });
2158
+ axes.push(axis2);
2159
+ });
2160
+ const plan = planAxisPin(cands, viewportH);
2161
+ if (!plan.pin || !plan.band) return noPin(plan.reason);
2162
+ const axis = axes[plan.index];
2163
+ const band = { top: Math.max(0, plan.band.top), bottom: Math.min(svgBox.height, plan.band.bottom) };
2164
+ const bandPx = band.bottom - band.top;
2165
+ if (!(bandPx > 0)) return noPin("unmeasurable");
2166
+ const carried = [];
2167
+ const texts = svg.querySelectorAll("text");
2168
+ for (let i = 0; i < texts.length && carried.length < AXIS_PIN_MAX_CARRIED; i++) {
2169
+ const t = texts[i];
2170
+ if (t.closest("svg") !== svg) continue;
2171
+ if (t.closest(".tick") || t.closest(".d3-mark") || t.closest(".d3-legend-mark")) continue;
2172
+ if (axis.contains(t) || textHidden(t, view)) continue;
2173
+ const r = rel(t.getBoundingClientRect());
2174
+ if (!(r.right > r.left) || !(r.bottom > r.top)) continue;
2175
+ if (r.top >= band.top && r.bottom <= band.bottom) carried.push(t);
2176
+ }
2177
+ const pinSvg = doc.createElementNS(SVG_NS2, "svg");
2178
+ for (const a of ["font-family", "font-size", "font-weight", "font", "fill", "color", "text-rendering"]) {
2179
+ const v = svg.getAttribute(a);
2180
+ if (v) pinSvg.setAttribute(a, v);
2181
+ }
2182
+ const styles = svg.querySelectorAll("style");
2183
+ for (let i = 0; i < styles.length; i++) pinSvg.appendChild(styles[i].cloneNode(true));
2184
+ const vbX = (svgBox.left - m.e) / m.a, vbY = (svgBox.top + band.top - m.f) / m.d;
2185
+ const vbW = svgBox.width / m.a, vbH = bandPx / m.d;
2186
+ pinSvg.setAttribute("viewBox", `${vbX} ${vbY} ${vbW} ${vbH}`);
2187
+ pinSvg.setAttribute("preserveAspectRatio", "none");
2188
+ pinSvg.setAttribute("width", String(svgBox.width));
2189
+ pinSvg.setAttribute("height", String(bandPx));
2190
+ pinSvg.setAttribute("aria-hidden", "true");
2191
+ pinSvg.style.display = "block";
2192
+ pinSvg.style.pointerEvents = "none";
2193
+ const back = doc.createElementNS(SVG_NS2, "rect");
2194
+ back.setAttribute("x", String(vbX));
2195
+ back.setAttribute("y", String(vbY));
2196
+ back.setAttribute("width", String(vbW));
2197
+ back.setAttribute("height", String(vbH));
2198
+ back.setAttribute("fill", backdropFill(svg, container, view, svgBox));
2199
+ pinSvg.appendChild(back);
2200
+ cloneWithChain(axis, svg, pinSvg);
2201
+ for (const t of carried) cloneWithChain(t, svg, pinSvg);
2202
+ const overlay = doc.createElement("div");
2203
+ overlay.setAttribute("data-bic-axis-pin", "1");
2204
+ const o = overlay.style;
2205
+ o.position = "absolute";
2206
+ o.margin = "0";
2207
+ o.padding = "0";
2208
+ o.border = "0";
2209
+ o.width = `${svgBox.width}px`;
2210
+ o.height = `${bandPx}px`;
2211
+ o.overflow = "hidden";
2212
+ o.pointerEvents = "none";
2213
+ o.zIndex = "3";
2214
+ o.display = "none";
2215
+ overlay.appendChild(pinSvg);
2216
+ try {
2217
+ const cs = view.getComputedStyle(container);
2218
+ const pos = cs && cs.position || container.style.position;
2219
+ if (!pos || pos === "static") container.style.position = "relative";
2220
+ } catch {
2221
+ container.style.position = container.style.position || "relative";
2222
+ }
2223
+ const cRect = container.getBoundingClientRect();
2224
+ const svgLeft = svgBox.left - cRect.left - container.clientLeft + container.scrollLeft;
2225
+ const svgTop = svgBox.top - cRect.top - container.clientTop + container.scrollTop;
2226
+ const contentBand = { top: svgTop + band.top, bottom: svgTop + band.bottom };
2227
+ const place = () => {
2228
+ const st = container.scrollTop, vh = container.clientHeight;
2229
+ const edge2 = axisPinPlacement(contentBand, st, vh);
2230
+ if (!edge2) {
2231
+ o.display = "none";
2232
+ return null;
2233
+ }
2234
+ o.display = "block";
2235
+ o.left = `${svgLeft}px`;
2236
+ o.top = `${edge2 === "top" ? st : st + vh - bandPx}px`;
2237
+ return edge2;
2238
+ };
2239
+ const onScroll = () => {
2240
+ try {
2241
+ if (!overlay.isConnected) {
2242
+ unpinScrolledAxis(container);
2243
+ return;
2244
+ }
2245
+ place();
2246
+ } catch {
2247
+ }
2248
+ };
2249
+ container.appendChild(overlay);
2250
+ container.addEventListener("scroll", onScroll, { passive: true });
2251
+ container[CONTAINER_SLOT_AXIS_PIN] = { overlay, onScroll };
2252
+ const edge = place();
2253
+ const mid = (band.top + band.bottom) / 2 / svgBox.height;
2254
+ return {
2255
+ pinned: true,
2256
+ reason: "pinned",
2257
+ bandPx: Math.round(bandPx),
2258
+ axisAt: mid < 1 / 3 ? "top" : mid > 2 / 3 ? "bottom" : "middle",
2259
+ labels: cands[plan.index].labels.length,
2260
+ carried: carried.length,
2261
+ edge
2262
+ };
2263
+ } catch {
2264
+ unpinScrolledAxis(container);
2265
+ return noPin("threw");
2266
+ }
2267
+ }
2268
+ function fitRenderedChart(container, opts = {}) {
2269
+ const slackPx = opts.slackPx ?? SCROLL_SLACK_PX;
2270
+ const result = {
2271
+ grew: false,
2272
+ growFrom: 0,
2273
+ growTo: 0,
2274
+ growReason: "no-container",
2275
+ overflowX: "hidden",
2276
+ overflowY: "hidden",
2277
+ reading: null,
2278
+ axisPin: noPin("no-container")
2279
+ };
2280
+ if (!container) return result;
2281
+ try {
2282
+ let svg = container.querySelector(`svg[${SNAPSHOT_SVG_ATTR}]`);
2283
+ for (let i = 0; i < container.children.length && !svg; i++) {
2284
+ const el = container.children[i];
2285
+ if (el.tagName && el.tagName.toLowerCase() === "svg") svg = el;
2286
+ }
2287
+ if (!svg) svg = container.querySelector("svg");
2288
+ let inkCache;
2289
+ const svgInk = (el) => {
2290
+ if (inkCache === void 0) inkCache = svgInkReach(el);
2291
+ return inkCache;
2292
+ };
2293
+ const inkOf = (el) => el && el.tagName && el.tagName.toLowerCase() === "svg" ? svgInk(el) : null;
2294
+ if (svg && opts.grow !== false) {
2295
+ try {
2296
+ const ink = svgInk(svg);
2297
+ const elH = svg.getBoundingClientRect().height;
2298
+ const vb = svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal.height : 0;
2299
+ const ctm = typeof svg.getScreenCTM === "function" ? svg.getScreenCTM() : null;
2300
+ const plan = planFrameGrow(
2301
+ ink ? ink.bottom : NaN,
2302
+ elH,
2303
+ vb,
2304
+ ctmScaleOf(ctm),
2305
+ slackPx
2306
+ );
2307
+ result.growFrom = Math.round(elH);
2308
+ result.growTo = plan.heightPx;
2309
+ result.growReason = plan.reason;
2310
+ if (plan.grow) {
2311
+ svg.setAttribute("height", String(plan.heightPx));
2312
+ if (vb > 0) {
2313
+ const b = svg.viewBox.baseVal;
2314
+ svg.setAttribute("viewBox", `${b.x} ${b.y} ${b.width} ${plan.viewBoxH}`);
2315
+ }
2316
+ result.grew = true;
2317
+ }
2318
+ } catch {
2319
+ result.growReason = "grow-threw";
2320
+ }
2321
+ } else if (!svg) {
2322
+ result.growReason = "no-svg";
2323
+ } else {
2324
+ result.growReason = "grow-disabled";
2325
+ }
2326
+ const reading = fitReadingFor(opts.lane ?? "chart", container, slackPx, inkOf);
2327
+ result.reading = reading;
2328
+ const boxes = measureContainerBoxes(container, inkOf);
2329
+ const extent = contentExtentOf(boxes);
2330
+ const svgBox = svg ? svg.getBoundingClientRect() : null;
2331
+ const contentW = extent.source === "none" ? Math.max(container.scrollWidth, svgBox ? svgBox.width : 0) : extent.w;
2332
+ const contentH = extent.source === "none" ? Math.max(container.scrollHeight, svgBox ? svgBox.height : 0) : extent.h;
2333
+ const fit = scrollFitFor(contentW, contentH, container.clientWidth, container.clientHeight, slackPx);
2334
+ result.overflowX = fit.overflowX;
2335
+ result.overflowY = fit.overflowY;
2336
+ if (opts.applyOverflow !== false) {
2337
+ container.style.overflowX = fit.overflowX;
2338
+ container.style.overflowY = fit.overflowY;
2339
+ if (fit.overflowY !== "auto") {
2340
+ unpinScrolledAxis(container);
2341
+ result.axisPin = noPin("not-scrolling");
2342
+ } else if (opts.pinAxis === false) {
2343
+ unpinScrolledAxis(container);
2344
+ result.axisPin = noPin("disabled");
2345
+ } else result.axisPin = pinScrolledAxis(container, svg);
2346
+ } else {
2347
+ unpinScrolledAxis(container);
2348
+ result.axisPin = noPin("overflow-not-applied");
2349
+ }
2350
+ } catch {
2351
+ }
2352
+ return result;
2353
+ }
2354
+
2355
+ // src/labelContrastDom.ts
2356
+ var LABEL_CONTRAST_DONE_ATTR = "data-lch-contrast";
2357
+ var LABEL_CONTRAST_CAP = 2e3;
2358
+ var EMPTY5 = {
2359
+ rects: 0,
2360
+ scanned: 0,
2361
+ fixed: 0,
2362
+ pillsBoosted: 0,
2363
+ offFill: 0,
2364
+ pageMajority: 0,
2365
+ paintedOver: 0,
2366
+ straddlePills: 0,
2367
+ pillsExtended: 0,
2368
+ raised: 0
2369
+ };
2370
+ function backedSamples(el, pts) {
2371
+ try {
2372
+ const ge2 = el;
2373
+ if (typeof ge2.isPointInFill !== "function" || typeof ge2.getScreenCTM !== "function") return null;
2374
+ const m = ge2.getScreenCTM();
2375
+ const svg = ge2.ownerSVGElement;
2376
+ if (!m || !svg || typeof svg.createSVGPoint !== "function") return null;
2377
+ if (pts.length === 0) return null;
2378
+ const inv = m.inverse();
2379
+ const hit = [];
2380
+ for (let i = 0; i < pts.length; i++) {
2381
+ const p = svg.createSVGPoint();
2382
+ p.x = pts[i].x;
2383
+ p.y = pts[i].y;
2384
+ hit.push(ge2.isPointInFill(p.matrixTransform(inv)));
2385
+ }
2386
+ return hit;
2387
+ } catch {
2388
+ return null;
2389
+ }
2390
+ }
2391
+ var DOC_POSITION_FOLLOWING = 4;
2392
+ function paintRoot(el, container) {
2393
+ let root = null;
2394
+ for (let n = el.parentElement; n; n = n.parentElement) {
2395
+ if (String(n.tagName || "").toLowerCase() === "svg") root = n;
2396
+ if (n === container) break;
2397
+ }
2398
+ return root;
2399
+ }
2400
+ function drawnBeneath(shape, text2, textRoot) {
2401
+ if (!shape.root || shape.root !== textRoot) return true;
2402
+ if (typeof shape.el.compareDocumentPosition !== "function") return true;
2403
+ return (shape.el.compareDocumentPosition(text2) & DOC_POSITION_FOLLOWING) !== 0;
2404
+ }
2405
+ function sampleBackgrounds(shapes, pts, pageRGB, effAlpha) {
2406
+ if (pts.length === 0 || shapes.length === 0) return null;
2407
+ const ordered = shapes.slice().sort((a, b) => a.ord - b.ord);
2408
+ const masks = [];
2409
+ for (const mk of ordered) {
2410
+ const m = backedSamples(mk.el, pts);
2411
+ if (m === null) return null;
2412
+ masks.push(m);
2413
+ }
2414
+ return pts.map((_, i) => {
2415
+ let bg = [pageRGB[0], pageRGB[1], pageRGB[2]];
2416
+ for (let k = 0; k < ordered.length; k++) {
2417
+ if (!masks[k][i]) continue;
2418
+ const a = effAlpha(ordered[k]);
2419
+ if (a < PILL_MIN_ALPHA) continue;
2420
+ const c = toRGBA(ordered[k].fill);
2421
+ if (c) bg = compositeOver([c[0], c[1], c[2], a], bg);
2422
+ }
2423
+ return bg;
2424
+ });
2425
+ }
2426
+ function extendPillOver(pill, tr) {
2427
+ try {
2428
+ if (String(pill.tagName || "").toLowerCase() !== "rect") return false;
2429
+ const ge2 = pill;
2430
+ if (typeof ge2.getScreenCTM !== "function") return false;
2431
+ const m = ge2.getScreenCTM();
2432
+ if (!m || Math.abs(m.b) > 1e-6 || Math.abs(m.c) > 1e-6 || !(m.a > 0) || !(m.d > 0)) return false;
2433
+ const inv = m.inverse();
2434
+ const toUser = (x2, y2) => ({ x: inv.a * x2 + inv.c * y2 + inv.e, y: inv.b * x2 + inv.d * y2 + inv.f });
2435
+ const a = toUser(tr.left, tr.top), b = toUser(tr.right, tr.bottom);
2436
+ const num2 = (k) => {
2437
+ const v = parseFloat(pill.getAttribute(k) || "");
2438
+ return isFinite(v) ? v : NaN;
2439
+ };
2440
+ const x = num2("x"), y = num2("y"), w = num2("width"), h = num2("height");
2441
+ if (![x, y, w, h].every((v) => isFinite(v)) || !(w > 0) || !(h > 0)) return false;
2442
+ const pad = 1 / m.a;
2443
+ const x0 = Math.min(x, Math.min(a.x, b.x) - pad), x1 = Math.max(x + w, Math.max(a.x, b.x) + pad);
2444
+ const y0 = Math.min(y, Math.min(a.y, b.y) - pad), y1 = Math.max(y + h, Math.max(a.y, b.y) + pad);
2445
+ if (x0 >= x - 0.01 && x1 <= x + w + 0.01 && y0 >= y - 0.01 && y1 <= y + h + 0.01) return false;
2446
+ pill.setAttribute("x", String(+x0.toFixed(2)));
2447
+ pill.setAttribute("y", String(+y0.toFixed(2)));
2448
+ pill.setAttribute("width", String(+(x1 - x0).toFixed(2)));
2449
+ pill.setAttribute("height", String(+(y1 - y0).toFixed(2)));
2450
+ return true;
2451
+ } catch {
2452
+ return false;
2453
+ }
2454
+ }
2455
+ function raiseOverOccluders(tx, occluders, tr, effAlpha) {
2456
+ try {
2457
+ const parent = tx.parentNode;
2458
+ if (!parent || typeof parent.contains !== "function") return false;
2459
+ if (!occluders.every((o) => parent.contains(o.el))) return false;
2460
+ const pts = straddleSampleGrid(tr);
2461
+ if (pts.length === 0) return false;
2462
+ const covered = pts.map(() => false);
2463
+ let opaque = 0;
2464
+ for (const o of occluders) {
2465
+ if (effAlpha(o) < PILL_OPAQUE_ALPHA) continue;
2466
+ const m = backedSamples(o.el, pts);
2467
+ if (m === null) return false;
2468
+ opaque++;
2469
+ for (let i = 0; i < m.length; i++) if (m[i]) covered[i] = true;
2470
+ }
2471
+ const n = covered.filter(Boolean).length;
2472
+ if (opaque === 0 || n === 0 || n === pts.length) return false;
2473
+ parent.appendChild(tx);
2474
+ return true;
2475
+ } catch {
2476
+ return false;
2477
+ }
2478
+ }
2479
+ var LABEL_PILL_CLASS = "lch-label-pill";
2480
+ function addPill(tx, fill) {
2481
+ try {
2482
+ const parent = tx.parentNode;
2483
+ if (!parent || typeof tx.getBBox !== "function") return null;
2484
+ const bb = tx.getBBox();
2485
+ if (!bb || !(bb.width > 0) || !(bb.height > 0)) return null;
2486
+ const doc = tx.ownerDocument;
2487
+ if (!doc) return null;
2488
+ const r = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
2489
+ const px = Math.max(2, bb.height * 0.2), py = 1;
2490
+ r.setAttribute("x", String(+(bb.x - px).toFixed(2)));
2491
+ r.setAttribute("y", String(+(bb.y - py).toFixed(2)));
2492
+ r.setAttribute("width", String(+(bb.width + 2 * px).toFixed(2)));
2493
+ r.setAttribute("height", String(+(bb.height + 2 * py).toFixed(2)));
2494
+ r.setAttribute("rx", "2");
2495
+ r.setAttribute("fill", fill);
2496
+ r.setAttribute("fill-opacity", String(STRADDLE_PILL_ALPHA));
2497
+ r.setAttribute("pointer-events", "none");
2498
+ r.setAttribute("class", LABEL_PILL_CLASS);
2499
+ r.setAttribute(LABEL_CONTRAST_DONE_ATTR, "1");
2500
+ const tf = tx.getAttribute("transform");
2501
+ if (tf) r.setAttribute("transform", tf);
2502
+ parent.insertBefore(r, tx);
2503
+ return r;
2504
+ } catch {
2505
+ return null;
2506
+ }
2507
+ }
2508
+ function applyLabelContrast(container, opts = {}) {
2509
+ const report = { ...EMPTY5 };
2510
+ if (!container) {
2511
+ report.skipped = "no-container";
2512
+ return report;
2513
+ }
2514
+ const CAP = opts.cap ?? LABEL_CONTRAST_CAP;
2515
+ const pageBg = opts.pageBg || "#ffffff";
2516
+ try {
2517
+ const rects = [];
2518
+ let ord = 0;
2519
+ const pushShape = (el) => {
2520
+ const tag = el.tagName.toLowerCase();
2521
+ if (tag !== "rect" && tag !== "path") return;
2522
+ const r = el.getBoundingClientRect();
2523
+ if (r.width <= 0 || r.height <= 0) return;
2524
+ let fill = el.getAttribute("fill") || "";
2525
+ const win2 = container.ownerDocument?.defaultView;
2526
+ if ((!fill || fill === "none") && win2 && typeof win2.getComputedStyle === "function") {
2527
+ fill = win2.getComputedStyle(el).fill || "";
2528
+ }
2529
+ if (!fill || fill === "none") return;
2530
+ const opAttr = el.getAttribute("fill-opacity");
2531
+ const op = opAttr === null ? 1 : parseFloat(opAttr);
2532
+ rects.push({
2533
+ r,
2534
+ fill,
2535
+ op: isFinite(op) ? op : 1,
2536
+ area: r.width * r.height,
2537
+ el,
2538
+ ord: ord++,
2539
+ root: paintRoot(el, container)
2540
+ });
2541
+ };
2542
+ const shapeEls = container.querySelectorAll("rect, path");
2543
+ if (shapeEls.length === 0) {
2544
+ report.skipped = "no-shapes";
2545
+ return report;
2546
+ }
2547
+ if (shapeEls.length > CAP) {
2548
+ report.skipped = "too-many-shapes";
2549
+ return report;
2550
+ }
2551
+ for (let i = 0; i < shapeEls.length; i++) pushShape(shapeEls[i]);
2552
+ if (rects.length === 0) {
2553
+ report.skipped = "no-shapes";
2554
+ return report;
2555
+ }
2556
+ report.rects = rects.length;
2557
+ const pageRGBA = toRGBA(pageBg) || [255, 255, 255, 1];
2558
+ const pageRGB = [pageRGBA[0], pageRGBA[1], pageRGBA[2]];
2559
+ const effAlpha = (mk) => {
2560
+ const c = toRGBA(mk.fill);
2561
+ return (c ? c[3] : 1) * mk.op;
2562
+ };
2563
+ const texts = container.querySelectorAll("text");
2564
+ if (texts.length > CAP) {
2565
+ report.skipped = "too-many-texts";
2566
+ return report;
2567
+ }
2568
+ const win = container.ownerDocument?.defaultView;
2569
+ for (let t = 0; t < texts.length; t++) {
2570
+ const tx = texts[t];
2571
+ if (tx.getAttribute(LABEL_CONTRAST_DONE_ATTR) === "1") continue;
2572
+ const tr = tx.getBoundingClientRect();
2573
+ if (tr.width <= 0 || tr.height <= 0) continue;
2574
+ const ovArea = (r) => Math.max(0, Math.min(r.right, tr.right) - Math.max(r.left, tr.left)) * Math.max(0, Math.min(r.bottom, tr.bottom) - Math.max(r.top, tr.top));
2575
+ const glyphArea = tr.width * tr.height;
2576
+ const txRoot = paintRoot(tx, container);
2577
+ let over = 0;
2578
+ const occluders = [];
2579
+ const boxed = rects.map((mk) => ({ mk, ov: ovArea(mk.r) })).filter((x) => {
2580
+ if (!(x.ov > 0)) return false;
2581
+ if (drawnBeneath(x.mk, tx, txRoot)) return true;
2582
+ if (effAlpha(x.mk) >= PILL_MIN_ALPHA) {
2583
+ over++;
2584
+ occluders.push(x.mk);
2585
+ }
2586
+ return false;
2587
+ });
2588
+ const raised = occluders.length > 0 && raiseOverOccluders(tx, occluders, tr, effAlpha);
2589
+ if (raised) {
2590
+ report.raised++;
2591
+ tx.setAttribute(LABEL_CONTRAST_DONE_ATTR, "1");
2592
+ for (const mk of occluders) boxed.push({ mk, ov: ovArea(mk.r) });
2593
+ over = 0;
2594
+ }
2595
+ if (over > 0) report.paintedOver++;
2596
+ const pts = glyphSampleGrid({ left: tr.left, top: tr.top, width: tr.width, height: tr.height });
2597
+ const painted = pts.map(() => false);
2598
+ let unmeasured = 0;
2599
+ const under = boxed.map((x) => {
2600
+ const mask = backedSamples(x.mk.el, pts);
2601
+ if (mask === null) {
2602
+ unmeasured++;
2603
+ return x;
2604
+ }
2605
+ let inside = 0;
2606
+ const paints = effAlpha(x.mk) >= PILL_MIN_ALPHA;
2607
+ for (let i = 0; i < mask.length; i++) {
2608
+ if (!mask[i]) continue;
2609
+ inside++;
2610
+ if (paints) painted[i] = true;
2611
+ }
2612
+ return { mk: x.mk, ov: glyphArea * (inside / mask.length) };
2613
+ }).filter((x) => x.ov > 0);
2614
+ if (under.length === 0 && boxed.length > 0) report.offFill++;
2615
+ if (under.length === 0) continue;
2616
+ let raisedOnPage = false;
2617
+ if (unmeasured === 0 && pts.length > 0) {
2618
+ let held = 0;
2619
+ for (let i = 0; i < painted.length; i++) if (painted[i]) held++;
2620
+ if (!backingHoldsGlyph(glyphArea * (held / pts.length), glyphArea)) {
2621
+ if (!raised) {
2622
+ report.pageMajority++;
2623
+ continue;
2624
+ }
2625
+ raisedOnPage = true;
2626
+ }
2627
+ }
2628
+ report.scanned++;
2629
+ const opaqueUnder = under.filter((x) => effAlpha(x.mk) >= PILL_OPAQUE_ALPHA);
2630
+ const cellEntry = (opaqueUnder.length ? opaqueUnder : under).reduce((a, b) => b.ov > a.ov || b.ov === a.ov && b.mk.ord > a.mk.ord ? b : a);
2631
+ const cell = cellEntry.mk;
2632
+ const pill = under.find((x) => x.mk !== cell && isPillBackdropAlpha(effAlpha(x.mk)) && x.mk.ord > cell.ord && pillBacksGlyph(x.ov, glyphArea))?.mk;
2633
+ const cellRGBA = toRGBA(cell.fill);
2634
+ let bgRGB = cellRGBA ? compositeOver([cellRGBA[0], cellRGBA[1], cellRGBA[2], cellRGBA[3] * cell.op], pageRGB) : pageRGB;
2635
+ if (pill) {
2636
+ const pr = toRGBA(pill.fill);
2637
+ if (pr) {
2638
+ bgRGB = compositeOver([pr[0], pr[1], pr[2], 0.9], bgRGB);
2639
+ pill.el.setAttribute("fill", `rgb(${pr[0]}, ${pr[1]}, ${pr[2]})`);
2640
+ pill.el.setAttribute("fill-opacity", "0.9");
2641
+ report.pillsBoosted++;
2642
+ if (extendPillOver(pill.el, tr)) report.pillsExtended++;
2643
+ }
2644
+ }
2645
+ const cur = tx.getAttribute("fill") || (win && typeof win.getComputedStyle === "function" ? win.getComputedStyle(tx).fill : "");
2646
+ if (!pill && unmeasured === 0) {
2647
+ const bgs = sampleBackgrounds(boxed.map((x) => x.mk), straddleSampleGrid(tr), pageRGB, effAlpha);
2648
+ if (bgs && straddles(bgs)) {
2649
+ if (minContrastOver(cur, bgs) >= STRADDLE_TARGET_CONTRAST) continue;
2650
+ const pillEl = addPill(tx, `rgb(${pageRGB[0]}, ${pageRGB[1]}, ${pageRGB[2]})`);
2651
+ const behind = pillEl ? bgs.map((b) => compositeOver([pageRGB[0], pageRGB[1], pageRGB[2], STRADDLE_PILL_ALPHA], b)) : bgs;
2652
+ if (pillEl) report.straddlePills++;
2653
+ const pick = pickColorOver(cur, behind);
2654
+ if (!pick.keep) {
2655
+ tx.setAttribute("fill", pick.color);
2656
+ report.fixed++;
2657
+ }
2658
+ tx.setAttribute(LABEL_CONTRAST_DONE_ATTR, "1");
2659
+ continue;
2660
+ }
2661
+ }
2662
+ if (raisedOnPage) continue;
2663
+ const normalize = !cellSuppressesNormalize(cell.el.getAttribute("class"));
2664
+ const d = decideLabelColor(cur, `rgb(${bgRGB[0]}, ${bgRGB[1]}, ${bgRGB[2]})`, 1, pageBg, MIN_CONTRAST, normalize);
2665
+ if (d.fix) {
2666
+ tx.setAttribute("fill", d.color);
2667
+ report.fixed++;
2668
+ }
2669
+ if (pill || d.fix) tx.setAttribute(LABEL_CONTRAST_DONE_ATTR, "1");
2670
+ }
2671
+ return report;
2672
+ } catch {
2673
+ report.skipped = "error";
2674
+ return report;
2675
+ }
2676
+ }
2677
+
2678
+ // src/idScope.ts
2679
+ var seq = 0;
2680
+ function newIdScope() {
2681
+ seq += 1;
2682
+ return "h" + seq;
2683
+ }
2684
+ var URL_REF = /url\(\s*(['"]?)#([^'")\s]+)\1\s*\)/g;
2685
+ function scopeChartIds(c, scope) {
2686
+ const r = { renamed: 0, collisions: [] };
2687
+ try {
2688
+ const tag = "--" + scope, doc = c.ownerDocument, map = /* @__PURE__ */ new Map();
2689
+ const refs = [], used = /* @__PURE__ */ new Set();
2690
+ c.querySelectorAll("*").forEach((el) => {
2691
+ for (const a of el.getAttributeNames()) {
2692
+ const v = el.getAttribute(a);
2693
+ const href = (a === "href" || a === "xlink:href") && v[0] === "#";
2694
+ if (!href && v.indexOf("url(") < 0) continue;
2695
+ refs.push([el, a, v]);
2696
+ if (href) used.add(v.slice(1));
2697
+ for (const m of v.matchAll(URL_REF)) used.add(m[2]);
2698
+ }
2699
+ });
2700
+ c.querySelectorAll("[id]").forEach((el) => {
2701
+ const id = el.id;
2702
+ if (!used.has(id) || id.endsWith(tag)) return;
2703
+ const q = id.replace(/["\\]/g, "\\$&");
2704
+ if (doc && Array.from(doc.querySelectorAll(`[id="${q}"],[id^="${q}--h"]`)).some((o) => !c.contains(o))) {
2705
+ r.collisions.push(id);
2706
+ }
2707
+ map.set(id, el.id = id + tag);
2708
+ r.renamed++;
2709
+ });
2710
+ for (const [el, a, v] of refs) {
2711
+ const nv = v[0] === "#" && map.has(v.slice(1)) ? "#" + map.get(v.slice(1)) : v.replace(URL_REF, (m, q, id) => map.has(id) ? `url(${q}#${map.get(id)}${q})` : m);
2712
+ if (nv !== v) el.setAttribute(a, nv);
2713
+ }
2714
+ } catch {
2715
+ }
2716
+ return r;
2717
+ }
2718
+
2719
+ // src/invalidSentinel.ts
2720
+ function isInvalidSentinelError(text2) {
2721
+ if (!text2) return false;
2722
+ return /\bINVALID\s*:/.test(text2);
2723
+ }
2724
+ function invalidSentinelReason(text2, max = 240) {
2725
+ if (!text2) return "";
2726
+ const m = /\bINVALID\s*:\s*([^\r\n]*)/.exec(text2);
2727
+ if (!m) return "";
2728
+ const reason = m[1].trim();
2729
+ const cap = Math.max(0, Math.floor(max));
2730
+ if (reason.length <= cap) return reason;
2731
+ return cap <= 3 ? reason.slice(0, cap) : reason.slice(0, cap - 3).trimEnd() + "...";
2732
+ }
2733
+
2734
+ // src/annotations.ts
2735
+ var ANNOTATION_LAYER_CLASS = "bic-annotations";
2736
+ var ANNOTATION_CLASS = "bic-annotation";
2737
+ var ZOOM_PAD_CLASS = "llm-zoom-pad";
2738
+ var text = (v) => v == null ? "" : String(v).trim();
2739
+ var FREE = { l: -Infinity, t: -Infinity, r: Infinity, b: Infinity };
2740
+ var box = (r) => ({ l: r.left, t: r.top, r: r.right, b: r.bottom });
2741
+ var area = (b) => (b.r - b.l) * (b.b - b.t);
2742
+ var cut = (a, b) => {
2743
+ if (!a) return null;
2744
+ const c = { l: Math.max(a.l, b.l), t: Math.max(a.t, b.t), r: Math.min(a.r, b.r), b: Math.min(a.b, b.b) };
2745
+ return c.r > c.l && c.b > c.t ? c : null;
2746
+ };
2747
+ function shapeTest(mark) {
2748
+ const g = mark;
2749
+ let inv = null;
2750
+ try {
2751
+ inv = g?.isPointInFill ? g.getScreenCTM?.()?.inverse() ?? null : null;
2752
+ } catch {
2753
+ inv = null;
2754
+ }
2755
+ if (!g || !inv) return () => true;
2756
+ const m = inv;
2757
+ return (x, y) => {
2758
+ try {
2759
+ return g.isPointInFill({ x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f });
2760
+ } catch {
2761
+ return true;
2762
+ }
2763
+ };
2764
+ }
2765
+ var parseIdxs = (s) => (s || "").split(",").map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n));
2766
+ function createAnnotationLayer(container, onClick) {
2767
+ let list = [];
2768
+ let report = { shown: 0, notShown: 0 };
2769
+ let last = null;
2770
+ const doc = container.ownerDocument;
2771
+ const win = doc.defaultView;
2772
+ let observer = null;
2773
+ let queued = false;
2774
+ const redrawSoon = () => {
2775
+ if (queued || !last) return;
2776
+ queued = true;
2777
+ const run = () => {
2778
+ queued = false;
2779
+ if (last) paint(last.data, last.theme);
2780
+ };
2781
+ if (win?.requestAnimationFrame) win.requestAnimationFrame(run);
2782
+ else setTimeout(run, 16);
2783
+ };
2784
+ const watch = (on) => {
2785
+ const MO = win?.MutationObserver;
2786
+ if (on && !observer && MO) {
2787
+ observer = new MO((records) => {
2788
+ if (records.some((r) => !r.target.closest?.(`.${ANNOTATION_LAYER_CLASS}`))) redrawSoon();
2789
+ });
2790
+ observer.observe(container, { subtree: true, attributes: true, attributeFilter: ["transform", "d", "cx", "cy", "x", "y", "points"] });
2791
+ } else if (!on && observer) {
2792
+ observer.disconnect();
2793
+ observer = null;
2794
+ }
2795
+ };
2796
+ function paint(data, theme) {
2797
+ let layer = container.querySelector(`:scope > .${ANNOTATION_LAYER_CLASS}`);
2798
+ if (!list.length) {
2799
+ layer?.remove();
2800
+ report = { shown: 0, notShown: 0 };
2801
+ return;
2802
+ }
2803
+ if (!layer) {
2804
+ layer = doc.createElement("div");
2805
+ layer.className = ANNOTATION_LAYER_CLASS;
2806
+ layer.setAttribute("aria-hidden", "false");
2807
+ Object.assign(layer.style, { position: "absolute", left: "0", top: "0", width: "0", height: "0", overflow: "visible", zIndex: "2" });
2808
+ if (win?.getComputedStyle(container).position === "static") container.style.position = "relative";
2809
+ container.appendChild(layer);
2810
+ }
2811
+ layer.textContent = "";
2812
+ const names = (data.columns || []).map((c) => typeof c === "string" ? c : c?.name);
2813
+ const marks = Array.from(container.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}]`)).filter((m) => !m.closest(`.${ANNOTATION_LAYER_CLASS}`));
2814
+ const marksByRow = /* @__PURE__ */ new Map();
2815
+ for (const m of marks) for (const r of parseIdxs(m.getAttribute(ROW_IDX_ATTR))) {
2816
+ const at = marksByRow.get(r);
2817
+ if (at) at.push(m);
2818
+ else marksByRow.set(r, [m]);
2819
+ }
2820
+ const origin = container.getBoundingClientRect();
2821
+ const open = origin.width > 0 && origin.height > 0 ? box(origin) : FREE;
2822
+ const clipOf = /* @__PURE__ */ new Map();
2823
+ const viewOf = (m) => {
2824
+ let v = open;
2825
+ const svg = m.closest("svg");
2826
+ if (svg) {
2827
+ const s = svg.getBoundingClientRect();
2828
+ if (s.width > 0 && s.height > 0) v = cut(v, box(s));
2829
+ }
2830
+ for (let el = m.parentElement; v && el && el !== svg && el !== container; el = el.parentElement) {
2831
+ if (!clipOf.has(el)) {
2832
+ const w2 = clipWindowOf(el);
2833
+ clipOf.set(el, w2 && box(w2));
2834
+ }
2835
+ const w = clipOf.get(el);
2836
+ if (w) v = cut(v, w);
2837
+ }
2838
+ return v;
2839
+ };
2840
+ const controls = Array.from(container.querySelectorAll(`.${CONTROL_CLASS}, .${ZOOM_PAD_CLASS}`)).map((c) => c.getBoundingClientRect()).filter((r) => r.width > 0 && r.height > 0).map(box);
2841
+ const clear = (v, len, mark) => {
2842
+ const hw = Math.max(16, len * 6 + 8) / 2, hh = 8, g = 2;
2843
+ const cx = (v.l + v.r) / 2, cy = (v.t + v.b) / 2;
2844
+ const free = (x, y) => x >= v.l && x <= v.r && y >= v.t && y <= v.b && controls.every((c) => x + hw <= c.l || x - hw >= c.r || y + hh <= c.t || y - hh >= c.b);
2845
+ const onShape = shapeTest(mark);
2846
+ const tries = [[cx, cy]];
2847
+ for (let i = 0; i <= 8; i++) for (let j = 0; j <= 6; j++) tries.push([v.l + (v.r - v.l) * i / 8, v.t + (v.b - v.t) * j / 6]);
2848
+ tries.sort((p, q) => Math.hypot(p[0] - cx, p[1] - cy) - Math.hypot(q[0] - cx, q[1] - cy));
2849
+ for (const c of controls) tries.push([cx, c.b + hh + g], [cx, c.t - hh - g], [c.l - hw - g, cy], [c.r + hw + g, cy]);
2850
+ return tries.find(([x, y]) => free(x, y) && onShape(x, y)) ?? tries.find(([x, y]) => free(x, y)) ?? null;
2851
+ };
2852
+ let shown = 0;
2853
+ for (const a of list) {
2854
+ const pairs = (a.where ? Object.entries(a.where) : a.column != null ? [[a.column, a.value]] : []).map(([c, v]) => [names.indexOf(c), text(v)]);
2855
+ const want = pairs.map((p) => p[1]).join(" / ");
2856
+ const hits = /* @__PURE__ */ new Set();
2857
+ if (pairs.length && pairs.every((p) => p[0] >= 0)) (data.rows || []).forEach((row, r) => {
2858
+ if (pairs.every(([ci, v]) => text(row?.[ci]) === v)) for (const m of marksByRow.get(r) ?? []) hits.add(m);
2859
+ });
2860
+ if (!hits.size) continue;
2861
+ let best = null, bestMark = null, laidOut = false;
2862
+ for (const m of hits) {
2863
+ const b = m.getBoundingClientRect();
2864
+ if (!(b.width > 0 && b.height > 0)) continue;
2865
+ laidOut = true;
2866
+ const v = cut(box(b), viewOf(m));
2867
+ if (v && (!best || area(v) > area(best))) {
2868
+ best = v;
2869
+ bestMark = m;
2870
+ }
2871
+ }
2872
+ const pt = best ? clear(best, (a.label ?? "\u2022").length, bestMark) : null;
2873
+ if (laidOut && !pt) continue;
2874
+ shown++;
2875
+ const badge = doc.createElement("div");
2876
+ badge.className = ANNOTATION_CLASS;
2877
+ badge.textContent = a.label ?? "\u2022";
2878
+ if (a.title) badge.setAttribute("title", a.title);
2879
+ badge.setAttribute("role", "button");
2880
+ badge.setAttribute("aria-label", a.title ? `Note: ${a.title}` : `Note on ${want}`);
2881
+ Object.assign(badge.style, {
2882
+ position: "absolute",
2883
+ left: `${Math.round(pt ? pt[0] - origin.left : 0)}px`,
2884
+ top: `${Math.round(pt ? pt[1] - origin.top : 0)}px`,
2885
+ transform: "translate(-50%, -50%)",
2886
+ minWidth: "16px",
2887
+ height: "16px",
2888
+ padding: "0 4px",
2889
+ boxSizing: "border-box",
2890
+ borderRadius: "8px",
2891
+ font: "600 10px/16px system-ui, sans-serif",
2892
+ textAlign: "center",
2893
+ color: theme.bg || "#ffffff",
2894
+ background: theme.accent || theme.fg || "#0f6cbd",
2895
+ boxShadow: `0 0 0 1.5px ${theme.bg || "#ffffff"}`,
2896
+ cursor: onClick ? "pointer" : "default",
2897
+ // Never pointer-events (a host failsafe turns it back on); visibility hides a badge whose mark
2898
+ // has no box yet.
2899
+ visibility: laidOut ? "visible" : "hidden"
2900
+ });
2901
+ badge.addEventListener("click", (e) => {
2902
+ e.stopPropagation();
2903
+ onClick?.(a);
2904
+ });
2905
+ layer.appendChild(badge);
2906
+ }
2907
+ report = { shown, notShown: list.length - shown };
2908
+ }
2909
+ return {
2910
+ set(next) {
2911
+ list = Array.isArray(next) ? next.slice() : [];
2912
+ watch(list.length > 0);
2913
+ if (last) paint(last.data, last.theme);
2914
+ },
2915
+ draw(data, theme) {
2916
+ last = { data, theme };
2917
+ paint(data, theme);
2918
+ },
2919
+ get report() {
2920
+ return report;
2921
+ },
2922
+ destroy() {
2923
+ watch(false);
2924
+ list = [];
2925
+ last = null;
2926
+ container.querySelector(`:scope > .${ANNOTATION_LAYER_CLASS}`)?.remove();
2927
+ }
2928
+ };
2929
+ }
2930
+
2931
+ // src/host.ts
2932
+ function sessionViewStateProvider(container) {
2933
+ const holder = container;
2934
+ const existing = holder[CONTAINER_SLOT_UI_STATE];
2935
+ const store = existing && typeof existing === "object" ? existing : holder[CONTAINER_SLOT_UI_STATE] = {};
2936
+ return {
2937
+ load: () => store,
2938
+ save: (next) => {
2939
+ if (!next || typeof next !== "object") return;
2940
+ for (const k of Object.keys(store)) delete store[k];
2941
+ Object.assign(store, next);
2942
+ }
2943
+ };
2944
+ }
2945
+ function noopViewStateProvider() {
2946
+ return { load: () => ({}), save: () => {
2947
+ } };
2948
+ }
2949
+ var parseRowIdxs = (s) => (s || "").split(",").map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n));
2950
+ function assembleD3(base, ...plugins) {
2951
+ return Object.assign({}, base, ...plugins);
2952
+ }
2953
+ var D3_PLUGIN_PACKAGES = Object.freeze({
2954
+ sankey: "d3-sankey",
2955
+ sankeyLinkHorizontal: "d3-sankey",
2956
+ sankeyJustify: "d3-sankey",
2957
+ sankeyCenter: "d3-sankey",
2958
+ sankeyLeft: "d3-sankey",
2959
+ sankeyRight: "d3-sankey",
2960
+ hexbin: "d3-hexbin",
2961
+ voronoiTreemap: "d3-voronoi-treemap",
2962
+ voronoiMap: "d3-voronoi-map",
2963
+ weightedVoronoi: "d3-weighted-voronoi",
2964
+ // Attached onto d3 like the rest, but reached as d3.mermaid.render(...) — a NAMESPACE
2965
+ // OBJECT with methods, not a function on d3. The scan below accounts for that shape.
2966
+ mermaid: "mermaid",
2967
+ // AND THE NAME A REAL CHART ACTUALLY WRITES. This one is not a member of the mermaid
2968
+ // package at all: it is a helper the SERVER prepends into the generated code, and the
2969
+ // helper is what reaches for the library. So a genuine diagram chart contains
2970
+ // `d3.llmMermaid(` and never once writes `d3.mermaid` — measured on the first real
2971
+ // generation of that type, for which this scan returned an EMPTY list. It drew, because
2972
+ // the host it ran in happened to carry the library; a host relying on this answer to
2973
+ // decide what to install would have been told nothing and failed several frames deep.
2974
+ //
2975
+ // Mapping the helper is right rather than a hack, because this map answers "what must a
2976
+ // host INSTALL for this code to run" and not "which package exports this symbol". For
2977
+ // code that calls the helper, the answer is the library the helper reaches for.
2978
+ llmMermaid: "mermaid"
2979
+ });
2980
+ function requiredD3Plugins(code) {
2981
+ const out = /* @__PURE__ */ new Set();
2982
+ const body = stripJsComments(String(code || ""));
2983
+ const add = (name) => {
2984
+ const pkg = D3_PLUGIN_PACKAGES[name];
2985
+ if (pkg) out.add(pkg);
2986
+ };
2987
+ for (const m of body.matchAll(/\bd3\s*\.\s*(\w+)/g)) add(m[1]);
2988
+ for (const m of body.matchAll(/\bd3\s*\[\s*['"](\w+)['"]\s*\]/g)) add(m[1]);
2989
+ for (const m of body.matchAll(/\{([^{}]*)\}\s*=\s*d3\b/g)) {
2990
+ for (const part of m[1].split(",")) add(part.split(":")[0].trim());
2991
+ }
2992
+ return [...out].sort();
2993
+ }
2994
+ function explainRenderFailure(err, d3) {
2995
+ const msg = String(err?.message ?? err ?? "");
2996
+ if (!d3) {
2997
+ return new Error(
2998
+ "[@bicharts/chart-host] no d3 was provided, so the generated chart could not run. Pass it explicitly \u2014 createChartHost(el, { d3 }) / <BicChart d3={d3} /> \u2014 or set window.d3. The chart needs D3 v7 (npm install d3@7). Original error: " + msg
2999
+ );
3000
+ }
3001
+ if (typeof d3?.mermaid?.render !== "function" && /\bmermaid\b/i.test(msg)) {
3002
+ return new Error(
3003
+ "[@bicharts/chart-host] this chart draws its diagram with Mermaid, which it reaches as d3.mermaid.render(...), and the d3 you passed has no mermaid on it \u2014 this host did not load the library. A host supplies it exactly the way the other plugins are supplied: hand createChartHost ONE d3 that carries it, the SAME d3 every chart gets \u2014 npm install mermaid then import * as d3base from 'd3'; import mermaid from 'mermaid'; import { assembleD3 } from '@bicharts/chart-host'; const d3 = assembleD3(d3base, { mermaid }); (an ES-module d3 namespace is frozen, so assigning onto it throws) Original error: " + msg
3004
+ );
3005
+ }
3006
+ const m = /d3\.(\w+) is not a function|(\w+) is not a function/.exec(msg);
3007
+ const name = m?.[1] ?? m?.[2] ?? "";
3008
+ const pkg = D3_PLUGIN_PACKAGES[name];
3009
+ if (pkg) {
3010
+ return new Error(
3011
+ `[@bicharts/chart-host] this chart calls d3.${name}(), which comes from the separate "${pkg}" package and is not attached to the d3 you passed. Install it and hand the host ONE d3 that carries it, the SAME d3 every chart gets: npm install ${pkg} then import * as d3base from "d3"; import { ${name} } from "${pkg}"; import { assembleD3 } from "@bicharts/chart-host"; const d3 = assembleD3(d3base, { ${name} }); (an ES-module d3 namespace is frozen, so assigning onto it throws) Original error: ` + msg
3012
+ );
3013
+ }
3014
+ return err;
3015
+ }
3016
+ var ESM_EXPORT_CLAUSE = /^[ \t]*export[ \t]+(?:\{[^}]*\}|default[ \t]+[A-Za-z_$][\w$]*)[ \t]*;?[ \t]*$/gm;
3017
+ var ESM_EXPORT_DECL = /^([ \t]*)export[ \t]+(?=(?:async[ \t]+)?function\b|const\b|let\b|var\b|class\b)/gm;
3018
+ function stripEsmExports(code) {
3019
+ return String(code || "").replace(ESM_EXPORT_CLAUSE, "").replace(ESM_EXPORT_DECL, "$1");
3020
+ }
3021
+ function writableD3(d3) {
3022
+ if (d3 == null || typeof d3 !== "object") return d3;
3023
+ if (Object.isExtensible(d3)) return d3;
3024
+ return { ...d3 };
3025
+ }
3026
+ function compileRenderFn(code, win, doc, d3) {
3027
+ const body = stripEsmExports(code) + "\n;return (typeof render === 'function') ? render : null;";
3028
+ let factory;
3029
+ try {
3030
+ factory = new Function("container", "data", "options", "window", "document", "d3", body);
3031
+ } catch (e) {
3032
+ const msg = String(e?.message ?? e ?? "");
3033
+ const modular = /\bimport\b|\bexport\b/.test(String(code || ""));
3034
+ throw new Error(
3035
+ "[@bicharts/chart-host] the generated code could not be COMPILED, so nothing was drawn. This path evaluates the chart as a code string, which cannot contain ES-module syntax" + (modular ? " \u2014 and this code still has `import`/`export` in it after the export clause was stripped. Either request the plain form from the generator, or `import` the module yourself and pass the render function directly." : ".") + " Original error: " + msg
3036
+ );
3037
+ }
3038
+ const fn = factory.call(null, null, null, null, win, doc, writableD3(d3));
3039
+ if (typeof fn !== "function") throw new Error("generated code did not define render(container, data, options)");
3040
+ return fn;
3041
+ }
3042
+ var ASYNC_RENDER_TIMEOUT_MS = 15e3;
3043
+ var isThenable = (v) => !!v && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
3044
+ function createChartHost(container, config) {
3045
+ const doc = container.ownerDocument;
3046
+ const win = doc?.defaultView ?? globalThis;
3047
+ const d3 = config.d3 ?? globalThis.d3 ?? win?.d3;
3048
+ let data = config.data;
3049
+ {
3050
+ const stamped = config.data?.meta?.hostContract;
3051
+ const major = (v) => (v || "").split(".")[0];
3052
+ if (stamped && major(stamped) !== major(HOST_CONTRACT_VERSION)) {
3053
+ try {
3054
+ (win?.console ?? console)?.warn(
3055
+ `[@bicharts/chart-host] contract MAJOR mismatch: this chart was generated against v${stamped}, the host implements v${HOST_CONTRACT_VERSION}. Marks, container slots or the cross-filter event may not line up \u2014 regenerate the chart.`
3056
+ );
3057
+ } catch {
3058
+ }
3059
+ }
3060
+ }
3061
+ let raw = { ...config.options ?? {} };
3062
+ if (typeof raw.setUiState !== "function") {
3063
+ const provider = config.viewState ?? sessionViewStateProvider(container);
3064
+ let bag;
3065
+ try {
3066
+ bag = provider.load() ?? {};
3067
+ } catch {
3068
+ bag = {};
3069
+ }
3070
+ if (raw.uiState && typeof raw.uiState === "object" && Object.keys(bag).length === 0)
3071
+ Object.assign(bag, raw.uiState);
3072
+ raw.uiState = bag;
3073
+ raw.setUiState = (s) => {
3074
+ if (!s || typeof s !== "object") return;
3075
+ try {
3076
+ provider.save(s);
3077
+ } catch {
3078
+ }
3079
+ };
3080
+ }
3081
+ const resolveForHost = (input) => {
3082
+ const out = resolveOptions(input);
3083
+ return out.allowTooltips == null ? { ...out, allowTooltips: true } : out;
3084
+ };
3085
+ let resolved = resolveForHost(raw);
3086
+ let renderFn = config.renderFn ?? null;
3087
+ let destroyed = false;
3088
+ let warnedGeo = false;
3089
+ let warnedBlank = false;
3090
+ let warnedIds = false;
3091
+ const idScope = newIdScope();
3092
+ let renderSeq = 0;
3093
+ const annotationLayer = createAnnotationLayer(container, (a) => config.onAnnotationClick?.(a));
3094
+ const drawAnnotations = () => annotationLayer.draw(
3095
+ { columns: data.columns, rows: data.rows },
3096
+ {
3097
+ fg: resolved.themeFg,
3098
+ accent: resolved.themeAccent,
3099
+ bg: resolved.backgroundColor ?? resolved.themeBg
3100
+ }
3101
+ );
3102
+ annotationLayer.set(config.annotations ?? []);
3103
+ let renderedPromise = Promise.resolve();
3104
+ const settle = (p) => {
3105
+ renderedPromise = p;
3106
+ p.catch(() => {
3107
+ });
3108
+ };
3109
+ let current = null;
3110
+ let activeTickEl = null;
3111
+ let selectionIsPeriod = false;
3112
+ let selectionFromChart = false;
3113
+ const clickedMarks = () => container[CONTAINER_SLOT_CLICKED_MARKS] ?? null;
3114
+ const setClickedMarks = (c) => {
3115
+ try {
3116
+ container[CONTAINER_SLOT_CLICKED_MARKS] = c;
3117
+ } catch {
3118
+ }
3119
+ };
3120
+ const subs = /* @__PURE__ */ new Set();
3121
+ const ensureAffordanceStyles = () => {
3122
+ try {
3123
+ if (!doc || doc.getElementById("bic-chart-host-affordances")) return;
3124
+ const st = doc.createElement("style");
3125
+ st.id = "bic-chart-host-affordances";
3126
+ st.textContent = `
3127
+ /* TYPOGRAPHY FIREWALL. Generated charts set font-size but almost never line-height or
3128
+ letter-spacing, so they INHERIT whatever the host page cascades in. A page with
3129
+ \`:root { font: 18px/145% }\` (the stock Vite template does exactly this) hands every
3130
+ chart an inherited 26.1px line box; an 11px label in a 14px band is then sliced through
3131
+ the middle. The chart is not wrong and the host is not wrong \u2014 the boundary between
3132
+ them is, so the boundary fixes it. Both properties are overridable: a chart that sets
3133
+ its own line-height still wins. Deliberately NOT resetting font-family or color, which
3134
+ a host SHOULD be able to theme. Found 2026-07-26 in the React demo, on a chart that
3135
+ renders perfectly inside Power BI. */
3136
+ .${HOST_CONTAINER_CLASS} { line-height: normal; letter-spacing: normal; }
3137
+ /* BACKGROUND MARKS ARE NOT DATA. A mark whose ${ROW_IDX_ATTR} is EMPTY has no rows in the
3138
+ CURRENT frame \u2014 an animated map's region before its country enters the series. Every
3139
+ affordance below therefore skips it: brightening or dimming it manufactures a shade
3140
+ that appears in no legend, and at 25% grey over white it comes out LIGHTER than plain
3141
+ land, which reads as a real bottom-of-scale value rather than as absence. The guard is
3142
+ exactly [${ROW_IDX_ATTR}=""] \u2014 an ABSENT attribute is a tagged companion layer (an
3143
+ anomaly badge, a highlight stroke) and must keep dimming with its datum. */
3144
+ .${HOST_CONTAINER_CLASS} .${MARK_CLASS} { cursor: pointer; transition: opacity 120ms ease-in-out, filter 100ms ease-in-out; }
3145
+ .${HOST_CONTAINER_CLASS} .${MARK_CLASS}:not([${ROW_IDX_ATTR}=""]):hover { filter: brightness(1.1) drop-shadow(0 0 1.5px rgba(0,0,0,0.5)) !important; }
3146
+ .${HOST_CONTAINER_CLASS}:has(.${MARK_CLASS}:not([${ROW_IDX_ATTR}=""]):hover) .${MARK_CLASS}:not(:hover):not([${ROW_IDX_ATTR}=""]) { opacity: 0.55; }
3147
+ .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}:not(.${MARK_SELECTED_CLASS}):not([${ROW_IDX_ATTR}=""]) {
3148
+ opacity: var(${DIM_OPACITY_VAR}, ${DIM_OPACITY_DEFAULT}) !important;
3149
+ }
3150
+ /* THE LIFT, BY DECLARATION ONLY. Dimming the others is all the rule above does; a selected
3151
+ mark keeps whatever paint its chart gave it. A chart that draws every mark pale on purpose
3152
+ (a parallel-coordinates plot at 0.38 so overplotting reads) therefore shows its SELECTED
3153
+ lines at 38% - the same eye-reading as dimmed. Inside a group the chart stamps
3154
+ ${LIFT_SELECTED_CLASS} on, a selected mark is painted at full opacity. Opacity only, never
3155
+ stroke geometry. Undeclared charts are byte-identical, which is what keeps an alpha
3156
+ ENCODING (a depth cue, a hierarchy level) from being flattened by a fix meant for one. */
3157
+ .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${LIFT_SELECTED_CLASS} .${MARK_CLASS}.${MARK_SELECTED_CLASS},
3158
+ .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}.${LIFT_SELECTED_CLASS}.${MARK_SELECTED_CLASS} {
3159
+ opacity: 1 !important; stroke-opacity: 1 !important; fill-opacity: 1 !important;
3160
+ }
3161
+ .${HOST_CONTAINER_CLASS} .${LEGEND_MARK_CLASS} { cursor: pointer; }
3162
+ /* An axis/group header that filters when clicked LOOKS like inert text, and until it is
3163
+ marked there is no way to tell a landed filter from a dead label \u2014 the report reads
3164
+ "the axis labels are not clickable" even when every click resolved. Weight + underline
3165
+ rather than a colour: these labels sit wherever the host page's theme puts them, and
3166
+ the host has no reliable contrast partner out in the axis gutter. This paints NO marks,
3167
+ so it is the one acknowledgement a PERIOD tick can have without breaking the rule that
3168
+ a timeline never dims its own chart. */
3169
+ .${HOST_CONTAINER_CLASS} .${AXIS_FILTER_CLASS} { cursor: pointer; }
3170
+ .${HOST_CONTAINER_CLASS} .${ACTIVE_TICK_CLASS},
3171
+ .${HOST_CONTAINER_CLASS} .${ACTIVE_TICK_CLASS} text {
3172
+ font-weight: 700 !important;
3173
+ text-decoration: underline !important;
3174
+ }`;
3175
+ (doc.head || doc.documentElement)?.appendChild(st);
3176
+ } catch {
3177
+ }
3178
+ };
3179
+ const paintSelection = () => {
3180
+ try {
3181
+ const sel = current;
3182
+ const active = !!sel && sel.length > 0;
3183
+ const dim = active && !selectionIsPeriod;
3184
+ container.classList?.[dim ? "add" : "remove"](SELECTION_ACTIVE_CLASS);
3185
+ const lit = container.querySelectorAll(`.${ACTIVE_TICK_CLASS}`);
3186
+ for (const t of Array.from(lit)) t.classList?.remove(ACTIVE_TICK_CLASS);
3187
+ const tickStillDrawn = !!activeTickEl && (typeof container.contains === "function" ? container.contains(activeTickEl) : activeTickEl.isConnected !== false);
3188
+ if (active && tickStillDrawn) activeTickEl.classList?.add(ACTIVE_TICK_CLASS);
3189
+ const wanted = new Set(sel ?? []);
3190
+ const marks = container.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}], .${LEGEND_MARK_CLASS}[${ROW_IDX_ATTR}]`);
3191
+ const rule = selectionRuleOf(container);
3192
+ if (rule !== "any-row") {
3193
+ const list = Array.from(marks);
3194
+ const plan = planSelectionPaint(
3195
+ list.map((m) => ({ key: markKeyOf(m), rows: parseRowIdxs(m.getAttribute(ROW_IDX_ATTR)) })),
3196
+ active ? wanted : [],
3197
+ rule,
3198
+ clickedMarks()
3199
+ );
3200
+ list.forEach((m, i) => m.classList?.[plan.on[i] ? "add" : "remove"](MARK_SELECTED_CLASS));
3201
+ setClickedMarks(plan.keys.length ? { keys: plan.keys, selection: Array.from(wanted) } : null);
3202
+ if (active) {
3203
+ try {
3204
+ config.onSelectionPaint?.({
3205
+ rule,
3206
+ mode: plan.mode,
3207
+ lit: plan.on.filter(Boolean).length,
3208
+ marks: list.length,
3209
+ selection: wanted.size
3210
+ });
3211
+ } catch {
3212
+ }
3213
+ }
3214
+ return;
3215
+ }
3216
+ for (const m of Array.from(marks)) {
3217
+ const rows = parseRowIdxs(m.getAttribute(ROW_IDX_ATTR));
3218
+ const on = active && rows.some((r) => wanted.has(r));
3219
+ m.classList?.[on ? "add" : "remove"](MARK_SELECTED_CLASS);
3220
+ }
3221
+ } catch {
3222
+ }
3223
+ };
3224
+ const notify = (rowIdxs, source, tick, fromChart = false) => {
3225
+ current = rowIdxs;
3226
+ selectionFromChart = rowIdxs.length > 0 && fromChart;
3227
+ activeTickEl = rowIdxs.length ? tick ?? null : null;
3228
+ selectionIsPeriod = rowIdxs.length > 0 && periodTickSuppressesFeedback(!!tick, chartOwnsTimeline(container));
3229
+ paintSelection();
3230
+ for (const cb of subs) {
3231
+ try {
3232
+ cb(rowIdxs, source);
3233
+ } catch {
3234
+ }
3235
+ }
3236
+ };
3237
+ let xfAt = 0;
3238
+ const XF_ECHO_MS = 150;
3239
+ const onXf = (e) => {
3240
+ const d = e?.detail || {};
3241
+ xfAt = Date.now();
3242
+ if (d.clear) notify([], d.source || "chart");
3243
+ else if (d.mark) notify(parseRowIdxs(d.mark.getAttribute?.(ROW_IDX_ATTR)), d.source || "chart", d.mark, true);
3244
+ };
3245
+ const resolver = createMarkResolver({
3246
+ root: container,
3247
+ doc,
3248
+ log: () => {
3249
+ }
3250
+ });
3251
+ const clickSel = `.${MARK_CLASS}[${ROW_IDX_ATTR}], .${LEGEND_MARK_CLASS}[${ROW_IDX_ATTR}], .${AXIS_FILTER_CLASS}[${ROW_IDX_ATTR}]`;
3252
+ const onClick = (e) => {
3253
+ if (Date.now() - xfAt < XF_ECHO_MS) return;
3254
+ if (isInsideControl(e?.target, container)) return;
3255
+ let el = resolver.findMark(e?.target, clickSel, e);
3256
+ if (!el) el = resolver.penetrateOverlayAt(e?.clientX, e?.clientY, clickSel);
3257
+ if (!el) el = resolver.resolveByGeometry(e?.clientX, e?.clientY, clickSel);
3258
+ if (!el) {
3259
+ if (current && current.length) {
3260
+ const slot = container[CONTAINER_SLOT_XF_CLEAR];
3261
+ if (selectionFromChart && typeof slot === "function") {
3262
+ try {
3263
+ slot();
3264
+ } catch {
3265
+ }
3266
+ }
3267
+ if (current && current.length) notify([], "user");
3268
+ }
3269
+ return;
3270
+ }
3271
+ const rows = parseRowIdxs(el.getAttribute(ROW_IDX_ATTR));
3272
+ if (!rows.length) return;
3273
+ const tick = el.classList?.contains?.(AXIS_FILTER_CLASS) ? el : null;
3274
+ const multi = !!(e?.ctrlKey || e?.metaKey || e?.shiftKey);
3275
+ const next = nextSelection(current, rows, { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey });
3276
+ const toggledOff = !multi && next.length === 0;
3277
+ setClickedMarks(nextClickedMarks(
3278
+ clickedMarks(),
3279
+ current,
3280
+ markKeyOf(el),
3281
+ next,
3282
+ { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey }
3283
+ ));
3284
+ notify(next, "user", toggledOff ? null : tick);
3285
+ };
3286
+ container.addEventListener(XFILTER_REFRESH_EVENT, onXf);
3287
+ container.addEventListener("click", onClick);
3288
+ container.classList?.add(HOST_CONTAINER_CLASS);
3289
+ ensureAffordanceStyles();
3290
+ const fitOpts = config.fit ?? true;
3291
+ const labelContrastOpts = config.labelContrast ?? true;
3292
+ const stopAnim = () => {
3293
+ const s = container[CONTAINER_SLOT_ANIM_STOP];
3294
+ if (typeof s === "function") {
3295
+ try {
3296
+ s();
3297
+ } catch {
3298
+ }
3299
+ }
3300
+ };
3301
+ const runPostRenderPasses = () => {
3302
+ try {
3303
+ const r = scopeChartIds(container, idScope);
3304
+ if (r.collisions.length && !warnedIds) {
3305
+ warnedIds = true;
3306
+ try {
3307
+ (win?.console ?? console)?.warn(
3308
+ `[@bicharts/chart-host] ids shared with another chart, scoped: ${r.collisions.slice(0, 5).join(", ")}`
3309
+ );
3310
+ } catch {
3311
+ }
3312
+ }
3313
+ config.onIdScope?.(r);
3314
+ } catch {
3315
+ }
3316
+ if (labelContrastOpts !== false) {
3317
+ try {
3318
+ const lc = labelContrastOpts === true ? {} : { ...labelContrastOpts };
3319
+ if (!lc.pageBg) {
3320
+ const bg = resolved.backgroundColor ?? resolved.themeBg;
3321
+ if (typeof bg === "string" && bg) lc.pageBg = bg;
3322
+ }
3323
+ const r = applyLabelContrast(container, lc);
3324
+ config.onLabelContrast?.(r);
3325
+ } catch {
3326
+ }
3327
+ }
3328
+ ensureCrossfilterHitTargets(container, doc);
3329
+ paintSelection();
3330
+ try {
3331
+ const census = censusMarks(container);
3332
+ if (isBlankRender({
3333
+ markCount: census.markCount,
3334
+ rows: data.rows ? data.rows.length : 0,
3335
+ animated: config.animated,
3336
+ contractUntagged: config.contractUntagged,
3337
+ declaresNoRowMarks: census.declaresNoRowMarks
3338
+ })) {
3339
+ if (!warnedBlank) {
3340
+ warnedBlank = true;
3341
+ try {
3342
+ (win?.console ?? console)?.warn(
3343
+ `[@bicharts/chart-host] this chart rendered without error but painted no data marks, against ${data.rows.length} row(s) \u2014 so the container is empty. The usual cause is that the generated code binds a column BY NAME that is no longer in the data, sending it down its own "no data" branch. Check that the columns in your table still match the ones the chart was generated for.`
3344
+ );
3345
+ } catch {
3346
+ }
3347
+ }
3348
+ config.onBlankRender?.({ census, rows: data.rows ? data.rows.length : 0 });
3349
+ }
3350
+ } catch {
3351
+ }
3352
+ if (config.onHitBandCensus) {
3353
+ try {
3354
+ config.onHitBandCensus(censusHitBands(container, doc));
3355
+ } catch {
3356
+ }
3357
+ }
3358
+ if (config.onColourSpreadCensus) {
3359
+ try {
3360
+ config.onColourSpreadCensus(censusColourSpread(container, doc));
3361
+ } catch {
3362
+ }
3363
+ }
3364
+ if (config.onClippedTextCensus) {
3365
+ try {
3366
+ config.onClippedTextCensus(censusClippedText(container));
3367
+ } catch {
3368
+ }
3369
+ }
3370
+ if (config.onValuePlacementCensus) {
3371
+ try {
3372
+ config.onValuePlacementCensus(censusValuePlacement(container, data));
3373
+ } catch {
3374
+ }
3375
+ }
3376
+ if (fitOpts !== false) {
3377
+ try {
3378
+ const r = fitRenderedChart(container, fitOpts === true ? {} : fitOpts);
3379
+ config.onFit?.(r);
3380
+ } catch {
3381
+ }
3382
+ }
3383
+ try {
3384
+ drawAnnotations();
3385
+ } catch {
3386
+ }
3387
+ };
3388
+ const renderFailure = (err) => {
3389
+ const message = String(err?.message ?? err ?? "");
3390
+ if (config.onInvalidSentinel && isInvalidSentinelError(message)) {
3391
+ stopAnim();
3392
+ container.innerHTML = "";
3393
+ config.onInvalidSentinel({ reason: invalidSentinelReason(message), message });
3394
+ return null;
3395
+ }
3396
+ return explainRenderFailure(err, d3);
3397
+ };
3398
+ const host = {
3399
+ render() {
3400
+ if (destroyed) return;
3401
+ const seq2 = ++renderSeq;
3402
+ stopAnim();
3403
+ container.innerHTML = "";
3404
+ if (!renderFn) {
3405
+ if (!config.code) throw new Error("createChartHost: provide code or renderFn");
3406
+ renderFn = compileRenderFn(config.code, win, doc, d3);
3407
+ }
3408
+ if (resolved.geo === void 0 && config.geoKind) {
3409
+ const g = config.geoProvider?.(config.geoKind) ?? geoFromCache(config.geoKind);
3410
+ if (g) resolved = { ...resolved, geo: g };
3411
+ else if (!warnedGeo) {
3412
+ warnedGeo = true;
3413
+ try {
3414
+ (win?.console ?? console)?.warn(
3415
+ `[@bicharts/chart-host] geoKind "${config.geoKind}" was set but no geometry is available, so this chart will draw without a basemap. Either \`await loadGeo("${config.geoKind}")\` before rendering, pass a geoProvider, or import { geoForKind } from "@bicharts/chart-host/geo" and set options.geo.`
3416
+ );
3417
+ } catch {
3418
+ }
3419
+ }
3420
+ }
3421
+ if (resolved.geoUnmatched === void 0 && data.geoUnmatched) resolved = { ...resolved, geoUnmatched: data.geoUnmatched };
3422
+ if (resolved.geoPoint === void 0 && data.geoPoint) resolved = { ...resolved, geoPoint: data.geoPoint };
3423
+ if (resolved.geoPointDest === void 0 && data.geoPointDest) resolved = { ...resolved, geoPointDest: data.geoPointDest };
3424
+ if (resolved.dateCellsAreUtcDays === void 0 && data.dateCellsAreUtcDays) resolved = { ...resolved, dateCellsAreUtcDays: true };
3425
+ let outcome;
3426
+ try {
3427
+ outcome = renderFn(container, { columns: data.columns, rows: data.rows }, resolved);
3428
+ } catch (err) {
3429
+ const fatal = renderFailure(err);
3430
+ if (fatal) {
3431
+ settle(Promise.reject(fatal));
3432
+ throw fatal;
3433
+ }
3434
+ settle(Promise.resolve());
3435
+ return;
3436
+ }
3437
+ if (!isThenable(outcome)) {
3438
+ runPostRenderPasses();
3439
+ settle(Promise.resolve());
3440
+ return;
3441
+ }
3442
+ settle(new Promise((resolve, reject) => {
3443
+ const timer = setTimeout(() => {
3444
+ reject(new Error(
3445
+ `[@bicharts/chart-host] the chart did not finish drawing within ${Math.round(ASYNC_RENDER_TIMEOUT_MS / 1e3)}s, so the host stopped waiting for it. Nothing past that point ran: no label-contrast pass, no hit-target heal, no census, no fit. A render() that returns a promise has to settle it.`
3446
+ ));
3447
+ }, ASYNC_RENDER_TIMEOUT_MS);
3448
+ const done = (fn) => {
3449
+ clearTimeout(timer);
3450
+ fn();
3451
+ };
3452
+ Promise.resolve(outcome).then(
3453
+ () => done(() => {
3454
+ if (destroyed || seq2 !== renderSeq) {
3455
+ resolve();
3456
+ return;
3457
+ }
3458
+ runPostRenderPasses();
3459
+ resolve();
3460
+ }),
3461
+ (err) => done(() => {
3462
+ if (destroyed || seq2 !== renderSeq) {
3463
+ resolve();
3464
+ return;
3465
+ }
3466
+ const fatal = renderFailure(err);
3467
+ if (fatal) reject(fatal);
3468
+ else resolve();
3469
+ })
3470
+ );
3471
+ }));
3472
+ },
3473
+ setOptions(partial) {
3474
+ raw = { ...raw, ...partial };
3475
+ resolved = resolveForHost(raw);
3476
+ host.render();
3477
+ },
3478
+ setData(next) {
3479
+ data = next;
3480
+ host.render();
3481
+ },
3482
+ get options() {
3483
+ return resolved;
3484
+ },
3485
+ setAnnotations(list) {
3486
+ annotationLayer.set(list);
3487
+ if (!destroyed) try {
3488
+ drawAnnotations();
3489
+ } catch {
3490
+ }
3491
+ },
3492
+ get annotationReport() {
3493
+ return annotationLayer.report;
3494
+ },
3495
+ // A GETTER, not a captured value: `rendered` names the LATEST render, so a host that
3496
+ // read it once would otherwise hold the promise of a draw long since replaced.
3497
+ get rendered() {
3498
+ return renderedPromise;
3499
+ },
3500
+ selection: {
3501
+ onChange(cb) {
3502
+ subs.add(cb);
3503
+ return () => subs.delete(cb);
3504
+ },
3505
+ clear() {
3506
+ const s = container[CONTAINER_SLOT_XF_CLEAR];
3507
+ if (typeof s === "function") {
3508
+ try {
3509
+ s();
3510
+ } catch {
3511
+ }
3512
+ }
3513
+ setClickedMarks(null);
3514
+ notify([], "host");
3515
+ },
3516
+ highlight(rowIdxs) {
3517
+ notify(Array.isArray(rowIdxs) ? rowIdxs.slice() : [], "host");
3518
+ },
3519
+ get current() {
3520
+ return current;
3521
+ }
3522
+ },
3523
+ animation: { stop: stopAnim },
3524
+ initialMark() {
3525
+ return container[CONTAINER_SLOT_INITIAL_XF_MARK] ?? null;
3526
+ },
3527
+ destroy() {
3528
+ if (destroyed) return;
3529
+ destroyed = true;
3530
+ stopAnim();
3531
+ container.removeEventListener(XFILTER_REFRESH_EVENT, onXf);
3532
+ container.removeEventListener("click", onClick);
3533
+ subs.clear();
3534
+ annotationLayer.destroy();
3535
+ unpinScrolledAxis(container);
3536
+ container.innerHTML = "";
3537
+ }
3538
+ };
3539
+ return host;
3540
+ }
3541
+
3542
+ // src/group.ts
3543
+ var EMPTY6 = Object.freeze({ sourceId: null, rows: Object.freeze([]) });
3544
+ function toSourceRows(rowMap, payloadRowIdxs) {
3545
+ if (!rowMap) return payloadRowIdxs.slice();
3546
+ return payloadRowIdxs.map((i) => rowMap[i]).filter((i) => i !== void 0);
3547
+ }
3548
+ function toPayloadRows(rowMap, sourceRowIdxs) {
3549
+ const at = /* @__PURE__ */ new Map();
3550
+ rowMap.forEach((src, k) => {
3551
+ if (!at.has(src)) at.set(src, k);
3552
+ });
3553
+ return sourceRowIdxs.map((s) => at.get(s)).filter((k) => k !== void 0);
3554
+ }
3555
+ var sameRows = (a, b) => {
3556
+ if (a.length !== b.length) return false;
3557
+ const x = a.slice().sort((p, q) => p - q), y = b.slice().sort((p, q) => p - q);
3558
+ return x.every((v, i) => v === y[i]);
3559
+ };
3560
+ function syncMemberSelection(host, selection, id, highlight, incoming, rowMap) {
3561
+ const mine = selection.sourceId === id && selection.rows.length > 0;
3562
+ if (mine) {
3563
+ if (rowMap) {
3564
+ const want = toPayloadRows(rowMap, selection.rows);
3565
+ if (!sameRows(want, host.selection.current ?? [])) host.selection.highlight(want);
3566
+ }
3567
+ return;
3568
+ }
3569
+ if (highlight && incoming && incoming.length) {
3570
+ host.selection.highlight(incoming.slice());
3571
+ return;
3572
+ }
3573
+ if (host.selection.current && host.selection.current.length) host.selection.clear();
3574
+ }
3575
+ function createChartGroup(columns, rows, opts = {}) {
3576
+ let cols = columns;
3577
+ let table = rows;
3578
+ let geo = opts.geo ?? null;
3579
+ let point = opts.point ?? null;
3580
+ let destination = opts.destination ?? null;
3581
+ let selection = EMPTY6;
3582
+ const subs = /* @__PURE__ */ new Set();
3583
+ const members = /* @__PURE__ */ new Set();
3584
+ const notify = (change) => {
3585
+ for (const cb of Array.from(subs)) cb(selection, change);
3586
+ };
3587
+ const incomingFor = (id, filteredBy) => {
3588
+ const s = selection;
3589
+ const forId = s.rows.length && s.sourceId && s.sourceId !== id ? s.rows.slice() : null;
3590
+ if (filteredBy) return s.sourceId === filteredBy ? forId : null;
3591
+ return forId;
3592
+ };
3593
+ const payloadFor = (sourceRowIdxs) => {
3594
+ const idxs = sourceRowIdxs ? sourceRowIdxs.slice() : table.map((_, i) => i);
3595
+ const subset = idxs.map((i) => table[i]);
3596
+ const payload = buildRenderPayload(
3597
+ cols,
3598
+ subset,
3599
+ geo ?? void 0,
3600
+ point ?? void 0,
3601
+ destination ?? void 0
3602
+ );
3603
+ return { payload, rowMap: idxs };
3604
+ };
3605
+ const memberPayload = (id, o = {}) => payloadFor(o.respondsWith === "highlight" ? null : incomingFor(id, o.filteredBy));
3606
+ const group = {
3607
+ get selection() {
3608
+ return selection;
3609
+ },
3610
+ get columns() {
3611
+ return cols;
3612
+ },
3613
+ get rows() {
3614
+ return table;
3615
+ },
3616
+ incomingFor,
3617
+ payloadFor,
3618
+ memberPayload,
3619
+ publish(id, sourceRowIdxs) {
3620
+ selection = sourceRowIdxs.length ? { sourceId: id, rows: sourceRowIdxs.slice() } : { sourceId: null, rows: [] };
3621
+ notify(sourceRowIdxs.length ? "select" : "clear");
3622
+ },
3623
+ clear() {
3624
+ selection = { sourceId: null, rows: [] };
3625
+ notify("clear");
3626
+ },
3627
+ setSource(nextColumns, nextRows, o = {}) {
3628
+ cols = nextColumns;
3629
+ table = nextRows;
3630
+ geo = o.geo ?? null;
3631
+ point = o.point ?? null;
3632
+ destination = o.destination ?? null;
3633
+ notify("source");
3634
+ },
3635
+ onChange(cb) {
3636
+ subs.add(cb);
3637
+ return () => {
3638
+ subs.delete(cb);
3639
+ };
3640
+ },
3641
+ attach(id, host, o = {}) {
3642
+ const highlight = o.respondsWith === "highlight";
3643
+ let rowMap = memberPayload(id, o).rowMap;
3644
+ const filterKey = (incoming) => highlight || !incoming ? "*" : incoming.join(",");
3645
+ let drawnUnder = filterKey(incomingFor(id, o.filteredBy));
3646
+ const offHost = host.selection.onChange((payloadIdxs, source) => {
3647
+ if (source === "host") return;
3648
+ const sourceIdxs = toSourceRows(rowMap, payloadIdxs);
3649
+ if (id) group.publish(id, sourceIdxs);
3650
+ o.onSelect?.(sourceIdxs);
3651
+ });
3652
+ const offGroup = group.onChange((sel, change) => {
3653
+ const incoming = incomingFor(id, o.filteredBy);
3654
+ const key = filterKey(incoming);
3655
+ if (change === "source" || key !== drawnUnder) {
3656
+ const next = payloadFor(highlight ? null : incoming);
3657
+ rowMap = next.rowMap;
3658
+ drawnUnder = key;
3659
+ host.setData(next.payload);
3660
+ }
3661
+ syncMemberSelection(host, sel, id, highlight, incoming, rowMap);
3662
+ });
3663
+ const member = {
3664
+ get rowMap() {
3665
+ return rowMap;
3666
+ },
3667
+ detach() {
3668
+ offHost();
3669
+ offGroup();
3670
+ members.delete(member);
3671
+ }
3672
+ };
3673
+ members.add(member);
3674
+ return member;
3675
+ },
3676
+ destroy() {
3677
+ for (const m of Array.from(members)) m.detach();
3678
+ subs.clear();
3679
+ }
3680
+ };
3681
+ return group;
3682
+ }
3683
+
3684
+ export {
3685
+ DARK_TEXT,
3686
+ LIGHT_TEXT,
3687
+ MIN_CONTRAST,
3688
+ WHITE_TEXT_BG_LUM,
3689
+ PILL_MIN_ALPHA,
3690
+ PILL_OPAQUE_ALPHA,
3691
+ PAGE_MATCH_TOLERANCE,
3692
+ isPillBackdropAlpha,
3693
+ PILL_MIN_COVERAGE,
3694
+ pillBacksGlyph,
3695
+ BACKING_MAJORITY,
3696
+ backingHoldsGlyph,
3697
+ cellSuppressesNormalize,
3698
+ toRGBA,
3699
+ compositeOver,
3700
+ relativeLuminance,
3701
+ contrastRatio,
3702
+ decideLabelColor,
3703
+ GLYPH_SAMPLE_N,
3704
+ glyphSampleGrid,
3705
+ STRADDLE_COLS,
3706
+ STRADDLE_ROWS,
3707
+ STRADDLE_MIN_SHARE,
3708
+ STRADDLE_TARGET_CONTRAST,
3709
+ STRADDLE_PILL_ALPHA,
3710
+ straddleSampleGrid,
3711
+ straddles,
3712
+ minContrastOver,
3713
+ pickColorOver,
3714
+ deltaEHex,
3715
+ SAME_SHADE_DELTA_E,
3716
+ MIN_RAMP_FILLS,
3717
+ censusColourSpread,
3718
+ colourSpreadFlag,
3719
+ AUTO_RAMP_CLASSES,
3720
+ AUTO_RAMP_DRAWN_OPACITY,
3721
+ AUTO_RAMP_MIN_STEP_DELTA_E,
3722
+ autoRampMinStep,
3723
+ autoRampCanvas,
3724
+ resolveAutoColorScale,
3725
+ resolveOptions,
3726
+ geoAssetFor,
3727
+ loadGeo,
3728
+ geoFromCache,
3729
+ registerGeo,
3730
+ registerGeoAsset,
3731
+ clearGeoCache,
3732
+ buildRenderPayload,
3733
+ stripJsComments,
3734
+ rowIdxsFromMark,
3735
+ nextSelection,
3736
+ selectionRuleOf,
3737
+ markKeyOf,
3738
+ sameRowSet,
3739
+ planSelectionPaint,
3740
+ nextClickedMarks,
3741
+ isInsideControl,
3742
+ createMarkResolver,
3743
+ collectD3GlyphCenters,
3744
+ SELECTION_GLYPH_CLASS,
3745
+ clearSelectionGlyphs,
3746
+ paintSelectionGlyphs,
3747
+ ensureCrossfilterHitTargets,
3748
+ censusMarks,
3749
+ isBlankRender,
3750
+ blankRenderFlag,
3751
+ MIN_HIT_BAND_PX,
3752
+ censusHitBands,
3753
+ hitBandFlag,
3754
+ CLIP_TOLERANCE_PX,
3755
+ censusClippedText,
3756
+ clippedTextFlag,
3757
+ VALUE_AXIS_MIN_FIT,
3758
+ censusValuePlacement,
3759
+ valuePlacementFlag,
3760
+ SCROLL_SLACK_PX,
3761
+ needsScroll,
3762
+ contentExtentOf,
3763
+ scrollFitFor,
3764
+ FIT_CONTENT_SELECTOR,
3765
+ PHANTOM_FRACTION,
3766
+ isPhantomBox,
3767
+ MAX_FRAME_GROW_FACTOR,
3768
+ planFrameGrow,
3769
+ AXIS_PIN_MIN_LABELS,
3770
+ AXIS_PIN_BAND_PAD_PX,
3771
+ AXIS_PIN_MAX_BAND_FRACTION,
3772
+ AXIS_PIN_TRACK_REACH_PX,
3773
+ isHorizontalLabelRow,
3774
+ labelBand,
3775
+ planAxisPin,
3776
+ axisPinPlacement,
3777
+ ctmScaleOf,
3778
+ svgInkReach,
3779
+ measureContainerBoxes,
3780
+ fitReadingFor,
3781
+ unpinScrolledAxis,
3782
+ pinScrolledAxis,
3783
+ fitRenderedChart,
3784
+ LABEL_CONTRAST_DONE_ATTR,
3785
+ LABEL_CONTRAST_CAP,
3786
+ LABEL_PILL_CLASS,
3787
+ applyLabelContrast,
3788
+ newIdScope,
3789
+ scopeChartIds,
3790
+ isInvalidSentinelError,
3791
+ invalidSentinelReason,
3792
+ ANNOTATION_LAYER_CLASS,
3793
+ ANNOTATION_CLASS,
3794
+ sessionViewStateProvider,
3795
+ noopViewStateProvider,
3796
+ assembleD3,
3797
+ D3_PLUGIN_PACKAGES,
3798
+ requiredD3Plugins,
3799
+ explainRenderFailure,
3800
+ stripEsmExports,
3801
+ compileRenderFn,
3802
+ createChartHost,
3803
+ toSourceRows,
3804
+ syncMemberSelection,
3805
+ createChartGroup
3806
+ };