@bicharts/chart-host 0.5.44 → 0.5.46
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-BMYEYUEN.mjs → chunk-FC3XJPVX.mjs} +292 -158
- package/dist/index.mjs +73 -12
- package/dist/react.mjs +1 -1
- package/dist/types/blankRender.d.ts +60 -0
- package/dist/types/host.d.ts +21 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/selectionCard.d.ts +39 -4
- package/package.json +1 -1
|
@@ -433,20 +433,20 @@ function A3(n) {
|
|
|
433
433
|
if (r && i && c3(r) !== i && (r = null), n.city !== void 0 && n.city !== null) {
|
|
434
434
|
let a2 = u(String(n.city)), s = O(a2), f = s ? F(s, n.mapKind) : null;
|
|
435
435
|
if (f && f.length) {
|
|
436
|
-
let d = n.state === void 0 || n.state === null ? "" : String(n.state).trim(), R = d ? u(d) : "", g = d ? Y(d) : null,
|
|
436
|
+
let d = n.state === void 0 || n.state === null ? "" : String(n.state).trim(), R = d ? u(d) : "", g = d ? Y(d) : null, S = [], m2 = -1;
|
|
437
437
|
for (let c of f) {
|
|
438
438
|
let l = c.row, A = 0;
|
|
439
439
|
if (d && l.a1 !== "") if (g && l.a1 === g || u(l.a1) === R) A++;
|
|
440
440
|
else continue;
|
|
441
441
|
if (i) if (T(l.cc) === i) A++;
|
|
442
442
|
else continue;
|
|
443
|
-
A > m2 ? (m2 = A,
|
|
443
|
+
A > m2 ? (m2 = A, S = [c]) : A === m2 && S.push(c);
|
|
444
444
|
}
|
|
445
|
-
|
|
445
|
+
S.some((c) => c.primary) && (S = S.filter((c) => c.primary));
|
|
446
446
|
let p2 = /* @__PURE__ */ new Map();
|
|
447
|
-
for (let c of
|
|
447
|
+
for (let c of S) p2.set(c.row.lon.toFixed(2) + "," + c.row.lat.toFixed(2), c.row);
|
|
448
448
|
if (p2.size === 1) {
|
|
449
|
-
let c =
|
|
449
|
+
let c = S[0].row;
|
|
450
450
|
return { lat: c.lat, lon: c.lon, precision: "city" };
|
|
451
451
|
}
|
|
452
452
|
if (p2.size > 1) return { ambiguous: true, matches: p2.size };
|
|
@@ -469,26 +469,26 @@ function A3(n) {
|
|
|
469
469
|
var G = { latlon: 0, city: 1, zip3: 2, state: 3, country: 4 };
|
|
470
470
|
var x = 8;
|
|
471
471
|
function y3(n, e) {
|
|
472
|
-
let t = new Array(n.length), o = new Array(n.length), i = new Array(n.length), r = /* @__PURE__ */ new Set(), a2 = [], s = /* @__PURE__ */ new Set(), f = [], d = /* @__PURE__ */ new Set(), R = [], g = { latlon: 0, city: 0, zip3: 0, state: 0, country: 0 },
|
|
472
|
+
let t = new Array(n.length), o = new Array(n.length), i = new Array(n.length), r = /* @__PURE__ */ new Set(), a2 = [], s = /* @__PURE__ */ new Set(), f = [], d = /* @__PURE__ */ new Set(), R = [], g = { latlon: 0, city: 0, zip3: 0, state: 0, country: 0 }, S = 0, m2 = 0, p2 = null;
|
|
473
473
|
for (let l = 0; l < n.length; ++l) {
|
|
474
|
-
let A = n[l],
|
|
475
|
-
if (S3(
|
|
474
|
+
let A = n[l], C = A3({ ...A, mapKind: e });
|
|
475
|
+
if (S3(C)) {
|
|
476
476
|
t[l] = null, o[l] = null, i[l] = null, m2++;
|
|
477
|
-
let
|
|
478
|
-
|
|
479
|
-
} else if (
|
|
480
|
-
if (t[l] =
|
|
481
|
-
let
|
|
482
|
-
|
|
477
|
+
let M2 = K(A);
|
|
478
|
+
M2 && !d.has(M2.toLowerCase()) && (d.add(M2.toLowerCase()), R.length < x && R.push(M2));
|
|
479
|
+
} else if (C) {
|
|
480
|
+
if (t[l] = C.lat, o[l] = C.lon, i[l] = C.precision, S++, g[C.precision]++, (p2 === null || G[C.precision] > G[p2]) && (p2 = C.precision), G[C.precision] > G.city) {
|
|
481
|
+
let M2 = K(A);
|
|
482
|
+
M2 && !s.has(M2.toLowerCase()) && (s.add(M2.toLowerCase()), f.length < x && f.push(M2));
|
|
483
483
|
}
|
|
484
484
|
} else {
|
|
485
485
|
t[l] = null, o[l] = null, i[l] = null;
|
|
486
|
-
let
|
|
487
|
-
|
|
486
|
+
let M2 = K(A);
|
|
487
|
+
M2 && !r.has(M2.toLowerCase()) && (r.add(M2.toLowerCase()), a2.push(M2));
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
let c = !n3() && n.some((l) => l.city !== null && l.city !== void 0 && String(l.city).trim() !== "");
|
|
491
|
-
return { lat: t, lon: o, precisions: i, precision: p2, precisionCounts: g, coarseExamples: f, unmatched: a2, ambiguousRows: m2, ambiguousExamples: R, matchedRows:
|
|
491
|
+
return { lat: t, lon: o, precisions: i, precision: p2, precisionCounts: g, coarseExamples: f, unmatched: a2, ambiguousRows: m2, ambiguousExamples: R, matchedRows: S, totalRows: n.length, ...c ? { cityTableMissing: true } : {} };
|
|
492
492
|
}
|
|
493
493
|
function K(n) {
|
|
494
494
|
let e = [n.city, n.state, n.zip, n.country].filter((t) => t != null && String(t).trim() !== "").map((t) => String(t).trim()).join(", ");
|
|
@@ -569,13 +569,13 @@ function wn(n) {
|
|
|
569
569
|
}
|
|
570
570
|
function Kn(n, s) {
|
|
571
571
|
let i = new Array(n.length), r = /* @__PURE__ */ new Set(), A = [], y2 = 0;
|
|
572
|
-
for (let
|
|
573
|
-
let
|
|
574
|
-
if (i[
|
|
572
|
+
for (let S = 0; S < n.length; ++S) {
|
|
573
|
+
let O2 = hn(n[S], s);
|
|
574
|
+
if (i[S] = O2, O2 !== null) y2++;
|
|
575
575
|
else {
|
|
576
|
-
let
|
|
577
|
-
if (
|
|
578
|
-
let d = String(
|
|
576
|
+
let C = n[S];
|
|
577
|
+
if (C != null) {
|
|
578
|
+
let d = String(C).trim();
|
|
579
579
|
if (d.length > 0) {
|
|
580
580
|
let g = d.toLowerCase();
|
|
581
581
|
r.has(g) || (r.add(g), A.push(d));
|
|
@@ -588,142 +588,211 @@ function Kn(n, s) {
|
|
|
588
588
|
|
|
589
589
|
// ../shape-core/dist/index.mjs
|
|
590
590
|
/*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
591
|
-
var
|
|
592
|
-
var
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
591
|
+
var W2 = ["rate", "ratio", "pct", "percent", "percentage", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "z[_-]?score", "average", "avg", "mean", "median", "stddev", "variance", "gpa", "percentile", "quantile", "efficiency", "accuracy", "probability", "multiplier", "coefficient", "correlation", "occupancy", "age", "bmi", "temperature", "density", "pressure", "humidity", "ph", "velocity", "speed", "elevation", "altitude", "latency", "throughput", "bandwidth", "iops"];
|
|
592
|
+
var X2 = ["pct", "percent", "percentage", "rate", "ratio", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "gpa", "percentile", "quantile", "bmi", "density", "pressure", "humidity", "velocity", "efficiency", "occupancy", "latency", "throughput", "bandwidth", "iops"];
|
|
593
|
+
var Y2 = ["latitude", "longitude"];
|
|
594
|
+
var J2 = ["latitude", "longitude"];
|
|
595
|
+
var Q2 = /^\s*(sum|count|count\s+distinct|distinct\s+count)\s+of\s+/i;
|
|
596
|
+
var Ut = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])/g;
|
|
597
|
+
var Zt = new RegExp("\\b(" + W2.join("|") + ")\\b|(" + X2.join("|") + ")$", "i");
|
|
598
|
+
var qt = new RegExp("\\b(" + Y2.join("|") + ")\\b|(" + J2.join("|") + ")$", "i");
|
|
599
|
+
function M(e) {
|
|
600
|
+
return e ? String(e).replace(Q2, "").trim() : "";
|
|
601
|
+
}
|
|
602
|
+
function tt(e) {
|
|
603
|
+
return !!e && Q2.test(String(e));
|
|
604
|
+
}
|
|
605
|
+
function jt(e) {
|
|
606
|
+
let t = M(e);
|
|
607
|
+
return t.length === 0 ? "" : t.replace(Ut, " ");
|
|
608
|
+
}
|
|
609
|
+
function et(e, t) {
|
|
610
|
+
return t ? e.test(jt(t)) || e.test(M(t)) : false;
|
|
611
|
+
}
|
|
612
|
+
function L2(e) {
|
|
613
|
+
return et(qt, e);
|
|
614
|
+
}
|
|
615
|
+
function nt(e) {
|
|
616
|
+
return et(Zt, e) || L2(e);
|
|
617
|
+
}
|
|
618
|
+
var it = /int|double|decimal|single|float|number|currency|money/i;
|
|
619
|
+
function Wt(e) {
|
|
620
|
+
let t = String(e?.valueNature ?? "").trim().toLowerCase();
|
|
621
|
+
return t === "categorical" ? "Categorical" : t === "ordinal" ? "Ordinal" : t === "continuous" || e?.isMeasure === true || it.test(String(e?.dataType ?? "")) ? "Continuous" : "Categorical";
|
|
622
|
+
}
|
|
623
|
+
function Xt(e) {
|
|
624
|
+
return e?.isMeasure === true || it.test(String(e?.dataType ?? ""));
|
|
625
|
+
}
|
|
626
|
+
function z2(e) {
|
|
627
|
+
let t = e ?? {}, i = Wt(t);
|
|
628
|
+
if (i !== "Continuous") return { nature: i, additivity: "unknown", basis: "role", suppressed: false };
|
|
629
|
+
if (L2(t.name)) return { nature: i, additivity: "positional", basis: "name", suppressed: true };
|
|
630
|
+
let r = String(t.additivity ?? "").trim().toLowerCase();
|
|
631
|
+
if (!(r === "additive" && tt(t.name))) {
|
|
632
|
+
if (r === "additive") return { nature: i, additivity: "additive", basis: "measured", suppressed: false };
|
|
633
|
+
if (r === "part_of_whole") return { nature: i, additivity: "part_of_whole", basis: "measured", suppressed: false };
|
|
634
|
+
if (r === "intensive_rate") return { nature: i, additivity: "intensive_rate", basis: "measured", suppressed: false };
|
|
602
635
|
}
|
|
603
|
-
return
|
|
604
|
-
}
|
|
605
|
-
function
|
|
606
|
-
let
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
function E2(r) {
|
|
617
|
-
let n = P2(r);
|
|
618
|
-
if (n.length === 0) return 0;
|
|
619
|
-
let e = 0;
|
|
620
|
-
for (let s of n) N2(s) && e++;
|
|
621
|
-
return Math.round(e / n.length * 1e3) / 10;
|
|
622
|
-
}
|
|
623
|
-
function S(r) {
|
|
624
|
-
let n = P2(r);
|
|
625
|
-
if (n.length === 0) return 0;
|
|
626
|
-
let e = 0;
|
|
627
|
-
for (let s of n) Y(s) && e++;
|
|
628
|
-
return Math.round(e / n.length * 1e3) / 10;
|
|
629
|
-
}
|
|
630
|
-
function D2(r) {
|
|
631
|
-
let n = /* @__PURE__ */ new Set();
|
|
632
|
-
for (let e of r) {
|
|
633
|
-
if (e == null) continue;
|
|
634
|
-
let s = String(e).trim();
|
|
635
|
-
if (!V(u(s)) && !n.has(s) && (n.add(s), n.size >= j2)) break;
|
|
636
|
+
return t.discourageAggregationAcrossGroups === true ? { nature: i, additivity: "intensive_rate", basis: "host-flag", suppressed: false } : nt(t.name) ? { nature: i, additivity: "intensive_rate", basis: "name", suppressed: false } : { nature: i, additivity: "additive", basis: "default", suppressed: false };
|
|
637
|
+
}
|
|
638
|
+
function G2(e) {
|
|
639
|
+
let t = z2(e), i = e ?? {};
|
|
640
|
+
if (t.nature === "Categorical") return ["first", "distinctcount", "count"];
|
|
641
|
+
if (t.nature === "Ordinal") return Xt(i) ? ["median", "min", "max", "first", "distinctcount", "count"] : ["first", "distinctcount", "count"];
|
|
642
|
+
switch (t.additivity) {
|
|
643
|
+
case "positional":
|
|
644
|
+
return ["min", "max", "count", "distinctcount", "first"];
|
|
645
|
+
case "intensive_rate":
|
|
646
|
+
return ["average", "median", "min", "max", "count", "distinctcount", "first"];
|
|
647
|
+
default:
|
|
648
|
+
return ["sum", "average", "median", "min", "max", "count", "distinctcount", "first"];
|
|
636
649
|
}
|
|
637
|
-
return Array.from(n);
|
|
638
|
-
}
|
|
639
|
-
function O2(r) {
|
|
640
|
-
let n = D2(r);
|
|
641
|
-
if (n.length === 0) return 0;
|
|
642
|
-
let e = 0;
|
|
643
|
-
for (let s of n) Z(s).length > 0 && e++;
|
|
644
|
-
return Math.round(e / n.length * 1e3) / 10;
|
|
645
|
-
}
|
|
646
|
-
function Z2(r, n) {
|
|
647
|
-
return p(n, en) ? true : D2(r).some((e) => /^\d{5}(-\d{4})?$/.test(e.replace(/\.0+$/, "")));
|
|
648
|
-
}
|
|
649
|
-
function Gt(r, n) {
|
|
650
|
-
let e = P2(r);
|
|
651
|
-
if (e.length === 0) return 0;
|
|
652
|
-
let s = 0;
|
|
653
|
-
for (let l of e) p3(l, n) && s++;
|
|
654
|
-
return Math.round(s / e.length * 1e3) / 10;
|
|
655
|
-
}
|
|
656
|
-
function $2(r, n) {
|
|
657
|
-
let e = 0;
|
|
658
|
-
for (let s of P2(r)) n(s) && e++;
|
|
659
|
-
return e;
|
|
660
|
-
}
|
|
661
|
-
function _t(r, n) {
|
|
662
|
-
let e = 0;
|
|
663
|
-
for (let s of D2(r)) n(s) && e++;
|
|
664
|
-
return e;
|
|
665
650
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
651
|
+
function Yt(e) {
|
|
652
|
+
return G2(e)[0];
|
|
653
|
+
}
|
|
654
|
+
function Qt(e, t) {
|
|
655
|
+
if (t !== "sum" && t !== "count") return false;
|
|
656
|
+
if (t === "count") return true;
|
|
657
|
+
let i = z2(e);
|
|
658
|
+
return i.additivity === "additive" || i.additivity === "part_of_whole" || i.additivity === "unknown";
|
|
659
|
+
}
|
|
660
|
+
var F2 = 2;
|
|
661
|
+
var st = 400;
|
|
662
|
+
function D2(e) {
|
|
663
|
+
return !!E(e);
|
|
664
|
+
}
|
|
665
|
+
function v2(e) {
|
|
666
|
+
let t = /* @__PURE__ */ new Set();
|
|
667
|
+
for (let i of e) {
|
|
668
|
+
if (i == null) continue;
|
|
669
|
+
let r = u(String(i));
|
|
670
|
+
if (!V(r) && !t.has(r) && (t.add(r), t.size >= st)) break;
|
|
671
|
+
}
|
|
672
|
+
return Array.from(t);
|
|
673
|
+
}
|
|
674
|
+
function h2(e) {
|
|
675
|
+
let t = v2(e);
|
|
676
|
+
if (t.length === 0 || K2(e) < 95) return false;
|
|
677
|
+
let i = t.filter((u2) => D2(u2) && !Y(u2)).length, r = t.filter((u2) => !D2(u2) && !!Y(u2)).length;
|
|
678
|
+
return i >= 1 && i >= r;
|
|
679
|
+
}
|
|
680
|
+
function ot(e, t) {
|
|
681
|
+
if (!Y(e)) return false;
|
|
682
|
+
let i = E(e);
|
|
683
|
+
return i ? t === "world" ? true : s3.has(i) : false;
|
|
684
|
+
}
|
|
685
|
+
function K2(e) {
|
|
686
|
+
let t = v2(e);
|
|
687
|
+
if (t.length === 0) return 0;
|
|
688
|
+
let i = 0;
|
|
689
|
+
for (let r of t) D2(r) && i++;
|
|
690
|
+
return Math.round(i / t.length * 1e3) / 10;
|
|
691
|
+
}
|
|
692
|
+
function P2(e) {
|
|
693
|
+
let t = v2(e);
|
|
694
|
+
if (t.length === 0) return 0;
|
|
695
|
+
let i = 0;
|
|
696
|
+
for (let r of t) Y(r) && i++;
|
|
697
|
+
return Math.round(i / t.length * 1e3) / 10;
|
|
698
|
+
}
|
|
699
|
+
function H2(e) {
|
|
700
|
+
let t = /* @__PURE__ */ new Set();
|
|
701
|
+
for (let i of e) {
|
|
702
|
+
if (i == null) continue;
|
|
703
|
+
let r = String(i).trim();
|
|
704
|
+
if (!V(u(r)) && !t.has(r) && (t.add(r), t.size >= st)) break;
|
|
705
|
+
}
|
|
706
|
+
return Array.from(t);
|
|
707
|
+
}
|
|
708
|
+
function B2(e) {
|
|
709
|
+
let t = H2(e);
|
|
710
|
+
if (t.length === 0) return 0;
|
|
711
|
+
let i = 0;
|
|
712
|
+
for (let r of t) Z(r).length > 0 && i++;
|
|
713
|
+
return Math.round(i / t.length * 1e3) / 10;
|
|
714
|
+
}
|
|
715
|
+
function rt(e, t) {
|
|
716
|
+
return p(t, en) ? true : H2(e).some((i) => /^\d{5}(-\d{4})?$/.test(i.replace(/\.0+$/, "")));
|
|
717
|
+
}
|
|
718
|
+
function te(e, t) {
|
|
719
|
+
let i = v2(e);
|
|
720
|
+
if (i.length === 0) return 0;
|
|
721
|
+
let r = 0;
|
|
722
|
+
for (let u2 of i) p3(u2, t) && r++;
|
|
723
|
+
return Math.round(r / i.length * 1e3) / 10;
|
|
724
|
+
}
|
|
725
|
+
function $2(e, t) {
|
|
726
|
+
let i = 0;
|
|
727
|
+
for (let r of v2(e)) t(r) && i++;
|
|
728
|
+
return i;
|
|
729
|
+
}
|
|
730
|
+
function ee(e, t) {
|
|
731
|
+
let i = 0;
|
|
732
|
+
for (let r of H2(e)) t(r) && i++;
|
|
733
|
+
return i;
|
|
734
|
+
}
|
|
735
|
+
function ne(e, t, i, r) {
|
|
736
|
+
let u2 = [], c = [], o = {}, I2 = {}, R = new Set(e), l = (n) => t.map((s) => s[n]);
|
|
737
|
+
for (let n of ["city", "lat", "lon", "country"]) {
|
|
738
|
+
let s = i?.[n];
|
|
739
|
+
s && R.has(s) && (o[n] = s);
|
|
671
740
|
}
|
|
672
|
-
let
|
|
673
|
-
if (
|
|
674
|
-
let
|
|
675
|
-
|
|
741
|
+
let p2 = i?.zip;
|
|
742
|
+
if (p2 && R.has(p2)) {
|
|
743
|
+
let n = l(p2), s = B2(n);
|
|
744
|
+
s < 95 ? c.push(`zip=${p2} (only ${s}% of values read as ZIPs)`) : rt(n, p2) ? o.zip = p2 : c.push(`zip=${p2} (all values are 3-4 digits with nothing to say they are postal)`);
|
|
676
745
|
}
|
|
677
|
-
let
|
|
678
|
-
if (
|
|
679
|
-
let
|
|
680
|
-
|
|
746
|
+
let f = i?.state;
|
|
747
|
+
if (f && R.has(f)) {
|
|
748
|
+
let n = l(f), s = v2(n);
|
|
749
|
+
s.length > 0 && s.every((a2) => ot(a2, r)) ? c.push(`state=${f} (every value is "CA", which is both Canada and California)`) : h2(n) ? (c.push(`state=${f} (every value is a country, not a state)`), o.country || (o.country = f)) : P2(n) < 95 ? c.push(`state=${f} (only ${P2(n)}% of values are states/provinces)`) : o.state = f;
|
|
681
750
|
}
|
|
682
|
-
let
|
|
751
|
+
let m2 = new Set(Object.values(o).filter(Boolean)), x2 = e.filter((n) => !m2.has(n) && !n.startsWith("__"));
|
|
683
752
|
if (!o.state) {
|
|
684
|
-
let
|
|
685
|
-
for (let
|
|
686
|
-
let a2 =
|
|
687
|
-
if (
|
|
688
|
-
let
|
|
689
|
-
if (
|
|
690
|
-
let
|
|
691
|
-
|
|
753
|
+
let n = null;
|
|
754
|
+
for (let s of x2) {
|
|
755
|
+
let a2 = l(s);
|
|
756
|
+
if (h2(a2)) continue;
|
|
757
|
+
let d = P2(a2);
|
|
758
|
+
if (d < 95) continue;
|
|
759
|
+
let E2 = $2(a2, (C) => !!Y(C)), ut = $2(a2, (C) => !!Y(C) && !ot(C, r));
|
|
760
|
+
E2 < F2 && ut < 1 || (!n || d > n.pct) && (n = { name: s, pct: d });
|
|
692
761
|
}
|
|
693
|
-
|
|
762
|
+
n && (o.state = n.name, m2.add(n.name), u2.push(`state=${n.name} (${n.pct}% states/provinces)`));
|
|
694
763
|
}
|
|
695
|
-
if (!o.zip) for (let
|
|
696
|
-
if (
|
|
697
|
-
let
|
|
698
|
-
if (!(a2 < 95) &&
|
|
699
|
-
o.zip =
|
|
764
|
+
if (!o.zip) for (let n of x2) {
|
|
765
|
+
if (m2.has(n)) continue;
|
|
766
|
+
let s = l(n), a2 = B2(s);
|
|
767
|
+
if (!(a2 < 95) && rt(s, n) && !(ee(s, (d) => Z(d).length > 0) < F2)) {
|
|
768
|
+
o.zip = n, m2.add(n), u2.push(`zip=${n} (${a2}% ZIP codes)`);
|
|
700
769
|
break;
|
|
701
770
|
}
|
|
702
771
|
}
|
|
703
|
-
let
|
|
704
|
-
if (!
|
|
705
|
-
let
|
|
706
|
-
for (let
|
|
707
|
-
if (
|
|
708
|
-
let a2 =
|
|
709
|
-
(!
|
|
772
|
+
let A = o.country;
|
|
773
|
+
if (!A || !h2(l(A))) {
|
|
774
|
+
let n = null;
|
|
775
|
+
for (let s of x2) {
|
|
776
|
+
if (m2.has(s) || !h2(l(s))) continue;
|
|
777
|
+
let a2 = K2(l(s));
|
|
778
|
+
(!n || a2 > n.pct) && (n = { name: s, pct: a2 });
|
|
710
779
|
}
|
|
711
|
-
|
|
780
|
+
n && (A && (c.push(`country=${A} (only ${K2(l(A))}% of values are country identifiers; using ${n.name} instead)`), I2.country = A), o.country = n.name, m2.add(n.name), u2.push(`country=${n.name}`));
|
|
712
781
|
}
|
|
713
782
|
if (!o.city) {
|
|
714
|
-
let
|
|
715
|
-
for (let
|
|
716
|
-
if (
|
|
717
|
-
let a2 =
|
|
718
|
-
|
|
783
|
+
let n = null;
|
|
784
|
+
for (let s of x2) {
|
|
785
|
+
if (m2.has(s)) continue;
|
|
786
|
+
let a2 = l(s), d = te(a2, r);
|
|
787
|
+
d < m3 || $2(a2, (E2) => p3(E2, r)) < F2 || (!n || d > n.pct) && (n = { name: s, pct: d });
|
|
719
788
|
}
|
|
720
|
-
|
|
789
|
+
n && (o.city = n.name, u2.push(`city=${n.name} (${n.pct}% known cities)`));
|
|
721
790
|
}
|
|
722
|
-
let
|
|
723
|
-
let
|
|
724
|
-
return isFinite(
|
|
791
|
+
let V2 = o.country ? l(o.country) : [], N2 = !!o.country && h2(V2), at = N2 ? new Set(V2.map((n) => E(n == null ? null : String(n))).filter(Boolean)).size : 0, U2 = N2 && at >= 2, O2 = !!(o.lat && o.lon) && t.some((n) => {
|
|
792
|
+
let s = +String(n[o.lat] ?? "").trim(), a2 = +String(n[o.lon] ?? "").trim();
|
|
793
|
+
return isFinite(s) && isFinite(a2) && s >= -90 && s <= 90 && a2 >= -180 && a2 <= 180 && String(n[o.lat] ?? "").trim() !== "" && String(n[o.lon] ?? "").trim() !== "";
|
|
725
794
|
});
|
|
726
|
-
return o.lat && o.lon && !
|
|
795
|
+
return o.lat && o.lon && !O2 && c.push(`lat/lon=${o.lat}/${o.lon} (no row holds a usable coordinate pair)`), o.country && !U2 && !(o.city || o.state || o.zip || O2) && c.push(N2 ? `country=${o.country} (every row resolves to the same country - nothing to place apart)` : `country=${o.country} (values are not country identifiers)`), { bind: !!(o.city || o.state || o.zip || U2 || O2) ? o : null, backfilled: u2, refused: c, ...Object.keys(I2).length ? { labelFallback: I2 } : {} };
|
|
727
796
|
}
|
|
728
797
|
|
|
729
798
|
// src/payload.ts
|
|
@@ -738,7 +807,7 @@ function resolvePointChannel(cols, rowObjs, point) {
|
|
|
738
807
|
let labelCol;
|
|
739
808
|
if (bind) {
|
|
740
809
|
const dims = cols.filter((c) => !c.isMeasure).map((c) => c.name);
|
|
741
|
-
const res =
|
|
810
|
+
const res = ne(dims, rowObjs, bind, point?.mapKind);
|
|
742
811
|
bind = res.bind;
|
|
743
812
|
rolesBackfilled = res.backfilled;
|
|
744
813
|
rolesRefused = res.refused;
|
|
@@ -806,11 +875,11 @@ function buildRenderPayload(cols, rowObjs, geo, point, destination) {
|
|
|
806
875
|
const row = rowObjs[r];
|
|
807
876
|
const a2 = new Array(cols.length + extra);
|
|
808
877
|
for (let c = 0; c < cols.length; c++) {
|
|
809
|
-
const
|
|
810
|
-
if (
|
|
811
|
-
a2[c] =
|
|
878
|
+
const v3 = row[colNames[c]];
|
|
879
|
+
if (v3 instanceof Date) {
|
|
880
|
+
a2[c] = v3.toISOString();
|
|
812
881
|
} else {
|
|
813
|
-
a2[c] =
|
|
882
|
+
a2[c] = v3 ?? null;
|
|
814
883
|
}
|
|
815
884
|
}
|
|
816
885
|
let k2 = cols.length;
|
|
@@ -862,8 +931,8 @@ function buildRenderPayload(cols, rowObjs, geo, point, destination) {
|
|
|
862
931
|
function createMarkResolver(env) {
|
|
863
932
|
const { root, doc, log } = env;
|
|
864
933
|
const isElement = (t) => {
|
|
865
|
-
const
|
|
866
|
-
if (
|
|
934
|
+
const E2 = doc?.defaultView?.Element ?? globalThis.Element;
|
|
935
|
+
if (E2 && t instanceof E2) return true;
|
|
867
936
|
return !!t && typeof t.closest === "function" && typeof t.getAttribute === "function";
|
|
868
937
|
};
|
|
869
938
|
const viewStyle = (n) => {
|
|
@@ -883,14 +952,14 @@ function createMarkResolver(env) {
|
|
|
883
952
|
return s === "transparent" || s === "none" || so <= 0.01;
|
|
884
953
|
};
|
|
885
954
|
const distToPathCenterline = (el, x2, y2) => {
|
|
886
|
-
const
|
|
887
|
-
if (!(
|
|
955
|
+
const L4 = el.getTotalLength();
|
|
956
|
+
if (!(L4 > 0)) return Number.MAX_VALUE;
|
|
888
957
|
const m2 = el.getScreenCTM();
|
|
889
958
|
if (!m2) return Number.MAX_VALUE;
|
|
890
959
|
let best = Number.MAX_VALUE;
|
|
891
|
-
const
|
|
892
|
-
for (let i = 0; i <=
|
|
893
|
-
const pt = el.getPointAtLength(
|
|
960
|
+
const N2 = 64;
|
|
961
|
+
for (let i = 0; i <= N2; i++) {
|
|
962
|
+
const pt = el.getPointAtLength(L4 * i / N2);
|
|
894
963
|
const dx = m2.a * pt.x + m2.c * pt.y + m2.e - x2;
|
|
895
964
|
const dy = m2.b * pt.x + m2.d * pt.y + m2.f - y2;
|
|
896
965
|
const d = dx * dx + dy * dy;
|
|
@@ -1086,6 +1155,42 @@ function ensureCrossfilterHitTargets(container, doc) {
|
|
|
1086
1155
|
return report;
|
|
1087
1156
|
}
|
|
1088
1157
|
|
|
1158
|
+
// src/blankRender.ts
|
|
1159
|
+
var EMPTY = { markCount: 0, legendMarkCount: 0, containerKind: "empty" };
|
|
1160
|
+
function censusMarks(container) {
|
|
1161
|
+
if (!container || typeof container.querySelectorAll !== "function") return EMPTY;
|
|
1162
|
+
try {
|
|
1163
|
+
const marks = /* @__PURE__ */ new Set();
|
|
1164
|
+
for (const el of Array.from(container.querySelectorAll(`.${MARK_CLASS}`))) marks.add(el);
|
|
1165
|
+
for (const el of Array.from(container.querySelectorAll(`[${ROW_IDX_ATTR}]`))) marks.add(el);
|
|
1166
|
+
const legend = /* @__PURE__ */ new Set();
|
|
1167
|
+
for (const el of Array.from(container.querySelectorAll(`.${LEGEND_MARK_CLASS}`))) {
|
|
1168
|
+
legend.add(el);
|
|
1169
|
+
marks.delete(el);
|
|
1170
|
+
}
|
|
1171
|
+
const hasSvg = !!container.querySelector("svg");
|
|
1172
|
+
const hasAnything = hasSvg || (container.childElementCount ?? 0) > 0;
|
|
1173
|
+
return {
|
|
1174
|
+
markCount: marks.size,
|
|
1175
|
+
legendMarkCount: legend.size,
|
|
1176
|
+
containerKind: hasSvg ? "svg" : hasAnything ? "html" : "empty"
|
|
1177
|
+
};
|
|
1178
|
+
} catch {
|
|
1179
|
+
return EMPTY;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function isBlankRender(i) {
|
|
1183
|
+
if (i.contractUntagged) return false;
|
|
1184
|
+
if (i.animated) return false;
|
|
1185
|
+
if (i.authoredNoDataText) return false;
|
|
1186
|
+
if (!(i.rows > 0)) return false;
|
|
1187
|
+
return i.markCount === 0;
|
|
1188
|
+
}
|
|
1189
|
+
function blankRenderFlag(lang, cause) {
|
|
1190
|
+
const clean = (s) => String(s || "unknown").toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
1191
|
+
return `blankrender:${clean(lang)}:${clean(cause)}`;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1089
1194
|
// src/host.ts
|
|
1090
1195
|
var parseRowIdxs = (s) => (s || "").split(",").map((x2) => parseInt(x2, 10)).filter((n) => Number.isFinite(n));
|
|
1091
1196
|
var D3_PLUGIN_PACKAGES = {
|
|
@@ -1158,7 +1263,7 @@ function createChartHost(container, config) {
|
|
|
1158
1263
|
let data = config.data;
|
|
1159
1264
|
{
|
|
1160
1265
|
const stamped = config.data?.meta?.hostContract;
|
|
1161
|
-
const major = (
|
|
1266
|
+
const major = (v3) => (v3 || "").split(".")[0];
|
|
1162
1267
|
if (stamped && major(stamped) !== major(HOST_CONTRACT_VERSION)) {
|
|
1163
1268
|
try {
|
|
1164
1269
|
(win?.console ?? console)?.warn(
|
|
@@ -1186,6 +1291,7 @@ function createChartHost(container, config) {
|
|
|
1186
1291
|
let renderFn = config.renderFn ?? null;
|
|
1187
1292
|
let destroyed = false;
|
|
1188
1293
|
let warnedGeo = false;
|
|
1294
|
+
let warnedBlank = false;
|
|
1189
1295
|
let current = null;
|
|
1190
1296
|
let activeTickEl = null;
|
|
1191
1297
|
let selectionIsPeriod = false;
|
|
@@ -1193,9 +1299,9 @@ function createChartHost(container, config) {
|
|
|
1193
1299
|
const ensureAffordanceStyles = () => {
|
|
1194
1300
|
try {
|
|
1195
1301
|
if (!doc || doc.getElementById("bic-chart-host-affordances")) return;
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1302
|
+
const st2 = doc.createElement("style");
|
|
1303
|
+
st2.id = "bic-chart-host-affordances";
|
|
1304
|
+
st2.textContent = `
|
|
1199
1305
|
/* TYPOGRAPHY FIREWALL. Generated charts set font-size but almost never line-height or
|
|
1200
1306
|
letter-spacing, so they INHERIT whatever the host page cascades in. A page with
|
|
1201
1307
|
\`:root { font: 18px/145% }\` (the stock Vite template does exactly this) hands every
|
|
@@ -1233,7 +1339,7 @@ function createChartHost(container, config) {
|
|
|
1233
1339
|
font-weight: 700 !important;
|
|
1234
1340
|
text-decoration: underline !important;
|
|
1235
1341
|
}`;
|
|
1236
|
-
(doc.head || doc.documentElement)?.appendChild(
|
|
1342
|
+
(doc.head || doc.documentElement)?.appendChild(st2);
|
|
1237
1343
|
} catch {
|
|
1238
1344
|
}
|
|
1239
1345
|
};
|
|
@@ -1353,6 +1459,27 @@ function createChartHost(container, config) {
|
|
|
1353
1459
|
}
|
|
1354
1460
|
ensureCrossfilterHitTargets(container, doc);
|
|
1355
1461
|
paintSelection();
|
|
1462
|
+
try {
|
|
1463
|
+
const census = censusMarks(container);
|
|
1464
|
+
if (isBlankRender({
|
|
1465
|
+
markCount: census.markCount,
|
|
1466
|
+
rows: data.rows ? data.rows.length : 0,
|
|
1467
|
+
animated: config.animated,
|
|
1468
|
+
contractUntagged: config.contractUntagged
|
|
1469
|
+
})) {
|
|
1470
|
+
if (!warnedBlank) {
|
|
1471
|
+
warnedBlank = true;
|
|
1472
|
+
try {
|
|
1473
|
+
(win?.console ?? console)?.warn(
|
|
1474
|
+
`[@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.`
|
|
1475
|
+
);
|
|
1476
|
+
} catch {
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
config.onBlankRender?.({ census, rows: data.rows ? data.rows.length : 0 });
|
|
1480
|
+
}
|
|
1481
|
+
} catch {
|
|
1482
|
+
}
|
|
1356
1483
|
},
|
|
1357
1484
|
setOptions(partial) {
|
|
1358
1485
|
raw = { ...raw, ...partial };
|
|
@@ -1445,9 +1572,16 @@ export {
|
|
|
1445
1572
|
registerGeoAsset,
|
|
1446
1573
|
clearGeoCache,
|
|
1447
1574
|
L3,
|
|
1575
|
+
z2 as z,
|
|
1576
|
+
G2 as G,
|
|
1577
|
+
Yt,
|
|
1578
|
+
Qt,
|
|
1448
1579
|
buildRenderPayload,
|
|
1449
1580
|
createMarkResolver,
|
|
1450
1581
|
ensureCrossfilterHitTargets,
|
|
1582
|
+
censusMarks,
|
|
1583
|
+
isBlankRender,
|
|
1584
|
+
blankRenderFlag,
|
|
1451
1585
|
requiredD3Plugins,
|
|
1452
1586
|
explainRenderFailure,
|
|
1453
1587
|
stripEsmExports,
|
package/dist/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
DIM_OPACITY_DEFAULT,
|
|
21
21
|
DIM_OPACITY_VAR,
|
|
22
22
|
FLIP_MODE_DEFAULT,
|
|
23
|
+
G,
|
|
23
24
|
GEO_POINT_PRECISIONS,
|
|
24
25
|
HOST_CONTAINER_CLASS,
|
|
25
26
|
HOST_CONTRACT_VERSION,
|
|
@@ -27,10 +28,14 @@ import {
|
|
|
27
28
|
LEGEND_MARK_CLASS,
|
|
28
29
|
MARK_CLASS,
|
|
29
30
|
MARK_SELECTED_CLASS,
|
|
31
|
+
Qt,
|
|
30
32
|
ROW_IDX_ATTR,
|
|
31
33
|
SELECTION_ACTIVE_CLASS,
|
|
32
34
|
XFILTER_REFRESH_EVENT,
|
|
35
|
+
Yt,
|
|
36
|
+
blankRenderFlag,
|
|
33
37
|
buildRenderPayload,
|
|
38
|
+
censusMarks,
|
|
34
39
|
chartOwnsTimeline,
|
|
35
40
|
clearGeoCache,
|
|
36
41
|
compileRenderFn,
|
|
@@ -40,14 +45,16 @@ import {
|
|
|
40
45
|
explainRenderFailure,
|
|
41
46
|
geoAssetFor,
|
|
42
47
|
geoFromCache,
|
|
48
|
+
isBlankRender,
|
|
43
49
|
loadGeo,
|
|
44
50
|
periodTickSuppressesFeedback,
|
|
45
51
|
registerGeo,
|
|
46
52
|
registerGeoAsset,
|
|
47
53
|
requiredD3Plugins,
|
|
48
54
|
resolveOptions,
|
|
49
|
-
stripEsmExports
|
|
50
|
-
|
|
55
|
+
stripEsmExports,
|
|
56
|
+
z
|
|
57
|
+
} from "./chunk-FC3XJPVX.mjs";
|
|
51
58
|
import "./chunk-A2GMXZP7.mjs";
|
|
52
59
|
|
|
53
60
|
// src/trivial.ts
|
|
@@ -622,9 +629,6 @@ function normaliseAggregation(raw) {
|
|
|
622
629
|
if (/^(distinctcount|dcount|countdistinct|uniques?)$/.test(s)) return "distinctcount";
|
|
623
630
|
return "sum";
|
|
624
631
|
}
|
|
625
|
-
function isAdditive(agg) {
|
|
626
|
-
return agg === "sum" || agg === "count";
|
|
627
|
-
}
|
|
628
632
|
function aggLabel(agg) {
|
|
629
633
|
switch (agg) {
|
|
630
634
|
case "average":
|
|
@@ -651,6 +655,20 @@ function isMeasureColumn(col) {
|
|
|
651
655
|
if (col.isMeasure === true) return true;
|
|
652
656
|
return NUMERIC_DATATYPE.test(String(col.dataType ?? ""));
|
|
653
657
|
}
|
|
658
|
+
function isRealColumn(col) {
|
|
659
|
+
return !!col && !isSynthetic(col.name);
|
|
660
|
+
}
|
|
661
|
+
function isSuppressed(col) {
|
|
662
|
+
return z(col).suppressed;
|
|
663
|
+
}
|
|
664
|
+
function resolveAgg(col, requested, auto) {
|
|
665
|
+
const fallback = () => {
|
|
666
|
+
const d = Yt(col);
|
|
667
|
+
return d === "first" ? "count" : d;
|
|
668
|
+
};
|
|
669
|
+
if (auto) return fallback();
|
|
670
|
+
return G(col).indexOf(requested) >= 0 ? requested : fallback();
|
|
671
|
+
}
|
|
654
672
|
function toNumber(v) {
|
|
655
673
|
if (v == null || v === "") return null;
|
|
656
674
|
const n = typeof v === "number" ? v : Number(v);
|
|
@@ -706,9 +724,11 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
706
724
|
if (pos != null && pos >= 0 && pos < rows.length) positions.push(pos);
|
|
707
725
|
}
|
|
708
726
|
if (!positions.length) return null;
|
|
727
|
+
const auto = String(opts.aggregation ?? "").trim().length === 0;
|
|
709
728
|
const agg = normaliseAggregation(opts.aggregation);
|
|
710
729
|
const maxMeasures = opts.maxMeasures ?? 4;
|
|
711
730
|
const maxHeaderValues = opts.maxHeaderValues ?? 3;
|
|
731
|
+
const maxDimensions = opts.maxDimensions ?? 2;
|
|
712
732
|
const dimIdx = columns.findIndex((c) => c && !isSynthetic(c.name) && !isMeasureColumn(c));
|
|
713
733
|
let header;
|
|
714
734
|
if (dimIdx >= 0) {
|
|
@@ -728,10 +748,20 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
728
748
|
header = `${positions.length} mark${positions.length === 1 ? "" : "s"}`;
|
|
729
749
|
}
|
|
730
750
|
const measureCols = [];
|
|
731
|
-
|
|
751
|
+
const suppressedColumns = [];
|
|
752
|
+
for (let c = 0; c < columns.length; c++) {
|
|
753
|
+
if (!isMeasureColumn(columns[c])) continue;
|
|
754
|
+
if (isSuppressed(columns[c])) {
|
|
755
|
+
suppressedColumns.push(String(columns[c]?.name ?? ""));
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
measureCols.push(c);
|
|
759
|
+
}
|
|
732
760
|
const shown = measureCols.slice(0, maxMeasures);
|
|
733
761
|
const lines = [];
|
|
734
762
|
for (const c of shown) {
|
|
763
|
+
const col = columns[c];
|
|
764
|
+
const colAgg = resolveAgg(col, agg, auto);
|
|
735
765
|
const selVals = [];
|
|
736
766
|
const selRaw = [];
|
|
737
767
|
for (const p of positions) {
|
|
@@ -739,9 +769,9 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
739
769
|
selRaw.push(rows[p]?.[c]);
|
|
740
770
|
if (n != null) selVals.push(n);
|
|
741
771
|
}
|
|
742
|
-
const value = reduce(selVals,
|
|
772
|
+
const value = reduce(selVals, colAgg, selRaw);
|
|
743
773
|
let sharePct = null;
|
|
744
|
-
if (
|
|
774
|
+
if (Qt(col, colAgg) && value != null) {
|
|
745
775
|
const allVals = [];
|
|
746
776
|
const allRaw = [];
|
|
747
777
|
for (let r = 0; r < rows.length; r++) {
|
|
@@ -749,21 +779,49 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
749
779
|
allRaw.push(rows[r]?.[c]);
|
|
750
780
|
if (n != null) allVals.push(n);
|
|
751
781
|
}
|
|
752
|
-
const total = reduce(allVals,
|
|
782
|
+
const total = reduce(allVals, colAgg, allRaw);
|
|
753
783
|
if (total != null && total > 0) sharePct = value / total * 100;
|
|
754
784
|
}
|
|
755
785
|
lines.push({
|
|
756
|
-
column: String(
|
|
757
|
-
label: `${aggLabel(
|
|
786
|
+
column: String(col?.name ?? ""),
|
|
787
|
+
label: `${aggLabel(colAgg)} of ${col?.name ?? ""}`,
|
|
758
788
|
value,
|
|
759
789
|
valueText: value == null ? "\u2014" : fmtNumber(value, opts.cultureCode),
|
|
760
790
|
sharePct,
|
|
761
|
-
shareText: sharePct == null ? "" : `${sharePct < 0.1 ? "<0.1" : sharePct.toFixed(1)}% of total
|
|
791
|
+
shareText: sharePct == null ? "" : `${sharePct < 0.1 ? "<0.1" : sharePct.toFixed(1)}% of total`,
|
|
792
|
+
aggregation: colAgg
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
const dimensionLines = [];
|
|
796
|
+
for (let c = 0; c < columns.length && dimensionLines.length < maxDimensions; c++) {
|
|
797
|
+
if (c === dimIdx) continue;
|
|
798
|
+
if (!isRealColumn(columns[c]) || isMeasureColumn(columns[c])) continue;
|
|
799
|
+
const name = String(columns[c]?.name ?? "");
|
|
800
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
801
|
+
let firstText = "";
|
|
802
|
+
for (const p of positions) {
|
|
803
|
+
const raw = rows[p]?.[c];
|
|
804
|
+
const s = raw == null || raw === "" ? "(blank)" : String(raw);
|
|
805
|
+
if (!distinct.size) firstText = s;
|
|
806
|
+
distinct.add(s);
|
|
807
|
+
}
|
|
808
|
+
if (!distinct.size) continue;
|
|
809
|
+
const single = distinct.size === 1;
|
|
810
|
+
dimensionLines.push({
|
|
811
|
+
column: name,
|
|
812
|
+
label: single ? name : `Distinct ${name}`,
|
|
813
|
+
value: single ? null : distinct.size,
|
|
814
|
+
valueText: single ? firstText : fmtNumber(distinct.size, opts.cultureCode),
|
|
815
|
+
sharePct: null,
|
|
816
|
+
shareText: "",
|
|
817
|
+
aggregation: single ? "first" : "distinctcount"
|
|
762
818
|
});
|
|
763
819
|
}
|
|
764
820
|
return {
|
|
765
821
|
header,
|
|
766
822
|
lines,
|
|
823
|
+
dimensionLines,
|
|
824
|
+
suppressedColumns,
|
|
767
825
|
rowsText: `${positions.length} of ${rows.length} rows`,
|
|
768
826
|
selectedRows: positions.length,
|
|
769
827
|
totalRows: rows.length,
|
|
@@ -811,10 +869,12 @@ export {
|
|
|
811
869
|
actionFor,
|
|
812
870
|
askApplyImprovements,
|
|
813
871
|
bareBase64,
|
|
872
|
+
blankRenderFlag,
|
|
814
873
|
buildRenderPayload,
|
|
815
874
|
buildReviewWire,
|
|
816
875
|
canConfirmLaunch,
|
|
817
876
|
captureSvgSnapshot,
|
|
877
|
+
censusMarks,
|
|
818
878
|
chartOwnsTimeline,
|
|
819
879
|
chooserFitsViewport,
|
|
820
880
|
clearGeoCache,
|
|
@@ -828,6 +888,7 @@ export {
|
|
|
828
888
|
explainRenderFailure,
|
|
829
889
|
geoAssetFor,
|
|
830
890
|
geoFromCache,
|
|
891
|
+
isBlankRender,
|
|
831
892
|
launchFavorStyle,
|
|
832
893
|
launchGenerates,
|
|
833
894
|
loadGeo,
|
package/dist/react.mjs
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface MarkCensus {
|
|
2
|
+
/** Data marks found anywhere in the container. */
|
|
3
|
+
markCount: number;
|
|
4
|
+
/** Legend swatches / axis furniture — clickable, but NOT evidence the data drew. */
|
|
5
|
+
legendMarkCount: number;
|
|
6
|
+
/** What the chart built. `html` is a first-class answer, not a failure — see below. */
|
|
7
|
+
containerKind: "svg" | "html" | "empty";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Count what the render actually left behind.
|
|
11
|
+
*
|
|
12
|
+
* NOT SCOPED TO `<svg>`, and that is what makes this work at all. Plenty of chart types are pure
|
|
13
|
+
* HTML - a table with embedded bars, a KPI card - and never create an SVG, so a census that
|
|
14
|
+
* starts from `container.querySelector("svg")` finds nothing for exactly the charts it most needs
|
|
15
|
+
* to measure. Those charts still honour the contract: their working path tags each row
|
|
16
|
+
* `class="d3-mark"` + `data-row-idx`, and their no-data branch appends a bare element with
|
|
17
|
+
* neither. The class and the attribute ARE the signal, in both DOM worlds; the element name never
|
|
18
|
+
* was.
|
|
19
|
+
*
|
|
20
|
+
* Counts by contract, never by tag: a chart marking `<rect>`, `<text>`, `<path>`, `<circle>` or
|
|
21
|
+
* `<div>` is equally visible here. Never throws — telemetry must not be why a delivered render
|
|
22
|
+
* fails.
|
|
23
|
+
*/
|
|
24
|
+
export declare function censusMarks(container: Element | null | undefined): MarkCensus;
|
|
25
|
+
export interface BlankVerdictInput {
|
|
26
|
+
/** From censusMarks. */
|
|
27
|
+
markCount: number;
|
|
28
|
+
/** Rows the chart was HANDED. Zero rows is a different, already-handled story. */
|
|
29
|
+
rows: number;
|
|
30
|
+
/**
|
|
31
|
+
* This chart declares time keyframes. Its first frame is legitimately allowed to be empty
|
|
32
|
+
* (an animated map before any country enters the series), so a verdict on frame one would be
|
|
33
|
+
* a lie about the commonest animated opening.
|
|
34
|
+
*/
|
|
35
|
+
animated?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The author configured their own "No Data Text". They asked for this rectangle to say
|
|
38
|
+
* something when there is nothing to draw, and overruling them would be telling someone their
|
|
39
|
+
* deliberate empty state is a defect.
|
|
40
|
+
*/
|
|
41
|
+
authoredNoDataText?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The chart's marks are not tagged by our contract at all — a Vega lane, whose marks the
|
|
44
|
+
* generated spec names itself. Absence of `.d3-mark` there proves nothing, so the census is
|
|
45
|
+
* evidence-free and must not return a verdict.
|
|
46
|
+
*/
|
|
47
|
+
contractUntagged?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Did this render paint nothing, against data that had something to paint?
|
|
51
|
+
*
|
|
52
|
+
* EVERY CLAUSE IS A FALSE POSITIVE WE WOULD OTHERWISE SHIP. The value of this verdict is that a
|
|
53
|
+
* human believes it; one wrong accusation on a chart that is fine costs more than the case it
|
|
54
|
+
* catches, which is the same argument `columnsGoneFromDiff` makes about type narrowings.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isBlankRender(i: BlankVerdictInput): boolean;
|
|
57
|
+
/** A short, stable token a host can attach to its own per-render telemetry. Deliberately
|
|
58
|
+
* formatted for an always-on channel rather than a debug log: a blank render is most
|
|
59
|
+
* interesting in aggregate, and the charts worth counting are the ones nobody is watching. */
|
|
60
|
+
export declare function blankRenderFlag(lang: string, cause: string): string;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RenderOptions } from "./contract";
|
|
2
2
|
import { type ResolveOptionsInput } from "./defaults";
|
|
3
|
+
import { type MarkCensus } from "./blankRender";
|
|
3
4
|
export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
|
|
4
5
|
export interface ChartHostConfig {
|
|
5
6
|
data: {
|
|
@@ -36,6 +37,26 @@ export interface ChartHostConfig {
|
|
|
36
37
|
* already holds its assets (e.g. `geoForKind` from "@bicharts/chart-host/geo", or the
|
|
37
38
|
* data.geo.json the MCP wrote next to the chart). */
|
|
38
39
|
geoProvider?: (geoKind: string) => any | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Called when a render completes cleanly having painted NO data marks against a non-empty
|
|
42
|
+
* table (2026-09-01). The chart did not throw, so nothing else will tell you — this is
|
|
43
|
+
* the only notification that the rectangle your reader is looking at is empty.
|
|
44
|
+
*
|
|
45
|
+
* The commonest cause by far is schema drift: generated code binds columns BY NAME, so a
|
|
46
|
+
* renamed or removed field sends its own `findIndex(...) === -1` guard down the no-data
|
|
47
|
+
* branch. A host that knows the binding can say WHICH column; one that does not should still
|
|
48
|
+
* surface it, because "empty and silent" is the state this exists to abolish.
|
|
49
|
+
*/
|
|
50
|
+
onBlankRender?: (info: {
|
|
51
|
+
census: MarkCensus;
|
|
52
|
+
rows: number;
|
|
53
|
+
}) => void;
|
|
54
|
+
/** This chart declares time keyframes: frame one is allowed to be empty, so no blank verdict
|
|
55
|
+
* is issued for it. */
|
|
56
|
+
animated?: boolean;
|
|
57
|
+
/** The chart's marks are not tagged with the shared contract (a Vega lane names its own
|
|
58
|
+
* marks), so a mark count proves nothing and the blank verdict is suppressed. */
|
|
59
|
+
contractUntagged?: boolean;
|
|
39
60
|
}
|
|
40
61
|
export interface ChartHost {
|
|
41
62
|
render(): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -15,3 +15,4 @@ export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, typ
|
|
|
15
15
|
export { qualifyPick, qualifyAuto, qualifyCancel, launchGenerates, launchFavorStyle, chooserFitsViewport, shouldOpenChooserOnGenerate, shouldOpenInlineChooserOnGenerate, canConfirmLaunch, confirmLaunch, qualifyFailureFallsOpen, CHOOSER_MIN_WIDTH_PX, CHOOSER_MIN_HEIGHT_PX, type QualifyLaunchOutcome, type ChooserGateInput, } from "./qualifyLaunch";
|
|
16
16
|
export { computeSelectionCard, normaliseAggregation, type SelectionCardModel, type SelectionCardLine, type SelectionCardOptions, } from "./selectionCard";
|
|
17
17
|
export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
|
|
18
|
+
export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type BlankVerdictInput } from "./blankRender";
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type { RenderPayload } from "./payload";
|
|
2
2
|
export interface SelectionCardOptions {
|
|
3
3
|
/** The chart's own aggregation, so the card AGREES with the picture it sits on. A card
|
|
4
|
-
* reading "Sum" beside a chart drawn from averages is two answers to one question.
|
|
4
|
+
* reading "Sum" beside a chart drawn from averages is two answers to one question.
|
|
5
|
+
*
|
|
6
|
+
* BLANK MEANS AUTO, and Auto is now per COLUMN. The Excel settings panel has offered
|
|
7
|
+
* "Auto — sum amounts, average rates" since the control shipped; `normaliseAggregation("")`
|
|
8
|
+
* answered `sum` for every column alike, so the label described a rule the code did not
|
|
9
|
+
* implement. A blank now resolves through `defaultAggregation` per column: amounts sum,
|
|
10
|
+
* rates average, dimensions report their value. A NAMED aggregation is still honoured
|
|
11
|
+
* wherever it is honest, and substituted per column where it is not — the label carries
|
|
12
|
+
* the substitution, so "Average of ChurnRate" beside "Sum of Revenue" reads as the answer
|
|
13
|
+
* it is rather than a silent override. */
|
|
5
14
|
aggregation?: string;
|
|
6
15
|
cultureCode?: string;
|
|
7
16
|
/** Cards are capped at roughly a third of the tile, so the measure list is capped too.
|
|
@@ -10,29 +19,55 @@ export interface SelectionCardOptions {
|
|
|
10
19
|
/** Past this many distinct dimension values the header becomes "N marks" — naming twelve
|
|
11
20
|
* cities in a header is not a header. */
|
|
12
21
|
maxHeaderValues?: number;
|
|
22
|
+
/** How many DIMENSION lines to add below the measures. Capped separately and small on
|
|
23
|
+
* purpose: dimensions must never crowd out the amounts, which is the failure the measure
|
|
24
|
+
* cap already had (on the sample table the two coordinate columns took two of the four
|
|
25
|
+
* measure slots and pushed the real measures under "+2 more"). Default 2. */
|
|
26
|
+
maxDimensions?: number;
|
|
13
27
|
}
|
|
14
28
|
export interface SelectionCardLine {
|
|
15
29
|
column: string;
|
|
16
|
-
/** "Sum of Revenue" — the aggregation is named because it is not always sum.
|
|
30
|
+
/** "Sum of Revenue" — the aggregation is named because it is not always sum. For a
|
|
31
|
+
* dimension resolving to ONE value it is just the column name, because "First of Segment:
|
|
32
|
+
* East" says nothing "Segment: East" does not. */
|
|
17
33
|
label: string;
|
|
34
|
+
/** Null for a dimension line, whose answer is text. `valueText` is what a host renders. */
|
|
18
35
|
value: number | null;
|
|
19
36
|
valueText: string;
|
|
20
|
-
/** Share of the same aggregate over ALL rows. Null whenever a share would be a lie —
|
|
21
|
-
*
|
|
37
|
+
/** Share of the same aggregate over ALL rows. Null whenever a share would be a lie — it
|
|
38
|
+
* needs BOTH an additive aggregation and an additive COLUMN. The average of a subset is
|
|
39
|
+
* not a percentage of the average of the whole; neither is a total of latitudes. */
|
|
22
40
|
sharePct: number | null;
|
|
23
41
|
shareText: string;
|
|
42
|
+
/** Which aggregation this line actually used, after any per-column substitution. */
|
|
43
|
+
aggregation: string;
|
|
24
44
|
}
|
|
25
45
|
export interface SelectionCardModel {
|
|
26
46
|
header: string;
|
|
47
|
+
/** MEASURE lines. Kept measures-only so `maxMeasures` and `hiddenMeasures` keep meaning
|
|
48
|
+
* exactly what they meant; dimensions have their own list and their own cap. */
|
|
27
49
|
lines: SelectionCardLine[];
|
|
50
|
+
/** DIMENSION lines — the "First, Count, Distinct" half. Before 2026-09-01 the FIRST dimension
|
|
51
|
+
* supplied the header and every other one was discarded, so on the sample table `Segment`
|
|
52
|
+
* appeared nowhere at all. The header column is still excluded here: it is already on
|
|
53
|
+
* screen. */
|
|
54
|
+
dimensionLines: SelectionCardLine[];
|
|
28
55
|
/** "6 of 24 rows" — the one line that is true regardless of what the columns contain. */
|
|
29
56
|
rowsText: string;
|
|
30
57
|
selectedRows: number;
|
|
31
58
|
totalRows: number;
|
|
32
59
|
/** Measures the cap left out, so a host can say "+2 more" instead of implying there were none. */
|
|
33
60
|
hiddenMeasures: number;
|
|
61
|
+
/** Columns the CLASSIFIER withheld rather than the cap — coordinates today. Not for display:
|
|
62
|
+
* a reader does not need telling that a latitude was not totalled. It is here so a host can
|
|
63
|
+
* log it, because a column vanishing from a card with no trace is the kind of thing that
|
|
64
|
+
* costs an afternoon later. */
|
|
65
|
+
suppressedColumns: string[];
|
|
34
66
|
aggregation: string;
|
|
35
67
|
}
|
|
68
|
+
/** The set this file reduces over. `first` is shape-core's eighth kind and is handled on the
|
|
69
|
+
* dimension path only, which is why it is absent here — a `reduce` over numbers has nothing to
|
|
70
|
+
* do with it. */
|
|
36
71
|
type Agg = "sum" | "average" | "min" | "max" | "count" | "median" | "distinctcount";
|
|
37
72
|
/** Normalise the many spellings the hosts use into the set this file reduces over. Unknown
|
|
38
73
|
* spellings fall back to sum rather than throwing: a card is a courtesy, and refusing to draw
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.46",
|
|
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",
|