@bicharts/chart-host 0.5.51 → 0.5.53

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/README.md CHANGED
@@ -185,8 +185,17 @@ visual — override them if you want a different look:
185
185
  ```css
186
186
  .bic-chart-host { --lch-dim-opacity: 0.25; }
187
187
  .bic-chart-host.lch-has-selection .d3-mark:not(.lch-mark-selected) { opacity: var(--lch-dim-opacity); }
188
+ .bic-chart-host.lch-has-selection .d3-lift-selected .d3-mark.lch-mark-selected { opacity: 1; stroke-opacity: 1; fill-opacity: 1; }
188
189
  ```
189
190
 
191
+ The second rule is opt-in. The affordance is dim-only — a selected mark is merely *exempt* from
192
+ the dim and keeps whatever paint the chart gave it — so a chart that rests its marks below full
193
+ opacity by design (a parallel-coordinates plot drawing every line at 0.38 so overplotting reads)
194
+ would show its selected lines at 38%, which the eye reads as dimmed. Such a chart stamps
195
+ `d3-lift-selected` on the group holding those marks, and inside that group a selected mark is
196
+ painted at full opacity. Undeclared charts are unchanged, so alpha used as an encoding (a depth
197
+ cue, a hierarchy level) is never flattened by a fix meant for another chart.
198
+
190
199
  ## Architecture — three layers, and why it matters if you contribute
191
200
 
192
201
  Read this before adding code. The package has **three** concerns, and they are independent.
@@ -6,11 +6,12 @@ import {
6
6
  } from "./chunk-A2GMXZP7.mjs";
7
7
 
8
8
  // src/contract.ts
9
- var HOST_CONTRACT_VERSION = "1.6.0";
9
+ var HOST_CONTRACT_VERSION = "1.7.0";
10
10
  var GEO_POINT_PRECISIONS = ["latlon", "city", "zip3", "state", "country"];
11
11
  var MARK_CLASS = "d3-mark";
12
12
  var LEGEND_MARK_CLASS = "d3-legend-mark";
13
13
  var AXIS_FILTER_CLASS = "d3-axis-filter";
14
+ var LIFT_SELECTED_CLASS = "d3-lift-selected";
14
15
  var ROW_IDX_ATTR = "data-row-idx";
15
16
  var XFILTER_REFRESH_EVENT = "llm-xfilter-refresh";
16
17
  var CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
@@ -593,38 +594,38 @@ var Y2 = ["pct", "percent", "percentage", "rate", "ratio", "rating", "share", "s
593
594
  var J2 = ["latitude", "longitude"];
594
595
  var Q2 = ["latitude", "longitude"];
595
596
  var tt = /^\s*(sum|count|count\s+distinct|distinct\s+count)\s+of\s+/i;
596
- var qt = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])/g;
597
- var jt = new RegExp("\\b(" + X2.join("|") + ")\\b|(" + Y2.join("|") + ")$", "i");
598
- var Wt = new RegExp("\\b(" + J2.join("|") + ")\\b|(" + Q2.join("|") + ")$", "i");
597
+ var jt = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])/g;
598
+ var Wt = new RegExp("\\b(" + X2.join("|") + ")\\b|(" + Y2.join("|") + ")$", "i");
599
+ var Xt = new RegExp("\\b(" + J2.join("|") + ")\\b|(" + Q2.join("|") + ")$", "i");
599
600
  function L2(e) {
600
601
  return e ? String(e).replace(tt, "").trim() : "";
601
602
  }
602
603
  function et(e) {
603
604
  return !!e && tt.test(String(e));
604
605
  }
605
- function Xt(e) {
606
+ function Yt(e) {
606
607
  let t = L2(e);
607
- return t.length === 0 ? "" : t.replace(qt, " ");
608
+ return t.length === 0 ? "" : t.replace(jt, " ");
608
609
  }
609
610
  function nt(e, t) {
610
- return t ? e.test(Xt(t)) || e.test(L2(t)) : false;
611
+ return t ? e.test(Yt(t)) || e.test(L2(t)) : false;
611
612
  }
612
613
  function z2(e) {
613
- return nt(Wt, e);
614
+ return nt(Xt, e);
614
615
  }
615
616
  function it(e) {
616
- return nt(jt, e) || z2(e);
617
+ return nt(Wt, e) || z2(e);
617
618
  }
618
619
  var ot = /int|double|decimal|single|float|number|currency|money/i;
619
- function Yt(e) {
620
+ function Jt(e) {
620
621
  let t = String(e?.valueNature ?? "").trim().toLowerCase();
621
622
  return t === "categorical" ? "Categorical" : t === "ordinal" ? "Ordinal" : t === "continuous" || e?.isMeasure === true || ot.test(String(e?.dataType ?? "")) ? "Continuous" : "Categorical";
622
623
  }
623
- function Jt(e) {
624
+ function Qt(e) {
624
625
  return e?.isMeasure === true || ot.test(String(e?.dataType ?? ""));
625
626
  }
626
627
  function G2(e) {
627
- let t = e ?? {}, i = Yt(t);
628
+ let t = e ?? {}, i = Jt(t);
628
629
  if (i !== "Continuous") return { nature: i, additivity: "unknown", basis: "role", suppressed: false };
629
630
  if (z2(t.name)) return { nature: i, additivity: "positional", basis: "name", suppressed: true };
630
631
  let s = String(t.additivity ?? "").trim().toLowerCase();
@@ -635,10 +636,10 @@ function G2(e) {
635
636
  }
636
637
  return t.discourageAggregationAcrossGroups === true ? { nature: i, additivity: "intensive_rate", basis: "host-flag", suppressed: false } : it(t.name) ? { nature: i, additivity: "intensive_rate", basis: "name", suppressed: false } : { nature: i, additivity: "additive", basis: "default", suppressed: false };
637
638
  }
638
- function F2(e) {
639
+ function D2(e) {
639
640
  let t = G2(e), i = e ?? {};
640
641
  if (t.nature === "Categorical") return ["first", "distinctcount", "count"];
641
- if (t.nature === "Ordinal") return Jt(i) ? ["median", "average", "min", "max", "first", "distinctcount", "count"] : ["first", "distinctcount", "count"];
642
+ if (t.nature === "Ordinal") return Qt(i) ? ["median", "average", "min", "max", "first", "distinctcount", "count"] : ["first", "distinctcount", "count"];
642
643
  switch (t.additivity) {
643
644
  case "positional":
644
645
  return ["min", "max", "count", "distinctcount", "first"];
@@ -648,16 +649,16 @@ function F2(e) {
648
649
  return ["sum", "average", "median", "min", "max", "count", "distinctcount", "first"];
649
650
  }
650
651
  }
651
- function Qt(e) {
652
- return F2(e)[0];
652
+ function te(e) {
653
+ return D2(e)[0];
653
654
  }
654
- function ee(e, t) {
655
+ function ne(e, t) {
655
656
  if (t !== "sum" && t !== "count") return false;
656
657
  if (t === "count") return true;
657
658
  let i = G2(e);
658
659
  return i.additivity === "additive" || i.additivity === "part_of_whole" || i.additivity === "unknown";
659
660
  }
660
- var $2 = 2;
661
+ var F2 = 2;
661
662
  var at = 400;
662
663
  function K2(e) {
663
664
  return !!E(e);
@@ -715,24 +716,24 @@ function H2(e) {
715
716
  function st(e, t) {
716
717
  return p(t, en) ? true : V2(e).some((i) => /^\d{5}(-\d{4})?$/.test(i.replace(/\.0+$/, "")));
717
718
  }
718
- function ne(e, t) {
719
+ function ie(e, t) {
719
720
  let i = v2(e);
720
721
  if (i.length === 0) return 0;
721
722
  let s = 0;
722
723
  for (let u2 of i) p3(u2, t) && s++;
723
724
  return Math.round(s / i.length * 1e3) / 10;
724
725
  }
725
- function D2(e, t) {
726
+ function $2(e, t) {
726
727
  let i = 0;
727
728
  for (let s of v2(e)) t(s) && i++;
728
729
  return i;
729
730
  }
730
- function ie(e, t) {
731
+ function oe(e, t) {
731
732
  let i = 0;
732
733
  for (let s of V2(e)) t(s) && i++;
733
734
  return i;
734
735
  }
735
- function oe(e, t, i, s, u2) {
736
+ function re(e, t, i, s, u2) {
736
737
  let x2 = [], c = [], o = {}, R = {}, N2 = new Set(e), l = (n) => t.map((r) => r[n]);
737
738
  for (let n of ["city", "country"]) {
738
739
  let r = i?.[n];
@@ -761,15 +762,15 @@ function oe(e, t, i, s, u2) {
761
762
  if (h2(a2)) continue;
762
763
  let d = I2(a2);
763
764
  if (d < 95) continue;
764
- let E2 = D2(a2, (b2) => !!Y(b2)), ct = D2(a2, (b2) => !!Y(b2) && !rt(b2, s));
765
- E2 < $2 && ct < 1 || (!n || d > n.pct) && (n = { name: r, pct: d });
765
+ let E2 = $2(a2, (b2) => !!Y(b2)), ct = $2(a2, (b2) => !!Y(b2) && !rt(b2, s));
766
+ E2 < F2 && ct < 1 || (!n || d > n.pct) && (n = { name: r, pct: d });
766
767
  }
767
768
  n && (o.state = n.name, m2.add(n.name), x2.push(`state=${n.name} (${n.pct}% states/provinces)`));
768
769
  }
769
770
  if (!o.zip) for (let n of C) {
770
771
  if (m2.has(n)) continue;
771
772
  let r = l(n), a2 = H2(r);
772
- if (!(a2 < 95) && st(r, n) && !(ie(r, (d) => Z(d).length > 0) < $2)) {
773
+ if (!(a2 < 95) && st(r, n) && !(oe(r, (d) => Z(d).length > 0) < F2)) {
773
774
  o.zip = n, m2.add(n), x2.push(`zip=${n} (${a2}% ZIP codes)`);
774
775
  break;
775
776
  }
@@ -788,8 +789,8 @@ function oe(e, t, i, s, u2) {
788
789
  let n = null;
789
790
  for (let r of C) {
790
791
  if (m2.has(r)) continue;
791
- let a2 = l(r), d = ne(a2, s);
792
- d < m3 || D2(a2, (E2) => p3(E2, s)) < $2 || (!n || d > n.pct) && (n = { name: r, pct: d });
792
+ let a2 = l(r), d = ie(a2, s);
793
+ d < m3 || $2(a2, (E2) => p3(E2, s)) < F2 || (!n || d > n.pct) && (n = { name: r, pct: d });
793
794
  }
794
795
  n && (o.city = n.name, x2.push(`city=${n.name} (${n.pct}% known cities)`));
795
796
  }
@@ -812,7 +813,7 @@ function resolvePointChannel(cols, rowObjs, point) {
812
813
  let labelCol;
813
814
  if (bind) {
814
815
  const dims = cols.filter((c) => !c.isMeasure).map((c) => c.name);
815
- const res = oe(dims, rowObjs, bind, point?.mapKind, cols.map((c) => c.name));
816
+ const res = re(dims, rowObjs, bind, point?.mapKind, cols.map((c) => c.name));
816
817
  bind = res.bind;
817
818
  rolesBackfilled = res.backfilled;
818
819
  rolesRefused = res.refused;
@@ -1353,6 +1354,17 @@ function createChartHost(container, config) {
1353
1354
  .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}:not(.${MARK_SELECTED_CLASS}):not([${ROW_IDX_ATTR}=""]) {
1354
1355
  opacity: var(${DIM_OPACITY_VAR}, ${DIM_OPACITY_DEFAULT}) !important;
1355
1356
  }
1357
+ /* THE LIFT, BY DECLARATION ONLY. Dimming the others is all the rule above does; a selected
1358
+ mark keeps whatever paint its chart gave it. A chart that draws every mark pale on purpose
1359
+ (a parallel-coordinates plot at 0.38 so overplotting reads) therefore shows its SELECTED
1360
+ lines at 38% - the same eye-reading as dimmed. Inside a group the chart stamps
1361
+ ${LIFT_SELECTED_CLASS} on, a selected mark is painted at full opacity. Opacity only, never
1362
+ stroke geometry. Undeclared charts are byte-identical, which is what keeps an alpha
1363
+ ENCODING (a depth cue, a hierarchy level) from being flattened by a fix meant for one. */
1364
+ .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${LIFT_SELECTED_CLASS} .${MARK_CLASS}.${MARK_SELECTED_CLASS},
1365
+ .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}.${LIFT_SELECTED_CLASS}.${MARK_SELECTED_CLASS} {
1366
+ opacity: 1 !important; stroke-opacity: 1 !important; fill-opacity: 1 !important;
1367
+ }
1356
1368
  .${HOST_CONTAINER_CLASS} .${LEGEND_MARK_CLASS} { cursor: pointer; }
1357
1369
  /* An axis/group header that filters when clicked LOOKS like inert text, and until it is
1358
1370
  marked there is no way to tell a landed filter from a dead label \u2014 the report reads
@@ -1566,6 +1578,7 @@ export {
1566
1578
  MARK_CLASS,
1567
1579
  LEGEND_MARK_CLASS,
1568
1580
  AXIS_FILTER_CLASS,
1581
+ LIFT_SELECTED_CLASS,
1569
1582
  ROW_IDX_ATTR,
1570
1583
  XFILTER_REFRESH_EVENT,
1571
1584
  CONTAINER_SLOT_ANIM_STOP,
@@ -1601,9 +1614,9 @@ export {
1601
1614
  clearGeoCache,
1602
1615
  L3,
1603
1616
  G2 as G,
1604
- F2 as F,
1605
- Qt,
1606
- ee,
1617
+ D2 as D,
1618
+ te,
1619
+ ne,
1607
1620
  buildRenderPayload,
1608
1621
  createMarkResolver,
1609
1622
  ensureCrossfilterHitTargets,
package/dist/index.mjs CHANGED
@@ -17,9 +17,9 @@ import {
17
17
  CONTAINER_SLOT_INITIAL_XF_MARK,
18
18
  CONTAINER_SLOT_UI_STATE,
19
19
  CONTAINER_SLOT_XF_CLEAR,
20
+ D,
20
21
  DIM_OPACITY_DEFAULT,
21
22
  DIM_OPACITY_VAR,
22
- F,
23
23
  FLIP_MODE_DEFAULT,
24
24
  G,
25
25
  GEO_POINT_PRECISIONS,
@@ -27,9 +27,9 @@ import {
27
27
  HOST_CONTRACT_VERSION,
28
28
  L3,
29
29
  LEGEND_MARK_CLASS,
30
+ LIFT_SELECTED_CLASS,
30
31
  MARK_CLASS,
31
32
  MARK_SELECTED_CLASS,
32
- Qt,
33
33
  ROW_IDX_ATTR,
34
34
  SELECTION_ACTIVE_CLASS,
35
35
  XFILTER_REFRESH_EVENT,
@@ -41,13 +41,13 @@ import {
41
41
  compileRenderFn,
42
42
  createChartHost,
43
43
  createMarkResolver,
44
- ee,
45
44
  ensureCrossfilterHitTargets,
46
45
  explainRenderFailure,
47
46
  geoAssetFor,
48
47
  geoFromCache,
49
48
  isBlankRender,
50
49
  loadGeo,
50
+ ne,
51
51
  noopViewStateProvider,
52
52
  periodTickSuppressesFeedback,
53
53
  registerGeo,
@@ -55,8 +55,9 @@ import {
55
55
  requiredD3Plugins,
56
56
  resolveOptions,
57
57
  sessionViewStateProvider,
58
- stripEsmExports
59
- } from "./chunk-B4RZSFAC.mjs";
58
+ stripEsmExports,
59
+ te
60
+ } from "./chunk-COSG4M73.mjs";
60
61
  import "./chunk-A2GMXZP7.mjs";
61
62
 
62
63
  // src/trivial.ts
@@ -693,11 +694,11 @@ function isSuppressed(col) {
693
694
  }
694
695
  function resolveAgg(col, requested, auto) {
695
696
  const fallback = () => {
696
- const d = Qt(col);
697
+ const d = te(col);
697
698
  return d === "first" ? "count" : d;
698
699
  };
699
700
  if (auto) return fallback();
700
- return F(col).indexOf(requested) >= 0 ? requested : fallback();
701
+ return D(col).indexOf(requested) >= 0 ? requested : fallback();
701
702
  }
702
703
  function toNumber(v) {
703
704
  if (v == null || v === "") return null;
@@ -801,7 +802,7 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
801
802
  }
802
803
  const value = reduce(selVals, colAgg, selRaw);
803
804
  let sharePct = null;
804
- if (ee(col, colAgg) && value != null) {
805
+ if (ne(col, colAgg) && value != null) {
805
806
  const allVals = [];
806
807
  const allRaw = [];
807
808
  for (let r = 0; r < rows.length; r++) {
@@ -891,6 +892,7 @@ export {
891
892
  HOST_CONTAINER_CLASS,
892
893
  HOST_CONTRACT_VERSION,
893
894
  LEGEND_MARK_CLASS,
895
+ LIFT_SELECTED_CLASS,
894
896
  MARK_CLASS,
895
897
  MARK_SELECTED_CLASS,
896
898
  ROW_IDX_ATTR,
package/dist/react.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createChartHost,
5
5
  geoFromCache,
6
6
  loadGeo
7
- } from "./chunk-B4RZSFAC.mjs";
7
+ } from "./chunk-COSG4M73.mjs";
8
8
  import "./chunk-A2GMXZP7.mjs";
9
9
 
10
10
  // src/react.tsx
@@ -1,4 +1,4 @@
1
- export declare const HOST_CONTRACT_VERSION = "1.6.0";
1
+ export declare const HOST_CONTRACT_VERSION = "1.7.0";
2
2
  export type GeoPointPrecision = "latlon" | "city" | "zip3" | "state" | "country";
3
3
  /** Every valid tier, ordered most precise → coarsest. Runtime form of GeoPointPrecision. */
4
4
  export declare const GEO_POINT_PRECISIONS: readonly GeoPointPrecision[];
@@ -10,6 +10,7 @@ export type GeoMapKind = "north-america" | "world";
10
10
  export declare const MARK_CLASS = "d3-mark";
11
11
  export declare const LEGEND_MARK_CLASS = "d3-legend-mark";
12
12
  export declare const AXIS_FILTER_CLASS = "d3-axis-filter";
13
+ export declare const LIFT_SELECTED_CLASS = "d3-lift-selected";
13
14
  export declare const ROW_IDX_ATTR = "data-row-idx";
14
15
  export declare const XFILTER_REFRESH_EVENT = "llm-xfilter-refresh";
15
16
  export declare const CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
@@ -1,4 +1,4 @@
1
- export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type ColorScaleScope, type RenderOptions, type ViewStateProvider, } from "./contract";
1
+ export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, LIFT_SELECTED_CLASS, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type ColorScaleScope, type RenderOptions, type ViewStateProvider, } from "./contract";
2
2
  export { resolveOptions, type ResolveOptionsInput } from "./defaults";
3
3
  export { loadGeo, geoFromCache, registerGeo, registerGeoAsset, geoAssetFor, clearGeoCache, type GeoAssetName, } from "./geoLazy";
4
4
  /** REPLACE the bundled city gazetteer with another packed table. Rarely needed — see the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.5.51",
3
+ "version": "0.5.53",
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",