@bicharts/chart-host 0.6.56 → 0.6.58
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/dist/{chunk-CMHGGW3Y.mjs → chunk-5NSU3MYW.mjs} +41 -110
- package/dist/chunk-WNGJUPEI.mjs +114 -0
- package/dist/chunk-ZEZXNAFL.mjs +467 -0
- package/dist/dax.mjs +60 -0
- package/dist/geo.mjs +3 -3
- package/dist/index.mjs +49 -47
- package/dist/page.mjs +28 -0
- package/dist/react.mjs +207 -8
- package/dist/testing.mjs +46 -1
- package/dist/types/controls.d.ts +50 -0
- package/dist/types/dax.d.ts +22 -0
- package/dist/types/filterScope.d.ts +133 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/markNotes.d.ts +30 -0
- package/dist/types/page.d.ts +3 -0
- package/dist/types/react.d.ts +73 -2
- package/dist/types/testing/index.d.ts +1 -0
- package/dist/types/testing/interact.d.ts +17 -0
- package/package.json +11 -1
package/dist/page.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
attachControls,
|
|
4
|
+
badgeKey,
|
|
5
|
+
bindFilter,
|
|
6
|
+
createControls,
|
|
7
|
+
createFilter,
|
|
8
|
+
createFilterScope,
|
|
9
|
+
fromVegaInteraction,
|
|
10
|
+
noteBadges,
|
|
11
|
+
notesFor,
|
|
12
|
+
payloadRowReader,
|
|
13
|
+
readControls
|
|
14
|
+
} from "./chunk-ZEZXNAFL.mjs";
|
|
15
|
+
import "./chunk-GHODWOAL.mjs";
|
|
16
|
+
export {
|
|
17
|
+
attachControls,
|
|
18
|
+
badgeKey,
|
|
19
|
+
bindFilter,
|
|
20
|
+
createControls,
|
|
21
|
+
createFilter,
|
|
22
|
+
createFilterScope,
|
|
23
|
+
fromVegaInteraction,
|
|
24
|
+
noteBadges,
|
|
25
|
+
notesFor,
|
|
26
|
+
payloadRowReader,
|
|
27
|
+
readControls
|
|
28
|
+
};
|
package/dist/react.mjs
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
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
|
+
import {
|
|
3
|
+
attachControls,
|
|
4
|
+
badgeKey,
|
|
5
|
+
bindFilter,
|
|
6
|
+
createControls,
|
|
7
|
+
createFilter,
|
|
8
|
+
createFilterScope,
|
|
9
|
+
fromVegaInteraction,
|
|
10
|
+
noteBadges,
|
|
11
|
+
notesFor,
|
|
12
|
+
payloadRowReader
|
|
13
|
+
} from "./chunk-ZEZXNAFL.mjs";
|
|
2
14
|
import {
|
|
3
15
|
assembleD3,
|
|
4
16
|
createChartGroup,
|
|
@@ -7,13 +19,14 @@ import {
|
|
|
7
19
|
loadGeo,
|
|
8
20
|
syncMemberSelection,
|
|
9
21
|
toSourceRows
|
|
10
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-5NSU3MYW.mjs";
|
|
11
23
|
import "./chunk-A2GMXZP7.mjs";
|
|
24
|
+
import "./chunk-WNGJUPEI.mjs";
|
|
12
25
|
import "./chunk-GHODWOAL.mjs";
|
|
13
26
|
|
|
14
27
|
// src/react.tsx
|
|
15
|
-
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
16
|
-
import { jsx } from "react/jsx-runtime";
|
|
28
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
29
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
30
|
var Ctx = createContext(null);
|
|
18
31
|
function useBicSelection() {
|
|
19
32
|
const g = useContext(Ctx);
|
|
@@ -56,7 +69,10 @@ function BicChart(props) {
|
|
|
56
69
|
annotations,
|
|
57
70
|
onAnnotationClick,
|
|
58
71
|
className,
|
|
59
|
-
style
|
|
72
|
+
style,
|
|
73
|
+
selects,
|
|
74
|
+
controls,
|
|
75
|
+
ariaLabel
|
|
60
76
|
} = props;
|
|
61
77
|
const ref = useRef(null);
|
|
62
78
|
const hostRef = useRef(null);
|
|
@@ -72,6 +88,18 @@ function BicChart(props) {
|
|
|
72
88
|
}, [group, ctx?.source, filterSel && filterSel.join(",")]);
|
|
73
89
|
const data = built ? built.payload : props.data;
|
|
74
90
|
rowMapRef.current = built ? built.rowMap : null;
|
|
91
|
+
const dataRef = useRef(data);
|
|
92
|
+
dataRef.current = data;
|
|
93
|
+
const groupRef = useRef(group);
|
|
94
|
+
groupRef.current = group;
|
|
95
|
+
const bindingRef = useRef(null);
|
|
96
|
+
const controlsRef = useRef(null);
|
|
97
|
+
const readControlsSoon = () => {
|
|
98
|
+
const h = hostRef.current, c = controlsRef.current;
|
|
99
|
+
if (h && c) h.rendered.then(c.read, () => {
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const userSelectedRef = useRef(false);
|
|
75
103
|
const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
|
|
76
104
|
const builtWithRef = useRef(null);
|
|
77
105
|
const onSelectRef = useRef(onSelect);
|
|
@@ -102,11 +130,14 @@ function BicChart(props) {
|
|
|
102
130
|
useLayoutEffect(() => {
|
|
103
131
|
const el = ref.current;
|
|
104
132
|
if (!el || !code && !renderFn || !data) return;
|
|
133
|
+
const ctl = controls ? attachControls(() => hostRef.current, el, controls) : null;
|
|
134
|
+
controlsRef.current = ctl;
|
|
135
|
+
const hostOptions = ctl ? { ...options ?? {}, ...ctl.options } : options;
|
|
105
136
|
const host = createChartHost(el, {
|
|
106
137
|
code,
|
|
107
138
|
renderFn,
|
|
108
139
|
data,
|
|
109
|
-
options,
|
|
140
|
+
options: hostOptions,
|
|
110
141
|
d3,
|
|
111
142
|
geoKind,
|
|
112
143
|
viewState,
|
|
@@ -127,23 +158,53 @@ function BicChart(props) {
|
|
|
127
158
|
builtAnnKeyRef.current = annKey;
|
|
128
159
|
const off = host.selection.onChange((payloadIdxs, source) => {
|
|
129
160
|
if (source === "host") return;
|
|
161
|
+
userSelectedRef.current = payloadIdxs.length > 0;
|
|
130
162
|
const sourceIdxs = toSourceRows(rowMapRef.current, payloadIdxs);
|
|
131
163
|
if (group && id) group.publish(id, sourceIdxs);
|
|
132
164
|
onSelectRef.current?.(sourceIdxs);
|
|
133
165
|
});
|
|
134
166
|
host.render();
|
|
167
|
+
readControlsSoon();
|
|
135
168
|
builtWithRef.current = { data, optKey };
|
|
136
169
|
return () => {
|
|
137
170
|
off();
|
|
171
|
+
ctl?.detach();
|
|
172
|
+
if (controlsRef.current === ctl) controlsRef.current = null;
|
|
138
173
|
host.destroy();
|
|
139
174
|
hostRef.current = null;
|
|
140
175
|
};
|
|
141
|
-
}, [code, renderFn, d3, geoKind, labelContrast]);
|
|
176
|
+
}, [code, renderFn, d3, geoKind, labelContrast, controls]);
|
|
177
|
+
useLayoutEffect(() => {
|
|
178
|
+
const host = hostRef.current;
|
|
179
|
+
if (!host || !selects) return;
|
|
180
|
+
const payloadRows = payloadRowReader(() => dataRef.current);
|
|
181
|
+
const b = bindFilter(host, selects, {
|
|
182
|
+
rowAt(i) {
|
|
183
|
+
const g = groupRef.current, map = rowMapRef.current;
|
|
184
|
+
if (g && map) {
|
|
185
|
+
const s = map[i];
|
|
186
|
+
return s === void 0 ? null : g.rows[s] ?? null;
|
|
187
|
+
}
|
|
188
|
+
return payloadRows.rowAt(i);
|
|
189
|
+
},
|
|
190
|
+
rowCount() {
|
|
191
|
+
const map = rowMapRef.current;
|
|
192
|
+
return groupRef.current && map ? map.length : payloadRows.rowCount();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
bindingRef.current = b;
|
|
196
|
+
b.reconcile();
|
|
197
|
+
return () => {
|
|
198
|
+
b.detach();
|
|
199
|
+
if (bindingRef.current === b) bindingRef.current = null;
|
|
200
|
+
};
|
|
201
|
+
}, [selects, code, renderFn, d3, geoKind, labelContrast]);
|
|
142
202
|
useEffect(() => {
|
|
143
203
|
if (!hostRef.current || !options) return;
|
|
144
204
|
if (builtWithRef.current?.optKey === optKey) return;
|
|
145
205
|
builtWithRef.current = { ...builtWithRef.current, optKey };
|
|
146
206
|
hostRef.current.setOptions(options);
|
|
207
|
+
readControlsSoon();
|
|
147
208
|
}, [optKey]);
|
|
148
209
|
const builtAnnKeyRef = useRef(null);
|
|
149
210
|
useEffect(() => {
|
|
@@ -156,13 +217,139 @@ function BicChart(props) {
|
|
|
156
217
|
if (builtWithRef.current?.data === data) return;
|
|
157
218
|
builtWithRef.current = { ...builtWithRef.current, data };
|
|
158
219
|
hostRef.current.setData(data);
|
|
220
|
+
bindingRef.current?.reconcile();
|
|
221
|
+
readControlsSoon();
|
|
159
222
|
}, [data]);
|
|
223
|
+
const lastSourceRef = useRef(ctx?.source);
|
|
160
224
|
useEffect(() => {
|
|
161
225
|
const host = hostRef.current;
|
|
162
226
|
if (!host || !ctx) return;
|
|
227
|
+
const sourceChanged = lastSourceRef.current !== ctx.source;
|
|
228
|
+
lastSourceRef.current = ctx.source;
|
|
229
|
+
if (selects) return;
|
|
230
|
+
const before = (host.selection.current ?? []).length;
|
|
163
231
|
syncMemberSelection(host, ctx.selection, id, highlightMode, incoming, rowMapRef.current);
|
|
164
|
-
|
|
165
|
-
|
|
232
|
+
if (sourceChanged && before && !(host.selection.current ?? []).length && userSelectedRef.current) {
|
|
233
|
+
userSelectedRef.current = false;
|
|
234
|
+
onSelectRef.current?.([]);
|
|
235
|
+
}
|
|
236
|
+
}, [ctx?.selection, id, ctx, highlightMode, incoming && incoming.join(","), selects]);
|
|
237
|
+
return /* @__PURE__ */ jsx(
|
|
238
|
+
"div",
|
|
239
|
+
{
|
|
240
|
+
ref,
|
|
241
|
+
className,
|
|
242
|
+
style,
|
|
243
|
+
role: ariaLabel ? "figure" : void 0,
|
|
244
|
+
"aria-label": ariaLabel || void 0
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
var defaultScope = createFilterScope();
|
|
249
|
+
var PageCtx = createContext(defaultScope);
|
|
250
|
+
function BicPage({ children }) {
|
|
251
|
+
const ref = useRef(null);
|
|
252
|
+
if (!ref.current) ref.current = createFilterScope();
|
|
253
|
+
return /* @__PURE__ */ jsx(PageCtx.Provider, { value: ref.current, children });
|
|
254
|
+
}
|
|
255
|
+
function useBicFilter(columns, opts) {
|
|
256
|
+
const scope = useContext(PageCtx);
|
|
257
|
+
const ref = useRef(null);
|
|
258
|
+
if (!ref.current) ref.current = createFilter(columns, opts);
|
|
259
|
+
const f = ref.current;
|
|
260
|
+
useEffect(() => scope.add(f), [scope, f]);
|
|
261
|
+
useSyncExternalStore((cb) => f.onChange(() => cb()), () => f.version, () => f.version);
|
|
262
|
+
return f;
|
|
263
|
+
}
|
|
264
|
+
function useBicControls(initial) {
|
|
265
|
+
const ref = useRef(null);
|
|
266
|
+
if (!ref.current) ref.current = createControls(initial);
|
|
267
|
+
const c = ref.current;
|
|
268
|
+
useSyncExternalStore((cb) => c.onChange(() => cb()), () => c.version, () => c.version);
|
|
269
|
+
return c;
|
|
270
|
+
}
|
|
271
|
+
function useBicFilters() {
|
|
272
|
+
const scope = useContext(PageCtx);
|
|
273
|
+
const [, tick] = useState(0);
|
|
274
|
+
useEffect(() => scope.onChange(() => tick((t) => t + 1)), [scope]);
|
|
275
|
+
return { filters: scope.filters, active: scope.active, clearAll: () => scope.clearAll() };
|
|
276
|
+
}
|
|
277
|
+
function BicFilterChips({ className, chipClassName, clearAllText = "Clear all", style }) {
|
|
278
|
+
const { active, clearAll } = useBicFilters();
|
|
279
|
+
if (!active.length) return null;
|
|
280
|
+
const chip = {
|
|
281
|
+
display: "inline-flex",
|
|
282
|
+
alignItems: "center",
|
|
283
|
+
gap: 6,
|
|
284
|
+
padding: "2px 4px 2px 10px",
|
|
285
|
+
border: "1px solid currentColor",
|
|
286
|
+
borderRadius: 999,
|
|
287
|
+
font: "inherit",
|
|
288
|
+
lineHeight: 1.6
|
|
289
|
+
};
|
|
290
|
+
const x = {
|
|
291
|
+
border: 0,
|
|
292
|
+
background: "transparent",
|
|
293
|
+
color: "inherit",
|
|
294
|
+
cursor: "pointer",
|
|
295
|
+
font: "inherit",
|
|
296
|
+
padding: "0 6px",
|
|
297
|
+
lineHeight: 1
|
|
298
|
+
};
|
|
299
|
+
return /* @__PURE__ */ jsxs(
|
|
300
|
+
"div",
|
|
301
|
+
{
|
|
302
|
+
className: ["bic-filter-chips", className].filter(Boolean).join(" "),
|
|
303
|
+
role: "group",
|
|
304
|
+
"aria-label": "Filters",
|
|
305
|
+
style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", ...style },
|
|
306
|
+
children: [
|
|
307
|
+
active.map((f) => /* @__PURE__ */ jsxs(
|
|
308
|
+
"span",
|
|
309
|
+
{
|
|
310
|
+
className: ["bic-filter-chip", chipClassName].filter(Boolean).join(" "),
|
|
311
|
+
style: chipClassName ? void 0 : chip,
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ jsxs("span", { className: "bic-filter-chip-label", children: [
|
|
314
|
+
f.label,
|
|
315
|
+
": "
|
|
316
|
+
] }),
|
|
317
|
+
/* @__PURE__ */ jsx("span", { className: "bic-filter-chip-value", children: f.text }),
|
|
318
|
+
/* @__PURE__ */ jsx(
|
|
319
|
+
"button",
|
|
320
|
+
{
|
|
321
|
+
type: "button",
|
|
322
|
+
className: "bic-filter-chip-clear",
|
|
323
|
+
"aria-label": `Clear ${f.label}`,
|
|
324
|
+
style: chipClassName ? void 0 : x,
|
|
325
|
+
onClick: () => f.clear(),
|
|
326
|
+
children: "\xD7"
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
f.id
|
|
332
|
+
)),
|
|
333
|
+
active.length > 1 && /* @__PURE__ */ jsx(
|
|
334
|
+
"button",
|
|
335
|
+
{
|
|
336
|
+
type: "button",
|
|
337
|
+
className: "bic-filter-chips-clear-all",
|
|
338
|
+
onClick: clearAll,
|
|
339
|
+
style: {
|
|
340
|
+
font: "inherit",
|
|
341
|
+
background: "transparent",
|
|
342
|
+
border: 0,
|
|
343
|
+
color: "inherit",
|
|
344
|
+
textDecoration: "underline",
|
|
345
|
+
cursor: "pointer"
|
|
346
|
+
},
|
|
347
|
+
children: clearAllText
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
);
|
|
166
353
|
}
|
|
167
354
|
function useMeasuredSize(ref) {
|
|
168
355
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
@@ -185,7 +372,19 @@ function useMeasuredSize(ref) {
|
|
|
185
372
|
export {
|
|
186
373
|
BicChart,
|
|
187
374
|
BicChartGroup,
|
|
375
|
+
BicFilterChips,
|
|
376
|
+
BicPage,
|
|
188
377
|
assembleD3,
|
|
378
|
+
badgeKey,
|
|
379
|
+
createControls,
|
|
380
|
+
createFilter,
|
|
381
|
+
createFilterScope,
|
|
382
|
+
fromVegaInteraction,
|
|
383
|
+
noteBadges,
|
|
384
|
+
notesFor,
|
|
385
|
+
useBicControls,
|
|
386
|
+
useBicFilter,
|
|
387
|
+
useBicFilters,
|
|
189
388
|
useBicSelection,
|
|
190
389
|
useMeasuredSize
|
|
191
390
|
};
|
package/dist/testing.mjs
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
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
|
+
import {
|
|
3
|
+
HOST_CONTAINER_CLASS,
|
|
4
|
+
MARK_CLASS,
|
|
5
|
+
MARK_SELECTED_CLASS,
|
|
6
|
+
ROW_IDX_ATTR
|
|
7
|
+
} from "./chunk-WNGJUPEI.mjs";
|
|
2
8
|
import "./chunk-GHODWOAL.mjs";
|
|
3
9
|
|
|
10
|
+
// src/testing/interact.ts
|
|
11
|
+
function chartContainers(root) {
|
|
12
|
+
return Array.from(root.querySelectorAll(`.${HOST_CONTAINER_CLASS}`));
|
|
13
|
+
}
|
|
14
|
+
function marksOf(chart) {
|
|
15
|
+
return Array.from(chart.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}]`)).filter((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "") !== "");
|
|
16
|
+
}
|
|
17
|
+
function findMark(chart, which) {
|
|
18
|
+
const marks = marksOf(chart);
|
|
19
|
+
if (typeof which === "function") return marks.find(which) ?? null;
|
|
20
|
+
return marks.find((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",").map(Number).includes(which)) ?? null;
|
|
21
|
+
}
|
|
22
|
+
function clickMark(mark, opts = {}) {
|
|
23
|
+
const win = mark.ownerDocument?.defaultView;
|
|
24
|
+
const Ev = win?.MouseEvent ?? MouseEvent;
|
|
25
|
+
mark.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true, ctrlKey: !!opts.add }));
|
|
26
|
+
}
|
|
27
|
+
function clickEmpty(chart) {
|
|
28
|
+
const win = chart.ownerDocument?.defaultView;
|
|
29
|
+
const Ev = win?.MouseEvent ?? MouseEvent;
|
|
30
|
+
chart.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true }));
|
|
31
|
+
}
|
|
32
|
+
function selectedRows(chart) {
|
|
33
|
+
const out = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const m of Array.from(chart.querySelectorAll(`.${MARK_CLASS}.${MARK_SELECTED_CLASS}`))) {
|
|
35
|
+
for (const p of (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",")) {
|
|
36
|
+
const n = parseInt(p, 10);
|
|
37
|
+
if (Number.isFinite(n)) out.add(n);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...out].sort((a, b) => a - b);
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
// src/testing/index.ts
|
|
5
44
|
var ConformanceError = class extends Error {
|
|
6
45
|
contract;
|
|
@@ -63,5 +102,11 @@ async function assertMarkerStoreConformance(store) {
|
|
|
63
102
|
}
|
|
64
103
|
export {
|
|
65
104
|
ConformanceError,
|
|
66
|
-
assertMarkerStoreConformance
|
|
105
|
+
assertMarkerStoreConformance,
|
|
106
|
+
chartContainers,
|
|
107
|
+
clickEmpty,
|
|
108
|
+
clickMark,
|
|
109
|
+
findMark,
|
|
110
|
+
marksOf,
|
|
111
|
+
selectedRows
|
|
67
112
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChartHost } from "./host";
|
|
2
|
+
export interface ControlInfo {
|
|
3
|
+
/** The knob's name in the chart's bag ("rate", "horizon"). */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Its value as the chart stores it (a number, or an ISO date string for a date knob). */
|
|
6
|
+
value: unknown;
|
|
7
|
+
/** The label the chart draws beside it ("growth per year"), or "" if none. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** The readout the chart draws ("+5.5%", "7 years") - the value in the chart's own words. */
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Controls {
|
|
13
|
+
/** Every knob's current value, by key - the opening values as soon as the chart has drawn. */
|
|
14
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
15
|
+
/** Each knob in the order the chart draws them, with its label and readout. */
|
|
16
|
+
readonly info: readonly ControlInfo[];
|
|
17
|
+
/** "growth per year +5.5% · horizon 7 years": what to show beside a saved scenario. */
|
|
18
|
+
readonly summary: string;
|
|
19
|
+
/** True once a chart has drawn and reported its controls. */
|
|
20
|
+
readonly ready: boolean;
|
|
21
|
+
/** Bumped on every change. */
|
|
22
|
+
readonly version: number;
|
|
23
|
+
/** Apply values (a saved scenario): the chart redraws with them. Unknown keys are ignored by the chart. */
|
|
24
|
+
set(values: Readonly<Record<string, unknown>>): void;
|
|
25
|
+
/** Back to the chart's own defaults. */
|
|
26
|
+
reset(): void;
|
|
27
|
+
onChange(cb: (c: Controls) => void): () => void;
|
|
28
|
+
}
|
|
29
|
+
/** What attachControls hands the host: the uiState pair the chart reads and writes. */
|
|
30
|
+
export interface ControlsAttachment {
|
|
31
|
+
/** Merge into the host's options at creation (and they stay: setOptions merges). */
|
|
32
|
+
readonly options: {
|
|
33
|
+
uiState: Record<string, unknown>;
|
|
34
|
+
setUiState: (s: unknown) => void;
|
|
35
|
+
};
|
|
36
|
+
/** Read the chart's controls now (call once a render has settled). */
|
|
37
|
+
read(): void;
|
|
38
|
+
detach(): void;
|
|
39
|
+
}
|
|
40
|
+
/** Make a controls handle: give it to one chart (a BicChart's `controls`, or attachControls). */
|
|
41
|
+
export declare function createControls(initial?: Readonly<Record<string, unknown>>): Controls;
|
|
42
|
+
/** Read each knob off a rendered chart: values from the bag, label and readout from what it drew. */
|
|
43
|
+
export declare function readControls(container: HTMLElement): ControlInfo[];
|
|
44
|
+
/**
|
|
45
|
+
* Wire a controls handle to one chart. Call it before creating the host and pass
|
|
46
|
+
* `attachment.options` into createChartHost's options (a raw uiState / setUiState pair takes
|
|
47
|
+
* precedence over any viewState provider); `getHost` returns the host once it exists. Call
|
|
48
|
+
* `read()` after each render settles.
|
|
49
|
+
*/
|
|
50
|
+
export declare function attachControls(getHost: () => ChartHost | null, container: HTMLElement, controls: Controls): ControlsAttachment;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Filter, FilterKey } from "./filterScope";
|
|
2
|
+
/** One value as a DAX literal: text quoted with "" doubled, numbers, TRUE()/FALSE(), dates, BLANK(). */
|
|
3
|
+
export declare function daxLiteral(v: unknown): string;
|
|
4
|
+
/**
|
|
5
|
+
* TREATAS over a filter's selected keys (or keys given directly), onto the named model columns -
|
|
6
|
+
* or null when nothing is selected, so `calculateTable` leaves the query unfiltered.
|
|
7
|
+
*/
|
|
8
|
+
export declare function treatAs(selection: Filter | readonly (FilterKey | unknown)[] | null | undefined, ...columnRefs: string[]): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* CALCULATETABLE(table, filters...) with the null filters dropped - the table expression unchanged
|
|
11
|
+
* when none is left. `table` is a table expression (SUMMARIZECOLUMNS(...)), not a whole query with
|
|
12
|
+
* EVALUATE.
|
|
13
|
+
*/
|
|
14
|
+
export declare function calculateTable(table: string, ...filters: Array<string | null | undefined | false>): string;
|
|
15
|
+
/**
|
|
16
|
+
* A whole query (`[DEFINE ...] EVALUATE <table> [ORDER BY ...]`, as a .dax file holds it) with its table filtered:
|
|
17
|
+
* the table expression goes inside CALCULATETABLE with the filters, DEFINE and ORDER BY stay where they are. With no
|
|
18
|
+
* live filter the query comes back unchanged. One EVALUATE only (throws on more).
|
|
19
|
+
*
|
|
20
|
+
* filterQuery(FORECAST_DAX, treatAs(region, "Geography[RegionCode]"))
|
|
21
|
+
*/
|
|
22
|
+
export declare function filterQuery(query: string, ...filters: Array<string | null | undefined | false>): string;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { ChartHost } from "./host";
|
|
2
|
+
/** Why a filter changed. */
|
|
3
|
+
export type FilterChangeReason = "click" | "clear" | "page" | "data";
|
|
4
|
+
/** One selected key: a value per key column, in the filter's column order. */
|
|
5
|
+
export type FilterKey = readonly unknown[];
|
|
6
|
+
export interface FilterOptions {
|
|
7
|
+
/** What a chip calls this filter ("Country"). Defaults to the key columns. */
|
|
8
|
+
label?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The column(s) a chip shows for a selected key, read from the selected rows ("Country" for a
|
|
11
|
+
* RegionCode key; ["OriginName", "DestinationName"] for a route, joined " → ").
|
|
12
|
+
* Defaults to the key columns.
|
|
13
|
+
*/
|
|
14
|
+
display?: string | readonly string[];
|
|
15
|
+
/** A stable id for this filter within its scope. Generated when absent. */
|
|
16
|
+
id?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Filter {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
/** The key column(s). */
|
|
21
|
+
readonly columns: readonly string[];
|
|
22
|
+
readonly label: string;
|
|
23
|
+
/** True while anything is selected. */
|
|
24
|
+
readonly active: boolean;
|
|
25
|
+
/** The selected keys, each one value per key column. */
|
|
26
|
+
readonly keys: readonly FilterKey[];
|
|
27
|
+
/** The rows the selection was made from (for display names); rows of keys set by value are absent. */
|
|
28
|
+
readonly rows: readonly Record<string, unknown>[];
|
|
29
|
+
/** The first selected row, or null. */
|
|
30
|
+
readonly row: Record<string, unknown> | null;
|
|
31
|
+
/** The first key's first value, or null - the common one-column, one-pick case. */
|
|
32
|
+
readonly value: unknown;
|
|
33
|
+
/** Every selected key's first value. */
|
|
34
|
+
readonly values: readonly unknown[];
|
|
35
|
+
/** What a chip shows: the display column(s) of each selected key, "A → B" within a key, ", " between keys. */
|
|
36
|
+
readonly text: string;
|
|
37
|
+
/**
|
|
38
|
+
* The display text for ANY key - a badge's, a key set by value - read from `rows` (a table carrying the display
|
|
39
|
+
* columns, e.g. the chart's own rows) or else from the rows this filter has seen; the key's values when neither
|
|
40
|
+
* has it.
|
|
41
|
+
*/
|
|
42
|
+
textOf(key: FilterKey | unknown, rows?: readonly Record<string, unknown>[]): string;
|
|
43
|
+
/** Why the last change happened (null before any). */
|
|
44
|
+
readonly reason: FilterChangeReason | null;
|
|
45
|
+
/** Bumped on every change: a cheap identity for memoising on the filter's state. */
|
|
46
|
+
readonly version: number;
|
|
47
|
+
/** Select these rows' keys (replacing the selection). An empty list clears. */
|
|
48
|
+
selectRows(rows: readonly Record<string, unknown>[], reason?: FilterChangeReason): void;
|
|
49
|
+
/** Select keys by value: one value per key (a one-column filter) or one array per key. */
|
|
50
|
+
set(keys: readonly unknown[] | readonly FilterKey[] | unknown, reason?: FilterChangeReason): void;
|
|
51
|
+
/** Clear the selection (and every bound chart's marks). */
|
|
52
|
+
clear(reason?: FilterChangeReason): void;
|
|
53
|
+
/** Does this row carry a selected key? Read on `columns` (default: the key columns). */
|
|
54
|
+
has(row: Record<string, unknown>, columns?: readonly string[]): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The rows that carry a selected key - every row while nothing is selected. `columns` names
|
|
57
|
+
* where this table keeps the key when it differs from the filter's: a one-column filter
|
|
58
|
+
* matched against several columns keeps a row when ANY of them carries the key (a route
|
|
59
|
+
* table filtered by a country keeps routes from OR to it). The same array comes back while
|
|
60
|
+
* the input and the filter are unchanged, so it's safe to call during render.
|
|
61
|
+
*/
|
|
62
|
+
keep<T extends Record<string, unknown>>(rows: readonly T[], columns?: readonly string[]): T[];
|
|
63
|
+
/** Subscribe to changes. Returns the unsubscribe. */
|
|
64
|
+
onChange(cb: (filter: Filter, reason: FilterChangeReason) => void): () => void;
|
|
65
|
+
}
|
|
66
|
+
/** A page's filters: what a chip bar lists and what clear-all clears. */
|
|
67
|
+
export interface FilterScope {
|
|
68
|
+
readonly filters: readonly Filter[];
|
|
69
|
+
/** The filters with something selected. */
|
|
70
|
+
readonly active: readonly Filter[];
|
|
71
|
+
/** Make a filter in this scope (see createFilter). */
|
|
72
|
+
filter(columns: string | readonly string[], opts?: FilterOptions): Filter;
|
|
73
|
+
/** Add an existing filter. Returns the remove. */
|
|
74
|
+
add(filter: Filter): () => void;
|
|
75
|
+
/** Clear every filter. */
|
|
76
|
+
clearAll(reason?: FilterChangeReason): void;
|
|
77
|
+
/** Subscribe to any change of any filter, and to filters being added or removed. */
|
|
78
|
+
onChange(cb: (scope: FilterScope) => void): () => void;
|
|
79
|
+
}
|
|
80
|
+
/** Make a filter keyed by `columns` (one name, or several for a compound key such as a route). */
|
|
81
|
+
export declare function createFilter(columns: string | readonly string[], opts?: FilterOptions): Filter;
|
|
82
|
+
/** Make a page's filter scope. */
|
|
83
|
+
export declare function createFilterScope(): FilterScope;
|
|
84
|
+
export interface FilterBinding {
|
|
85
|
+
/**
|
|
86
|
+
* Call after the chart is handed new data. A selection whose key is still drawn is repainted;
|
|
87
|
+
* one whose key is gone is cleared (reason "data") when this chart made it. An empty payload is
|
|
88
|
+
* treated as data still arriving, not as the key being gone.
|
|
89
|
+
*/
|
|
90
|
+
reconcile(): void;
|
|
91
|
+
/** Repaint the filter's selection onto the chart's marks. */
|
|
92
|
+
paint(): void;
|
|
93
|
+
/** Unwire. The filter keeps its selection. */
|
|
94
|
+
detach(): void;
|
|
95
|
+
}
|
|
96
|
+
export interface BindFilterOptions {
|
|
97
|
+
/** The source row (keyed by column name) behind payload row `i`, or null. */
|
|
98
|
+
rowAt(i: number): Record<string, unknown> | null;
|
|
99
|
+
/** How many rows the chart is drawing now. */
|
|
100
|
+
rowCount(): number;
|
|
101
|
+
}
|
|
102
|
+
/** The source row behind each payload row, from a payload of { columns, rows: unknown[][] }. */
|
|
103
|
+
export declare function payloadRowReader(getPayload: () => {
|
|
104
|
+
columns: readonly {
|
|
105
|
+
name: string;
|
|
106
|
+
}[];
|
|
107
|
+
rows: readonly unknown[][];
|
|
108
|
+
} | null | undefined): BindFilterOptions;
|
|
109
|
+
/**
|
|
110
|
+
* Wire one chart host to one filter. A click on the chart sets the filter (reason "click"), the
|
|
111
|
+
* chart's own clear gesture clears it (reason "clear"), and any change to the filter - from this
|
|
112
|
+
* chart, another chart, or the page - is painted onto this chart's marks. Paints notify the host's
|
|
113
|
+
* subscribers with source "host", which this binding (and a chart group) never republishes.
|
|
114
|
+
*/
|
|
115
|
+
export declare function bindFilter(host: ChartHost, filter: Filter, opts: BindFilterOptions): FilterBinding;
|
|
116
|
+
/** The interaction events @microsoft/fabric-visuals' VegaVisual and DataGrid emit (onInteraction). */
|
|
117
|
+
export interface VegaInteractionEvent {
|
|
118
|
+
action: string;
|
|
119
|
+
selections?: Array<{
|
|
120
|
+
predicates?: Array<{
|
|
121
|
+
type?: string;
|
|
122
|
+
name?: string;
|
|
123
|
+
values?: unknown[];
|
|
124
|
+
}>;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Apply a VegaVisual / DataGrid `onInteraction` event list to a filter, so Microsoft's charts take
|
|
129
|
+
* part in the same page filters as ours: `onInteraction={e => fromVegaInteraction(country, e)}`.
|
|
130
|
+
* A `select` sets the filter from the set predicates that name its key columns (a selection
|
|
131
|
+
* missing one of them is skipped); a `clear` clears it. Returns whether the filter changed.
|
|
132
|
+
*/
|
|
133
|
+
export declare function fromVegaInteraction(filter: Filter, events: readonly VegaInteractionEvent[] | null | undefined): boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -9,7 +9,10 @@ export { buildRenderPayload, type RenderPayload, type GeoPointBinding } from "./
|
|
|
9
9
|
export { codeReadsDatesInLocalTime, vegaSpecReadsDatesInLocalTime } from "./dateCells";
|
|
10
10
|
export { createChartHost, compileRenderFn, stripEsmExports, requiredD3Plugins, explainRenderFailure, sessionViewStateProvider, noopViewStateProvider, assembleD3, D3_PLUGIN_PACKAGES, type ChartHost, type ChartHostConfig, type RenderFn } from "./host";
|
|
11
11
|
export { ANNOTATION_CLASS, ANNOTATION_LAYER_CLASS, type MarkAnnotation, type AnnotationReport } from "./annotations";
|
|
12
|
+
export type { MarkNoteLike, NoteBadgeOptions } from "./markNotes";
|
|
13
|
+
export type { Controls, ControlInfo, ControlsAttachment } from "./controls";
|
|
12
14
|
export { createChartGroup, toSourceRows, syncMemberSelection, type ChartGroup, type ChartGroupMember, type ChartGroupSelection, type ChartGroupChange, type ChartGroupMemberOptions, type ChartGroupSourceOptions, type ChartGroupPayload } from "./group";
|
|
15
|
+
export type { Filter, FilterScope, FilterOptions, FilterKey, FilterChangeReason, FilterBinding, BindFilterOptions, VegaInteractionEvent } from "./filterScope";
|
|
13
16
|
export { createMarkResolver, isInsideControl, rowIdxsFromMark, nextSelection, collectD3GlyphCenters, clearSelectionGlyphs, paintSelectionGlyphs, SELECTION_GLYPH_CLASS, type MarkResolver, type MarkResolverEnv, type GlyphCenter, type SelectionGlyphStyle, type SelectionModifiers, } from "./selection";
|
|
14
17
|
export { planTrivialChart, compileTrivialSource, type TrivialPlan, type TrivialShapeKind } from "./trivial";
|
|
15
18
|
export { captureSvgSnapshot, svgToDataUrl, svgNaturalSize, rasterizeSvgToPngDataUrl, tableHostOf, chartSvgOf, htmlToSvgDataUrl, htmlNaturalSize, type SnapshotOptions } from "./snapshot";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MarkAnnotation } from "./annotations";
|
|
2
|
+
import type { Filter, FilterKey } from "./filterScope";
|
|
3
|
+
export interface MarkNoteLike {
|
|
4
|
+
chart?: string | null;
|
|
5
|
+
markKey?: unknown;
|
|
6
|
+
markKeyTo?: unknown;
|
|
7
|
+
body?: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface NoteBadgeOptions<N> {
|
|
10
|
+
/** The chart's key column(s) the note keys live in: "RegionCode", or a route's two ends. */
|
|
11
|
+
columns: string | readonly string[];
|
|
12
|
+
/** Only notes whose `chart` is this (one notes table serving several charts). */
|
|
13
|
+
chart?: string;
|
|
14
|
+
/** A note's key, one value per column. Default: [markKey] or [markKey, markKeyTo]. */
|
|
15
|
+
keyOf?: (note: N) => readonly unknown[];
|
|
16
|
+
/** A note's text. Default: body. */
|
|
17
|
+
textOf?: (note: N) => string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* One badge per noted mark: the count of its notes, the latest note as the tooltip (notes are
|
|
21
|
+
* taken in the order given - pass them newest first). Pass to a chart's `annotations`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function noteBadges<N extends MarkNoteLike>(notes: readonly N[] | null | undefined, opts: NoteBadgeOptions<N>): MarkAnnotation[];
|
|
24
|
+
/** The key values of a badge noteBadges made (for a click: which mark's notes to open). */
|
|
25
|
+
export declare function badgeKey(a: MarkAnnotation): FilterKey;
|
|
26
|
+
/**
|
|
27
|
+
* The notes on one mark - a key given directly, or the first key selected in a page filter - in
|
|
28
|
+
* the order given. Keys compare as trimmed text, so a numeric code and its string agree.
|
|
29
|
+
*/
|
|
30
|
+
export declare function notesFor<N extends MarkNoteLike>(notes: readonly N[] | null | undefined, key: Filter | FilterKey | null | undefined, opts: Omit<NoteBadgeOptions<N>, "textOf">): N[];
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createFilter, createFilterScope, bindFilter, payloadRowReader, fromVegaInteraction, type Filter, type FilterScope, type FilterOptions, type FilterKey, type FilterChangeReason, type FilterBinding, type BindFilterOptions, type VegaInteractionEvent } from "./filterScope";
|
|
2
|
+
export { createControls, attachControls, readControls, type Controls, type ControlInfo, type ControlsAttachment } from "./controls";
|
|
3
|
+
export { noteBadges, badgeKey, notesFor, type MarkNoteLike, type NoteBadgeOptions } from "./markNotes";
|