@bicharts/chart-host 0.6.5 → 0.6.6

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
@@ -114,6 +114,31 @@ host.setOptions({ colorScaleLow: "#eee" }); // live restyle — never a regene
114
114
  host.destroy(); // call on unmount
115
115
  ```
116
116
 
117
+ ### Several charts, one selection, without React
118
+
119
+ `createChartGroup` is the group `<BicChartGroup>` wraps: it owns the source table, derives each
120
+ member's payload, and translates every click to **source** row indices, so the row-index hazard
121
+ above is handled the same way in any framework (or none).
122
+
123
+ ```js
124
+ import { createChartGroup, createChartHost, assembleD3 } from "@bicharts/chart-host";
125
+
126
+ const group = createChartGroup(columns, rows, { point: { city: "City" } });
127
+ const d3all = assembleD3(d3, d3Sankey); // one extensible d3 for every chart
128
+
129
+ function mount(id, el, code, opts = {}) { // opts: { filteredBy, respondsWith }
130
+ const host = createChartHost(el, { code, d3: d3all, data: group.memberPayload(id, opts).payload,
131
+ options: { width: 800, height: 500 } });
132
+ host.render();
133
+ return group.attach(id, host, opts); // .detach() on teardown
134
+ }
135
+ mount("map", mapEl, mapCode, { respondsWith: "highlight" });
136
+ mount("table", tableEl, tableCode);
137
+
138
+ group.onChange(sel => console.log(sel.sourceId, sel.rows)); // SOURCE row indices
139
+ group.clear();
140
+ ```
141
+
117
142
  ## Sizing is yours
118
143
 
119
144
  `width`/`height` have **no defaults**. A chart draws once at whatever it is handed and does not
@@ -125,6 +150,9 @@ a background or headless tab, where frames are barely produced.
125
150
  useLayoutEffect(() => setW(ref.current.getBoundingClientRect().width), []);
126
151
  ```
127
152
 
153
+ In React, `useMeasuredSize(ref)` from `@bicharts/chart-host/react` does exactly that and follows
154
+ later resizes: give the element its size in CSS and pass what it measures.
155
+
128
156
  ## Maps
129
157
 
130
158
  Geometry is **not** bundled into the entry — the three assets are ~1.3 MB raw, and a bar chart
@@ -3331,6 +3331,9 @@ function noopViewStateProvider() {
3331
3331
  } };
3332
3332
  }
3333
3333
  var parseRowIdxs = (s) => (s || "").split(",").map((x3) => parseInt(x3, 10)).filter((n) => Number.isFinite(n));
3334
+ function assembleD3(base, ...plugins) {
3335
+ return Object.assign({}, base, ...plugins);
3336
+ }
3334
3337
  var D3_PLUGIN_PACKAGES = {
3335
3338
  sankey: "d3-sankey",
3336
3339
  sankeyLinkHorizontal: "d3-sankey",
@@ -3839,6 +3842,116 @@ function createChartHost(container, config) {
3839
3842
  return host;
3840
3843
  }
3841
3844
 
3845
+ // src/group.ts
3846
+ var EMPTY6 = Object.freeze({ sourceId: null, rows: Object.freeze([]) });
3847
+ function toSourceRows(rowMap, payloadRowIdxs) {
3848
+ if (!rowMap) return payloadRowIdxs.slice();
3849
+ return payloadRowIdxs.map((i) => rowMap[i]).filter((i) => i !== void 0);
3850
+ }
3851
+ function syncMemberSelection(host, selection, id, highlight, incoming) {
3852
+ const mine = selection.sourceId === id && selection.rows.length > 0;
3853
+ if (mine) return;
3854
+ if (highlight && incoming && incoming.length) {
3855
+ host.selection.highlight(incoming.slice());
3856
+ return;
3857
+ }
3858
+ if (host.selection.current && host.selection.current.length) host.selection.clear();
3859
+ }
3860
+ function createChartGroup(columns, rows, opts = {}) {
3861
+ let cols = columns;
3862
+ let table = rows;
3863
+ let geo = opts.geo ?? null;
3864
+ let point = opts.point ?? null;
3865
+ let selection = EMPTY6;
3866
+ const subs = /* @__PURE__ */ new Set();
3867
+ const members = /* @__PURE__ */ new Set();
3868
+ const notify = (change) => {
3869
+ for (const cb of Array.from(subs)) cb(selection, change);
3870
+ };
3871
+ const incomingFor = (id, filteredBy) => {
3872
+ const s = selection;
3873
+ const forId = s.rows.length && s.sourceId && s.sourceId !== id ? s.rows.slice() : null;
3874
+ if (filteredBy) return s.sourceId === filteredBy ? forId : null;
3875
+ return forId;
3876
+ };
3877
+ const payloadFor = (sourceRowIdxs) => {
3878
+ const idxs = sourceRowIdxs ? sourceRowIdxs.slice() : table.map((_2, i) => i);
3879
+ const subset = idxs.map((i) => table[i]);
3880
+ const payload = buildRenderPayload(cols, subset, geo ?? void 0, point ?? void 0);
3881
+ return { payload, rowMap: idxs };
3882
+ };
3883
+ const memberPayload = (id, o = {}) => payloadFor(o.respondsWith === "highlight" ? null : incomingFor(id, o.filteredBy));
3884
+ const group = {
3885
+ get selection() {
3886
+ return selection;
3887
+ },
3888
+ get columns() {
3889
+ return cols;
3890
+ },
3891
+ get rows() {
3892
+ return table;
3893
+ },
3894
+ incomingFor,
3895
+ payloadFor,
3896
+ memberPayload,
3897
+ publish(id, sourceRowIdxs) {
3898
+ selection = sourceRowIdxs.length ? { sourceId: id, rows: sourceRowIdxs.slice() } : { sourceId: null, rows: [] };
3899
+ notify(sourceRowIdxs.length ? "select" : "clear");
3900
+ },
3901
+ clear() {
3902
+ selection = { sourceId: null, rows: [] };
3903
+ notify("clear");
3904
+ },
3905
+ setSource(nextColumns, nextRows, o = {}) {
3906
+ cols = nextColumns;
3907
+ table = nextRows;
3908
+ geo = o.geo ?? null;
3909
+ point = o.point ?? null;
3910
+ notify("source");
3911
+ },
3912
+ onChange(cb) {
3913
+ subs.add(cb);
3914
+ return () => {
3915
+ subs.delete(cb);
3916
+ };
3917
+ },
3918
+ attach(id, host, o = {}) {
3919
+ const highlight = o.respondsWith === "highlight";
3920
+ let rowMap = memberPayload(id, o).rowMap;
3921
+ const offHost = host.selection.onChange((payloadIdxs, source) => {
3922
+ if (source === "host") return;
3923
+ const sourceIdxs = toSourceRows(rowMap, payloadIdxs);
3924
+ if (id) group.publish(id, sourceIdxs);
3925
+ o.onSelect?.(sourceIdxs);
3926
+ });
3927
+ const offGroup = group.onChange((sel) => {
3928
+ const incoming = incomingFor(id, o.filteredBy);
3929
+ const next = payloadFor(highlight ? null : incoming);
3930
+ rowMap = next.rowMap;
3931
+ host.setData(next.payload);
3932
+ syncMemberSelection(host, sel, id, highlight, incoming);
3933
+ });
3934
+ const member = {
3935
+ get rowMap() {
3936
+ return rowMap;
3937
+ },
3938
+ detach() {
3939
+ offHost();
3940
+ offGroup();
3941
+ members.delete(member);
3942
+ }
3943
+ };
3944
+ members.add(member);
3945
+ return member;
3946
+ },
3947
+ destroy() {
3948
+ for (const m of Array.from(members)) m.detach();
3949
+ subs.clear();
3950
+ }
3951
+ };
3952
+ return group;
3953
+ }
3954
+
3842
3955
  export {
3843
3956
  HOST_CONTRACT_VERSION,
3844
3957
  GEO_POINT_PRECISIONS,
@@ -3970,9 +4083,13 @@ export {
3970
4083
  invalidSentinelReason,
3971
4084
  sessionViewStateProvider,
3972
4085
  noopViewStateProvider,
4086
+ assembleD3,
3973
4087
  requiredD3Plugins,
3974
4088
  explainRenderFailure,
3975
4089
  stripEsmExports,
3976
4090
  compileRenderFn,
3977
- createChartHost
4091
+ createChartHost,
4092
+ toSourceRows,
4093
+ syncMemberSelection,
4094
+ createChartGroup
3978
4095
  };
package/dist/index.mjs CHANGED
@@ -69,6 +69,7 @@ import {
69
69
  We,
70
70
  XFILTER_REFRESH_EVENT,
71
71
  applyLabelContrast,
72
+ assembleD3,
72
73
  autoRampCanvas,
73
74
  autoRampMinStep,
74
75
  axisPinPlacement,
@@ -87,6 +88,7 @@ import {
87
88
  compositeOver,
88
89
  contentExtentOf,
89
90
  contrastRatio,
91
+ createChartGroup,
90
92
  createChartHost,
91
93
  createMarkResolver,
92
94
  ctmScaleOf,
@@ -131,11 +133,13 @@ import {
131
133
  stripEsmExports,
132
134
  stripJsComments,
133
135
  svgInkReach,
136
+ syncMemberSelection,
134
137
  toRGBA,
138
+ toSourceRows,
135
139
  unpinScrolledAxis,
136
140
  valuePlacementFlag,
137
141
  withoutViewOnlyKeys
138
- } from "./chunk-CZRYIPEL.mjs";
142
+ } from "./chunk-ZPWRXCS5.mjs";
139
143
  import "./chunk-A2GMXZP7.mjs";
140
144
  import "./chunk-GHODWOAL.mjs";
141
145
 
@@ -2499,6 +2503,7 @@ export {
2499
2503
  actionFor,
2500
2504
  applyLabelContrast,
2501
2505
  askApplyImprovements,
2506
+ assembleD3,
2502
2507
  autoRampCanvas,
2503
2508
  autoRampMinStep,
2504
2509
  axisPinPlacement,
@@ -2531,6 +2536,7 @@ export {
2531
2536
  confirmLaunch,
2532
2537
  contentExtentOf,
2533
2538
  contrastRatio,
2539
+ createChartGroup,
2534
2540
  createChartHost,
2535
2541
  createMarkResolver,
2536
2542
  ctmScaleOf,
@@ -2631,7 +2637,9 @@ export {
2631
2637
  svgInkReach,
2632
2638
  svgNaturalSize,
2633
2639
  svgToDataUrl,
2640
+ syncMemberSelection,
2634
2641
  toRGBA,
2642
+ toSourceRows,
2635
2643
  transportFailureShouldRecover,
2636
2644
  unpinScrolledAxis,
2637
2645
  valuePlacementFlag,
package/dist/react.mjs CHANGED
@@ -1,10 +1,13 @@
1
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
2
  import {
3
- buildRenderPayload,
3
+ assembleD3,
4
+ createChartGroup,
4
5
  createChartHost,
5
6
  geoFromCache,
6
- loadGeo
7
- } from "./chunk-CZRYIPEL.mjs";
7
+ loadGeo,
8
+ syncMemberSelection,
9
+ toSourceRows
10
+ } from "./chunk-ZPWRXCS5.mjs";
8
11
  import "./chunk-A2GMXZP7.mjs";
9
12
  import "./chunk-GHODWOAL.mjs";
10
13
 
@@ -17,32 +20,28 @@ function useBicSelection() {
17
20
  return {
18
21
  rows: g?.selection.rows ?? [],
19
22
  sourceId: g?.selection.sourceId ?? null,
20
- clear: () => g?.clear()
23
+ clear: () => g?.group.clear()
21
24
  };
22
25
  }
23
26
  function BicChartGroup({ rows, columns, geo, point, children }) {
24
- const [sel, setSel] = useState({ sourceId: null, rows: [] });
25
- const value = useMemo(() => ({
26
- rows,
27
- columns,
28
- geo,
29
- point,
30
- selection: sel,
31
- publish: (id, sourceRowIdxs) => setSel(sourceRowIdxs.length ? { sourceId: id, rows: sourceRowIdxs } : { sourceId: null, rows: [] }),
32
- // A chart never filters itself — that is what keeps the gesture reversible:
33
- // the origin chart still shows every mark, so there is always something left
34
- // to click to change or clear the selection.
35
- filterFor: (id) => sel.rows.length && sel.sourceId && sel.sourceId !== id ? sel.rows : null,
36
- clear: () => setSel({ sourceId: null, rows: [] })
37
- }), [rows, columns, geo, point, sel]);
27
+ const groupRef = useRef(null);
28
+ if (!groupRef.current) groupRef.current = createChartGroup(columns, rows, { geo, point });
29
+ const group = groupRef.current;
30
+ const [sel, setSel] = useState(group.selection);
31
+ const source = useMemo(() => {
32
+ group.setSource(columns, rows, { geo, point });
33
+ return {};
34
+ }, [group, rows, columns, geo, point]);
35
+ useEffect(() => group.onChange((s, change) => {
36
+ if (change !== "source") setSel(s);
37
+ }), [group]);
38
+ const value = useMemo(
39
+ () => ({ group, selection: sel }),
40
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41
+ [group, sel, source]
42
+ );
38
43
  return /* @__PURE__ */ jsx(Ctx.Provider, { value, children });
39
44
  }
40
- function payloadFor(g, sourceIdxs) {
41
- const idxs = sourceIdxs ?? g.rows.map((_, i) => i);
42
- const subset = idxs.map((i) => g.rows[i]);
43
- const p = buildRenderPayload(g.columns, subset, g.geo ?? void 0, g.point ?? void 0);
44
- return { payload: p, rowMap: idxs };
45
- }
46
45
  function BicChart(props) {
47
46
  const {
48
47
  code,
@@ -64,15 +63,15 @@ function BicChart(props) {
64
63
  const ref = useRef(null);
65
64
  const hostRef = useRef(null);
66
65
  const rowMapRef = useRef(null);
67
- const group = useContext(Ctx);
68
- const active = group ? group.selection : null;
69
- const incoming = !group ? null : filteredBy ? active.sourceId === filteredBy ? group.filterFor(id) : null : group.filterFor(id);
66
+ const ctx = useContext(Ctx);
67
+ const group = ctx ? ctx.group : null;
68
+ const incoming = group ? group.incomingFor(id, filteredBy) : null;
70
69
  const highlightMode = respondsWith === "highlight";
71
70
  const filterSel = highlightMode ? null : incoming;
72
71
  const built = useMemo(() => {
73
72
  if (!group) return null;
74
- return payloadFor(group, filterSel);
75
- }, [group, filterSel && filterSel.join(",")]);
73
+ return group.payloadFor(filterSel);
74
+ }, [ctx, filterSel && filterSel.join(",")]);
76
75
  const data = built ? built.payload : props.data;
77
76
  rowMapRef.current = built ? built.rowMap : null;
78
77
  const onSelectRef = useRef(onSelect);
@@ -120,8 +119,7 @@ function BicChart(props) {
120
119
  hostRef.current = host;
121
120
  const off = host.selection.onChange((payloadIdxs, source) => {
122
121
  if (source === "host") return;
123
- const map = rowMapRef.current;
124
- const sourceIdxs = map ? payloadIdxs.map((i) => map[i]).filter((i) => i !== void 0) : payloadIdxs;
122
+ const sourceIdxs = toSourceRows(rowMapRef.current, payloadIdxs);
125
123
  if (group && id) group.publish(id, sourceIdxs);
126
124
  onSelectRef.current?.(sourceIdxs);
127
125
  });
@@ -141,19 +139,33 @@ function BicChart(props) {
141
139
  }, [data]);
142
140
  useEffect(() => {
143
141
  const host = hostRef.current;
144
- if (!host || !group) return;
145
- const mine = group.selection.sourceId === id && group.selection.rows.length > 0;
146
- if (mine) return;
147
- if (highlightMode && incoming && incoming.length) {
148
- host.selection.highlight(incoming);
149
- return;
150
- }
151
- if (host.selection.current && host.selection.current.length) host.selection.clear();
152
- }, [group?.selection, id, group, highlightMode, incoming && incoming.join(",")]);
142
+ if (!host || !ctx) return;
143
+ syncMemberSelection(host, ctx.selection, id, highlightMode, incoming);
144
+ }, [ctx?.selection, id, ctx, highlightMode, incoming && incoming.join(",")]);
153
145
  return /* @__PURE__ */ jsx("div", { ref, className, style });
154
146
  }
147
+ function useMeasuredSize(ref) {
148
+ const [size, setSize] = useState({ width: 0, height: 0 });
149
+ useLayoutEffect(() => {
150
+ const el = ref.current;
151
+ if (!el) return;
152
+ const read = () => {
153
+ const r = el.getBoundingClientRect();
154
+ const width = Math.round(r.width), height = Math.round(r.height);
155
+ setSize((s) => s.width === width && s.height === height ? s : { width, height });
156
+ };
157
+ read();
158
+ if (typeof ResizeObserver === "undefined") return;
159
+ const ro = new ResizeObserver(read);
160
+ ro.observe(el);
161
+ return () => ro.disconnect();
162
+ }, [ref]);
163
+ return size;
164
+ }
155
165
  export {
156
166
  BicChart,
157
167
  BicChartGroup,
158
- useBicSelection
168
+ assembleD3,
169
+ useBicSelection,
170
+ useMeasuredSize
159
171
  };
@@ -0,0 +1,88 @@
1
+ import type { ChartHost } from "./host";
2
+ import { type GeoPointBinding, type RenderPayload } from "./payload";
3
+ /** The group's one active selection, in SOURCE row indices. `sourceId` is the member that made it. */
4
+ export interface ChartGroupSelection {
5
+ readonly sourceId: string | null;
6
+ readonly rows: readonly number[];
7
+ }
8
+ /** The table every member's payload is derived from, and the geo bindings applied to each. */
9
+ export interface ChartGroupSourceOptions {
10
+ /** Choropleth join binding, applied when building each member's payload. */
11
+ geo?: {
12
+ column: string;
13
+ kind: string;
14
+ } | null;
15
+ /** Point-map geocoding binding (city/state/zip/lat/lon column names). */
16
+ point?: GeoPointBinding | null;
17
+ }
18
+ /** How one member takes part in the group. */
19
+ export interface ChartGroupMemberOptions {
20
+ /** Respond only to this member's selection (one-way wiring). Absent: respond to any sibling. */
21
+ filteredBy?: string;
22
+ /** "filter" (default) re-derives the member's payload; "highlight" keeps it and paints. */
23
+ respondsWith?: "filter" | "highlight";
24
+ }
25
+ /** A member's payload and the map from each payload row to its source row. */
26
+ export interface ChartGroupPayload {
27
+ payload: RenderPayload;
28
+ /** `rowMap[k]` is the source row index of payload row `k`. */
29
+ rowMap: number[];
30
+ }
31
+ /** Why the group notified: a member's selection, a clear, or a new source table. */
32
+ export type ChartGroupChange = "select" | "clear" | "source";
33
+ /** A live chart wired into the group by `attach`. */
34
+ export interface ChartGroupMember {
35
+ /** The payload-row -> source-row map of the payload the member is drawing now. */
36
+ readonly rowMap: readonly number[];
37
+ /** Unwire the member: its clicks stop publishing and the group stops redrawing it. */
38
+ detach(): void;
39
+ }
40
+ export interface ChartGroup {
41
+ /** The active selection. A new object on every publish and clear; kept across a new source. */
42
+ readonly selection: ChartGroupSelection;
43
+ readonly columns: readonly any[];
44
+ readonly rows: readonly Record<string, any>[];
45
+ /**
46
+ * The source rows member `id` should be FILTERED to now, or null for all of them. Never the
47
+ * member's own selection. With `filteredBy`, only that partner's selection counts.
48
+ */
49
+ incomingFor(id: string | undefined, filteredBy?: string): number[] | null;
50
+ /** A payload for a subset of source rows (null = every row), with its row map. */
51
+ payloadFor(sourceRowIdxs: readonly number[] | null): ChartGroupPayload;
52
+ /** The payload member `id` should draw now, under the given options. */
53
+ memberPayload(id: string | undefined, opts?: ChartGroupMemberOptions): ChartGroupPayload;
54
+ /** Publish member `id`'s selection in SOURCE rows. An empty list clears the selection. */
55
+ publish(id: string, sourceRowIdxs: readonly number[]): void;
56
+ /** Clear the group's selection. */
57
+ clear(): void;
58
+ /** Replace the source table (and bindings). The selection is kept, as source indices. */
59
+ setSource(columns: readonly any[], rows: readonly Record<string, any>[], opts?: ChartGroupSourceOptions): void;
60
+ /** Subscribe to every change. Returns the unsubscribe. */
61
+ onChange(cb: (selection: ChartGroupSelection, change: ChartGroupChange) => void): () => void;
62
+ /**
63
+ * Wire a live chart host into the group as member `id` (undefined: a member that responds but
64
+ * never publishes). Create the host with `memberPayload(id, opts).payload` as its data. From
65
+ * then on its clicks publish SOURCE rows (and reach `onSelect`), and every group change hands
66
+ * it a re-derived payload and paints or clears the selection it should show.
67
+ */
68
+ attach(id: string | undefined, host: ChartHost, opts?: ChartGroupMemberOptions & {
69
+ onSelect?: (sourceRowIdxs: number[]) => void;
70
+ }): ChartGroupMember;
71
+ /** Detach every member and drop every subscriber. */
72
+ destroy(): void;
73
+ }
74
+ /** Payload row indices -> source row indices, dropping any the map does not cover. */
75
+ export declare function toSourceRows(rowMap: readonly number[] | null | undefined, payloadRowIdxs: readonly number[]): number[];
76
+ /**
77
+ * Make a member's painted selection agree with the group's. A member that made the selection
78
+ * keeps what its host already painted. A highlight member with an incoming selection paints it.
79
+ * Any other member that still shows a selection clears it - a stale highlight left on the origin
80
+ * after the selection moved elsewhere, or after a clear. Both paths notify with source "host",
81
+ * so nothing done here is republished.
82
+ */
83
+ export declare function syncMemberSelection(host: ChartHost, selection: ChartGroupSelection, id: string | undefined, highlight: boolean, incoming: readonly number[] | null): void;
84
+ /**
85
+ * Create a cross-filter group over one source table: `rows` are objects keyed by column name,
86
+ * `columns` the column descriptors (`ingest()` returns both).
87
+ */
88
+ export declare function createChartGroup(columns: readonly any[], rows: readonly Record<string, any>[], opts?: ChartGroupSourceOptions): ChartGroup;
@@ -235,6 +235,16 @@ export interface ChartHost {
235
235
  initialMark(): Element | null;
236
236
  destroy(): void;
237
237
  }
238
+ /**
239
+ * ONE d3 OBJECT FROM d3 AND ITS PLUGINS - the object a chart is handed as `d3`.
240
+ *
241
+ * In a bundled app d3 and each plugin (d3-sankey, d3-hexbin, ...) are separate module
242
+ * namespaces, and a chart reaches all of them through one `d3`. A namespace is also FROZEN, and a
243
+ * generated chart installs its own helpers onto the d3 it is given, so the object must be a plain
244
+ * extensible copy: `assembleD3(d3, d3Sankey, d3Hexbin)`. Later arguments win on a name clash.
245
+ * `requiredD3Plugins(code)` names the plugin packages a given chart needs.
246
+ */
247
+ export declare function assembleD3(base: object, ...plugins: object[]): Record<string, any>;
238
248
  export declare function requiredD3Plugins(code: string): string[];
239
249
  export declare function explainRenderFailure(err: unknown, d3: any): unknown;
240
250
  export declare function stripEsmExports(code: string): string;
@@ -6,7 +6,8 @@ export { loadGeo, geoFromCache, registerGeo, registerGeoAsset, geoAssetFor, clea
6
6
  export declare function registerCityTable(packed: string): void;
7
7
  export { buildRenderPayload, type RenderPayload, type GeoPointBinding } from "./payload";
8
8
  export { codeReadsDatesInLocalTime, vegaSpecReadsDatesInLocalTime } from "./dateCells";
9
- export { createChartHost, compileRenderFn, stripEsmExports, requiredD3Plugins, explainRenderFailure, sessionViewStateProvider, noopViewStateProvider, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
9
+ export { createChartHost, compileRenderFn, stripEsmExports, requiredD3Plugins, explainRenderFailure, sessionViewStateProvider, noopViewStateProvider, assembleD3, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
10
+ export { createChartGroup, toSourceRows, syncMemberSelection, type ChartGroup, type ChartGroupMember, type ChartGroupSelection, type ChartGroupChange, type ChartGroupMemberOptions, type ChartGroupSourceOptions, type ChartGroupPayload } from "./group";
10
11
  export { createMarkResolver, isInsideControl, type MarkResolver, type MarkResolverEnv } from "./selection";
11
12
  export { planTrivialChart, compileTrivialSource, type TrivialPlan, type TrivialShapeKind } from "./trivial";
12
13
  export { captureSvgSnapshot, svgToDataUrl, svgNaturalSize, rasterizeSvgToPngDataUrl, type SnapshotOptions } from "./snapshot";
@@ -1,7 +1,8 @@
1
- import type { ReactNode } from "react";
1
+ import type { ReactNode, RefObject } from "react";
2
2
  import { type ChartHostConfig } from "./host";
3
3
  import type { ResolveOptionsInput } from "./defaults";
4
- import { type GeoPointBinding } from "./payload";
4
+ import type { GeoPointBinding } from "./payload";
5
+ export { assembleD3 } from "./host";
5
6
  export interface BicChartProps {
6
7
  /** Generated render() source (an ES module's `code` export, or the raw string). */
7
8
  code?: string;
@@ -89,3 +90,15 @@ export interface BicChartGroupProps {
89
90
  */
90
91
  export declare function BicChartGroup({ rows, columns, geo, point, children }: BicChartGroupProps): import("react").JSX.Element;
91
92
  export declare function BicChart(props: BicChartProps): import("react").JSX.Element;
93
+ /**
94
+ * THE SIZE A CHART SHOULD DRAW AT: the element's measured box, kept current as it resizes.
95
+ *
96
+ * A chart draws to `options.width` / `options.height`, and a page that hard-codes them draws the
97
+ * same size in a phone and on a wall. Give the element its size in CSS (a width of 100%, a height
98
+ * or an aspect ratio) and pass what this measures. `{ width: 0, height: 0 }` until the element has
99
+ * been measured - skip rendering until then rather than drawing at zero.
100
+ */
101
+ export declare function useMeasuredSize(ref: RefObject<Element | null>): {
102
+ width: number;
103
+ height: number;
104
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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",