@bicharts/chart-host 0.6.80 → 0.6.82
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 +73 -0
- package/dist/card.mjs +2 -2
- package/dist/{chunk-Y3CCDWVO.mjs → chunk-7HPVGRQ2.mjs} +13 -13
- package/dist/{chunk-YYJCE5TO.mjs → chunk-MP5S6MVW.mjs} +18 -18
- package/dist/{chunk-P3HUTWQB.mjs → chunk-ZRSEKV4I.mjs} +360 -368
- package/dist/index.mjs +3 -3
- package/dist/react.mjs +2 -2
- package/dist/testing.mjs +177 -0
- package/dist/types/host/services.d.ts +70 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +40 -1
- package/dist/types/viewState.d.ts +125 -0
- package/dist/view-state.mjs +171 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -278,6 +278,79 @@ made for: every word shares a comment with many others, so the default lit most
|
|
|
278
278
|
click. The cross-filter sent out is the same under both rules. `onSelectionPaint` reports how each
|
|
279
279
|
paint of a declaring chart decided, including when no mark matched and the default was used.
|
|
280
280
|
|
|
281
|
+
## Remembering what the reader did
|
|
282
|
+
|
|
283
|
+
A chart keeps its resting state (a sort, an expanded row, a 3D camera angle) in a small bag. It
|
|
284
|
+
reads the bag from `options.uiState` and writes it through `options.setUiState`, and generated code
|
|
285
|
+
never learns where the bag goes. Where it goes is your host's choice, and so is which keys may
|
|
286
|
+
outlive what: a redraw, a close and reopen, or a new version of the chart.
|
|
287
|
+
|
|
288
|
+
`createChartHost` still takes one `viewState` provider, as before. When your host needs more than
|
|
289
|
+
one store, describe it with a `ViewStateService`:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { sessionViewStateProvider, type ViewStateService } from "@bicharts/chart-host";
|
|
293
|
+
|
|
294
|
+
const viewState: ViewStateService = {
|
|
295
|
+
viewing: sessionViewStateProvider(el), // survives a redraw; a page switch starts clean
|
|
296
|
+
durable: workbookStore, // optional: survives close and reopen
|
|
297
|
+
policy: { // the keys your host scopes, by name
|
|
298
|
+
camera: { lifetime: "durable", dropOnNewVersion: true, dropOnFreshViewing: false, durableWhen: "rememberView" },
|
|
299
|
+
userStopped: { lifetime: "durable", dropOnNewVersion: false, dropOnFreshViewing: true },
|
|
300
|
+
},
|
|
301
|
+
versionKey: "cameraVersion", // optional: how a version change is noticed
|
|
302
|
+
};
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**What you provide.** `viewing` and `policy` are required (`policy: {}` is fine). Everything else is
|
|
306
|
+
optional, and leaving it out says something about your host:
|
|
307
|
+
|
|
308
|
+
- no `durable` store: the host can't remember across a close (a static preview, a server-side
|
|
309
|
+
render). `viewStateIsDurable(service)` is the one definition of "remembers the view", so it can't
|
|
310
|
+
be claimed without a store to back it. `noopViewStateService()` says "nothing is stored" out loud.
|
|
311
|
+
- no `chartKeys`: keys your policy doesn't name belong to the chart. They're kept, durably, through a
|
|
312
|
+
new version and a fresh viewing, and the chart validates what it reads. Set `chartKeys` to give
|
|
313
|
+
them another rule. A host that forgets everything when its chart changes sets `dropOnNewVersion`.
|
|
314
|
+
- no `versionKey`: you apply the version rule yourself, where you learn the chart changed.
|
|
315
|
+
- no `durableMaxChars`: the durable store accepts any size.
|
|
316
|
+
|
|
317
|
+
**What a key can be.** `lifetime: "viewing"` keeps a key out of the durable store; `"durable"` puts it
|
|
318
|
+
in both. `dropOnNewVersion` drops it when the chart on screen is another version (a camera is aimed
|
|
319
|
+
at one chart's axes). `dropOnFreshViewing` drops it when the bag is read at the start of a viewing
|
|
320
|
+
instead of in the one that wrote it. `durableWhen` names a preference that must be on for the key to
|
|
321
|
+
reach the durable store; the viewing store keeps it either way.
|
|
322
|
+
|
|
323
|
+
**The rules are plain functions** in `@bicharts/chart-host/view-state`, with no DOM, timers or
|
|
324
|
+
storage of their own:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import { resolveViewState, commitViewState } from "@bicharts/chart-host/view-state";
|
|
328
|
+
|
|
329
|
+
const { bag, dropped } = resolveViewState(viewState, { codeVersion, freshViewing }); // before a render
|
|
330
|
+
commitViewState(next, viewState, prefs); // when the chart writes
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`resolveViewState` builds the bag the chart is handed: the viewing store, else the durable one, with
|
|
334
|
+
the policy applied. `commitViewState` decides what a write reaches: the viewing store gets all of
|
|
335
|
+
it, the durable store gets what the policy allows. Both report what they did and neither throws.
|
|
336
|
+
`viewStateAfterSwap(service, bag)` applies the version rule for a host that sees the swap itself.
|
|
337
|
+
|
|
338
|
+
**Check your stores** with the conformance suite in the testing subpath. It confirms `load` never
|
|
339
|
+
throws and reads `{}` when empty, that `save` replaces, that nested values round-trip, that the
|
|
340
|
+
viewing store survives your host being re-created on the same element, and that the durable store
|
|
341
|
+
holds what it's given across a reopen:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { assertViewStateConformance } from "@bicharts/chart-host/testing";
|
|
345
|
+
|
|
346
|
+
const report = await assertViewStateConformance(service, {
|
|
347
|
+
recreate: () => buildService(sameElement), // the host drawing again
|
|
348
|
+
reopen: () => buildService(newElement), // a new viewing over the same saved file
|
|
349
|
+
settle: () => flushPendingWrites(), // only if your durable save is debounced
|
|
350
|
+
});
|
|
351
|
+
expect(report.skipped).toEqual([]); // nothing went unchecked
|
|
352
|
+
```
|
|
353
|
+
|
|
281
354
|
## Architecture — three layers, and why it matters if you contribute
|
|
282
355
|
|
|
283
356
|
Read this before adding code. The package has **three** concerns, and they are independent.
|
package/dist/card.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
|
computeSelectionCard
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-7HPVGRQ2.mjs";
|
|
5
|
+
import "./chunk-ZRSEKV4I.mjs";
|
|
6
6
|
import "./chunk-GHODWOAL.mjs";
|
|
7
7
|
|
|
8
8
|
// src/selectionCardChrome.ts
|
|
@@ -1,18 +1,18 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
3
|
+
Dr,
|
|
4
|
+
Z,
|
|
5
|
+
g,
|
|
6
|
+
ht,
|
|
7
|
+
kr,
|
|
8
|
+
pt
|
|
9
|
+
} from "./chunk-ZRSEKV4I.mjs";
|
|
10
10
|
|
|
11
11
|
// src/sourceDateFormat.ts
|
|
12
12
|
function formatSourceDate(raw, opts = {}) {
|
|
13
13
|
const d = toDate(raw);
|
|
14
14
|
if (!d) return null;
|
|
15
|
-
const dayFrame =
|
|
15
|
+
const dayFrame = g(d);
|
|
16
16
|
const frame = dayFrame ?? (opts.dialect === "dotnet" ? "local" : "utc");
|
|
17
17
|
const p = partsIn(d, frame);
|
|
18
18
|
const format = cleanFormat(opts.format, opts.dialect);
|
|
@@ -33,7 +33,7 @@ var ISO_LIKE_RE = /^\s*\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(
|
|
|
33
33
|
function toDate(raw) {
|
|
34
34
|
if (raw instanceof Date) return isNaN(raw.getTime()) ? null : raw;
|
|
35
35
|
if (typeof raw !== "string" || !ISO_LIKE_RE.test(raw)) return null;
|
|
36
|
-
const d =
|
|
36
|
+
const d = Z(raw.trim());
|
|
37
37
|
return d && !isNaN(d.getTime()) ? d : null;
|
|
38
38
|
}
|
|
39
39
|
function partsIn(d, frame) {
|
|
@@ -435,15 +435,15 @@ function displayText(col, raw, opts) {
|
|
|
435
435
|
return String(raw);
|
|
436
436
|
}
|
|
437
437
|
function isSuppressed(col) {
|
|
438
|
-
return
|
|
438
|
+
return pt(col).suppressed;
|
|
439
439
|
}
|
|
440
440
|
function resolveAgg(col, requested, auto) {
|
|
441
441
|
const fallback = () => {
|
|
442
|
-
const d =
|
|
442
|
+
const d = Dr(col);
|
|
443
443
|
return d === "first" ? "count" : d;
|
|
444
444
|
};
|
|
445
445
|
if (auto) return fallback();
|
|
446
|
-
return
|
|
446
|
+
return ht(col).indexOf(requested) >= 0 ? requested : fallback();
|
|
447
447
|
}
|
|
448
448
|
function toNumber(v) {
|
|
449
449
|
if (v == null || v === "") return null;
|
|
@@ -546,7 +546,7 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
|
|
|
546
546
|
}
|
|
547
547
|
const value = reduce(selVals, colAgg, selRaw);
|
|
548
548
|
let sharePct = null;
|
|
549
|
-
if (
|
|
549
|
+
if (kr(col, colAgg) && value != null) {
|
|
550
550
|
const allVals = [];
|
|
551
551
|
const allRaw = [];
|
|
552
552
|
for (let r = 0; r < rows.length; r++) {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Fn,
|
|
4
4
|
J3,
|
|
5
|
+
X,
|
|
5
6
|
Xn,
|
|
6
|
-
Yn
|
|
7
|
-
|
|
8
|
-
} from "./chunk-P3HUTWQB.mjs";
|
|
7
|
+
Yn
|
|
8
|
+
} from "./chunk-ZRSEKV4I.mjs";
|
|
9
9
|
import {
|
|
10
10
|
censusHitBands,
|
|
11
11
|
createMarkResolver,
|
|
@@ -244,10 +244,10 @@ function f(t) {
|
|
|
244
244
|
}
|
|
245
245
|
function rgbToLab(r, g, b) {
|
|
246
246
|
const R = linearise(r), G = linearise(g), B = linearise(b);
|
|
247
|
-
const
|
|
247
|
+
const X2 = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B;
|
|
248
248
|
const Y = 0.2126729 * R + 0.7151522 * G + 0.072175 * B;
|
|
249
249
|
const Z = 0.0193339 * R + 0.119192 * G + 0.9503041 * B;
|
|
250
|
-
const fx = f(
|
|
250
|
+
const fx = f(X2 / Xn2), fy = f(Y / Yn2), fz = f(Z / Zn);
|
|
251
251
|
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
|
|
252
252
|
}
|
|
253
253
|
function hexToLab(hex2) {
|
|
@@ -302,13 +302,13 @@ function labToHex(lab) {
|
|
|
302
302
|
return t3 > EPS ? t3 : (116 * t - 16) / KAPPA;
|
|
303
303
|
};
|
|
304
304
|
const fy = (lab.L + 16) / 116, fx = fy + lab.a / 500, fz = fy - lab.b / 200;
|
|
305
|
-
const
|
|
305
|
+
const X2 = Xn2 * finv(fx);
|
|
306
306
|
const Y = Yn2 * (lab.L > KAPPA * EPS ? fy * fy * fy : lab.L / KAPPA);
|
|
307
307
|
const Z = Zn * finv(fz);
|
|
308
308
|
const lin = [
|
|
309
|
-
3.2404542 *
|
|
310
|
-
-0.969266 *
|
|
311
|
-
0.0556434 *
|
|
309
|
+
3.2404542 * X2 - 1.5371385 * Y - 0.4985314 * Z,
|
|
310
|
+
-0.969266 * X2 + 1.8760108 * Y + 0.041556 * Z,
|
|
311
|
+
0.0556434 * X2 - 0.2040259 * Y + 1.0572252 * Z
|
|
312
312
|
];
|
|
313
313
|
return "#" + lin.map((v) => {
|
|
314
314
|
const c = Math.max(0, Math.min(1, v));
|
|
@@ -852,7 +852,7 @@ function buildRenderPayload(cols, rowObjs, geo, point, destination, opts) {
|
|
|
852
852
|
cols = cols.filter((c) => c.name !== ROW_IDX_COLUMN);
|
|
853
853
|
let utcDayColumns = null;
|
|
854
854
|
if (opts?.utcDays) {
|
|
855
|
-
const norm =
|
|
855
|
+
const norm = X(cols, rowObjs);
|
|
856
856
|
rowObjs = norm.rows;
|
|
857
857
|
utcDayColumns = norm.columns;
|
|
858
858
|
}
|
|
@@ -1866,10 +1866,10 @@ var EMPTY4 = {
|
|
|
1866
1866
|
};
|
|
1867
1867
|
function backedSamples(el, pts) {
|
|
1868
1868
|
try {
|
|
1869
|
-
const
|
|
1870
|
-
if (typeof
|
|
1871
|
-
const m =
|
|
1872
|
-
const svg =
|
|
1869
|
+
const ge = el;
|
|
1870
|
+
if (typeof ge.isPointInFill !== "function" || typeof ge.getScreenCTM !== "function") return null;
|
|
1871
|
+
const m = ge.getScreenCTM();
|
|
1872
|
+
const svg = ge.ownerSVGElement;
|
|
1873
1873
|
if (!m || !svg || typeof svg.createSVGPoint !== "function") return null;
|
|
1874
1874
|
if (pts.length === 0) return null;
|
|
1875
1875
|
const inv = m.inverse();
|
|
@@ -1878,7 +1878,7 @@ function backedSamples(el, pts) {
|
|
|
1878
1878
|
const p = svg.createSVGPoint();
|
|
1879
1879
|
p.x = pts[i].x;
|
|
1880
1880
|
p.y = pts[i].y;
|
|
1881
|
-
hit.push(
|
|
1881
|
+
hit.push(ge.isPointInFill(p.matrixTransform(inv)));
|
|
1882
1882
|
}
|
|
1883
1883
|
return hit;
|
|
1884
1884
|
} catch {
|
|
@@ -1923,9 +1923,9 @@ function sampleBackgrounds(shapes, pts, pageRGB, effAlpha) {
|
|
|
1923
1923
|
function extendPillOver(pill, tr) {
|
|
1924
1924
|
try {
|
|
1925
1925
|
if (String(pill.tagName || "").toLowerCase() !== "rect") return false;
|
|
1926
|
-
const
|
|
1927
|
-
if (typeof
|
|
1928
|
-
const m =
|
|
1926
|
+
const ge = pill;
|
|
1927
|
+
if (typeof ge.getScreenCTM !== "function") return false;
|
|
1928
|
+
const m = ge.getScreenCTM();
|
|
1929
1929
|
if (!m || Math.abs(m.b) > 1e-6 || Math.abs(m.c) > 1e-6 || !(m.a > 0) || !(m.d > 0)) return false;
|
|
1930
1930
|
const inv = m.inverse();
|
|
1931
1931
|
const toUser = (x2, y2) => ({ x: inv.a * x2 + inv.c * y2 + inv.e, y: inv.b * x2 + inv.d * y2 + inv.f });
|