@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.
- package/dist/{chunk-Q6AGV652.mjs → chunk-ZTTBWP3I.mjs} +262 -197
- package/dist/index.mjs +5 -1
- package/dist/react.mjs +1 -1
- package/dist/types/host.d.ts +6 -0
- package/dist/types/idScope.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
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 ||
|
|
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 ||
|
|
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-
|
|
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
package/dist/types/host.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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.
|
|
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",
|