@bicharts/chart-host 0.1.14 → 0.3.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 +50 -0
- package/dist/{chunk-RTPHW4YT.mjs → chunk-4DULDBNH.mjs} +52 -42
- package/dist/index.mjs +5 -1
- package/dist/react.mjs +1 -1
- package/dist/types/host.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,19 @@ The integration contract returned by `generate_chart` names exactly which ones *
|
|
|
120
120
|
needs. If one is missing, the host throws a message naming the package instead of a bare
|
|
121
121
|
`d3.sankey is not a function`.
|
|
122
122
|
|
|
123
|
+
You can also ask the code itself, *before* rendering it — which is what you want when the chart
|
|
124
|
+
arrives at run time and a blank frame is not an acceptable answer:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
import { requiredD3Plugins } from "@bicharts/chart-host";
|
|
128
|
+
|
|
129
|
+
requiredD3Plugins(code); // -> ["d3-sankey"] (sorted, deduped, [] for core-only d3)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
It is a static scan, not a trial render: synchronous, side-effect free, and safe to run on
|
|
133
|
+
generated code you have not executed yet. It errs toward naming a plugin the chart may not
|
|
134
|
+
reach — a needless install is cheaper than a blank chart.
|
|
135
|
+
|
|
123
136
|
## Selection styling
|
|
124
137
|
|
|
125
138
|
Generated code emits marks and `data-row-idx`; it never styles "selected", because only the host
|
|
@@ -131,6 +144,43 @@ visual — override them if you want a different look:
|
|
|
131
144
|
.bic-chart-host.lch-has-selection .d3-mark:not(.lch-mark-selected) { opacity: var(--lch-dim-opacity); }
|
|
132
145
|
```
|
|
133
146
|
|
|
147
|
+
## Architecture — three layers, and why it matters if you contribute
|
|
148
|
+
|
|
149
|
+
Read this before adding code. The package has **three** concerns, and they are independent.
|
|
150
|
+
Most well-meaning changes that damage it do so by collapsing two of them.
|
|
151
|
+
|
|
152
|
+
| layer | knows about | must **not** know about |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| **1 · Core** | rows, marks as a *concept*, selection state, the cross-filter protocol, option resolution | any charting library, any host |
|
|
155
|
+
| **2 · Renderer adapter** | how *one* library expresses a binding, and where its marks live | the host it is running in |
|
|
156
|
+
| **3 · Host glue** | one host's lifecycle, identity model and settings | a library's internals |
|
|
157
|
+
|
|
158
|
+
Layer 3 is **not in this package** — it is the integrator's. `createChartHost` is the glue for a
|
|
159
|
+
plain web page; the BIC Power BI visual has its own, because a report host's lifecycle is
|
|
160
|
+
genuinely different. That separation is deliberate: merging them would drag host concepts into a
|
|
161
|
+
package whose entire value is not having them.
|
|
162
|
+
|
|
163
|
+
**Layer 2 is the one that is easy to get wrong**, because today only one adapter exists (D3) and
|
|
164
|
+
its assumptions are easy to mistake for universal truths. They are not. Two examples from charts
|
|
165
|
+
BIC already generates:
|
|
166
|
+
|
|
167
|
+
- **Marks are not always DOM elements.** D3 tags them in the DOM (`.d3-mark` + `data-row-idx`).
|
|
168
|
+
Other libraries carry the row binding in their *data model* instead, on the trace or point.
|
|
169
|
+
An abstraction phrased as `querySelectorAll(markClass)` is D3's implementation wearing the
|
|
170
|
+
costume of an interface.
|
|
171
|
+
- **A renderer may support no interaction at all.** Some chart types render to a raster image —
|
|
172
|
+
no DOM, no marks, nothing to hit-test. A null adapter has to be legal.
|
|
173
|
+
|
|
174
|
+
So: if you are adding support for another charting library, you should be implementing layer 2
|
|
175
|
+
and touching nothing else. If your change needs edits in the core to make one library work, the
|
|
176
|
+
seam is in the wrong place — please open an issue rather than widening the core.
|
|
177
|
+
|
|
178
|
+
**Known wart, and it is ours, not yours.** `contract.ts` currently mixes layers 1 and 2:
|
|
179
|
+
`MARK_CLASS` (`"d3-mark"`) and `ROW_IDX_ATTR` are D3's binding mechanism, while
|
|
180
|
+
`XFILTER_REFRESH_EVENT`, the `lch-*` selection classes and `DIM_OPACITY_VAR` are genuinely
|
|
181
|
+
renderer-neutral. The names are historical. Do not take the file's current shape as licence to
|
|
182
|
+
add more renderer-specific constants to it.
|
|
183
|
+
|
|
134
184
|
## Versioning
|
|
135
185
|
|
|
136
186
|
`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
|
|
89
|
-
if (/^country/.test(
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
92
|
-
if (
|
|
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],
|
|
250
|
-
if (
|
|
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
|
|
378
|
-
if (
|
|
379
|
-
let
|
|
380
|
-
e.has(
|
|
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
|
|
390
|
+
var b2 = 85;
|
|
391
391
|
var S2 = 2;
|
|
392
|
-
var
|
|
392
|
+
var st = 100;
|
|
393
393
|
var L3 = 400;
|
|
394
|
-
var
|
|
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) =>
|
|
406
|
+
return n.length === 0 || !n.every((e) => T2.has(e)) ? false : n.some((e) => !O(e));
|
|
407
407
|
}
|
|
408
|
-
function
|
|
409
|
-
return
|
|
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
|
|
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
|
|
428
|
-
let n =
|
|
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
|
|
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
|
|
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
|
|
446
|
+
function at(i, n) {
|
|
447
447
|
let e = 0;
|
|
448
|
-
for (let o of
|
|
448
|
+
for (let o of I2(i)) n(o) && e++;
|
|
449
449
|
return e;
|
|
450
450
|
}
|
|
451
|
-
function
|
|
452
|
-
let o = [], p2 = [], s = {},
|
|
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 &&
|
|
455
|
+
r && k2.has(r) && (s[t] = r);
|
|
456
456
|
}
|
|
457
457
|
let u = e?.state;
|
|
458
|
-
if (u &&
|
|
458
|
+
if (u && k2.has(u)) {
|
|
459
459
|
let t = d(u), r = m2(t);
|
|
460
|
-
r.length > 0 && r.every(
|
|
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 <
|
|
470
|
-
let
|
|
471
|
-
|
|
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 =
|
|
478
|
-
if (!(a <
|
|
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 =
|
|
494
|
-
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 =
|
|
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
|
|
565
|
-
a[
|
|
566
|
-
if (geoIso) a[
|
|
564
|
+
let k2 = cols.length;
|
|
565
|
+
a[k2++] = r;
|
|
566
|
+
if (geoIso) a[k2++] = geoIso[r];
|
|
567
567
|
if (pLat && pLon) {
|
|
568
|
-
a[
|
|
569
|
-
a[
|
|
568
|
+
a[k2++] = pLat[r];
|
|
569
|
+
a[k2++] = pLon[r];
|
|
570
570
|
}
|
|
571
571
|
out[r] = a;
|
|
572
572
|
}
|
|
@@ -742,6 +742,14 @@ var D3_PLUGIN_PACKAGES = {
|
|
|
742
742
|
voronoiMap: "d3-voronoi-map",
|
|
743
743
|
weightedVoronoi: "d3-weighted-voronoi"
|
|
744
744
|
};
|
|
745
|
+
function requiredD3Plugins(code) {
|
|
746
|
+
const out = /* @__PURE__ */ new Set();
|
|
747
|
+
for (const m3 of String(code || "").matchAll(/\bd3\s*\.\s*(\w+)\s*\(/g)) {
|
|
748
|
+
const pkg = D3_PLUGIN_PACKAGES[m3[1]];
|
|
749
|
+
if (pkg) out.add(pkg);
|
|
750
|
+
}
|
|
751
|
+
return [...out].sort();
|
|
752
|
+
}
|
|
745
753
|
function explainRenderFailure(err, d3) {
|
|
746
754
|
const msg = String(err?.message ?? err ?? "");
|
|
747
755
|
if (!d3) {
|
|
@@ -1013,6 +1021,8 @@ export {
|
|
|
1013
1021
|
clearGeoCache,
|
|
1014
1022
|
buildRenderPayload,
|
|
1015
1023
|
createMarkResolver,
|
|
1024
|
+
requiredD3Plugins,
|
|
1025
|
+
explainRenderFailure,
|
|
1016
1026
|
compileRenderFn,
|
|
1017
1027
|
createChartHost
|
|
1018
1028
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -29,13 +29,15 @@ import {
|
|
|
29
29
|
compileRenderFn,
|
|
30
30
|
createChartHost,
|
|
31
31
|
createMarkResolver,
|
|
32
|
+
explainRenderFailure,
|
|
32
33
|
geoAssetFor,
|
|
33
34
|
geoFromCache,
|
|
34
35
|
loadGeo,
|
|
35
36
|
registerGeo,
|
|
36
37
|
registerGeoAsset,
|
|
38
|
+
requiredD3Plugins,
|
|
37
39
|
resolveOptions
|
|
38
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-4DULDBNH.mjs";
|
|
39
41
|
import "./chunk-A2GMXZP7.mjs";
|
|
40
42
|
export {
|
|
41
43
|
ANIM_MAX_IDEAL_FRAMES_DEFAULT,
|
|
@@ -67,10 +69,12 @@ export {
|
|
|
67
69
|
compileRenderFn,
|
|
68
70
|
createChartHost,
|
|
69
71
|
createMarkResolver,
|
|
72
|
+
explainRenderFailure,
|
|
70
73
|
geoAssetFor,
|
|
71
74
|
geoFromCache,
|
|
72
75
|
loadGeo,
|
|
73
76
|
registerGeo,
|
|
74
77
|
registerGeoAsset,
|
|
78
|
+
requiredD3Plugins,
|
|
75
79
|
resolveOptions
|
|
76
80
|
};
|
package/dist/react.mjs
CHANGED
package/dist/types/host.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface ChartHost {
|
|
|
56
56
|
initialMark(): Element | null;
|
|
57
57
|
destroy(): void;
|
|
58
58
|
}
|
|
59
|
+
export declare function requiredD3Plugins(code: string): string[];
|
|
59
60
|
export declare function explainRenderFailure(err: unknown, d3: any): unknown;
|
|
60
61
|
export declare function compileRenderFn(code: string, win: any, doc: any, d3: any): RenderFn;
|
|
61
62
|
export declare function createChartHost(container: HTMLElement, config: ChartHostConfig): ChartHost;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export * 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
|
export { buildRenderPayload, type RenderPayload, type GeoPointBinding } from "./payload";
|
|
5
|
-
export { createChartHost, compileRenderFn, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
|
|
5
|
+
export { createChartHost, compileRenderFn, requiredD3Plugins, explainRenderFailure, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
|
|
6
6
|
export { createMarkResolver, type MarkResolver, type MarkResolverEnv } from "./selection";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|