@bicharts/chart-host 0.1.14 → 0.2.0

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
@@ -131,6 +131,43 @@ visual — override them if you want a different look:
131
131
  .bic-chart-host.lch-has-selection .d3-mark:not(.lch-mark-selected) { opacity: var(--lch-dim-opacity); }
132
132
  ```
133
133
 
134
+ ## Architecture — three layers, and why it matters if you contribute
135
+
136
+ Read this before adding code. The package has **three** concerns, and they are independent.
137
+ Most well-meaning changes that damage it do so by collapsing two of them.
138
+
139
+ | layer | knows about | must **not** know about |
140
+ | --- | --- | --- |
141
+ | **1 · Core** | rows, marks as a *concept*, selection state, the cross-filter protocol, option resolution | any charting library, any host |
142
+ | **2 · Renderer adapter** | how *one* library expresses a binding, and where its marks live | the host it is running in |
143
+ | **3 · Host glue** | one host's lifecycle, identity model and settings | a library's internals |
144
+
145
+ Layer 3 is **not in this package** — it is the integrator's. `createChartHost` is the glue for a
146
+ plain web page; the BIC Power BI visual has its own, because a report host's lifecycle is
147
+ genuinely different. That separation is deliberate: merging them would drag host concepts into a
148
+ package whose entire value is not having them.
149
+
150
+ **Layer 2 is the one that is easy to get wrong**, because today only one adapter exists (D3) and
151
+ its assumptions are easy to mistake for universal truths. They are not. Two examples from charts
152
+ BIC already generates:
153
+
154
+ - **Marks are not always DOM elements.** D3 tags them in the DOM (`.d3-mark` + `data-row-idx`).
155
+ Other libraries carry the row binding in their *data model* instead, on the trace or point.
156
+ An abstraction phrased as `querySelectorAll(markClass)` is D3's implementation wearing the
157
+ costume of an interface.
158
+ - **A renderer may support no interaction at all.** Some chart types render to a raster image —
159
+ no DOM, no marks, nothing to hit-test. A null adapter has to be legal.
160
+
161
+ So: if you are adding support for another charting library, you should be implementing layer 2
162
+ and touching nothing else. If your change needs edits in the core to make one library work, the
163
+ seam is in the wrong place — please open an issue rather than widening the core.
164
+
165
+ **Known wart, and it is ours, not yours.** `contract.ts` currently mixes layers 1 and 2:
166
+ `MARK_CLASS` (`"d3-mark"`) and `ROW_IDX_ATTR` are D3's binding mechanism, while
167
+ `XFILTER_REFRESH_EVENT`, the `lch-*` selection classes and `DIM_OPACITY_VAR` are genuinely
168
+ renderer-neutral. The names are historical. Do not take the file's current shape as licence to
169
+ add more renderer-specific constants to it.
170
+
134
171
  ## Versioning
135
172
 
136
173
  `HOST_CONTRACT_VERSION` is the grammar version (mark classes, container slots, the cross-filter
@@ -85,11 +85,11 @@ var byAsset = /* @__PURE__ */ new Map();
85
85
  var _na;
86
86
  function geoAssetFor(geoKind) {
87
87
  if (!geoKind) return null;
88
- const k3 = geoKind.toLowerCase();
89
- if (/^country/.test(k3)) return "world";
90
- if (k3 === "north-america") return "world";
91
- if (k3 === "us-state-code" || k3 === "us-state-name") return "us-states";
92
- if (k3 === "us-zip5") return "us-zip3";
88
+ const k2 = geoKind.toLowerCase();
89
+ if (/^country/.test(k2)) return "world";
90
+ if (k2 === "north-america") return "world";
91
+ if (k2 === "us-state-code" || k2 === "us-state-name") return "us-states";
92
+ if (k2 === "us-zip5") return "us-zip3";
93
93
  return null;
94
94
  }
95
95
  function northAmerica(world) {
@@ -246,8 +246,8 @@ var T = { latlon: 0, city: 1, zip3: 2, state: 3 };
246
246
  function F(a) {
247
247
  let e = new Array(a.length), n = new Array(a.length), o = /* @__PURE__ */ new Set(), l2 = [], t = 0, r = 0, i = null;
248
248
  for (let s = 0; s < a.length; ++s) {
249
- let M2 = a[s], A = k(M2);
250
- if (A) e[s] = A.lat, n[s] = A.lon, t++, A.ambiguous && r++, (i === null || T[A.precision] > T[i]) && (i = A.precision);
249
+ let M2 = a[s], A2 = k(M2);
250
+ if (A2) e[s] = A2.lat, n[s] = A2.lon, t++, A2.ambiguous && r++, (i === null || T[A2.precision] > T[i]) && (i = A2.precision);
251
251
  else {
252
252
  e[s] = null, n[s] = null;
253
253
  let d = [M2.city, M2.state, M2.zip].filter((u) => u != null && String(u).trim() !== "").map((u) => String(u).trim()).join(", ");
@@ -374,10 +374,10 @@ function Rn(n, o) {
374
374
  else {
375
375
  let I3 = n[c2];
376
376
  if (I3 != null) {
377
- let T2 = String(I3).trim();
378
- if (T2.length > 0) {
379
- let R2 = T2.toLowerCase();
380
- e.has(R2) || (e.add(R2), u.push(T2));
377
+ let T3 = String(I3).trim();
378
+ if (T3.length > 0) {
379
+ let R = T3.toLowerCase();
380
+ e.has(R) || (e.add(R), u.push(T3));
381
381
  }
382
382
  }
383
383
  }
@@ -387,11 +387,11 @@ function Rn(n, o) {
387
387
 
388
388
  // ../shape-core/dist/index.mjs
389
389
  /*! @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. */
390
- var R = 85;
390
+ var b2 = 85;
391
391
  var S2 = 2;
392
- var ot = 100;
392
+ var st = 100;
393
393
  var L3 = 400;
394
- var w = /* @__PURE__ */ new Set(["us", "usa", "u s", "u s a", "united states", "united states of america", "america", "ca", "can", "canada", "mx", "mex", "mexico", "estados unidos mexicanos"]);
394
+ var T2 = /* @__PURE__ */ new Set(["us", "usa", "u s", "u s a", "united states", "united states of america", "america", "ca", "can", "canada", "mx", "mex", "mexico", "estados unidos mexicanos"]);
395
395
  function m2(i) {
396
396
  let n = /* @__PURE__ */ new Set();
397
397
  for (let e of i) {
@@ -403,10 +403,10 @@ function m2(i) {
403
403
  }
404
404
  function P2(i) {
405
405
  let n = m2(i);
406
- return n.length === 0 || !n.every((e) => w.has(e)) ? false : n.some((e) => !O(e));
406
+ return n.length === 0 || !n.every((e) => T2.has(e)) ? false : n.some((e) => !O(e));
407
407
  }
408
- function M(i) {
409
- return w.has(i) && !!O(i);
408
+ function A(i) {
409
+ return T2.has(i) && !!O(i);
410
410
  }
411
411
  function x2(i) {
412
412
  let n = m2(i);
@@ -415,7 +415,7 @@ function x2(i) {
415
415
  for (let o of n) O(o) && e++;
416
416
  return Math.round(e / n.length * 1e3) / 10;
417
417
  }
418
- function z(i) {
418
+ function I2(i) {
419
419
  let n = /* @__PURE__ */ new Set();
420
420
  for (let e of i) {
421
421
  if (e == null) continue;
@@ -424,40 +424,40 @@ function z(i) {
424
424
  }
425
425
  return Array.from(n);
426
426
  }
427
- function I2(i) {
428
- let n = z(i);
427
+ function w(i) {
428
+ let n = I2(i);
429
429
  if (n.length === 0) return 0;
430
430
  let e = 0;
431
431
  for (let o of n) p(o).length > 0 && e++;
432
432
  return Math.round(e / n.length * 1e3) / 10;
433
433
  }
434
- function it(i) {
434
+ function rt(i) {
435
435
  let n = m2(i);
436
436
  if (n.length === 0) return 0;
437
437
  let e = 0;
438
438
  for (let o of n) W(o) && e++;
439
439
  return Math.round(e / n.length * 1e3) / 10;
440
440
  }
441
- function k2(i, n) {
441
+ function M(i, n) {
442
442
  let e = 0;
443
443
  for (let o of m2(i)) n(o) && e++;
444
444
  return e;
445
445
  }
446
- function st(i, n) {
446
+ function at(i, n) {
447
447
  let e = 0;
448
- for (let o of z(i)) n(o) && e++;
448
+ for (let o of I2(i)) n(o) && e++;
449
449
  return e;
450
450
  }
451
- function rt(i, n, e) {
452
- let o = [], p2 = [], s = {}, A = new Set(i), d = (t) => n.map((r) => r[t]);
451
+ function ct(i, n, e) {
452
+ let o = [], p2 = [], s = {}, k2 = new Set(i), d = (t) => n.map((r) => r[t]);
453
453
  for (let t of ["city", "zip", "lat", "lon", "country"]) {
454
454
  let r = e?.[t];
455
- r && A.has(r) && (s[t] = r);
455
+ r && k2.has(r) && (s[t] = r);
456
456
  }
457
457
  let u = e?.state;
458
- if (u && A.has(u)) {
458
+ if (u && k2.has(u)) {
459
459
  let t = d(u), r = m2(t);
460
- r.length > 0 && r.every(M) ? p2.push(`state=${u} (every value is "CA", which is both Canada and California)`) : P2(t) ? (p2.push(`state=${u} (every value is a country, not a state)`), s.country || (s.country = u)) : x2(t) < R ? p2.push(`state=${u} (only ${x2(t)}% of values are states/provinces)`) : s.state = u;
460
+ r.length > 0 && r.every(A) ? p2.push(`state=${u} (every value is "CA", which is both Canada and California)`) : P2(t) ? (p2.push(`state=${u} (every value is a country, not a state)`), s.country || (s.country = u)) : x2(t) < b2 ? p2.push(`state=${u} (only ${x2(t)}% of values are states/provinces)`) : s.state = u;
461
461
  }
462
462
  let l2 = new Set(Object.values(s).filter(Boolean)), y2 = i.filter((t) => !l2.has(t) && !t.startsWith("__"));
463
463
  if (!s.state) {
@@ -466,16 +466,16 @@ function rt(i, n, e) {
466
466
  let a = d(r);
467
467
  if (P2(a)) continue;
468
468
  let c2 = x2(a);
469
- if (c2 < R) continue;
470
- let v3 = k2(a, (h2) => !!O(h2)), T2 = k2(a, (h2) => !!O(h2) && !M(h2));
471
- v3 < S2 && T2 < 1 || (!t || c2 > t.pct) && (t = { name: r, pct: c2 });
469
+ if (c2 < b2) continue;
470
+ let C2 = M(a, (g2) => !!O(g2)), z = M(a, (g2) => !!O(g2) && !A(g2));
471
+ C2 < S2 && z < 1 || (!t || c2 > t.pct) && (t = { name: r, pct: c2 });
472
472
  }
473
473
  t && (s.state = t.name, l2.add(t.name), o.push(`state=${t.name} (${t.pct}% states/provinces)`));
474
474
  }
475
475
  if (!s.zip) for (let t of y2) {
476
476
  if (l2.has(t)) continue;
477
- let r = d(t), a = I2(r);
478
- if (!(a < ot) && !(st(r, (c2) => p(c2).length > 0) < S2)) {
477
+ let r = d(t), a = w(r);
478
+ if (!(a < st) && !(at(r, (c2) => p(c2).length > 0) < S2)) {
479
479
  s.zip = t, l2.add(t), o.push(`zip=${t} (${a}% ZIP codes)`);
480
480
  break;
481
481
  }
@@ -490,8 +490,8 @@ function rt(i, n, e) {
490
490
  let t = null;
491
491
  for (let r of y2) {
492
492
  if (l2.has(r)) continue;
493
- let a = d(r), c2 = it(a);
494
- c2 < R || k2(a, (v3) => W(v3)) < S2 || (!t || c2 > t.pct) && (t = { name: r, pct: c2 });
493
+ let a = d(r), c2 = rt(a);
494
+ c2 < b2 || M(a, (C2) => W(C2)) < S2 || (!t || c2 > t.pct) && (t = { name: r, pct: c2 });
495
495
  }
496
496
  t && (s.city = t.name, o.push(`city=${t.name} (${t.pct}% known cities)`));
497
497
  }
@@ -509,7 +509,7 @@ function buildRenderPayload(cols, rowObjs, geo, point) {
509
509
  let rolesRefused = [];
510
510
  if (bind) {
511
511
  const dims = cols.filter((c2) => !c2.isMeasure).map((c2) => c2.name);
512
- const res = rt(dims, rowObjs, bind);
512
+ const res = ct(dims, rowObjs, bind);
513
513
  bind = res.bind;
514
514
  rolesBackfilled = res.backfilled;
515
515
  rolesRefused = res.refused;
@@ -561,12 +561,12 @@ function buildRenderPayload(cols, rowObjs, geo, point) {
561
561
  a[c2] = v3 ?? null;
562
562
  }
563
563
  }
564
- let k3 = cols.length;
565
- a[k3++] = r;
566
- if (geoIso) a[k3++] = geoIso[r];
564
+ let k2 = cols.length;
565
+ a[k2++] = r;
566
+ if (geoIso) a[k2++] = geoIso[r];
567
567
  if (pLat && pLon) {
568
- a[k3++] = pLat[r];
569
- a[k3++] = pLon[r];
568
+ a[k2++] = pLat[r];
569
+ a[k2++] = pLon[r];
570
570
  }
571
571
  out[r] = a;
572
572
  }
package/dist/index.mjs CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  registerGeo,
36
36
  registerGeoAsset,
37
37
  resolveOptions
38
- } from "./chunk-RTPHW4YT.mjs";
38
+ } from "./chunk-ARQZJCD4.mjs";
39
39
  import "./chunk-A2GMXZP7.mjs";
40
40
  export {
41
41
  ANIM_MAX_IDEAL_FRAMES_DEFAULT,
package/dist/react.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  buildRenderPayload,
4
4
  createChartHost
5
- } from "./chunk-RTPHW4YT.mjs";
5
+ } from "./chunk-ARQZJCD4.mjs";
6
6
  import "./chunk-A2GMXZP7.mjs";
7
7
 
8
8
  // src/react.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
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",