@bicharts/chart-host 0.6.89 → 0.6.91
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 +34 -6
- package/dist/adapters.mjs +3 -3
- package/dist/{chunk-YDHXOIAI.mjs → chunk-45VILLJT.mjs} +2 -0
- package/dist/{chunk-GOIG756B.mjs → chunk-CR7DSJVG.mjs} +1 -1
- package/dist/{chunk-QHIPRAZK.mjs → chunk-NNLBFIWU.mjs} +51 -2
- package/dist/{chunk-XNS34AMM.mjs → chunk-PDVEIW42.mjs} +1 -1
- package/dist/{chunk-OG6RR2AJ.mjs → chunk-QHMLMF4L.mjs} +2 -2
- package/dist/{chunk-562LF7XM.mjs → chunk-RDUEXM2D.mjs} +88 -25
- package/dist/{chunk-UBB5LNH4.mjs → chunk-VC3Z5CH7.mjs} +1 -1
- package/dist/{chunk-DJ5IJV57.mjs → chunk-VEVBXK44.mjs} +1 -1
- package/dist/index.mjs +15 -7
- package/dist/legend-fit.mjs +3 -3
- package/dist/page.mjs +4 -4
- package/dist/react.mjs +17 -10
- package/dist/testing.mjs +1 -1
- package/dist/types/contract.d.ts +1 -0
- package/dist/types/fit.d.ts +23 -0
- package/dist/types/fitDom.d.ts +24 -1
- package/dist/types/host.d.ts +13 -3
- package/dist/types/index.d.ts +3 -1
- package/dist/types/react.d.ts +11 -2
- package/dist/types/zoomMemory/zoomMemoryDom.d.ts +15 -6
- package/dist/zoom-memory.mjs +38 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -269,9 +269,11 @@ cue, a hierarchy level) is never flattened by a fix meant for another chart.
|
|
|
269
269
|
|
|
270
270
|
**How far the rest dims, per chart.** `options.dimOpacity` (a share of full opacity, `0.01`-`1`;
|
|
271
271
|
`DIM_OPACITY_MIN` is the floor) sets the level for one host without writing CSS, and it is live
|
|
272
|
-
through `setOptions`.
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
through `setOptions`. It takes a number or a numeric string; a value above 1 is clamped to 1 (no
|
|
273
|
+
dim), never read as a percentage, and anything else is ignored. The host writes it as
|
|
274
|
+
`--lch-dim-opacity` on its own container and, when the option goes away or the host is destroyed,
|
|
275
|
+
puts back the value your page had set inline there, so with the option absent nothing is written
|
|
276
|
+
and the CSS above decides: the stylesheet's `0.25`, or whatever your page sets on the variable.
|
|
275
277
|
|
|
276
278
|
```js
|
|
277
279
|
createChartHost(el, { code, data, options: { dimOpacity: 0.15 } });
|
|
@@ -410,15 +412,41 @@ const zoomHost: ZoomMemoryHost = {
|
|
|
410
412
|
persistZoom: json => localStorage.setItem("chart-zoom", json), // "" forgets
|
|
411
413
|
log: () => {},
|
|
412
414
|
};
|
|
413
|
-
const zoomMemory = createZoomMemoryHook(zoomHost); // once per
|
|
415
|
+
const zoomMemory = createZoomMemoryHook(zoomHost); // once per chart, reused by every host built for it
|
|
414
416
|
const host = createChartHost(el, { code, data, d3, zoomMemory });
|
|
415
417
|
```
|
|
416
418
|
|
|
417
419
|
The view survives every redraw in the session whatever the preference says, and a close and reopen
|
|
418
420
|
only while `rememberAcrossSessions()` is true. It is keyed by the chart's code and the viewport size
|
|
419
421
|
(to the nearest 32 px), so a regenerated chart or a real resize opens at full extent instead of
|
|
420
|
-
somewhere arbitrary. A chart with several zoomable panels is left alone.
|
|
421
|
-
the
|
|
422
|
+
somewhere arbitrary. A chart with several zoomable panels is left alone. The `ZoomMemoryHost` is the
|
|
423
|
+
memory: keep one per chart for as long as the chart is on the page, and hand its hook to every host
|
|
424
|
+
built for that chart, on the same element or, after a remount, a new one (a destroyed host releases
|
|
425
|
+
the hook's watcher, so the next one can take it). In React, pass the hook as the `zoomMemory` prop of
|
|
426
|
+
`<BicChart>`, built once (`zoomHost` as above, kept outside the render):
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
const [zoomMemory] = useState(() => createZoomMemoryHook(zoomHost)); // once, however often this renders
|
|
430
|
+
return <BicChart code={code} data={data} d3={d3} zoomMemory={zoomMemory} />;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
A hook that arrives after mount, or goes away, rebuilds the chart's host; swapping one hook for another
|
|
434
|
+
on a mounted `<BicChart>` rebuilds nothing, and applies at the next rebuild (a new `code`, say) or at
|
|
435
|
+
once if you remount it with a `key`, so a hook built inline in the props costs no rebuild.
|
|
436
|
+
|
|
437
|
+
**A scrolled chart** keeps its place with no setup. A chart taller than its frame scrolls, and every
|
|
438
|
+
redraw empties the container, which used to send the reader back to the top. The host now parks the
|
|
439
|
+
scroll offset on the container before it clears it, and the fit puts it back once the new layout has
|
|
440
|
+
decided whether it still scrolls. The offset is clamped to what the new layout can scroll, and only
|
|
441
|
+
restored on an axis that still scrolls. That covers a resize, a filter, a restyle, and a new host
|
|
442
|
+
created on the same element. The memory is keyed by the chart (its `code`, or the `renderFn`'s
|
|
443
|
+
source text), so a different chart opens at the top, while a `renderFn` closure rebuilt on every draw
|
|
444
|
+
is still the same chart. It lives on the element for the session
|
|
445
|
+
only, and nothing is persisted. A page that manages the container's scrolling itself passes
|
|
446
|
+
`restoreScroll: false` (the `restoreScroll` prop of `<BicChart>`), and then nothing is remembered or
|
|
447
|
+
restored. A host that clears and fits its container on its own can use the same pieces:
|
|
448
|
+
`rememberScroll` before the clear, `takeScroll` after the render, and `fitRenderedChart`'s
|
|
449
|
+
`restoreScroll` option (or the pure `planScrollRestore`).
|
|
422
450
|
|
|
423
451
|
## Keeping a drawn chart readable inside its frame
|
|
424
452
|
|
package/dist/adapters.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
censusHitBands,
|
|
4
4
|
ensureCrossfilterHitTargets,
|
|
5
5
|
hitBandFlag
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PDVEIW42.mjs";
|
|
7
7
|
import {
|
|
8
8
|
clickWasDrag,
|
|
9
9
|
collectD3GlyphCenters,
|
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
markKeyOf,
|
|
13
13
|
rowIdxsFromMark,
|
|
14
14
|
selectionRuleOf
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VEVBXK44.mjs";
|
|
16
16
|
import {
|
|
17
17
|
AXIS_FILTER_CLASS,
|
|
18
18
|
LEGEND_MARK_CLASS,
|
|
19
19
|
MARK_CLASS,
|
|
20
20
|
ROW_IDX_ATTR
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-45VILLJT.mjs";
|
|
22
22
|
import "./chunk-GHODWOAL.mjs";
|
|
23
23
|
|
|
24
24
|
// src/adapters/types.ts
|
|
@@ -19,6 +19,7 @@ var CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
|
|
|
19
19
|
var CONTAINER_SLOT_XF_CLEAR = "__llmXfClear";
|
|
20
20
|
var CONTAINER_SLOT_INITIAL_XF_MARK = "__llmInitialXfMark";
|
|
21
21
|
var CONTAINER_SLOT_UI_STATE = "__lchUiState";
|
|
22
|
+
var CONTAINER_SLOT_SCROLL = "__lchScroll";
|
|
22
23
|
var CONTAINER_SLOT_CLICKED_MARKS = "__lchClickedMarks";
|
|
23
24
|
var VIEW_ONLY_UI_STATE_KEYS = Object.freeze(["camera", "llmZoom"]);
|
|
24
25
|
function withoutViewOnlyKeys(bag) {
|
|
@@ -89,6 +90,7 @@ export {
|
|
|
89
90
|
CONTAINER_SLOT_XF_CLEAR,
|
|
90
91
|
CONTAINER_SLOT_INITIAL_XF_MARK,
|
|
91
92
|
CONTAINER_SLOT_UI_STATE,
|
|
93
|
+
CONTAINER_SLOT_SCROLL,
|
|
92
94
|
CONTAINER_SLOT_CLICKED_MARKS,
|
|
93
95
|
VIEW_ONLY_UI_STATE_KEYS,
|
|
94
96
|
withoutViewOnlyKeys,
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
LASSO_KNOB_KEY
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QHMLMF4L.mjs";
|
|
5
5
|
|
|
6
6
|
// src/filterScope.ts
|
|
7
7
|
function viewToken(view) {
|
|
@@ -1,7 +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
|
+
CONTAINER_SLOT_SCROLL,
|
|
3
4
|
SNAPSHOT_SVG_ATTR
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-45VILLJT.mjs";
|
|
5
6
|
|
|
6
7
|
// src/fit.ts
|
|
7
8
|
var SCROLL_SLACK_PX = 8;
|
|
@@ -104,6 +105,15 @@ function axisPinPlacement(band, scrollTop, viewportH) {
|
|
|
104
105
|
if (band.bottom > scrollTop + viewportH) return "bottom";
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
108
|
+
var nonNegative = (n) => typeof n === "number" && isFinite(n) && n > 0 ? n : 0;
|
|
109
|
+
function planScrollRestore(saved, key, m) {
|
|
110
|
+
if (!saved || key === void 0 || key === null || saved.key !== key) return { x: 0, y: 0 };
|
|
111
|
+
const room = (content, view) => Math.max(0, nonNegative(content) - nonNegative(view));
|
|
112
|
+
return {
|
|
113
|
+
x: m.overflowX === "auto" ? Math.min(nonNegative(saved.x), room(m.scrollWidth, m.clientWidth)) : 0,
|
|
114
|
+
y: m.overflowY === "auto" ? Math.min(nonNegative(saved.y), room(m.scrollHeight, m.clientHeight)) : 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
107
117
|
|
|
108
118
|
// src/fitDom.ts
|
|
109
119
|
var FIT_WALK_CAP = 8e3;
|
|
@@ -256,6 +266,28 @@ function unpinScrolledAxis(container) {
|
|
|
256
266
|
} catch {
|
|
257
267
|
}
|
|
258
268
|
}
|
|
269
|
+
function rememberScroll(container, key) {
|
|
270
|
+
if (!container) return;
|
|
271
|
+
try {
|
|
272
|
+
const slot = container;
|
|
273
|
+
if (slot[CONTAINER_SLOT_SCROLL]) return;
|
|
274
|
+
if (container.querySelector(".llm-mm-scroll")) return;
|
|
275
|
+
const x = container.scrollLeft, y = container.scrollTop;
|
|
276
|
+
if (x > 0 || y > 0) slot[CONTAINER_SLOT_SCROLL] = { key, x, y };
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function takeScroll(container) {
|
|
281
|
+
if (!container) return null;
|
|
282
|
+
try {
|
|
283
|
+
const slot = container;
|
|
284
|
+
const m = slot[CONTAINER_SLOT_SCROLL] ?? null;
|
|
285
|
+
delete slot[CONTAINER_SLOT_SCROLL];
|
|
286
|
+
return m;
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
259
291
|
function textHidden(t, view) {
|
|
260
292
|
if (t.getAttribute("display") === "none" || t.getAttribute("visibility") === "hidden") return true;
|
|
261
293
|
try {
|
|
@@ -476,7 +508,8 @@ function fitRenderedChart(container, opts = {}) {
|
|
|
476
508
|
overflowX: "hidden",
|
|
477
509
|
overflowY: "hidden",
|
|
478
510
|
reading: null,
|
|
479
|
-
axisPin: noPin("no-container")
|
|
511
|
+
axisPin: noPin("no-container"),
|
|
512
|
+
scrolledTo: null
|
|
480
513
|
};
|
|
481
514
|
if (!container) return result;
|
|
482
515
|
try {
|
|
@@ -537,6 +570,19 @@ function fitRenderedChart(container, opts = {}) {
|
|
|
537
570
|
if (opts.applyOverflow !== false) {
|
|
538
571
|
container.style.overflowX = fit.overflowX;
|
|
539
572
|
container.style.overflowY = fit.overflowY;
|
|
573
|
+
if (opts.restoreScroll) {
|
|
574
|
+
const to = planScrollRestore(opts.restoreScroll.memory, opts.restoreScroll.key, {
|
|
575
|
+
scrollWidth: container.scrollWidth,
|
|
576
|
+
scrollHeight: container.scrollHeight,
|
|
577
|
+
clientWidth: container.clientWidth,
|
|
578
|
+
clientHeight: container.clientHeight,
|
|
579
|
+
overflowX: fit.overflowX,
|
|
580
|
+
overflowY: fit.overflowY
|
|
581
|
+
});
|
|
582
|
+
if (container.scrollLeft !== to.x) container.scrollLeft = to.x;
|
|
583
|
+
if (container.scrollTop !== to.y) container.scrollTop = to.y;
|
|
584
|
+
result.scrolledTo = to;
|
|
585
|
+
}
|
|
540
586
|
if (fit.overflowY !== "auto") {
|
|
541
587
|
unpinScrolledAxis(container);
|
|
542
588
|
result.axisPin = noPin("not-scrolling");
|
|
@@ -571,12 +617,15 @@ export {
|
|
|
571
617
|
labelBand,
|
|
572
618
|
planAxisPin,
|
|
573
619
|
axisPinPlacement,
|
|
620
|
+
planScrollRestore,
|
|
574
621
|
ctmScaleOf,
|
|
575
622
|
svgInkReach,
|
|
576
623
|
clipWindowOf,
|
|
577
624
|
measureContainerBoxes,
|
|
578
625
|
fitReadingFor,
|
|
579
626
|
unpinScrolledAxis,
|
|
627
|
+
rememberScroll,
|
|
628
|
+
takeScroll,
|
|
580
629
|
pinScrolledAxis,
|
|
581
630
|
fitRenderedChart
|
|
582
631
|
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
gestureWasDrag,
|
|
5
5
|
isInsideControl,
|
|
6
6
|
rowIdxsFromMark
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VEVBXK44.mjs";
|
|
8
8
|
import {
|
|
9
9
|
AXIS_FILTER_CLASS,
|
|
10
10
|
CONTROL_CLASS,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
MARK_CLASS,
|
|
13
13
|
ROW_IDX_ATTR,
|
|
14
14
|
SNAPSHOT_SVG_ATTR
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-45VILLJT.mjs";
|
|
16
16
|
|
|
17
17
|
// src/lasso.ts
|
|
18
18
|
var LASSO_ROW_CAP = 5e3;
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
LASSO_OUTLINE_CLASS,
|
|
4
4
|
installLasso
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QHMLMF4L.mjs";
|
|
6
6
|
import {
|
|
7
7
|
censusHitBands,
|
|
8
8
|
ensureCrossfilterHitTargets
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PDVEIW42.mjs";
|
|
10
10
|
import {
|
|
11
11
|
clearPress,
|
|
12
12
|
clearSelectionGlyphs,
|
|
@@ -23,12 +23,14 @@ import {
|
|
|
23
23
|
selectionRuleOf,
|
|
24
24
|
trackPress,
|
|
25
25
|
validRowSet
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-VEVBXK44.mjs";
|
|
27
27
|
import {
|
|
28
28
|
clipWindowOf,
|
|
29
29
|
fitRenderedChart,
|
|
30
|
+
rememberScroll,
|
|
31
|
+
takeScroll,
|
|
30
32
|
unpinScrolledAxis
|
|
31
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NNLBFIWU.mjs";
|
|
32
34
|
import {
|
|
33
35
|
Fn,
|
|
34
36
|
J3,
|
|
@@ -82,7 +84,7 @@ import {
|
|
|
82
84
|
XFILTER_REFRESH_EVENT,
|
|
83
85
|
chartOwnsTimeline,
|
|
84
86
|
periodTickSuppressesFeedback
|
|
85
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-45VILLJT.mjs";
|
|
86
88
|
|
|
87
89
|
// src/labelContrast.ts
|
|
88
90
|
function parseRGBA(c) {
|
|
@@ -612,10 +614,14 @@ function resolveOptions(p) {
|
|
|
612
614
|
cultureCode: p.cultureCode,
|
|
613
615
|
allowTooltips: p.allowTooltips,
|
|
614
616
|
noDataText: p.noDataText,
|
|
615
|
-
// A finite number
|
|
616
|
-
//
|
|
617
|
+
// A finite number, or a string that is numeric once trimmed, is clamped to DIM_OPACITY_MIN-1; anything else (absent,
|
|
618
|
+
// blank, a boolean, an array, an object) stays undefined so the stylesheet - and a page's own CSS override - still
|
|
619
|
+
// decides. Not numberOr: Number() reads true as 1 and false, [] and whitespace as 0, which would dim to the floor.
|
|
620
|
+
// Above 1 is clamped to 1, which dims nothing; it is never reinterpreted as a percentage, so the option keeps one
|
|
621
|
+
// unit (a share of full opacity) for every value. Never defaulted here: that would override every page's CSS.
|
|
617
622
|
dimOpacity: (() => {
|
|
618
|
-
const
|
|
623
|
+
const v = p.dimOpacity;
|
|
624
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
619
625
|
return Number.isFinite(n) ? clamp(n, DIM_OPACITY_MIN, 1) : void 0;
|
|
620
626
|
})(),
|
|
621
627
|
// A recognised glyph or none: an unknown string must never reach the painter as a live mode, and "none" is
|
|
@@ -2029,6 +2035,7 @@ function compileRenderFn(code, win, doc, d3) {
|
|
|
2029
2035
|
}
|
|
2030
2036
|
var ASYNC_RENDER_TIMEOUT_MS = 15e3;
|
|
2031
2037
|
var isThenable = (v) => !!v && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
2038
|
+
var dimVarPage = /* @__PURE__ */ new WeakMap();
|
|
2032
2039
|
function createChartHost(container, config) {
|
|
2033
2040
|
const doc = container.ownerDocument;
|
|
2034
2041
|
const win = doc?.defaultView ?? globalThis;
|
|
@@ -2096,6 +2103,19 @@ function createChartHost(container, config) {
|
|
|
2096
2103
|
});
|
|
2097
2104
|
};
|
|
2098
2105
|
let dimVarSet = false;
|
|
2106
|
+
const releaseDimVar = () => {
|
|
2107
|
+
if (!dimVarSet) return;
|
|
2108
|
+
dimVarSet = false;
|
|
2109
|
+
const page = dimVarPage.get(container);
|
|
2110
|
+
if (!page || --page.holders > 0) return;
|
|
2111
|
+
dimVarPage.delete(container);
|
|
2112
|
+
try {
|
|
2113
|
+
if (container.style?.getPropertyValue?.(DIM_OPACITY_VAR) !== page.wrote) return;
|
|
2114
|
+
if (page.was) container.style?.setProperty(DIM_OPACITY_VAR, page.was.value, page.was.priority);
|
|
2115
|
+
else container.style?.removeProperty(DIM_OPACITY_VAR);
|
|
2116
|
+
} catch {
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2099
2119
|
let current = null;
|
|
2100
2120
|
let activeTickEl = null;
|
|
2101
2121
|
let selectionIsPeriod = false;
|
|
@@ -2167,6 +2187,7 @@ function createChartHost(container, config) {
|
|
|
2167
2187
|
}
|
|
2168
2188
|
};
|
|
2169
2189
|
const paintSelection = () => {
|
|
2190
|
+
if (destroyed) return;
|
|
2170
2191
|
try {
|
|
2171
2192
|
const sel = current;
|
|
2172
2193
|
const active = !!sel && sel.length > 0;
|
|
@@ -2174,12 +2195,20 @@ function createChartHost(container, config) {
|
|
|
2174
2195
|
container.classList?.[dim ? "add" : "remove"](SELECTION_ACTIVE_CLASS);
|
|
2175
2196
|
const want = resolved.dimOpacity;
|
|
2176
2197
|
if (typeof want === "number") {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2198
|
+
const level = String(want);
|
|
2199
|
+
let page = dimVarPage.get(container);
|
|
2200
|
+
if (!dimVarSet) {
|
|
2201
|
+
if (!page) {
|
|
2202
|
+
const value = container.style?.getPropertyValue?.(DIM_OPACITY_VAR) ?? "";
|
|
2203
|
+
page = { was: value ? { value, priority: container.style?.getPropertyPriority?.(DIM_OPACITY_VAR) ?? "" } : null, holders: 0, wrote: level };
|
|
2204
|
+
dimVarPage.set(container, page);
|
|
2205
|
+
}
|
|
2206
|
+
page.holders++;
|
|
2207
|
+
dimVarSet = true;
|
|
2208
|
+
}
|
|
2209
|
+
if (page) page.wrote = level;
|
|
2210
|
+
container.style?.setProperty(DIM_OPACITY_VAR, level);
|
|
2211
|
+
} else releaseDimVar();
|
|
2183
2212
|
const lit = container.querySelectorAll(`.${ACTIVE_TICK_CLASS}`);
|
|
2184
2213
|
for (const t of Array.from(lit)) t.classList?.remove(ACTIVE_TICK_CLASS);
|
|
2185
2214
|
const tickStillDrawn = !!activeTickEl && (typeof container.contains === "function" ? container.contains(activeTickEl) : activeTickEl.isConnected !== false);
|
|
@@ -2416,6 +2445,7 @@ function createChartHost(container, config) {
|
|
|
2416
2445
|
}
|
|
2417
2446
|
};
|
|
2418
2447
|
const notify = (rowIdxs, source, tick, fromChart = false) => {
|
|
2448
|
+
if (destroyed) return;
|
|
2419
2449
|
current = rowIdxs;
|
|
2420
2450
|
selectionFromChart = rowIdxs.length > 0 && fromChart;
|
|
2421
2451
|
activeTickEl = rowIdxs.length ? tick ?? null : null;
|
|
@@ -2521,6 +2551,28 @@ function createChartHost(container, config) {
|
|
|
2521
2551
|
}
|
|
2522
2552
|
}
|
|
2523
2553
|
};
|
|
2554
|
+
const releaseZoom = () => {
|
|
2555
|
+
if (zoomMemory) {
|
|
2556
|
+
try {
|
|
2557
|
+
zoomMemory.release?.(container);
|
|
2558
|
+
} catch {
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
const keepScroll = config.restoreScroll !== false;
|
|
2563
|
+
let chartSource;
|
|
2564
|
+
const scrollKey = () => {
|
|
2565
|
+
if (config.code) return config.code;
|
|
2566
|
+
if (chartSource === void 0 && typeof config.renderFn === "function") {
|
|
2567
|
+
try {
|
|
2568
|
+
chartSource = Function.prototype.toString.call(config.renderFn);
|
|
2569
|
+
} catch {
|
|
2570
|
+
return config.renderFn;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
return chartSource;
|
|
2574
|
+
};
|
|
2575
|
+
let hasRendered = false;
|
|
2524
2576
|
const runPostRenderPasses = () => {
|
|
2525
2577
|
if (zoomMemory && zoomCap) {
|
|
2526
2578
|
try {
|
|
@@ -2608,9 +2660,11 @@ function createChartHost(container, config) {
|
|
|
2608
2660
|
} catch {
|
|
2609
2661
|
}
|
|
2610
2662
|
}
|
|
2663
|
+
const scrollMem = takeScroll(container);
|
|
2611
2664
|
if (fitOpts !== false) {
|
|
2612
2665
|
try {
|
|
2613
|
-
const
|
|
2666
|
+
const base = fitOpts === true ? {} : fitOpts;
|
|
2667
|
+
const r = fitRenderedChart(container, keepScroll ? { ...base, restoreScroll: { memory: scrollMem, key: scrollKey() } } : base);
|
|
2614
2668
|
config.onFit?.(r);
|
|
2615
2669
|
} catch {
|
|
2616
2670
|
}
|
|
@@ -2649,7 +2703,9 @@ function createChartHost(container, config) {
|
|
|
2649
2703
|
followGlyphs(false);
|
|
2650
2704
|
disposeLasso();
|
|
2651
2705
|
flushZoom();
|
|
2706
|
+
if (hasRendered && keepScroll) rememberScroll(container, scrollKey());
|
|
2652
2707
|
container.innerHTML = "";
|
|
2708
|
+
hasRendered = true;
|
|
2653
2709
|
if (!renderFn) {
|
|
2654
2710
|
if (!config.code) throw new Error("createChartHost: provide code or renderFn");
|
|
2655
2711
|
renderFn = compileRenderFn(config.code, win, doc, chartD3);
|
|
@@ -2741,12 +2797,18 @@ function createChartHost(container, config) {
|
|
|
2741
2797
|
);
|
|
2742
2798
|
}));
|
|
2743
2799
|
},
|
|
2800
|
+
// A DESTROYED HOST OWNS NOTHING ON THE CONTAINER any more. A host rebuilt on the same element has taken over
|
|
2801
|
+
// the chart's slots (its clear, its animation stop, its initial mark) and the clicked-marks record, so every
|
|
2802
|
+
// method below that reaches the container returns first: a stale reference to the old host (a closure, a late
|
|
2803
|
+
// callback) must not clear, stop or annotate the chart that is on screen now.
|
|
2744
2804
|
setOptions(partial) {
|
|
2805
|
+
if (destroyed) return;
|
|
2745
2806
|
raw = { ...raw, ...partial };
|
|
2746
2807
|
resolved = resolveForHost(raw);
|
|
2747
2808
|
host.render();
|
|
2748
2809
|
},
|
|
2749
2810
|
setData(next) {
|
|
2811
|
+
if (destroyed) return;
|
|
2750
2812
|
data = next;
|
|
2751
2813
|
host.render();
|
|
2752
2814
|
},
|
|
@@ -2754,8 +2816,9 @@ function createChartHost(container, config) {
|
|
|
2754
2816
|
return resolved;
|
|
2755
2817
|
},
|
|
2756
2818
|
setAnnotations(list) {
|
|
2819
|
+
if (destroyed) return;
|
|
2757
2820
|
annotationLayer.set(list);
|
|
2758
|
-
|
|
2821
|
+
try {
|
|
2759
2822
|
drawAnnotations();
|
|
2760
2823
|
} catch {
|
|
2761
2824
|
}
|
|
@@ -2774,6 +2837,7 @@ function createChartHost(container, config) {
|
|
|
2774
2837
|
return () => subs.delete(cb);
|
|
2775
2838
|
},
|
|
2776
2839
|
clear() {
|
|
2840
|
+
if (destroyed) return;
|
|
2777
2841
|
const s = container[CONTAINER_SLOT_XF_CLEAR];
|
|
2778
2842
|
if (typeof s === "function") {
|
|
2779
2843
|
try {
|
|
@@ -2786,6 +2850,7 @@ function createChartHost(container, config) {
|
|
|
2786
2850
|
notify([], "host");
|
|
2787
2851
|
},
|
|
2788
2852
|
highlight(rowIdxs) {
|
|
2853
|
+
if (destroyed) return;
|
|
2789
2854
|
const rows = Array.isArray(rowIdxs) ? rowIdxs.slice() : [];
|
|
2790
2855
|
if (lasso && lasso.rows().length && !sameRowSet(rows, lasso.rows())) lasso.clear({ publish: false, source: "host" });
|
|
2791
2856
|
notify(rows, "host");
|
|
@@ -2794,9 +2859,11 @@ function createChartHost(container, config) {
|
|
|
2794
2859
|
return current;
|
|
2795
2860
|
}
|
|
2796
2861
|
},
|
|
2797
|
-
animation: { stop
|
|
2862
|
+
animation: { stop() {
|
|
2863
|
+
if (!destroyed) stopAnim();
|
|
2864
|
+
} },
|
|
2798
2865
|
initialMark() {
|
|
2799
|
-
return container[CONTAINER_SLOT_INITIAL_XF_MARK] ?? null;
|
|
2866
|
+
return destroyed ? null : container[CONTAINER_SLOT_INITIAL_XF_MARK] ?? null;
|
|
2800
2867
|
},
|
|
2801
2868
|
destroy() {
|
|
2802
2869
|
if (destroyed) return;
|
|
@@ -2808,16 +2875,12 @@ function createChartHost(container, config) {
|
|
|
2808
2875
|
clearPress(container);
|
|
2809
2876
|
disposeLasso();
|
|
2810
2877
|
subs.clear();
|
|
2811
|
-
|
|
2812
|
-
try {
|
|
2813
|
-
container.style?.removeProperty(DIM_OPACITY_VAR);
|
|
2814
|
-
} catch {
|
|
2815
|
-
}
|
|
2816
|
-
dimVarSet = false;
|
|
2817
|
-
}
|
|
2878
|
+
releaseDimVar();
|
|
2818
2879
|
annotationLayer.destroy();
|
|
2880
|
+
if (hasRendered && keepScroll) rememberScroll(container, scrollKey());
|
|
2819
2881
|
unpinScrolledAxis(container);
|
|
2820
2882
|
flushZoom();
|
|
2883
|
+
releaseZoom();
|
|
2821
2884
|
followGlyphs(false);
|
|
2822
2885
|
container.innerHTML = "";
|
|
2823
2886
|
if (glyphPositionWas !== null) {
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
SNAPSHOT_SVG_ATTR
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-45VILLJT.mjs";
|
|
5
5
|
|
|
6
6
|
// src/snapshot.ts
|
|
7
7
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
svgNaturalSize,
|
|
16
16
|
svgToDataUrl,
|
|
17
17
|
tableHostOf
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VC3Z5CH7.mjs";
|
|
19
19
|
import {
|
|
20
20
|
ANNOTATION_CLASS,
|
|
21
21
|
ANNOTATION_LAYER_CLASS,
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
toRGBA,
|
|
98
98
|
toSourceRows,
|
|
99
99
|
valuePlacementFlag
|
|
100
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-RDUEXM2D.mjs";
|
|
101
101
|
import {
|
|
102
102
|
LASSO_AXES_SLOT,
|
|
103
103
|
LASSO_COUNT_CLASS,
|
|
@@ -122,13 +122,13 @@ import {
|
|
|
122
122
|
lassoVerdict,
|
|
123
123
|
pointInShape,
|
|
124
124
|
registerLassoAction
|
|
125
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-QHMLMF4L.mjs";
|
|
126
126
|
import {
|
|
127
127
|
MIN_HIT_BAND_PX,
|
|
128
128
|
censusHitBands,
|
|
129
129
|
ensureCrossfilterHitTargets,
|
|
130
130
|
hitBandFlag
|
|
131
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-PDVEIW42.mjs";
|
|
132
132
|
import {
|
|
133
133
|
CONTAINER_SLOT_PRESS_AT,
|
|
134
134
|
DRAG_NOT_CLICK_PX,
|
|
@@ -153,7 +153,7 @@ import {
|
|
|
153
153
|
selectionRuleOf,
|
|
154
154
|
trackPress,
|
|
155
155
|
validRowSet
|
|
156
|
-
} from "./chunk-
|
|
156
|
+
} from "./chunk-VEVBXK44.mjs";
|
|
157
157
|
import {
|
|
158
158
|
AXIS_PIN_BAND_PAD_PX,
|
|
159
159
|
AXIS_PIN_MAX_BAND_FRACTION,
|
|
@@ -176,10 +176,13 @@ import {
|
|
|
176
176
|
pinScrolledAxis,
|
|
177
177
|
planAxisPin,
|
|
178
178
|
planFrameGrow,
|
|
179
|
+
planScrollRestore,
|
|
180
|
+
rememberScroll,
|
|
179
181
|
scrollFitFor,
|
|
180
182
|
svgInkReach,
|
|
183
|
+
takeScroll,
|
|
181
184
|
unpinScrolledAxis
|
|
182
|
-
} from "./chunk-
|
|
185
|
+
} from "./chunk-NNLBFIWU.mjs";
|
|
183
186
|
import {
|
|
184
187
|
_3,
|
|
185
188
|
l,
|
|
@@ -205,6 +208,7 @@ import {
|
|
|
205
208
|
CONTAINER_SLOT_ANIM_STOP,
|
|
206
209
|
CONTAINER_SLOT_CLICKED_MARKS,
|
|
207
210
|
CONTAINER_SLOT_INITIAL_XF_MARK,
|
|
211
|
+
CONTAINER_SLOT_SCROLL,
|
|
208
212
|
CONTAINER_SLOT_UI_STATE,
|
|
209
213
|
CONTAINER_SLOT_XF_CLEAR,
|
|
210
214
|
CONTROL_CLASS,
|
|
@@ -237,7 +241,7 @@ import {
|
|
|
237
241
|
chartOwnsTimeline,
|
|
238
242
|
periodTickSuppressesFeedback,
|
|
239
243
|
withoutViewOnlyKeys
|
|
240
|
-
} from "./chunk-
|
|
244
|
+
} from "./chunk-45VILLJT.mjs";
|
|
241
245
|
import "./chunk-GHODWOAL.mjs";
|
|
242
246
|
|
|
243
247
|
// src/optionsVocabulary.ts
|
|
@@ -2154,6 +2158,7 @@ export {
|
|
|
2154
2158
|
CONTAINER_SLOT_CLICKED_MARKS,
|
|
2155
2159
|
CONTAINER_SLOT_INITIAL_XF_MARK,
|
|
2156
2160
|
CONTAINER_SLOT_PRESS_AT,
|
|
2161
|
+
CONTAINER_SLOT_SCROLL,
|
|
2157
2162
|
CONTAINER_SLOT_UI_STATE,
|
|
2158
2163
|
CONTAINER_SLOT_XF_CLEAR,
|
|
2159
2164
|
CONTROL_CLASS,
|
|
@@ -2393,6 +2398,7 @@ export {
|
|
|
2393
2398
|
pinScrolledAxis,
|
|
2394
2399
|
planAxisPin,
|
|
2395
2400
|
planFrameGrow,
|
|
2401
|
+
planScrollRestore,
|
|
2396
2402
|
planSelectionPaint,
|
|
2397
2403
|
planTrivialChart,
|
|
2398
2404
|
pointInShape,
|
|
@@ -2429,6 +2435,7 @@ export {
|
|
|
2429
2435
|
registerGeoAsset,
|
|
2430
2436
|
registerLassoAction,
|
|
2431
2437
|
relativeLuminance,
|
|
2438
|
+
rememberScroll,
|
|
2432
2439
|
requiredD3Plugins,
|
|
2433
2440
|
resolveAutoColorScale,
|
|
2434
2441
|
resolveOptions,
|
|
@@ -2455,6 +2462,7 @@ export {
|
|
|
2455
2462
|
svgToDataUrl,
|
|
2456
2463
|
syncMemberSelection,
|
|
2457
2464
|
tableHostOf,
|
|
2465
|
+
takeScroll,
|
|
2458
2466
|
toRGBA,
|
|
2459
2467
|
toSourceRows,
|
|
2460
2468
|
trackPress,
|
package/dist/legend-fit.mjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
chartSvgOf,
|
|
4
4
|
tableHostOf
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-VC3Z5CH7.mjs";
|
|
6
6
|
import {
|
|
7
7
|
FIT_CONTENT_SELECTOR,
|
|
8
8
|
ctmScaleOf,
|
|
9
9
|
isPhantomBox
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-NNLBFIWU.mjs";
|
|
11
|
+
import "./chunk-45VILLJT.mjs";
|
|
12
12
|
import "./chunk-GHODWOAL.mjs";
|
|
13
13
|
|
|
14
14
|
// src/legendFit/legendReconcile.ts
|
package/dist/page.mjs
CHANGED
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
readControls,
|
|
15
15
|
viewFromToken,
|
|
16
16
|
viewToken
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-CR7DSJVG.mjs";
|
|
18
|
+
import "./chunk-QHMLMF4L.mjs";
|
|
19
|
+
import "./chunk-VEVBXK44.mjs";
|
|
20
|
+
import "./chunk-45VILLJT.mjs";
|
|
21
21
|
import "./chunk-GHODWOAL.mjs";
|
|
22
22
|
export {
|
|
23
23
|
LINKED_HOVER_CLASS,
|
package/dist/react.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
payloadRowReader,
|
|
14
14
|
viewFromToken,
|
|
15
15
|
viewToken
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CR7DSJVG.mjs";
|
|
17
17
|
import {
|
|
18
18
|
assembleD3,
|
|
19
19
|
createChartGroup,
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
loadGeo,
|
|
23
23
|
syncMemberSelection,
|
|
24
24
|
toSourceRows
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-RDUEXM2D.mjs";
|
|
26
|
+
import "./chunk-QHMLMF4L.mjs";
|
|
27
|
+
import "./chunk-PDVEIW42.mjs";
|
|
28
|
+
import "./chunk-VEVBXK44.mjs";
|
|
29
|
+
import "./chunk-NNLBFIWU.mjs";
|
|
30
30
|
import "./chunk-KQN5C7SZ.mjs";
|
|
31
31
|
import "./chunk-A2GMXZP7.mjs";
|
|
32
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-45VILLJT.mjs";
|
|
33
33
|
import "./chunk-GHODWOAL.mjs";
|
|
34
34
|
|
|
35
35
|
// src/react.tsx
|
|
@@ -69,6 +69,7 @@ function BicChart(props) {
|
|
|
69
69
|
viewState,
|
|
70
70
|
zoomMemory,
|
|
71
71
|
labelContrast,
|
|
72
|
+
restoreScroll,
|
|
72
73
|
onLabelContrast,
|
|
73
74
|
onSelectionGlyphs,
|
|
74
75
|
layoutPass,
|
|
@@ -112,6 +113,11 @@ function BicChart(props) {
|
|
|
112
113
|
};
|
|
113
114
|
const userSelectedRef = useRef(false);
|
|
114
115
|
const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
|
|
116
|
+
const zoomOn = zoomMemory != null;
|
|
117
|
+
const zoomRef = useRef(zoomMemory ?? null);
|
|
118
|
+
useLayoutEffect(() => {
|
|
119
|
+
zoomRef.current = zoomMemory ?? null;
|
|
120
|
+
});
|
|
115
121
|
const builtWithRef = useRef(null);
|
|
116
122
|
const onSelectRef = useRef(onSelect);
|
|
117
123
|
onSelectRef.current = onSelect;
|
|
@@ -154,9 +160,10 @@ function BicChart(props) {
|
|
|
154
160
|
d3,
|
|
155
161
|
geoKind,
|
|
156
162
|
viewState,
|
|
157
|
-
zoomMemory,
|
|
163
|
+
zoomMemory: zoomRef.current,
|
|
158
164
|
labelContrast,
|
|
159
165
|
layoutPass,
|
|
166
|
+
restoreScroll,
|
|
160
167
|
onLabelContrast: (r) => onLabelContrastRef.current?.(r),
|
|
161
168
|
onSelectionGlyphs: (r) => onSelectionGlyphsRef.current?.(r),
|
|
162
169
|
// A GETTER, not a wrapper: the host decides whether to
|
|
@@ -189,7 +196,7 @@ function BicChart(props) {
|
|
|
189
196
|
host.destroy();
|
|
190
197
|
hostRef.current = null;
|
|
191
198
|
};
|
|
192
|
-
}, [code, renderFn, d3, geoKind, labelContrast, layoutPass, controls]);
|
|
199
|
+
}, [code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll, zoomOn, controls]);
|
|
193
200
|
useLayoutEffect(() => {
|
|
194
201
|
const host = hostRef.current;
|
|
195
202
|
if (!host || !selects) return;
|
|
@@ -215,7 +222,7 @@ function BicChart(props) {
|
|
|
215
222
|
b.detach();
|
|
216
223
|
if (bindingRef.current === b) bindingRef.current = null;
|
|
217
224
|
};
|
|
218
|
-
}, [selects, code, renderFn, d3, geoKind, labelContrast, layoutPass]);
|
|
225
|
+
}, [selects, code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll, zoomOn]);
|
|
219
226
|
useEffect(() => {
|
|
220
227
|
if (!hostRef.current || !options) return;
|
|
221
228
|
if (builtWithRef.current?.optKey === optKey) return;
|
package/dist/testing.mjs
CHANGED
package/dist/types/contract.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare const CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
|
|
|
35
35
|
export declare const CONTAINER_SLOT_XF_CLEAR = "__llmXfClear";
|
|
36
36
|
export declare const CONTAINER_SLOT_INITIAL_XF_MARK = "__llmInitialXfMark";
|
|
37
37
|
export declare const CONTAINER_SLOT_UI_STATE = "__lchUiState";
|
|
38
|
+
export declare const CONTAINER_SLOT_SCROLL = "__lchScroll";
|
|
38
39
|
export declare const CONTAINER_SLOT_CLICKED_MARKS = "__lchClickedMarks";
|
|
39
40
|
export declare const VIEW_ONLY_UI_STATE_KEYS: readonly string[];
|
|
40
41
|
/** The bag with every view-only key removed: what a host persists when the reader has not asked
|
package/dist/types/fit.d.ts
CHANGED
|
@@ -90,3 +90,26 @@ export type AxisPinEdge = "top" | "bottom";
|
|
|
90
90
|
* one drawn over it.
|
|
91
91
|
*/
|
|
92
92
|
export declare function axisPinPlacement(band: AxisBand, scrollTop: number, viewportH: number): AxisPinEdge | null;
|
|
93
|
+
/** A reader's scroll offsets and the identity of the chart they were made on. */
|
|
94
|
+
export interface ScrollMemory {
|
|
95
|
+
key: unknown;
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
}
|
|
99
|
+
/** What the container reports once the overflow is decided. */
|
|
100
|
+
export interface ScrollMetrics {
|
|
101
|
+
scrollWidth: number;
|
|
102
|
+
scrollHeight: number;
|
|
103
|
+
clientWidth: number;
|
|
104
|
+
clientHeight: number;
|
|
105
|
+
overflowX: string;
|
|
106
|
+
overflowY: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Where the scroll goes after a redraw. The saved offsets when the SAME chart is back, clamped to what the new layout can scroll and
|
|
110
|
+
* applied per axis only where the container scrolls; otherwise the origin. A new chart opens at the top.
|
|
111
|
+
*/
|
|
112
|
+
export declare function planScrollRestore(saved: ScrollMemory | null | undefined, key: unknown, m: ScrollMetrics): {
|
|
113
|
+
x: number;
|
|
114
|
+
y: number;
|
|
115
|
+
};
|
package/dist/types/fitDom.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentExtent, MeasuredBox, AxisPinEdge } from "./fit";
|
|
1
|
+
import type { ContentExtent, MeasuredBox, AxisPinEdge, ScrollMemory } from "./fit";
|
|
2
2
|
export declare function ctmScaleOf(m: {
|
|
3
3
|
a: number;
|
|
4
4
|
b: number;
|
|
@@ -59,6 +59,16 @@ export interface FitRenderedChartOptions {
|
|
|
59
59
|
* Set false for a host that draws its own sticky header.
|
|
60
60
|
*/
|
|
61
61
|
pinAxis?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Put the reader's scroll back once the overflow is decided: `memory` (what `takeScroll` handed
|
|
64
|
+
* back) is restored only when its key is `key`, the chart now drawn, clamped to what the new
|
|
65
|
+
* layout can scroll and only on an axis that scrolls. Applied before the axis pin, which places
|
|
66
|
+
* its copy from the scroll it finds. Absent, the container is left where the render put it.
|
|
67
|
+
*/
|
|
68
|
+
restoreScroll?: {
|
|
69
|
+
memory: ScrollMemory | null;
|
|
70
|
+
key: unknown;
|
|
71
|
+
};
|
|
62
72
|
}
|
|
63
73
|
export interface FitRenderedChartResult {
|
|
64
74
|
/** The frame was grown, so content that was being clipped is now painted. */
|
|
@@ -73,6 +83,11 @@ export interface FitRenderedChartResult {
|
|
|
73
83
|
reading: FitReading | null;
|
|
74
84
|
/** The axis pin: what was pinned when the container scrolls, or why nothing was. */
|
|
75
85
|
axisPin: AxisPinReport;
|
|
86
|
+
/** Where `restoreScroll` put the container ({0,0} for a different chart), or null when it was not asked or the overflow was not applied. */
|
|
87
|
+
scrolledTo: {
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
} | null;
|
|
76
91
|
}
|
|
77
92
|
export interface AxisPinReport {
|
|
78
93
|
pinned: boolean;
|
|
@@ -94,5 +109,13 @@ export interface AxisPinReport {
|
|
|
94
109
|
* when there is none. Hosts that clear the container themselves still need this for the
|
|
95
110
|
* listener, which outlives the overlay. */
|
|
96
111
|
export declare function unpinScrolledAxis(container: HTMLElement | null | undefined): void;
|
|
112
|
+
/**
|
|
113
|
+
* Remember where the container is scrolled, BEFORE the container is cleared. A memory already waiting is kept: a second render that
|
|
114
|
+
* starts before the first one restored sees an emptied (unscrolled) container, and must not overwrite what the reader really did.
|
|
115
|
+
* A diagram that scrolls inside itself remembers its own position, so it is left alone.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rememberScroll(container: HTMLElement | null | undefined, key: unknown): void;
|
|
118
|
+
/** The waiting memory, removed. The one place it is consumed, so a stale memory can never outlive a render. */
|
|
119
|
+
export declare function takeScroll(container: HTMLElement | null | undefined): ScrollMemory | null;
|
|
97
120
|
export declare function pinScrolledAxis(container: HTMLElement | null | undefined, svg: SVGSVGElement | null | undefined): AxisPinReport;
|
|
98
121
|
export declare function fitRenderedChart(container: HTMLElement | null | undefined, opts?: FitRenderedChartOptions): FitRenderedChartResult;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -80,9 +80,10 @@ export interface ChartHostConfig {
|
|
|
80
80
|
/**
|
|
81
81
|
* REMEMBER THE READER'S ZOOM AND PAN on a chart that calls d3.zoom(), across every re-render (and across a
|
|
82
82
|
* close and reopen, if the host's store says so). Build it with createZoomMemoryHook() from
|
|
83
|
-
* "@bicharts/chart-host/zoom-memory", ONCE per
|
|
84
|
-
*
|
|
85
|
-
*
|
|
83
|
+
* "@bicharts/chart-host/zoom-memory", ONCE per chart, and hand the same hook to every host built for that chart
|
|
84
|
+
* (on the same element or, after a remount, a new one): a host that is destroyed releases the hook's watcher, so
|
|
85
|
+
* the hook can go to the next. Absent (the default), d3.zoom is never wrapped and nothing is listened for. It
|
|
86
|
+
* applies to a chart compiled from `code` only: a precompiled `renderFn` closed over its own d3.
|
|
86
87
|
*/
|
|
87
88
|
zoomMemory?: ZoomMemoryHook | null;
|
|
88
89
|
/** D3 v7 instance handed to the compiled code. Default: (globalThis as any).d3. */
|
|
@@ -181,6 +182,15 @@ export interface ChartHostConfig {
|
|
|
181
182
|
* reading without touching the chart (`grow: false`, `applyOverflow: false`).
|
|
182
183
|
*/
|
|
183
184
|
fit?: boolean | FitRenderedChartOptions;
|
|
185
|
+
/**
|
|
186
|
+
* KEEP THE READER'S SCROLL when the SAME chart is drawn again - a resize, a filter, a theme
|
|
187
|
+
* switch, or a new host on the same element. Default true: the offset is parked on the
|
|
188
|
+
* container before each clear and put back by the fit, clamped to what the new layout can
|
|
189
|
+
* scroll; a different chart (other code, or a renderFn with other source) opens at the top. Pass `false`
|
|
190
|
+
* for a page that manages the container's scrolling itself: nothing is remembered or restored,
|
|
191
|
+
* and a memory an earlier host left on the element is discarded at the next render.
|
|
192
|
+
*/
|
|
193
|
+
restoreScroll?: boolean;
|
|
184
194
|
/** The fit result after each render - what was grown, and how the chart measured. */
|
|
185
195
|
onFit?: (result: FitRenderedChartResult) => void;
|
|
186
196
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, LIFT_SELECTED_CLASS, CONTROL_CLASS, NO_ROW_MARKS_ATTR, SELECTION_RULE_ATTR, SELECTION_RULE_CLICKED_MARKS, MARK_KEY_ATTR, CONTAINER_SLOT_CLICKED_MARKS, SNAPSHOT_SVG_ATTR, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, DIM_OPACITY_MIN, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, ANIMATION_OPTION_KEYS, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, FLIP_INTERVAL_MIN_MS, FLIP_INTERVAL_MAX_MS, FLIP_DIRECTION_DEFAULT, APPROXIMATE_POSITIONS_DEFAULT, VALUE_AXIS_BASELINE_DEFAULT, SEASONAL_MARKERS_DEFAULT, MAX_MAP_POINTS_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type FlipDirection, type ApproximatePositions, type ValueAxisBaseline, type SeasonalMarkers, type ColorScaleScope, type RenderOptions, type ViewStateProvider, type ChartControlChange, type XfilterRefreshDetail, } from "./contract";
|
|
1
|
+
export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, LIFT_SELECTED_CLASS, CONTROL_CLASS, NO_ROW_MARKS_ATTR, SELECTION_RULE_ATTR, SELECTION_RULE_CLICKED_MARKS, MARK_KEY_ATTR, CONTAINER_SLOT_CLICKED_MARKS, SNAPSHOT_SVG_ATTR, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, CONTAINER_SLOT_SCROLL, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, DIM_OPACITY_MIN, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, ANIMATION_OPTION_KEYS, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, FLIP_INTERVAL_MIN_MS, FLIP_INTERVAL_MAX_MS, FLIP_DIRECTION_DEFAULT, APPROXIMATE_POSITIONS_DEFAULT, VALUE_AXIS_BASELINE_DEFAULT, SEASONAL_MARKERS_DEFAULT, MAX_MAP_POINTS_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type FlipDirection, type ApproximatePositions, type ValueAxisBaseline, type SeasonalMarkers, type ColorScaleScope, type RenderOptions, type ViewStateProvider, type ChartControlChange, type XfilterRefreshDetail, } from "./contract";
|
|
2
2
|
export { resolveOptions, type ResolveOptionsInput } from "./defaults";
|
|
3
3
|
export { OPTIONS_VOCABULARY, LIVE_OPTION_KEYS, MAX_MAP_POINTS_MIN, MAX_MAP_POINTS_MAX, LIMIT_COUNT_MAX, wireLimitCount, codeReadsOption, type OptionVocabularyEntry, type OptionTiming, type OptionWire, type OptionKey, } from "./optionsVocabulary";
|
|
4
4
|
export { loadGeo, geoFromCache, registerGeo, registerGeoAsset, geoAssetFor, clearGeoCache, type GeoAssetName, } from "./geoLazy";
|
|
@@ -42,6 +42,8 @@ export { SCROLL_SLACK_PX, MAX_FRAME_GROW_FACTOR, PHANTOM_FRACTION, FIT_CONTENT_S
|
|
|
42
42
|
export { fitRenderedChart, fitReadingFor, measureContainerBoxes, svgInkReach, ctmScaleOf, type FitReading, type InkReach, type FitRenderedChartOptions, type FitRenderedChartResult, } from "./fitDom";
|
|
43
43
|
export { AXIS_PIN_MIN_LABELS, AXIS_PIN_BAND_PAD_PX, AXIS_PIN_MAX_BAND_FRACTION, AXIS_PIN_TRACK_REACH_PX, isHorizontalLabelRow, labelBand, planAxisPin, axisPinPlacement, type LabelRowBox, type AxisBand, type AxisPinCandidate, type AxisPinPlan, type AxisPinEdge, } from "./fit";
|
|
44
44
|
export { pinScrolledAxis, unpinScrolledAxis, type AxisPinReport } from "./fitDom";
|
|
45
|
+
export { planScrollRestore, type ScrollMemory, type ScrollMetrics } from "./fit";
|
|
46
|
+
export { rememberScroll, takeScroll } from "./fitDom";
|
|
45
47
|
export { MIN_DELTA_E, pickDistinctColorFromSeed, buildPalette, buildPaletteFromSeed, orderMostDistinct, LEGIBLE_LIGHTNESS, type PaletteResult, type SeedForSlot, type PaletteOptions, } from "./palette";
|
|
46
48
|
export { deltaEHex } from "./deltaE";
|
|
47
49
|
export { resolveAutoColorScale, autoRampMinStep, autoRampCanvas, AUTO_RAMP_CLASSES, AUTO_RAMP_DRAWN_OPACITY, AUTO_RAMP_MIN_STEP_DELTA_E, type AutoColorScale, type AutoColorScaleInput } from "./autoRamp";
|
package/dist/types/react.d.ts
CHANGED
|
@@ -35,8 +35,11 @@ export interface BicChartProps {
|
|
|
35
35
|
/**
|
|
36
36
|
* REMEMBER THE READER'S ZOOM AND PAN on a chart that calls d3.zoom(), across re-renders and, if
|
|
37
37
|
* the hook's store keeps it, across a reload. Build it with createZoomMemoryHook() from
|
|
38
|
-
* "@bicharts/chart-host/zoom-memory"
|
|
39
|
-
* keep the view. Absent, nothing is remembered.
|
|
38
|
+
* "@bicharts/chart-host/zoom-memory" over one ZoomMemoryHost per chart (module scope, useRef or
|
|
39
|
+
* useMemo) with somewhere to keep the view. Absent (null or undefined), nothing is remembered. Host config:
|
|
40
|
+
* a hook that arrives after mount, or goes away, rebuilds the host; a change from one hook to another does
|
|
41
|
+
* not, and applies at the next rebuild (a new `code`, say), or at once if the component is remounted with a
|
|
42
|
+
* `key`. So a hook built inline in the props is harmless. See createChartHost's `zoomMemory`.
|
|
40
43
|
*/
|
|
41
44
|
zoomMemory?: ChartHostConfig["zoomMemory"];
|
|
42
45
|
/**
|
|
@@ -46,6 +49,12 @@ export interface BicChartProps {
|
|
|
46
49
|
* themed page is not white, and readability is measured against it. See createChartHost.
|
|
47
50
|
*/
|
|
48
51
|
labelContrast?: ChartHostConfig["labelContrast"];
|
|
52
|
+
/**
|
|
53
|
+
* KEEP THE READER'S SCROLL when the same chart repaints (new data, new options, a rebuild).
|
|
54
|
+
* Default on; a different chart opens at the top. Pass `false` for a page that manages the
|
|
55
|
+
* chart's scrolling itself. Host config: changing it rebuilds the host. See createChartHost.
|
|
56
|
+
*/
|
|
57
|
+
restoreScroll?: ChartHostConfig["restoreScroll"];
|
|
49
58
|
/** What the label-contrast pass did after each render - counts, for a page that logs. */
|
|
50
59
|
onLabelContrast?: ChartHostConfig["onLabelContrast"];
|
|
51
60
|
/** What the selection-glyph pass did after each paint, when `options.selectionGlyph` is set - counts, for a page
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { type ZoomState } from "./zoomMemory";
|
|
2
2
|
/**
|
|
3
3
|
* WHAT A HOST SUPPLIES: where the remembered view lives, and whether it may outlive the session.
|
|
4
|
-
* One per chart
|
|
5
|
-
* and re-created on the same element
|
|
4
|
+
* One per chart, kept for as long as the chart is on the page, so it outlives a host that is destroyed
|
|
5
|
+
* and re-created (on the same element for a redraw on every resize, say, or on a new one after a remount).
|
|
6
6
|
*/
|
|
7
7
|
export interface ZoomMemoryHost {
|
|
8
8
|
/** The in-session tier, always on: survives every re-render, never written anywhere. */
|
|
9
9
|
liveZoomState: ZoomState | null;
|
|
10
10
|
/** The key the watcher stamps on what it records (the latest render's). */
|
|
11
11
|
zoomWatchKey: string;
|
|
12
|
-
/** Whether the watcher's listeners are on zoomContainer (they attach once
|
|
12
|
+
/** Whether the watcher's listeners are on zoomContainer (they attach once per element, and come off when the
|
|
13
|
+
* host that drew into it is destroyed). */
|
|
13
14
|
zoomWatchAttached: boolean;
|
|
14
15
|
/** The pending settle timer, 0 when none. */
|
|
15
16
|
zoomSettleTimer: number;
|
|
16
17
|
/** The element the chart draws into; it outlives each render, so listeners attach to it once. Null until
|
|
17
|
-
* known: createZoomMemoryHook's restore adopts the render's container then
|
|
18
|
+
* known: createZoomMemoryHook's restore adopts the render's container then, moves to a new one when a
|
|
19
|
+
* render arrives on a different element (a remount), and gives it back when the host is destroyed. An element
|
|
20
|
+
* set here beforehand is the page's choice: the hook watches it and neither replaces nor gives it back. */
|
|
18
21
|
zoomContainer: Element | null;
|
|
19
22
|
/** Is the persisted tier on NOW? Read on every restore and every write, so a change applies to the next. */
|
|
20
23
|
rememberAcrossSessions(): boolean;
|
|
@@ -68,7 +71,13 @@ export interface ZoomMemoryHook {
|
|
|
68
71
|
flush(): void;
|
|
69
72
|
/** forgetZoom: drop the session view and a pending gesture. */
|
|
70
73
|
forget(): void;
|
|
74
|
+
/** Let go of the watcher on `container`, the element a destroyed host drew into (after flush, which records a
|
|
75
|
+
* gesture still settling), so the next host handed this hook attaches afresh and a hook that is replaced leaves
|
|
76
|
+
* nothing on the element. Without `container`, wherever the watcher is. A watcher that has since moved to another
|
|
77
|
+
* element is left alone. Optional: a hook written by hand without it works as before. */
|
|
78
|
+
release?(container?: Element): void;
|
|
71
79
|
}
|
|
72
|
-
/** Bind the zoom memory to one host object. Build it once per
|
|
73
|
-
* host created
|
|
80
|
+
/** Bind the zoom memory to one host object. Build it once per chart and hand the same hook to every
|
|
81
|
+
* host created for it, so the session tier outlives each one - on the same element, or on a new one
|
|
82
|
+
* after a remount, where the watcher moves with it. A host that is destroyed releases the watcher. */
|
|
74
83
|
export declare function createZoomMemoryHook(host: ZoomMemoryHost): ZoomMemoryHook;
|
package/dist/zoom-memory.mjs
CHANGED
|
@@ -192,15 +192,34 @@ function attachZoomWatcher(host, key) {
|
|
|
192
192
|
captureCurrentZoom(host);
|
|
193
193
|
}, SETTLE_MS);
|
|
194
194
|
};
|
|
195
|
-
for (const evt of
|
|
195
|
+
for (const evt of WATCHED_EVENTS) {
|
|
196
196
|
try {
|
|
197
197
|
ctr.addEventListener(evt, settle, true);
|
|
198
198
|
} catch {
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
attachedWatchers.set(host, { ctr, settle });
|
|
201
202
|
host.zoomWatchAttached = true;
|
|
202
203
|
host.log("zoom-watcher-attached", {});
|
|
203
204
|
}
|
|
205
|
+
var WATCHED_EVENTS = ["wheel", "pointerup", "pointercancel", "click", "dblclick", "keyup"];
|
|
206
|
+
var attachedWatchers = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
var adoptedContainers = /* @__PURE__ */ new WeakMap();
|
|
208
|
+
function detachZoomWatcher(host) {
|
|
209
|
+
const w = attachedWatchers.get(host);
|
|
210
|
+
if (w) {
|
|
211
|
+
for (const evt of WATCHED_EVENTS) {
|
|
212
|
+
try {
|
|
213
|
+
w.ctr.removeEventListener(evt, w.settle, true);
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
attachedWatchers.delete(host);
|
|
218
|
+
}
|
|
219
|
+
if (host.zoomSettleTimer) clearTimeout(host.zoomSettleTimer);
|
|
220
|
+
host.zoomSettleTimer = 0;
|
|
221
|
+
host.zoomWatchAttached = false;
|
|
222
|
+
}
|
|
204
223
|
function captureCurrentZoom(host) {
|
|
205
224
|
try {
|
|
206
225
|
const key = host.zoomWatchKey;
|
|
@@ -242,17 +261,32 @@ function forgetZoom(host) {
|
|
|
242
261
|
host.liveZoomState = null;
|
|
243
262
|
}
|
|
244
263
|
function createZoomMemoryHook(host) {
|
|
245
|
-
|
|
264
|
+
const hook = {
|
|
246
265
|
keyFor: (code, width, height) => zoomStateKey(B(String(code ?? "")), width, height),
|
|
247
266
|
capture: (d3) => captureZoomBehaviors(d3, host),
|
|
248
267
|
restore: (container, capture, key, d3) => {
|
|
249
|
-
|
|
268
|
+
const mine = adoptedContainers.get(host);
|
|
269
|
+
if (host.zoomContainer && host.zoomContainer === mine && mine !== container) {
|
|
270
|
+
detachZoomWatcher(host);
|
|
271
|
+
host.zoomContainer = null;
|
|
272
|
+
}
|
|
273
|
+
if (!host.zoomContainer) {
|
|
274
|
+
host.zoomContainer = container;
|
|
275
|
+
adoptedContainers.set(host, container);
|
|
276
|
+
}
|
|
250
277
|
restoreZoomForRender(d3, host, capture, key);
|
|
251
278
|
attachZoomWatcher(host, key);
|
|
252
279
|
},
|
|
253
280
|
flush: () => flushPendingZoom(host),
|
|
254
|
-
forget: () => forgetZoom(host)
|
|
281
|
+
forget: () => forgetZoom(host),
|
|
282
|
+
release: (container) => {
|
|
283
|
+
if (container && attachedWatchers.get(host)?.ctr !== container) return;
|
|
284
|
+
detachZoomWatcher(host);
|
|
285
|
+
if (host.zoomContainer && host.zoomContainer === adoptedContainers.get(host)) host.zoomContainer = null;
|
|
286
|
+
adoptedContainers.delete(host);
|
|
287
|
+
}
|
|
255
288
|
};
|
|
289
|
+
return hook;
|
|
256
290
|
}
|
|
257
291
|
export {
|
|
258
292
|
attachZoomWatcher,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.91",
|
|
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",
|