Inspector: live data updates under interactive selections

This commit is contained in:
2026-06-29 09:58:32 +03:00
parent 6656811b8e
commit 8aa05e503d
13 changed files with 269 additions and 21 deletions
+21 -1
View File
@@ -211,6 +211,13 @@ export function LivePreview() {
// not `chartReady` — consecutive successful renders keep `chartReady` true, but
// each one is new data the inspector must pick up.
const [renderEpoch, setRenderEpoch] = useState(0);
// Bumped (debounced, inside the handle) when an interactive selection changes
// the inspected data without a re-render — the live data inspector (spec §04;
// multi-view scope doc M5). Kept separate from `renderEpoch` so a brush pulse
// re-reads the table without re-subscribing the listener; their sum is the
// inspector's single refresh trigger (each event bumps exactly one, so the sum
// is strictly monotonic — no collisions).
const [liveEpoch, setLiveEpoch] = useState(0);
// Busy-indication timer ref: if a render exceeds ~1s we surface a non-blocking
// overlay (arch §10.2 NN/g: >1s owes a busy indication; <1s shows nothing to
@@ -397,6 +404,19 @@ export function LivePreview() {
// on `renderEpoch`, so this need not depend on it (it always reads the latest handle).
const getInspectData = useCallback(() => handleRef.current?.inspectData() ?? null, []);
// Live data inspection (spec §04): while the inspector is open, re-read the table
// when an interactive selection changes the data it shows (a filtering brush). The
// handle owns the Vega listeners + debounce; we just bump `liveEpoch` on each fire.
// Re-subscribes whenever a render settles (`renderEpoch`) so it tracks the current
// handle, and only while the inspector is open so a collapsed one costs nothing.
// handleRef is a ref (read, not a dep); the cleanup unsubscribes.
useEffect(() => {
if (!inspectorOpen) return;
const handle = handleRef.current;
if (!handle) return;
return handle.onDataChange(() => setLiveEpoch((e) => e + 1));
}, [inspectorOpen, renderEpoch]);
// Re-fit the chart when its container resizes (e.g. a pane drag). Vega doesn't
// observe the element, so we do: one observer on the stable host node for the
// component's life. Only responsive fit modes depend on container size;
@@ -478,7 +498,7 @@ export function LivePreview() {
<DataInspector
id="preview-data-inspector"
getData={getInspectData}
renderEpoch={renderEpoch}
renderEpoch={renderEpoch + liveEpoch}
heightPx={inspectorOpen ? inspectorHeight : undefined}
/>
</div>