@bicharts/chart-host 0.6.61 → 0.6.63

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.
@@ -475,6 +475,170 @@ var require_papaparse_min = __commonJS({
475
475
  }
476
476
  });
477
477
 
478
+ // src/labelContrast.ts
479
+ function parseRGBA(c2) {
480
+ if (typeof c2 !== "string") return null;
481
+ const s2 = c2.trim();
482
+ const m4 = s2.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i);
483
+ if (m4) return [parseFloat(m4[1]), parseFloat(m4[2]), parseFloat(m4[3]), m4[4] !== void 0 ? parseFloat(m4[4]) : 1];
484
+ let h4 = s2.replace(/^#/, "");
485
+ if (h4.length === 3) h4 = h4.split("").map((ch) => ch + ch).join("");
486
+ if (h4.length === 6 && /^[0-9a-fA-F]{6}$/.test(h4)) {
487
+ return [parseInt(h4.slice(0, 2), 16), parseInt(h4.slice(2, 4), 16), parseInt(h4.slice(4, 6), 16), 1];
488
+ }
489
+ return null;
490
+ }
491
+ var DARK_TEXT = "#111111";
492
+ var LIGHT_TEXT = "#ffffff";
493
+ var MIN_CONTRAST = 3;
494
+ var WHITE_TEXT_BG_LUM = 0.5;
495
+ var PILL_MIN_ALPHA = 0.05;
496
+ var PILL_OPAQUE_ALPHA = 0.85;
497
+ var PAGE_MATCH_TOLERANCE = 2;
498
+ function isPillBackdropAlpha(a3) {
499
+ return a3 >= PILL_MIN_ALPHA && a3 < PILL_OPAQUE_ALPHA;
500
+ }
501
+ var PILL_MIN_COVERAGE = 0.6;
502
+ function pillBacksGlyph(overlapArea, glyphArea) {
503
+ if (!(glyphArea > 0) || !isFinite(overlapArea) || overlapArea <= 0) return false;
504
+ return overlapArea / glyphArea >= PILL_MIN_COVERAGE;
505
+ }
506
+ var BACKING_MAJORITY = 0.5;
507
+ function backingHoldsGlyph(backedArea, glyphArea) {
508
+ if (!(glyphArea > 0) || !isFinite(backedArea) || backedArea <= 0) return false;
509
+ return backedArea / glyphArea >= BACKING_MAJORITY;
510
+ }
511
+ function cellSuppressesNormalize(cellClass) {
512
+ return typeof cellClass === "string" && /(^|\s)lch-deck-panel(\s|$)/.test(cellClass);
513
+ }
514
+ var NAMED = {
515
+ white: [255, 255, 255, 1],
516
+ black: [0, 0, 0, 1],
517
+ none: [0, 0, 0, 0],
518
+ transparent: [0, 0, 0, 0]
519
+ };
520
+ function toRGBA(c2) {
521
+ if (typeof c2 !== "string") return null;
522
+ const k3 = c2.trim().toLowerCase();
523
+ if (k3 in NAMED) return NAMED[k3];
524
+ return parseRGBA(c2);
525
+ }
526
+ function compositeOver(fg, bg) {
527
+ const a3 = Math.max(0, Math.min(1, fg[3]));
528
+ return [
529
+ Math.round(fg[0] * a3 + bg[0] * (1 - a3)),
530
+ Math.round(fg[1] * a3 + bg[1] * (1 - a3)),
531
+ Math.round(fg[2] * a3 + bg[2] * (1 - a3))
532
+ ];
533
+ }
534
+ function relativeLuminance(rgb) {
535
+ const lin = rgb.map((v) => {
536
+ const s2 = v / 255;
537
+ return s2 <= 0.03928 ? s2 / 12.92 : Math.pow((s2 + 0.055) / 1.055, 2.4);
538
+ });
539
+ return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
540
+ }
541
+ function contrastRatio(l1, l2) {
542
+ const hi = Math.max(l1, l2);
543
+ const lo = Math.min(l1, l2);
544
+ return (hi + 0.05) / (lo + 0.05);
545
+ }
546
+ function decideLabelColor(textColor, fill, fillOpacity, pageBg, minContrast = MIN_CONTRAST, normalize = false) {
547
+ const fillRGBA = toRGBA(fill);
548
+ if (!fillRGBA) return { fix: false, color: "", reason: "fill-unparsed" };
549
+ const pageRGBA = toRGBA(pageBg) || [255, 255, 255, 1];
550
+ const pageRGB = [pageRGBA[0], pageRGBA[1], pageRGBA[2]];
551
+ const effFill = [fillRGBA[0], fillRGBA[1], fillRGBA[2], fillRGBA[3] * clamp01(fillOpacity)];
552
+ const bgRGB = compositeOver(effFill, pageRGB);
553
+ const bgLum = relativeLuminance(bgRGB);
554
+ 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;
555
+ const effNormalize = normalize && !bgIsPage;
556
+ const textRGBA = toRGBA(textColor);
557
+ if (!effNormalize && textRGBA) {
558
+ const txtRGB = compositeOver(textRGBA, bgRGB);
559
+ const cur = contrastRatio(relativeLuminance(txtRGB), bgLum);
560
+ if (cur >= minContrast) return { fix: false, color: "", reason: "ok" };
561
+ }
562
+ const darkC = contrastRatio(relativeLuminance(hexToRGB(DARK_TEXT)), bgLum);
563
+ const lightC = contrastRatio(relativeLuminance(hexToRGB(LIGHT_TEXT)), bgLum);
564
+ let useLight = bgLum <= WHITE_TEXT_BG_LUM;
565
+ if (useLight && lightC < minContrast && darkC >= minContrast) useLight = false;
566
+ if (!useLight && darkC < minContrast && lightC >= minContrast) useLight = true;
567
+ return useLight ? { fix: true, color: LIGHT_TEXT, reason: "fixed-light" } : { fix: true, color: DARK_TEXT, reason: "fixed-dark" };
568
+ }
569
+ var GLYPH_SAMPLE_N = 3;
570
+ function glyphSampleGrid(box, n = GLYPH_SAMPLE_N) {
571
+ const out = [];
572
+ if (!box || !(box.width > 0) || !(box.height > 0) || !(n >= 1)) return out;
573
+ const k3 = Math.floor(n);
574
+ for (let i = 0; i < k3; i++) {
575
+ for (let j4 = 0; j4 < k3; j4++) {
576
+ out.push({
577
+ x: box.left + box.width * (i + 0.5) / k3,
578
+ y: box.top + box.height * (j4 + 0.5) / k3
579
+ });
580
+ }
581
+ }
582
+ return out;
583
+ }
584
+ var STRADDLE_COLS = 8;
585
+ var STRADDLE_ROWS = 2;
586
+ var STRADDLE_MIN_SHARE = 0.125;
587
+ var STRADDLE_TARGET_CONTRAST = 4.5;
588
+ var STRADDLE_PILL_ALPHA = 0.9;
589
+ function straddleSampleGrid(box) {
590
+ const out = [];
591
+ if (!box || !(box.width > 0) || !(box.height > 0)) return out;
592
+ for (let i = 0; i < STRADDLE_COLS; i++) {
593
+ for (let j4 = 0; j4 < STRADDLE_ROWS; j4++) {
594
+ out.push({
595
+ x: box.left + box.width * (i + 0.5) / STRADDLE_COLS,
596
+ y: box.top + box.height * (j4 + 0.5) / STRADDLE_ROWS
597
+ });
598
+ }
599
+ }
600
+ return out;
601
+ }
602
+ function backgroundGroups(bgs, tol = 8) {
603
+ const groups = [];
604
+ for (const b4 of bgs || []) {
605
+ const g5 = groups.find((x3) => Math.abs(x3.rgb[0] - b4[0]) <= tol && Math.abs(x3.rgb[1] - b4[1]) <= tol && Math.abs(x3.rgb[2] - b4[2]) <= tol);
606
+ if (g5) g5.n++;
607
+ else groups.push({ rgb: [b4[0], b4[1], b4[2]], n: 1 });
608
+ }
609
+ return groups.sort((a3, b4) => b4.n - a3.n);
610
+ }
611
+ function straddles(bgs) {
612
+ if (!bgs || bgs.length < 2) return false;
613
+ const g5 = backgroundGroups(bgs);
614
+ return g5.length >= 2 && g5[1].n / bgs.length >= STRADDLE_MIN_SHARE;
615
+ }
616
+ function minContrastOver(textColor, bgs) {
617
+ const t = toRGBA(textColor);
618
+ if (!t || !bgs || !bgs.length) return 0;
619
+ let worst = Infinity;
620
+ for (const b4 of bgs) {
621
+ const tx = compositeOver(t, b4);
622
+ worst = Math.min(worst, contrastRatio(relativeLuminance(tx), relativeLuminance(b4)));
623
+ }
624
+ return worst;
625
+ }
626
+ function pickColorOver(textColor, bgs, target = STRADDLE_TARGET_CONTRAST) {
627
+ const cur = minContrastOver(textColor, bgs);
628
+ if (cur >= target) return { color: String(textColor), contrast: cur, keep: true };
629
+ const d2 = minContrastOver(DARK_TEXT, bgs), l2 = minContrastOver(LIGHT_TEXT, bgs);
630
+ const best = d2 >= l2 ? { color: DARK_TEXT, contrast: d2 } : { color: LIGHT_TEXT, contrast: l2 };
631
+ return best.contrast > cur ? { ...best, keep: false } : { color: String(textColor), contrast: cur, keep: true };
632
+ }
633
+ function clamp01(v) {
634
+ if (typeof v !== "number" || !isFinite(v)) return 1;
635
+ return Math.max(0, Math.min(1, v));
636
+ }
637
+ function hexToRGB(hex2) {
638
+ const p4 = toRGBA(hex2);
639
+ return [p4[0], p4[1], p4[2]];
640
+ }
641
+
478
642
  // src/deltaE.ts
479
643
  function linearise(c2) {
480
644
  const v = c2 / 255;
@@ -776,6 +940,12 @@ function resolveAutoColorScale(p4) {
776
940
  }
777
941
 
778
942
  // src/defaults.ts
943
+ function themedLand(p4) {
944
+ const bg = p4.geoLandColor ? null : toRGBA(p4.backgroundColor);
945
+ if (!bg || bg[3] < 1 || relativeLuminance([bg[0], bg[1], bg[2]]) > 0.2) return void 0;
946
+ const fg = toRGBA(p4.themeFg) ?? [255, 255, 255, 1];
947
+ 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("");
948
+ }
779
949
  var clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
780
950
  var numberOr = (raw, dflt) => {
781
951
  const n = raw == null || raw === "" ? NaN : Number(raw);
@@ -846,7 +1016,7 @@ function resolveOptions(p4) {
846
1016
  // Map fills: `raw || undefined` like the colour-scale endpoints. The no-data ->
847
1017
  // land cascade is left to the chart (so the fallback chain is visible in the
848
1018
  // generated code); HC substitution has already happened in the caller.
849
- geoLandColor: p4.geoLandColor || void 0,
1019
+ geoLandColor: p4.geoLandColor || themedLand(p4) || void 0,
850
1020
  geoNoDataColor: p4.geoNoDataColor || void 0,
851
1021
  aggregation: (p4.aggregation == null ? "" : String(p4.aggregation)) || void 0,
852
1022
  // A recognised value wins, anything else falls to the default: an unknown string must
@@ -3343,170 +3513,6 @@ function fitRenderedChart(container, opts = {}) {
3343
3513
  return result;
3344
3514
  }
3345
3515
 
3346
- // src/labelContrast.ts
3347
- function parseRGBA(c2) {
3348
- if (typeof c2 !== "string") return null;
3349
- const s2 = c2.trim();
3350
- const m4 = s2.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i);
3351
- if (m4) return [parseFloat(m4[1]), parseFloat(m4[2]), parseFloat(m4[3]), m4[4] !== void 0 ? parseFloat(m4[4]) : 1];
3352
- let h4 = s2.replace(/^#/, "");
3353
- if (h4.length === 3) h4 = h4.split("").map((ch) => ch + ch).join("");
3354
- if (h4.length === 6 && /^[0-9a-fA-F]{6}$/.test(h4)) {
3355
- return [parseInt(h4.slice(0, 2), 16), parseInt(h4.slice(2, 4), 16), parseInt(h4.slice(4, 6), 16), 1];
3356
- }
3357
- return null;
3358
- }
3359
- var DARK_TEXT = "#111111";
3360
- var LIGHT_TEXT = "#ffffff";
3361
- var MIN_CONTRAST = 3;
3362
- var WHITE_TEXT_BG_LUM = 0.5;
3363
- var PILL_MIN_ALPHA = 0.05;
3364
- var PILL_OPAQUE_ALPHA = 0.85;
3365
- var PAGE_MATCH_TOLERANCE = 2;
3366
- function isPillBackdropAlpha(a3) {
3367
- return a3 >= PILL_MIN_ALPHA && a3 < PILL_OPAQUE_ALPHA;
3368
- }
3369
- var PILL_MIN_COVERAGE = 0.6;
3370
- function pillBacksGlyph(overlapArea, glyphArea) {
3371
- if (!(glyphArea > 0) || !isFinite(overlapArea) || overlapArea <= 0) return false;
3372
- return overlapArea / glyphArea >= PILL_MIN_COVERAGE;
3373
- }
3374
- var BACKING_MAJORITY = 0.5;
3375
- function backingHoldsGlyph(backedArea, glyphArea) {
3376
- if (!(glyphArea > 0) || !isFinite(backedArea) || backedArea <= 0) return false;
3377
- return backedArea / glyphArea >= BACKING_MAJORITY;
3378
- }
3379
- function cellSuppressesNormalize(cellClass) {
3380
- return typeof cellClass === "string" && /(^|\s)lch-deck-panel(\s|$)/.test(cellClass);
3381
- }
3382
- var NAMED = {
3383
- white: [255, 255, 255, 1],
3384
- black: [0, 0, 0, 1],
3385
- none: [0, 0, 0, 0],
3386
- transparent: [0, 0, 0, 0]
3387
- };
3388
- function toRGBA(c2) {
3389
- if (typeof c2 !== "string") return null;
3390
- const k3 = c2.trim().toLowerCase();
3391
- if (k3 in NAMED) return NAMED[k3];
3392
- return parseRGBA(c2);
3393
- }
3394
- function compositeOver(fg, bg) {
3395
- const a3 = Math.max(0, Math.min(1, fg[3]));
3396
- return [
3397
- Math.round(fg[0] * a3 + bg[0] * (1 - a3)),
3398
- Math.round(fg[1] * a3 + bg[1] * (1 - a3)),
3399
- Math.round(fg[2] * a3 + bg[2] * (1 - a3))
3400
- ];
3401
- }
3402
- function relativeLuminance(rgb) {
3403
- const lin = rgb.map((v) => {
3404
- const s2 = v / 255;
3405
- return s2 <= 0.03928 ? s2 / 12.92 : Math.pow((s2 + 0.055) / 1.055, 2.4);
3406
- });
3407
- return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
3408
- }
3409
- function contrastRatio(l1, l2) {
3410
- const hi = Math.max(l1, l2);
3411
- const lo = Math.min(l1, l2);
3412
- return (hi + 0.05) / (lo + 0.05);
3413
- }
3414
- function decideLabelColor(textColor, fill, fillOpacity, pageBg, minContrast = MIN_CONTRAST, normalize = false) {
3415
- const fillRGBA = toRGBA(fill);
3416
- if (!fillRGBA) return { fix: false, color: "", reason: "fill-unparsed" };
3417
- const pageRGBA = toRGBA(pageBg) || [255, 255, 255, 1];
3418
- const pageRGB = [pageRGBA[0], pageRGBA[1], pageRGBA[2]];
3419
- const effFill = [fillRGBA[0], fillRGBA[1], fillRGBA[2], fillRGBA[3] * clamp01(fillOpacity)];
3420
- const bgRGB = compositeOver(effFill, pageRGB);
3421
- const bgLum = relativeLuminance(bgRGB);
3422
- 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;
3423
- const effNormalize = normalize && !bgIsPage;
3424
- const textRGBA = toRGBA(textColor);
3425
- if (!effNormalize && textRGBA) {
3426
- const txtRGB = compositeOver(textRGBA, bgRGB);
3427
- const cur = contrastRatio(relativeLuminance(txtRGB), bgLum);
3428
- if (cur >= minContrast) return { fix: false, color: "", reason: "ok" };
3429
- }
3430
- const darkC = contrastRatio(relativeLuminance(hexToRGB(DARK_TEXT)), bgLum);
3431
- const lightC = contrastRatio(relativeLuminance(hexToRGB(LIGHT_TEXT)), bgLum);
3432
- let useLight = bgLum <= WHITE_TEXT_BG_LUM;
3433
- if (useLight && lightC < minContrast && darkC >= minContrast) useLight = false;
3434
- if (!useLight && darkC < minContrast && lightC >= minContrast) useLight = true;
3435
- return useLight ? { fix: true, color: LIGHT_TEXT, reason: "fixed-light" } : { fix: true, color: DARK_TEXT, reason: "fixed-dark" };
3436
- }
3437
- var GLYPH_SAMPLE_N = 3;
3438
- function glyphSampleGrid(box, n = GLYPH_SAMPLE_N) {
3439
- const out = [];
3440
- if (!box || !(box.width > 0) || !(box.height > 0) || !(n >= 1)) return out;
3441
- const k3 = Math.floor(n);
3442
- for (let i = 0; i < k3; i++) {
3443
- for (let j4 = 0; j4 < k3; j4++) {
3444
- out.push({
3445
- x: box.left + box.width * (i + 0.5) / k3,
3446
- y: box.top + box.height * (j4 + 0.5) / k3
3447
- });
3448
- }
3449
- }
3450
- return out;
3451
- }
3452
- var STRADDLE_COLS = 8;
3453
- var STRADDLE_ROWS = 2;
3454
- var STRADDLE_MIN_SHARE = 0.125;
3455
- var STRADDLE_TARGET_CONTRAST = 4.5;
3456
- var STRADDLE_PILL_ALPHA = 0.9;
3457
- function straddleSampleGrid(box) {
3458
- const out = [];
3459
- if (!box || !(box.width > 0) || !(box.height > 0)) return out;
3460
- for (let i = 0; i < STRADDLE_COLS; i++) {
3461
- for (let j4 = 0; j4 < STRADDLE_ROWS; j4++) {
3462
- out.push({
3463
- x: box.left + box.width * (i + 0.5) / STRADDLE_COLS,
3464
- y: box.top + box.height * (j4 + 0.5) / STRADDLE_ROWS
3465
- });
3466
- }
3467
- }
3468
- return out;
3469
- }
3470
- function backgroundGroups(bgs, tol = 8) {
3471
- const groups = [];
3472
- for (const b4 of bgs || []) {
3473
- const g5 = groups.find((x3) => Math.abs(x3.rgb[0] - b4[0]) <= tol && Math.abs(x3.rgb[1] - b4[1]) <= tol && Math.abs(x3.rgb[2] - b4[2]) <= tol);
3474
- if (g5) g5.n++;
3475
- else groups.push({ rgb: [b4[0], b4[1], b4[2]], n: 1 });
3476
- }
3477
- return groups.sort((a3, b4) => b4.n - a3.n);
3478
- }
3479
- function straddles(bgs) {
3480
- if (!bgs || bgs.length < 2) return false;
3481
- const g5 = backgroundGroups(bgs);
3482
- return g5.length >= 2 && g5[1].n / bgs.length >= STRADDLE_MIN_SHARE;
3483
- }
3484
- function minContrastOver(textColor, bgs) {
3485
- const t = toRGBA(textColor);
3486
- if (!t || !bgs || !bgs.length) return 0;
3487
- let worst = Infinity;
3488
- for (const b4 of bgs) {
3489
- const tx = compositeOver(t, b4);
3490
- worst = Math.min(worst, contrastRatio(relativeLuminance(tx), relativeLuminance(b4)));
3491
- }
3492
- return worst;
3493
- }
3494
- function pickColorOver(textColor, bgs, target = STRADDLE_TARGET_CONTRAST) {
3495
- const cur = minContrastOver(textColor, bgs);
3496
- if (cur >= target) return { color: String(textColor), contrast: cur, keep: true };
3497
- const d2 = minContrastOver(DARK_TEXT, bgs), l2 = minContrastOver(LIGHT_TEXT, bgs);
3498
- const best = d2 >= l2 ? { color: DARK_TEXT, contrast: d2 } : { color: LIGHT_TEXT, contrast: l2 };
3499
- return best.contrast > cur ? { ...best, keep: false } : { color: String(textColor), contrast: cur, keep: true };
3500
- }
3501
- function clamp01(v) {
3502
- if (typeof v !== "number" || !isFinite(v)) return 1;
3503
- return Math.max(0, Math.min(1, v));
3504
- }
3505
- function hexToRGB(hex2) {
3506
- const p4 = toRGBA(hex2);
3507
- return [p4[0], p4[1], p4[2]];
3508
- }
3509
-
3510
3516
  // src/labelContrastDom.ts
3511
3517
  var LABEL_CONTRAST_DONE_ATTR = "data-lch-contrast";
3512
3518
  var LABEL_CONTRAST_CAP = 2e3;
@@ -3830,6 +3836,47 @@ function applyLabelContrast(container, opts = {}) {
3830
3836
  }
3831
3837
  }
3832
3838
 
3839
+ // src/idScope.ts
3840
+ var seq = 0;
3841
+ function newIdScope() {
3842
+ seq += 1;
3843
+ return "h" + seq;
3844
+ }
3845
+ var URL_REF = /url\(\s*(['"]?)#([^'")\s]+)\1\s*\)/g;
3846
+ function scopeChartIds(c2, scope) {
3847
+ const r2 = { renamed: 0, collisions: [] };
3848
+ try {
3849
+ const tag = "--" + scope, doc = c2.ownerDocument, map = /* @__PURE__ */ new Map();
3850
+ const refs = [], used = /* @__PURE__ */ new Set();
3851
+ c2.querySelectorAll("*").forEach((el) => {
3852
+ for (const a3 of el.getAttributeNames()) {
3853
+ const v = el.getAttribute(a3);
3854
+ const href = (a3 === "href" || a3 === "xlink:href") && v[0] === "#";
3855
+ if (!href && v.indexOf("url(") < 0) continue;
3856
+ refs.push([el, a3, v]);
3857
+ if (href) used.add(v.slice(1));
3858
+ for (const m4 of v.matchAll(URL_REF)) used.add(m4[2]);
3859
+ }
3860
+ });
3861
+ c2.querySelectorAll("[id]").forEach((el) => {
3862
+ const id = el.id;
3863
+ if (!used.has(id) || id.endsWith(tag)) return;
3864
+ const q2 = id.replace(/["\\]/g, "\\$&");
3865
+ if (doc && Array.from(doc.querySelectorAll(`[id="${q2}"],[id^="${q2}--h"]`)).some((o4) => !c2.contains(o4))) {
3866
+ r2.collisions.push(id);
3867
+ }
3868
+ map.set(id, el.id = id + tag);
3869
+ r2.renamed++;
3870
+ });
3871
+ for (const [el, a3, v] of refs) {
3872
+ const nv = v[0] === "#" && map.has(v.slice(1)) ? "#" + map.get(v.slice(1)) : v.replace(URL_REF, (m4, q2, id) => map.has(id) ? `url(${q2}#${map.get(id)}${q2})` : m4);
3873
+ if (nv !== v) el.setAttribute(a3, nv);
3874
+ }
3875
+ } catch {
3876
+ }
3877
+ return r2;
3878
+ }
3879
+
3833
3880
  // src/invalidSentinel.ts
3834
3881
  function isInvalidSentinelError(text2) {
3835
3882
  if (!text2) return false;
@@ -4136,6 +4183,8 @@ function createChartHost(container, config) {
4136
4183
  let destroyed = false;
4137
4184
  let warnedGeo = false;
4138
4185
  let warnedBlank = false;
4186
+ let warnedIds = false;
4187
+ const idScope = newIdScope();
4139
4188
  let renderSeq = 0;
4140
4189
  const annotationLayer = createAnnotationLayer(container, (a3) => config.onAnnotationClick?.(a3));
4141
4190
  const drawAnnotations = () => annotationLayer.draw(
@@ -4307,6 +4356,20 @@ function createChartHost(container, config) {
4307
4356
  }
4308
4357
  };
4309
4358
  const runPostRenderPasses = () => {
4359
+ try {
4360
+ const r2 = scopeChartIds(container, idScope);
4361
+ if (r2.collisions.length && !warnedIds) {
4362
+ warnedIds = true;
4363
+ try {
4364
+ (win?.console ?? console)?.warn(
4365
+ `[@bicharts/chart-host] ids shared with another chart, scoped: ${r2.collisions.slice(0, 5).join(", ")}`
4366
+ );
4367
+ } catch {
4368
+ }
4369
+ }
4370
+ config.onIdScope?.(r2);
4371
+ } catch {
4372
+ }
4310
4373
  if (labelContrastOpts !== false) {
4311
4374
  try {
4312
4375
  const lc = labelContrastOpts === true ? {} : { ...labelContrastOpts };
@@ -4386,7 +4449,7 @@ function createChartHost(container, config) {
4386
4449
  const host = {
4387
4450
  render() {
4388
4451
  if (destroyed) return;
4389
- const seq = ++renderSeq;
4452
+ const seq2 = ++renderSeq;
4390
4453
  stopAnim();
4391
4454
  container.innerHTML = "";
4392
4455
  if (!renderFn) {
@@ -4439,7 +4502,7 @@ function createChartHost(container, config) {
4439
4502
  };
4440
4503
  Promise.resolve(outcome).then(
4441
4504
  () => done(() => {
4442
- if (destroyed || seq !== renderSeq) {
4505
+ if (destroyed || seq2 !== renderSeq) {
4443
4506
  resolve();
4444
4507
  return;
4445
4508
  }
@@ -4447,7 +4510,7 @@ function createChartHost(container, config) {
4447
4510
  resolve();
4448
4511
  }),
4449
4512
  (err) => done(() => {
4450
- if (destroyed || seq !== renderSeq) {
4513
+ if (destroyed || seq2 !== renderSeq) {
4451
4514
  resolve();
4452
4515
  return;
4453
4516
  }
@@ -4669,6 +4732,35 @@ function createChartGroup(columns, rows, opts = {}) {
4669
4732
  }
4670
4733
 
4671
4734
  export {
4735
+ DARK_TEXT,
4736
+ LIGHT_TEXT,
4737
+ MIN_CONTRAST,
4738
+ WHITE_TEXT_BG_LUM,
4739
+ PILL_MIN_ALPHA,
4740
+ PILL_OPAQUE_ALPHA,
4741
+ PAGE_MATCH_TOLERANCE,
4742
+ isPillBackdropAlpha,
4743
+ PILL_MIN_COVERAGE,
4744
+ pillBacksGlyph,
4745
+ BACKING_MAJORITY,
4746
+ backingHoldsGlyph,
4747
+ cellSuppressesNormalize,
4748
+ toRGBA,
4749
+ compositeOver,
4750
+ relativeLuminance,
4751
+ contrastRatio,
4752
+ decideLabelColor,
4753
+ GLYPH_SAMPLE_N,
4754
+ glyphSampleGrid,
4755
+ STRADDLE_COLS,
4756
+ STRADDLE_ROWS,
4757
+ STRADDLE_MIN_SHARE,
4758
+ STRADDLE_TARGET_CONTRAST,
4759
+ STRADDLE_PILL_ALPHA,
4760
+ straddleSampleGrid,
4761
+ straddles,
4762
+ minContrastOver,
4763
+ pickColorOver,
4672
4764
  deltaEHex,
4673
4765
  SAME_SHADE_DELTA_E,
4674
4766
  MIN_RAMP_FILLS,
@@ -4740,39 +4832,12 @@ export {
4740
4832
  unpinScrolledAxis,
4741
4833
  pinScrolledAxis,
4742
4834
  fitRenderedChart,
4743
- DARK_TEXT,
4744
- LIGHT_TEXT,
4745
- MIN_CONTRAST,
4746
- WHITE_TEXT_BG_LUM,
4747
- PILL_MIN_ALPHA,
4748
- PILL_OPAQUE_ALPHA,
4749
- PAGE_MATCH_TOLERANCE,
4750
- isPillBackdropAlpha,
4751
- PILL_MIN_COVERAGE,
4752
- pillBacksGlyph,
4753
- BACKING_MAJORITY,
4754
- backingHoldsGlyph,
4755
- cellSuppressesNormalize,
4756
- toRGBA,
4757
- compositeOver,
4758
- relativeLuminance,
4759
- contrastRatio,
4760
- decideLabelColor,
4761
- GLYPH_SAMPLE_N,
4762
- glyphSampleGrid,
4763
- STRADDLE_COLS,
4764
- STRADDLE_ROWS,
4765
- STRADDLE_MIN_SHARE,
4766
- STRADDLE_TARGET_CONTRAST,
4767
- STRADDLE_PILL_ALPHA,
4768
- straddleSampleGrid,
4769
- straddles,
4770
- minContrastOver,
4771
- pickColorOver,
4772
4835
  LABEL_CONTRAST_DONE_ATTR,
4773
4836
  LABEL_CONTRAST_CAP,
4774
4837
  LABEL_PILL_CLASS,
4775
4838
  applyLabelContrast,
4839
+ newIdScope,
4840
+ scopeChartIds,
4776
4841
  isInvalidSentinelError,
4777
4842
  invalidSentinelReason,
4778
4843
  ANNOTATION_LAYER_CLASS,
package/dist/index.mjs CHANGED
@@ -91,6 +91,7 @@ import {
91
91
  measureContainerBoxes,
92
92
  minContrastOver,
93
93
  needsScroll,
94
+ newIdScope,
94
95
  nextSelection,
95
96
  noopViewStateProvider,
96
97
  o,
@@ -108,6 +109,7 @@ import {
108
109
  resolveAutoColorScale,
109
110
  resolveOptions,
110
111
  rowIdxsFromMark,
112
+ scopeChartIds,
111
113
  scrollFitFor,
112
114
  sessionViewStateProvider,
113
115
  straddleSampleGrid,
@@ -120,7 +122,7 @@ import {
120
122
  toSourceRows,
121
123
  unpinScrolledAxis,
122
124
  valuePlacementFlag
123
- } from "./chunk-Q6AGV652.mjs";
125
+ } from "./chunk-ZTTBWP3I.mjs";
124
126
  import "./chunk-A2GMXZP7.mjs";
125
127
  import {
126
128
  ACTIVE_TICK_CLASS,
@@ -3008,6 +3010,7 @@ export {
3008
3010
  minContrastOver,
3009
3011
  mountProductFeedbackBox,
3010
3012
  needsScroll,
3013
+ newIdScope,
3011
3014
  newQualifyGroupState,
3012
3015
  newQualifyRefusalGroupState,
3013
3016
  nextSelection,
@@ -3058,6 +3061,7 @@ export {
3058
3061
  resolveOptions,
3059
3062
  rowIdxsFromMark,
3060
3063
  sameMarker,
3064
+ scopeChartIds,
3061
3065
  scrollFitFor,
3062
3066
  servedCorrelationProves,
3063
3067
  sessionViewStateProvider,
package/dist/react.mjs CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  loadGeo,
23
23
  syncMemberSelection,
24
24
  toSourceRows
25
- } from "./chunk-Q6AGV652.mjs";
25
+ } from "./chunk-ZTTBWP3I.mjs";
26
26
  import "./chunk-A2GMXZP7.mjs";
27
27
  import "./chunk-WNGJUPEI.mjs";
28
28
  import "./chunk-GHODWOAL.mjs";
@@ -6,6 +6,7 @@ import { type ColourSpreadCensus } from "./colourSpread";
6
6
  import { type ValuePlacementCensus } from "./valuePlacement";
7
7
  import { type FitRenderedChartOptions, type FitRenderedChartResult } from "./fitDom";
8
8
  import { type LabelContrastOptions, type LabelContrastReport } from "./labelContrastDom";
9
+ import { type IdScopeReport } from "./idScope";
9
10
  import { type MarkAnnotation, type AnnotationReport } from "./annotations";
10
11
  export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
11
12
  /**
@@ -166,6 +167,11 @@ export interface ChartHostConfig {
166
167
  labelContrast?: boolean | LabelContrastOptions;
167
168
  /** What the label-contrast pass did after each render - counts, so a host can log them. */
168
169
  onLabelContrast?: (report: LabelContrastReport) => void;
170
+ /**
171
+ * What the id-scoping pass did after each render: how many of the chart's ids got this host's suffix, and which
172
+ * of them also existed elsewhere in the page (a clash it prevented - two same-size charts naming one clip path).
173
+ */
174
+ onIdScope?: (report: IdScopeReport) => void;
169
175
  /**
170
176
  * THE CHART SAID THE DATA LACKS SOMETHING IT IS BUILT ON. Generated code has one sanctioned
171
177
  * hard stop at runtime: a column the chart needs is gone from `data.columns`, and it throws
@@ -0,0 +1,9 @@
1
+ /** A fresh suffix for one chart host. Unique within the page for the page's lifetime. */
2
+ export declare function newIdScope(): string;
3
+ export interface IdScopeReport {
4
+ /** How many ids were given the suffix. */
5
+ renamed: number;
6
+ /** Ids that also existed outside this chart when the pass ran - each one a clash this pass prevented. */
7
+ collisions: string[];
8
+ }
9
+ export declare function scopeChartIds(c: Element, scope: string): IdScopeReport;
@@ -27,6 +27,7 @@ export { formatSourceDate, formatSourceDateFor, isExcelDateFormat, type SourceFo
27
27
  export { composeSummaryText } from "./summaryText";
28
28
  export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
29
29
  export { applyLabelContrast, LABEL_CONTRAST_DONE_ATTR, LABEL_CONTRAST_CAP, LABEL_PILL_CLASS, type LabelContrastOptions, type LabelContrastReport, } from "./labelContrastDom";
30
+ export { scopeChartIds, newIdScope, type IdScopeReport } from "./idScope";
30
31
  export { decideLabelColor, toRGBA, compositeOver, relativeLuminance, contrastRatio, isPillBackdropAlpha, pillBacksGlyph, backingHoldsGlyph, cellSuppressesNormalize, glyphSampleGrid, DARK_TEXT, LIGHT_TEXT, MIN_CONTRAST, WHITE_TEXT_BG_LUM, PILL_MIN_ALPHA, PILL_OPAQUE_ALPHA, PILL_MIN_COVERAGE, PAGE_MATCH_TOLERANCE, BACKING_MAJORITY, GLYPH_SAMPLE_N, straddles, straddleSampleGrid, minContrastOver, pickColorOver, STRADDLE_COLS, STRADDLE_ROWS, STRADDLE_MIN_SHARE, STRADDLE_TARGET_CONTRAST, STRADDLE_PILL_ALPHA, type LabelDecision, } from "./labelContrast";
31
32
  export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type BlankVerdictInput } from "./blankRender";
32
33
  export { isInvalidSentinelError, invalidSentinelReason } from "./invalidSentinel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.6.61",
3
+ "version": "0.6.63",
4
4
  "description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",