@bicharts/chart-host 0.1.5

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.
Files changed (41) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +50 -0
  3. package/README.md +167 -0
  4. package/dist/chunk-22B22PJU.mjs +70 -0
  5. package/dist/chunk-22B22PJU.mjs.map +7 -0
  6. package/dist/chunk-23GNNTXQ.mjs +9 -0
  7. package/dist/chunk-23GNNTXQ.mjs.map +7 -0
  8. package/dist/chunk-MHDS4Q5G.mjs +9 -0
  9. package/dist/chunk-MHDS4Q5G.mjs.map +7 -0
  10. package/dist/chunk-NWID6YOP.mjs +9 -0
  11. package/dist/chunk-NWID6YOP.mjs.map +7 -0
  12. package/dist/chunk-UNH36TGD.mjs +1185 -0
  13. package/dist/chunk-UNH36TGD.mjs.map +7 -0
  14. package/dist/geo.mjs +41 -0
  15. package/dist/geo.mjs.map +7 -0
  16. package/dist/geoNaRegions-SAINMZXG.mjs +12 -0
  17. package/dist/geoNaRegions-SAINMZXG.mjs.map +7 -0
  18. package/dist/geoUsStates.generated-2ZZRXWWJ.mjs +8 -0
  19. package/dist/geoUsStates.generated-2ZZRXWWJ.mjs.map +7 -0
  20. package/dist/geoUsZip3.generated-56OL4TTZ.mjs +8 -0
  21. package/dist/geoUsZip3.generated-56OL4TTZ.mjs.map +7 -0
  22. package/dist/geoWorld110m.generated-O2WOIRYY.mjs +8 -0
  23. package/dist/geoWorld110m.generated-O2WOIRYY.mjs.map +7 -0
  24. package/dist/index.mjs +70 -0
  25. package/dist/index.mjs.map +7 -0
  26. package/dist/react.mjs +97 -0
  27. package/dist/react.mjs.map +7 -0
  28. package/dist/types/contract.d.ts +69 -0
  29. package/dist/types/defaults.d.ts +5 -0
  30. package/dist/types/geo.d.ts +3 -0
  31. package/dist/types/geoLazy.d.ts +12 -0
  32. package/dist/types/geoNaRegions.d.ts +3 -0
  33. package/dist/types/geoUsStates.generated.d.ts +1 -0
  34. package/dist/types/geoUsZip3.generated.d.ts +1 -0
  35. package/dist/types/geoWorld110m.generated.d.ts +1 -0
  36. package/dist/types/host.d.ts +61 -0
  37. package/dist/types/index.d.ts +6 -0
  38. package/dist/types/payload.d.ts +31 -0
  39. package/dist/types/react.d.ts +52 -0
  40. package/dist/types/selection.d.ts +15 -0
  41. package/package.json +67 -0
package/dist/geo.mjs ADDED
@@ -0,0 +1,41 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ US_STATES
4
+ } from "./chunk-NWID6YOP.mjs";
5
+ import {
6
+ US_ZIP3
7
+ } from "./chunk-MHDS4Q5G.mjs";
8
+ import {
9
+ WORLD_110M
10
+ } from "./chunk-23GNNTXQ.mjs";
11
+ import {
12
+ NA_ISO3,
13
+ NA_LAT_MAX,
14
+ clipFeatureToLat
15
+ } from "./chunk-22B22PJU.mjs";
16
+
17
+ // src/geo.ts
18
+ function geoForKind(geoKind) {
19
+ if (!geoKind) return void 0;
20
+ const k = geoKind.toLowerCase();
21
+ if (/^country/.test(k)) return WORLD_110M;
22
+ if (k === "us-state-code" || k === "us-state-name") return US_STATES;
23
+ if (k === "us-zip5") return US_ZIP3;
24
+ if (k === "north-america") return northAmerica();
25
+ return void 0;
26
+ }
27
+ var _na;
28
+ function northAmerica() {
29
+ if (!_na) {
30
+ _na = {
31
+ type: "FeatureCollection",
32
+ features: WORLD_110M.features.filter((f) => NA_ISO3.has(f?.id) && f?.geometry && f.geometry.type !== "Point").map((f) => clipFeatureToLat(f, NA_LAT_MAX)).filter((f) => f !== null)
33
+ };
34
+ }
35
+ return _na;
36
+ }
37
+ export {
38
+ NA_ISO3,
39
+ geoForKind
40
+ };
41
+ //# sourceMappingURL=geo.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/geo.ts"],
4
+ "sourcesContent": ["import { WORLD_110M } from \"./geoWorld110m.generated\";\nimport { US_STATES } from \"./geoUsStates.generated\";\nimport { US_ZIP3 } from \"./geoUsZip3.generated\";\nimport { NA_ISO3, NA_LAT_MAX, clipFeatureToLat } from \"./geoNaRegions\";\n\n// Geometry the D3 choropleth lanes hand to generated code as `options.geo`\n// (2026-07-22 \u2014 bundled, not fetched: each asset is well under ~180 KB gzipped,\n// so it rides the visual, needs no /geo/ hosting, and renders offline/air-gapped).\n// Moved into @bicharts/chart-host (Phase B) so the Power BI visual AND the MCP client share\n// ONE copy \u2014 MCP no longer reaches into the visual's src/. Keyed by the server-supplied\n// geoKind \u2192 the FeatureCollection whose feature.id is the join key generated code binds\n// against __geoIso__:\n// country-* \u2192 WORLD_110M (feature.id = ISO 3166-1 alpha-3)\n// us-state-code|name \u2192 US_STATES (feature.id = USPS, e.g. \"CA\")\n// us-zip5 \u2192 US_ZIP3 (feature.id = 3-digit prefix; the archetype\n// joins __geoIso__.slice(0,3) == feature.id)\n// County / other assets slot in here (own generated modules) as those lanes land.\nexport function geoForKind(geoKind: string | null | undefined): any | undefined {\n if (!geoKind) return undefined;\n const k = geoKind.toLowerCase();\n if (/^country/.test(k)) return WORLD_110M;\n if (k === \"us-state-code\" || k === \"us-state-name\") return US_STATES;\n if (k === \"us-zip5\") return US_ZIP3;\n if (k === \"north-america\") return northAmerica();\n return undefined;\n}\n\n// North America BASEMAP for point-map (lat/lon) types \u2014 e.g. North America (Bubbles).\n// Not a join target (no __geoIso__): pure grey context polygons. Filtered from the\n// already-bundled world asset at runtime (zero extra bundle bytes); polygons only \u2014\n// the world asset's centroid Point features (micro-island hubs) are join aids for the\n// choropleth and would just clutter a context map. Cached after first build. The\n// server routes this via LLMChartType.GeoAssetKind -> res.GeoKind = \"north-america\".\n// Membership (and the Greenland exclusion + its rationale) lives in geoNaRegions.ts so it can\n// be read without pulling this module's ~1.3 MB of generated geometry. Re-exported for callers\n// that already hold a geo.ts import.\nexport { NA_ISO3 };\nlet _na: any | undefined;\nfunction northAmerica(): any {\n if (!_na) {\n // Membership filter, then the northern cutoff. The archetype fitExtent()s to this\n // collection, so clipping the geometry (rather than only the fit) is what actually\n // reclaims the frame: land above the cutoff is gone, so it can neither drive the fit\n // nor spill over the top of the plot area into the header strip.\n _na = {\n type: \"FeatureCollection\",\n features: (WORLD_110M.features as any[])\n .filter((f: any) => NA_ISO3.has(f?.id) && f?.geometry && f.geometry.type !== \"Point\")\n .map((f: any) => clipFeatureToLat(f, NA_LAT_MAX))\n .filter((f: any) => f !== null),\n };\n }\n return _na;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;AAiBO,SAAS,WAAW,SAAqD;AAC5E,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,IAAI,QAAQ,YAAY;AAC9B,MAAI,WAAW,KAAK,CAAC,EAAG,QAAO;AAC/B,MAAI,MAAM,mBAAmB,MAAM,gBAAiB,QAAO;AAC3D,MAAI,MAAM,UAAW,QAAO;AAC5B,MAAI,MAAM,gBAAiB,QAAO,aAAa;AAC/C,SAAO;AACX;AAYA,IAAI;AACJ,SAAS,eAAoB;AACzB,MAAI,CAAC,KAAK;AAKN,UAAM;AAAA,MACF,MAAM;AAAA,MACN,UAAW,WAAW,SACjB,OAAO,CAAC,MAAW,QAAQ,IAAI,GAAG,EAAE,KAAK,GAAG,YAAY,EAAE,SAAS,SAAS,OAAO,EACnF,IAAI,CAAC,MAAW,iBAAiB,GAAG,UAAU,CAAC,EAC/C,OAAO,CAAC,MAAW,MAAM,IAAI;AAAA,IACtC;AAAA,EACJ;AACA,SAAO;AACX;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ NA_ISO3,
4
+ NA_LAT_MAX,
5
+ clipFeatureToLat
6
+ } from "./chunk-22B22PJU.mjs";
7
+ export {
8
+ NA_ISO3,
9
+ NA_LAT_MAX,
10
+ clipFeatureToLat
11
+ };
12
+ //# sourceMappingURL=geoNaRegions-SAINMZXG.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,8 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ US_STATES
4
+ } from "./chunk-NWID6YOP.mjs";
5
+ export {
6
+ US_STATES
7
+ };
8
+ //# sourceMappingURL=geoUsStates.generated-2ZZRXWWJ.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,8 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ US_ZIP3
4
+ } from "./chunk-MHDS4Q5G.mjs";
5
+ export {
6
+ US_ZIP3
7
+ };
8
+ //# sourceMappingURL=geoUsZip3.generated-56OL4TTZ.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,8 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ WORLD_110M
4
+ } from "./chunk-23GNNTXQ.mjs";
5
+ export {
6
+ WORLD_110M
7
+ };
8
+ //# sourceMappingURL=geoWorld110m.generated-O2WOIRYY.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,70 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ ANIM_MAX_IDEAL_FRAMES_DEFAULT,
4
+ ANIM_MAX_IDEAL_FRAMES_MAX,
5
+ ANIM_MAX_IDEAL_FRAMES_MIN,
6
+ ANIM_PLAY_SPEED_DEFAULT,
7
+ ANIM_PLAY_SPEED_MAX,
8
+ ANIM_PLAY_SPEED_MIN,
9
+ AXIS_FILTER_CLASS,
10
+ COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
11
+ COLOR_SCALE_SELF_CLAMP_PCT_MAX,
12
+ COLOR_SCALE_SELF_CLAMP_PCT_MIN,
13
+ CONTAINER_SLOT_ANIM_STOP,
14
+ CONTAINER_SLOT_INITIAL_XF_MARK,
15
+ CONTAINER_SLOT_XF_CLEAR,
16
+ DIM_OPACITY_DEFAULT,
17
+ DIM_OPACITY_VAR,
18
+ GEO_POINT_PRECISIONS,
19
+ HOST_CONTAINER_CLASS,
20
+ HOST_CONTRACT_VERSION,
21
+ LEGEND_MARK_CLASS,
22
+ MARK_CLASS,
23
+ MARK_SELECTED_CLASS,
24
+ ROW_IDX_ATTR,
25
+ SELECTION_ACTIVE_CLASS,
26
+ XFILTER_REFRESH_EVENT,
27
+ buildRenderPayload,
28
+ compileRenderFn,
29
+ createChartHost,
30
+ createMarkResolver,
31
+ geoFromCache,
32
+ loadGeo,
33
+ registerGeo,
34
+ resolveOptions
35
+ } from "./chunk-UNH36TGD.mjs";
36
+ export {
37
+ ANIM_MAX_IDEAL_FRAMES_DEFAULT,
38
+ ANIM_MAX_IDEAL_FRAMES_MAX,
39
+ ANIM_MAX_IDEAL_FRAMES_MIN,
40
+ ANIM_PLAY_SPEED_DEFAULT,
41
+ ANIM_PLAY_SPEED_MAX,
42
+ ANIM_PLAY_SPEED_MIN,
43
+ AXIS_FILTER_CLASS,
44
+ COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
45
+ COLOR_SCALE_SELF_CLAMP_PCT_MAX,
46
+ COLOR_SCALE_SELF_CLAMP_PCT_MIN,
47
+ CONTAINER_SLOT_ANIM_STOP,
48
+ CONTAINER_SLOT_INITIAL_XF_MARK,
49
+ CONTAINER_SLOT_XF_CLEAR,
50
+ DIM_OPACITY_DEFAULT,
51
+ DIM_OPACITY_VAR,
52
+ GEO_POINT_PRECISIONS,
53
+ HOST_CONTAINER_CLASS,
54
+ HOST_CONTRACT_VERSION,
55
+ LEGEND_MARK_CLASS,
56
+ MARK_CLASS,
57
+ MARK_SELECTED_CLASS,
58
+ ROW_IDX_ATTR,
59
+ SELECTION_ACTIVE_CLASS,
60
+ XFILTER_REFRESH_EVENT,
61
+ buildRenderPayload,
62
+ compileRenderFn,
63
+ createChartHost,
64
+ createMarkResolver,
65
+ geoFromCache,
66
+ loadGeo,
67
+ registerGeo,
68
+ resolveOptions
69
+ };
70
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
package/dist/react.mjs ADDED
@@ -0,0 +1,97 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ buildRenderPayload,
4
+ createChartHost
5
+ } from "./chunk-UNH36TGD.mjs";
6
+
7
+ // src/react.tsx
8
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ var Ctx = createContext(null);
11
+ function useBicSelection() {
12
+ const g = useContext(Ctx);
13
+ return {
14
+ rows: g?.selection.rows ?? [],
15
+ sourceId: g?.selection.sourceId ?? null,
16
+ clear: () => g?.clear()
17
+ };
18
+ }
19
+ function BicChartGroup({ rows, columns, geo, point, children }) {
20
+ const [sel, setSel] = useState({ sourceId: null, rows: [] });
21
+ const value = useMemo(() => ({
22
+ rows,
23
+ columns,
24
+ geo,
25
+ point,
26
+ selection: sel,
27
+ publish: (id, sourceRowIdxs) => setSel(sourceRowIdxs.length ? { sourceId: id, rows: sourceRowIdxs } : { sourceId: null, rows: [] }),
28
+ // A chart never filters itself — that is what keeps the gesture reversible:
29
+ // the origin chart still shows every mark, so there is always something left
30
+ // to click to change or clear the selection.
31
+ filterFor: (id) => sel.rows.length && sel.sourceId && sel.sourceId !== id ? sel.rows : null,
32
+ clear: () => setSel({ sourceId: null, rows: [] })
33
+ }), [rows, columns, geo, point, sel]);
34
+ return /* @__PURE__ */ jsx(Ctx.Provider, { value, children });
35
+ }
36
+ function payloadFor(g, sourceIdxs) {
37
+ const idxs = sourceIdxs ?? g.rows.map((_, i) => i);
38
+ const subset = idxs.map((i) => g.rows[i]);
39
+ const p = buildRenderPayload(g.columns, subset, g.geo ?? void 0, g.point ?? void 0);
40
+ return { payload: p, rowMap: idxs };
41
+ }
42
+ function BicChart(props) {
43
+ const { code, renderFn, options, d3, geoKind, id, filteredBy, onSelect, className, style } = props;
44
+ const ref = useRef(null);
45
+ const hostRef = useRef(null);
46
+ const rowMapRef = useRef(null);
47
+ const group = useContext(Ctx);
48
+ const active = group ? group.selection : null;
49
+ const filterSel = !group ? null : filteredBy ? active.sourceId === filteredBy ? group.filterFor(id) : null : group.filterFor(id);
50
+ const built = useMemo(() => {
51
+ if (!group) return null;
52
+ return payloadFor(group, filterSel);
53
+ }, [group, filterSel && filterSel.join(",")]);
54
+ const data = built ? built.payload : props.data;
55
+ rowMapRef.current = built ? built.rowMap : null;
56
+ const onSelectRef = useRef(onSelect);
57
+ onSelectRef.current = onSelect;
58
+ useLayoutEffect(() => {
59
+ const el = ref.current;
60
+ if (!el || !code && !renderFn || !data) return;
61
+ const host = createChartHost(el, { code, renderFn, data, options, d3, geoKind });
62
+ hostRef.current = host;
63
+ const off = host.selection.onChange((payloadIdxs, source) => {
64
+ if (source === "host") return;
65
+ const map = rowMapRef.current;
66
+ const sourceIdxs = map ? payloadIdxs.map((i) => map[i]).filter((i) => i !== void 0) : payloadIdxs;
67
+ if (group && id) group.publish(id, sourceIdxs);
68
+ onSelectRef.current?.(sourceIdxs);
69
+ });
70
+ host.render();
71
+ return () => {
72
+ off();
73
+ host.destroy();
74
+ hostRef.current = null;
75
+ };
76
+ }, [code, renderFn, d3, geoKind]);
77
+ const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
78
+ useEffect(() => {
79
+ if (hostRef.current && options) hostRef.current.setOptions(options);
80
+ }, [optKey]);
81
+ useEffect(() => {
82
+ if (hostRef.current && data) hostRef.current.setData(data);
83
+ }, [data]);
84
+ useEffect(() => {
85
+ const host = hostRef.current;
86
+ if (!host || !group) return;
87
+ const mine = group.selection.sourceId === id && group.selection.rows.length > 0;
88
+ if (!mine && host.selection.current && host.selection.current.length) host.selection.clear();
89
+ }, [group?.selection, id, group]);
90
+ return /* @__PURE__ */ jsx("div", { ref, className, style });
91
+ }
92
+ export {
93
+ BicChart,
94
+ BicChartGroup,
95
+ useBicSelection
96
+ };
97
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/react.tsx"],
4
+ "sourcesContent": ["// React binding for @bicharts/chart-host \u2014 <BicChart> and <BicChartGroup>.\n//\n// This is ~150 lines that every consumer would otherwise write, and get subtly wrong in the\n// same four ways:\n//\n// 1. StrictMode double-mounts effects (React 19, dev). Without destroy() in cleanup an\n// animated chart leaks a running timer per mount \u2014 on every hot reload.\n// 2. Prop changes must not RECOMPILE. `options` -> setOptions (live restyle), `data` ->\n// setData; only new code/module tears down and rebuilds. A naive useEffect([deps])\n// recompiles on every prop change and throws away animation state.\n// 3. d3 must be INJECTED. There is no global in a bundler app, so `d3` is a required prop.\n// 4. The chart OWNS its container's DOM. React must never render children into it.\n//\n// Cross-chart filtering lives in <BicChartGroup> for a reason that is not cosmetic: rowIdx\n// values are positions WITHIN THE PAYLOAD A CHART RECEIVED. Filter the rows for chart B and\n// its payload renumbers from zero, so the same integers now denote different records \u2014\n// silently. The group owns the source table, builds each chart's payload, keeps the\n// payload-row -> source-row map, and translates selections across it. Consumers never see it.\nimport { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createChartHost, type ChartHost, type ChartHostConfig } from \"./host\";\nimport type { ResolveOptionsInput } from \"./defaults\";\nimport { buildRenderPayload, type GeoPointBinding } from \"./payload\";\n\nexport interface BicChartProps {\n /** Generated render() source (an ES module's `code` export, or the raw string). */\n code?: string;\n /** Pre-compiled render function \u2014 the alternative to `code`. */\n renderFn?: ChartHostConfig[\"renderFn\"];\n /** The payload: { columns, rows, geoUnmatched?, geoPoint? } \u2014 MCP's data.sample.json verbatim. */\n data?: ChartHostConfig[\"data\"];\n /** Raw options; shared defaults/clamps are applied by resolveOptions. */\n options?: ResolveOptionsInput;\n /** REQUIRED: the d3 v7 namespace. There is no global in a bundled app. */\n d3: any;\n /** Attach the bundled geometry for this kind as options.geo (choropleth or basemap). */\n geoKind?: string;\n /** Identity within a <BicChartGroup> \u2014 the key other charts filter by. */\n id?: string;\n /** Take this group member's selection as a filter on THIS chart's rows. */\n filteredBy?: string;\n /** Selection callback in SOURCE row indices (group) or payload indices (standalone). */\n onSelect?: (rowIdxs: number[]) => void;\n className?: string;\n style?: React.CSSProperties;\n}\n\n// \u2500\u2500 Group coordination \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// ONE active selection for the group, tagged with which chart produced it \u2014 the same\n// model every BI tool uses. Every OTHER chart filters to it; the originating chart does\n// not (it keeps showing all its marks, with the selected ones highlighted, so the user\n// can see what they picked in context and click again to change it). That single rule\n// gives mutual cross-filtering for free: click a bubble and the table filters, click a\n// table row and the map filters, with no possibility of a feedback loop.\ninterface GroupSelection { sourceId: string | null; rows: number[] }\ninterface GroupCtx {\n /** Source rows as objects, shared by every member. */\n rows: Record<string, any>[];\n columns: any[];\n geo?: { column: string; kind: string } | null;\n point?: GeoPointBinding | null;\n /** Publish a member's selection, already translated to SOURCE row indices. */\n publish(id: string, sourceRowIdxs: number[]): void;\n /** The filter `id` should APPLY (null = show everything). Never its own selection. */\n filterFor(id: string | undefined): number[] | null;\n /** The whole group's active selection, whoever produced it. */\n selection: GroupSelection;\n clear(): void;\n}\nconst Ctx = createContext<GroupCtx | null>(null);\n\n/** Read the group's selection from a page \u2014 for a \"clear\" button or a count. */\nexport function useBicSelection() {\n const g = useContext(Ctx);\n return {\n rows: g?.selection.rows ?? [],\n sourceId: g?.selection.sourceId ?? null,\n clear: () => g?.clear(),\n };\n}\n\nexport interface BicChartGroupProps {\n /** The ONE source table. Every member's payload is derived from it. */\n rows: Record<string, any>[];\n columns: any[];\n /** Choropleth join binding, applied when building each member's payload. */\n geo?: { column: string; kind: string } | null;\n /** Point-map geocoding binding (city/state/zip/lat/lon column names). */\n point?: GeoPointBinding | null;\n children: ReactNode;\n}\n\n/**\n * Owns the source table and the selection bus. Members declare `id` and optionally\n * `filteredBy`; the group re-derives the filtered member's payload and translates row\n * indices in both directions, so a click in one chart filters another CORRECTLY.\n */\nexport function BicChartGroup({ rows, columns, geo, point, children }: BicChartGroupProps) {\n const [sel, setSel] = useState<GroupSelection>({ sourceId: null, rows: [] });\n const value = useMemo<GroupCtx>(() => ({\n rows, columns, geo, point,\n selection: sel,\n publish: (id, sourceRowIdxs) =>\n setSel(sourceRowIdxs.length ? { sourceId: id, rows: sourceRowIdxs }\n : { sourceId: null, rows: [] }),\n // A chart never filters itself \u2014 that is what keeps the gesture reversible:\n // the origin chart still shows every mark, so there is always something left\n // to click to change or clear the selection.\n filterFor: (id) => (sel.rows.length && sel.sourceId && sel.sourceId !== id) ? sel.rows : null,\n clear: () => setSel({ sourceId: null, rows: [] }),\n }), [rows, columns, geo, point, sel]);\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\n/** Build a payload for a subset of source rows, keeping the payload->source index map. */\nfunction payloadFor(g: GroupCtx, sourceIdxs: number[] | null) {\n const idxs = sourceIdxs ?? g.rows.map((_, i) => i);\n const subset = idxs.map(i => g.rows[i]);\n const p = buildRenderPayload(g.columns, subset, g.geo ?? undefined, g.point ?? undefined);\n // p.rows[k] corresponds to source row idxs[k]. __rowIdx__ inside the payload was\n // re-based to 0..n-1 by the builder \u2014 THIS map is what makes the indices comparable\n // across charts, and it is exactly what a hand-rolled integration forgets.\n return { payload: p, rowMap: idxs };\n}\n\nexport function BicChart(props: BicChartProps) {\n const { code, renderFn, options, d3, geoKind, id, filteredBy, onSelect, className, style } = props;\n const ref = useRef<HTMLDivElement | null>(null);\n const hostRef = useRef<ChartHost | null>(null);\n const rowMapRef = useRef<number[] | null>(null);\n const group = useContext(Ctx);\n\n // Resolve this chart's payload: from the group (filtered + mapped) or the raw prop.\n // `filteredBy` narrows this to ONE partner when a page wants explicit one-way wiring;\n // omit it and the chart responds to whichever sibling published \u2014 mutual by default.\n const active = group ? group.selection : null;\n const filterSel = !group ? null\n : (filteredBy ? (active!.sourceId === filteredBy ? group.filterFor(id) : null)\n : group.filterFor(id));\n const built = useMemo(() => {\n if (!group) return null;\n return payloadFor(group, filterSel);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [group, filterSel && filterSel.join(\",\")]);\n const data = built ? built.payload : props.data;\n rowMapRef.current = built ? built.rowMap : null;\n\n // Keep the newest callback without making it a rebuild trigger.\n const onSelectRef = useRef(onSelect);\n onSelectRef.current = onSelect;\n\n // BUILD/TEARDOWN \u2014 only when the code identity changes. useLayoutEffect so the chart\n // paints before the browser does, avoiding a flash of empty container.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || (!code && !renderFn) || !data) return;\n const host = createChartHost(el, { code, renderFn, data, options, d3, geoKind });\n hostRef.current = host;\n const off = host.selection.onChange((payloadIdxs, source) => {\n // \"host\" = a programmatic clear WE issued (below) to drop a stale highlight.\n // Publishing it would overwrite the selection another chart just made \u2014 the\n // feedback loop that makes mutual cross-filtering fight itself.\n if (source === \"host\") return;\n const map = rowMapRef.current;\n // Translate to SOURCE indices before anything leaves this chart.\n const sourceIdxs = map ? payloadIdxs.map(i => map[i]).filter(i => i !== undefined) : payloadIdxs;\n if (group && id) group.publish(id, sourceIdxs);\n onSelectRef.current?.(sourceIdxs);\n });\n host.render();\n return () => {\n // StrictMode double-mount and every unmount land here: stop the timer, drop the\n // listeners, clear the DOM. Skipping this is the animated-chart leak.\n off();\n host.destroy();\n hostRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [code, renderFn, d3, geoKind]);\n\n // LIVE RESTYLE \u2014 options change without recompiling (colour scale, aggregation,\n // animMaxIdealFrames, maxMapPoints\u2026). This is the whole point of setOptions.\n const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);\n useEffect(() => {\n if (hostRef.current && options) hostRef.current.setOptions(options);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [optKey]);\n\n // DATA change (including a cross-filter re-derive) \u2014 repaint, never recompile.\n useEffect(() => {\n if (hostRef.current && data) hostRef.current.setData(data);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data]);\n\n // Drop a STALE highlight: once the group's selection belongs to someone else (or was\n // cleared), this chart must stop showing its own marks as selected. Without it a\n // \"Clear\" button empties the filter while the origin chart stays visibly dimmed.\n useEffect(() => {\n const host = hostRef.current;\n if (!host || !group) return;\n const mine = group.selection.sourceId === id && group.selection.rows.length > 0;\n if (!mine && host.selection.current && host.selection.current.length) host.selection.clear();\n }, [group?.selection, id, group]);\n\n // No children: the chart owns this element's contents.\n return <div ref={ref} className={className} style={style} />;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
1
+ export declare const HOST_CONTRACT_VERSION = "1.0.0";
2
+ export type GeoPointPrecision = "latlon" | "city" | "zip3" | "state";
3
+ /** Every valid tier, ordered most precise → coarsest. Runtime form of GeoPointPrecision. */
4
+ export declare const GEO_POINT_PRECISIONS: readonly GeoPointPrecision[];
5
+ export declare const MARK_CLASS = "d3-mark";
6
+ export declare const LEGEND_MARK_CLASS = "d3-legend-mark";
7
+ export declare const AXIS_FILTER_CLASS = "d3-axis-filter";
8
+ export declare const ROW_IDX_ATTR = "data-row-idx";
9
+ export declare const XFILTER_REFRESH_EVENT = "llm-xfilter-refresh";
10
+ export declare const CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
11
+ export declare const CONTAINER_SLOT_XF_CLEAR = "__llmXfClear";
12
+ export declare const CONTAINER_SLOT_INITIAL_XF_MARK = "__llmInitialXfMark";
13
+ export declare const HOST_CONTAINER_CLASS = "bic-chart-host";
14
+ export declare const SELECTION_ACTIVE_CLASS = "lch-has-selection";
15
+ export declare const MARK_SELECTED_CLASS = "lch-mark-selected";
16
+ export declare const DIM_OPACITY_VAR = "--lch-dim-opacity";
17
+ export declare const DIM_OPACITY_DEFAULT = 0.25;
18
+ export declare const ANIM_PLAY_SPEED_DEFAULT = 1000;
19
+ export declare const ANIM_PLAY_SPEED_MIN = 250;
20
+ export declare const ANIM_PLAY_SPEED_MAX = 5000;
21
+ export declare const ANIM_MAX_IDEAL_FRAMES_DEFAULT = 60;
22
+ export declare const ANIM_MAX_IDEAL_FRAMES_MIN = 3;
23
+ export declare const ANIM_MAX_IDEAL_FRAMES_MAX = 500;
24
+ export declare const COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT = 95;
25
+ export declare const COLOR_SCALE_SELF_CLAMP_PCT_MIN = 50;
26
+ export declare const COLOR_SCALE_SELF_CLAMP_PCT_MAX = 100;
27
+ export type TimelineStyle = "" | "line" | "boxes";
28
+ export type ColorScaleScope = "" | "frame" | "self";
29
+ export interface RenderOptions {
30
+ width: number;
31
+ height: number;
32
+ palette: string[];
33
+ geoUnmatched?: {
34
+ count: number;
35
+ examples: string[];
36
+ } | null;
37
+ geo?: any;
38
+ geoPoint?: {
39
+ precision: GeoPointPrecision | null;
40
+ unplaced: number;
41
+ unplacedExamples: string[];
42
+ ambiguousRows: number;
43
+ } | null;
44
+ maxMapPoints?: number;
45
+ colorScaleLow?: string;
46
+ colorScaleHigh?: string;
47
+ colorScaleScope?: ColorScaleScope;
48
+ colorScaleSelfClampPct?: number;
49
+ geoLandColor?: string;
50
+ geoNoDataColor?: string;
51
+ aggregation?: string;
52
+ uiState?: unknown;
53
+ setUiState?: (s: unknown) => void;
54
+ backgroundColor?: string;
55
+ themeBg?: string;
56
+ themeFg?: string;
57
+ isHighContrast?: boolean;
58
+ themeAccent?: string;
59
+ cultureCode?: string;
60
+ allowTooltips?: boolean;
61
+ noDataText?: string;
62
+ animAutoPlay?: boolean;
63
+ animPlaySpeedMs?: number;
64
+ animMaxIdealFrames?: number;
65
+ animStopAtEnd?: boolean;
66
+ filtersDuringPlay?: boolean;
67
+ animTimelineStyle?: TimelineStyle;
68
+ onSelect?: (rowIdxs: number[]) => void;
69
+ }
@@ -0,0 +1,5 @@
1
+ import { type RenderOptions } from "./contract";
2
+ export type ResolveOptionsInput = {
3
+ [K in keyof RenderOptions]?: any;
4
+ };
5
+ export declare function resolveOptions(p: ResolveOptionsInput): RenderOptions;
@@ -0,0 +1,3 @@
1
+ import { NA_ISO3 } from "./geoNaRegions";
2
+ export declare function geoForKind(geoKind: string | null | undefined): any | undefined;
3
+ export { NA_ISO3 };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Load (and cache) the geometry for a geoKind. Per-asset dynamic import: requesting a US
3
+ * state map never downloads the 810 KB zip3 asset. Resolves to undefined for an unknown
4
+ * kind — callers treat that exactly like "no geometry", same as the sync path.
5
+ */
6
+ export declare function loadGeo(geoKind: string | null | undefined): Promise<any | undefined>;
7
+ /** Synchronous cache read — what createChartHost uses, so render() stays sync. Returns
8
+ * undefined until loadGeo() has resolved for this kind (or a provider supplied it). */
9
+ export declare function geoFromCache(geoKind: string | null | undefined): any | undefined;
10
+ /** Seed the cache from geometry the caller already holds — e.g. the MCP's data.geo.json,
11
+ * or the sync geoForKind from the "@bicharts/chart-host/geo" subpath. */
12
+ export declare function registerGeo(geoKind: string, geo: any): void;
@@ -0,0 +1,3 @@
1
+ export declare const NA_ISO3: Set<string>;
2
+ export declare const NA_LAT_MAX = 75;
3
+ export declare function clipFeatureToLat(feature: any, latMax: number): any | null;
@@ -0,0 +1 @@
1
+ export declare const US_STATES: any;
@@ -0,0 +1 @@
1
+ export declare const US_ZIP3: any;
@@ -0,0 +1 @@
1
+ export declare const WORLD_110M: any;
@@ -0,0 +1,61 @@
1
+ import { type RenderOptions } from "./contract";
2
+ import { type ResolveOptionsInput } from "./defaults";
3
+ export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
4
+ export interface ChartHostConfig {
5
+ data: {
6
+ columns: any[];
7
+ rows: any[][];
8
+ geoUnmatched?: {
9
+ count: number;
10
+ examples: string[];
11
+ };
12
+ geoPoint?: RenderOptions["geoPoint"];
13
+ /** Provenance the MCP stamps into data.sample.json. `hostContract` is checked
14
+ * against HOST_CONTRACT_VERSION so a major-version drift is LOUD, not silent. */
15
+ meta?: {
16
+ hostContract?: string;
17
+ chart?: string;
18
+ };
19
+ };
20
+ /** Generated render() source — compiled exactly like the visual/preview/exec-gate. */
21
+ code?: string;
22
+ /** Pre-compiled alternative to `code`. */
23
+ renderFn?: RenderFn;
24
+ /** Partial raw options; resolveOptions applies the shared defaults/clamps. */
25
+ options?: ResolveOptionsInput;
26
+ /** D3 v7 instance handed to the compiled code. Default: (globalThis as any).d3. */
27
+ d3?: any;
28
+ /** Attach geometry for this geoKind as options.geo (choropleths / basemaps). Resolved
29
+ * from `geoProvider` or the geoLazy cache — see `loadGeo`. NOT eagerly bundled. */
30
+ geoKind?: string;
31
+ /** Supply geometry for a geoKind synchronously. The escape hatch for a host that
32
+ * already holds its assets (e.g. `geoForKind` from "@bicharts/chart-host/geo", or the
33
+ * data.geo.json the MCP wrote next to the chart). */
34
+ geoProvider?: (geoKind: string) => any | undefined;
35
+ }
36
+ export interface ChartHost {
37
+ render(): void;
38
+ /** Merge raw option changes and re-render — the live-restyle path (colour scale,
39
+ * aggregation, animMaxIdealFrames, timeline style…). Never a regeneration. */
40
+ setOptions(partial: ResolveOptionsInput): void;
41
+ setData(data: ChartHostConfig["data"]): void;
42
+ readonly options: RenderOptions;
43
+ selection: {
44
+ /** Subscribe to cross-filter changes (mark clicks + the chart's own
45
+ * llm-xfilter-refresh dispatches). rowIdxs = [] means cleared. */
46
+ onChange(cb: (rowIdxs: number[], source: string) => void): () => void;
47
+ /** Clear the chart's own selection (calls __llmXfClear when the chart set it). */
48
+ clear(): void;
49
+ readonly current: number[] | null;
50
+ };
51
+ animation: {
52
+ /** Stop playback (calls __llmAnimStop when set). Call before unmount. */
53
+ stop(): void;
54
+ };
55
+ /** The mark the chart nominated as its initial filter (__llmInitialXfMark), if any. */
56
+ initialMark(): Element | null;
57
+ destroy(): void;
58
+ }
59
+ export declare function explainRenderFailure(err: unknown, d3: any): unknown;
60
+ export declare function compileRenderFn(code: string, win: any, doc: any, d3: any): RenderFn;
61
+ export declare function createChartHost(container: HTMLElement, config: ChartHostConfig): ChartHost;
@@ -0,0 +1,6 @@
1
+ export * from "./contract";
2
+ export { resolveOptions, type ResolveOptionsInput } from "./defaults";
3
+ export { loadGeo, geoFromCache, registerGeo } from "./geoLazy";
4
+ export { buildRenderPayload, type RenderPayload, type GeoPointBinding } from "./payload";
5
+ export { createChartHost, compileRenderFn, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
6
+ export { createMarkResolver, type MarkResolver, type MarkResolverEnv } from "./selection";
@@ -0,0 +1,31 @@
1
+ import type { GeoPointPrecision } from "./contract";
2
+ /** Which columns hold the place parts a coordinate can be resolved from. Any subset;
3
+ * the cascade uses the most precise tier available per row. */
4
+ export interface GeoPointBinding {
5
+ city?: string;
6
+ state?: string;
7
+ zip?: string;
8
+ lat?: string;
9
+ lon?: string;
10
+ }
11
+ export interface RenderPayload {
12
+ columns: any[];
13
+ rows: any[][];
14
+ geoUnmatched?: {
15
+ count: number;
16
+ examples: string[];
17
+ };
18
+ geoUnmatchedDistinct?: number;
19
+ geoPoint?: {
20
+ precision: GeoPointPrecision | null;
21
+ unplaced: number;
22
+ unplacedExamples: string[];
23
+ ambiguousRows: number;
24
+ };
25
+ }
26
+ export declare function buildRenderPayload(cols: Array<{
27
+ name: string;
28
+ } & Record<string, any>>, rowObjs: Array<Record<string, any>>, geo?: {
29
+ column: string;
30
+ kind: string;
31
+ } | null, point?: GeoPointBinding | null): RenderPayload;
@@ -0,0 +1,52 @@
1
+ import type { ReactNode } from "react";
2
+ import { type ChartHostConfig } from "./host";
3
+ import type { ResolveOptionsInput } from "./defaults";
4
+ import { type GeoPointBinding } from "./payload";
5
+ export interface BicChartProps {
6
+ /** Generated render() source (an ES module's `code` export, or the raw string). */
7
+ code?: string;
8
+ /** Pre-compiled render function — the alternative to `code`. */
9
+ renderFn?: ChartHostConfig["renderFn"];
10
+ /** The payload: { columns, rows, geoUnmatched?, geoPoint? } — MCP's data.sample.json verbatim. */
11
+ data?: ChartHostConfig["data"];
12
+ /** Raw options; shared defaults/clamps are applied by resolveOptions. */
13
+ options?: ResolveOptionsInput;
14
+ /** REQUIRED: the d3 v7 namespace. There is no global in a bundled app. */
15
+ d3: any;
16
+ /** Attach the bundled geometry for this kind as options.geo (choropleth or basemap). */
17
+ geoKind?: string;
18
+ /** Identity within a <BicChartGroup> — the key other charts filter by. */
19
+ id?: string;
20
+ /** Take this group member's selection as a filter on THIS chart's rows. */
21
+ filteredBy?: string;
22
+ /** Selection callback in SOURCE row indices (group) or payload indices (standalone). */
23
+ onSelect?: (rowIdxs: number[]) => void;
24
+ className?: string;
25
+ style?: React.CSSProperties;
26
+ }
27
+ /** Read the group's selection from a page — for a "clear" button or a count. */
28
+ export declare function useBicSelection(): {
29
+ rows: number[];
30
+ sourceId: string;
31
+ clear: () => void;
32
+ };
33
+ export interface BicChartGroupProps {
34
+ /** The ONE source table. Every member's payload is derived from it. */
35
+ rows: Record<string, any>[];
36
+ columns: any[];
37
+ /** Choropleth join binding, applied when building each member's payload. */
38
+ geo?: {
39
+ column: string;
40
+ kind: string;
41
+ } | null;
42
+ /** Point-map geocoding binding (city/state/zip/lat/lon column names). */
43
+ point?: GeoPointBinding | null;
44
+ children: ReactNode;
45
+ }
46
+ /**
47
+ * Owns the source table and the selection bus. Members declare `id` and optionally
48
+ * `filteredBy`; the group re-derives the filtered member's payload and translates row
49
+ * indices in both directions, so a click in one chart filters another CORRECTLY.
50
+ */
51
+ export declare function BicChartGroup({ rows, columns, geo, point, children }: BicChartGroupProps): import("react").JSX.Element;
52
+ export declare function BicChart(props: BicChartProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ export interface MarkResolverEnv {
2
+ root: HTMLElement;
3
+ doc: Document;
4
+ log: (tag: string, data: object) => void;
5
+ }
6
+ export interface MarkResolver {
7
+ isInvisibleStrokePath(el: Element): boolean;
8
+ distToPathCenterline(el: SVGPathElement, x: number, y: number): number;
9
+ refineHitCorridor(mark: HTMLElement | null, x: number, y: number, sel: string): HTMLElement | null;
10
+ findMark(target: EventTarget | null, sel?: string, ev?: MouseEvent): HTMLElement | null;
11
+ rowIdxsFromMark(el: Element | null): number[];
12
+ penetrateOverlayAt(x: number, y: number, sel: string): HTMLElement | null;
13
+ resolveByGeometry(x: number, y: number, sel: string): HTMLElement | null;
14
+ }
15
+ export declare function createMarkResolver(env: MarkResolverEnv): MarkResolver;