@bicharts/chart-host 0.6.5 → 0.6.7
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 +28 -0
- package/dist/{chunk-CZRYIPEL.mjs → chunk-RKOFRMS4.mjs} +468 -351
- package/dist/index.mjs +9 -1
- package/dist/react.mjs +53 -41
- package/dist/types/group.d.ts +88 -0
- package/dist/types/host.d.ts +10 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/react.d.ts +15 -2
- package/package.json +1 -1
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-
|
|
142
|
+
} from "./chunk-RKOFRMS4.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
|
-
|
|
3
|
+
assembleD3,
|
|
4
|
+
createChartGroup,
|
|
4
5
|
createChartHost,
|
|
5
6
|
geoFromCache,
|
|
6
|
-
loadGeo
|
|
7
|
-
|
|
7
|
+
loadGeo,
|
|
8
|
+
syncMemberSelection,
|
|
9
|
+
toSourceRows
|
|
10
|
+
} from "./chunk-RKOFRMS4.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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
point
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
68
|
-
const
|
|
69
|
-
const incoming =
|
|
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(
|
|
75
|
-
}, [
|
|
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
|
|
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 || !
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/types/react.d.ts
CHANGED
|
@@ -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 {
|
|
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.
|
|
3
|
+
"version": "0.6.7",
|
|
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",
|