@perspective-dev/viewer-charts 4.5.2 → 5.0.0

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.
Files changed (61) hide show
  1. package/dist/cdn/perspective-viewer-charts.js +2 -2
  2. package/dist/cdn/perspective-viewer-charts.js.map +3 -3
  3. package/dist/esm/charts/candlestick/candlestick.d.ts +8 -10
  4. package/dist/esm/charts/chart-base.d.ts +58 -4
  5. package/dist/esm/charts/chart.d.ts +45 -9
  6. package/dist/esm/charts/series/glyphs/draw-areas.d.ts +3 -2
  7. package/dist/esm/charts/series/glyphs/draw-bars.d.ts +8 -1
  8. package/dist/esm/charts/series/glyphs/draw-lines.d.ts +3 -2
  9. package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +5 -2
  10. package/dist/esm/charts/series/series-build.d.ts +11 -0
  11. package/dist/esm/charts/series/series-interact.d.ts +10 -0
  12. package/dist/esm/charts/series/series-render.d.ts +17 -1
  13. package/dist/esm/charts/series/series.d.ts +53 -10
  14. package/dist/esm/data/view-reader.d.ts +10 -1
  15. package/dist/esm/event-detail.d.ts +7 -0
  16. package/dist/esm/layout/ticks.d.ts +5 -0
  17. package/dist/esm/perspective-viewer-charts.js +2 -2
  18. package/dist/esm/perspective-viewer-charts.js.map +3 -3
  19. package/dist/esm/plugin/charts.d.ts +6 -3
  20. package/dist/esm/plugin/plugin.d.ts +31 -2
  21. package/dist/esm/render/scheduler.d.ts +11 -8
  22. package/dist/esm/transport/protocol.d.ts +31 -2
  23. package/dist/esm/transport/renderer-transport.d.ts +74 -3
  24. package/dist/esm/webgl/instanced-attrs.d.ts +7 -1
  25. package/dist/esm/worker/renderer.worker.d.ts +12 -0
  26. package/package.json +1 -1
  27. package/src/ts/charts/candlestick/candlestick-render.ts +4 -1
  28. package/src/ts/charts/candlestick/candlestick.ts +12 -17
  29. package/src/ts/charts/cartesian/cartesian-render.ts +80 -63
  30. package/src/ts/charts/chart-base.ts +85 -5
  31. package/src/ts/charts/chart.ts +47 -9
  32. package/src/ts/charts/common/category-axis-resolver.ts +5 -1
  33. package/src/ts/charts/common/tree-data.ts +61 -16
  34. package/src/ts/charts/heatmap/heatmap-render.ts +6 -2
  35. package/src/ts/charts/series/glyphs/draw-areas.ts +10 -1
  36. package/src/ts/charts/series/glyphs/draw-bars.ts +30 -9
  37. package/src/ts/charts/series/glyphs/draw-lines.ts +10 -1
  38. package/src/ts/charts/series/glyphs/draw-scatter.ts +56 -3
  39. package/src/ts/charts/series/series-build.ts +21 -3
  40. package/src/ts/charts/series/series-interact.ts +86 -13
  41. package/src/ts/charts/series/series-render.ts +660 -17
  42. package/src/ts/charts/series/series.ts +96 -26
  43. package/src/ts/charts/sunburst/sunburst-interact.ts +2 -3
  44. package/src/ts/charts/sunburst/sunburst-render.ts +3 -1
  45. package/src/ts/charts/treemap/treemap-interact.ts +2 -5
  46. package/src/ts/charts/treemap/treemap-render.ts +3 -1
  47. package/src/ts/charts/treemap/treemap.ts +6 -1
  48. package/src/ts/data/view-reader.ts +22 -2
  49. package/src/ts/event-detail.ts +8 -0
  50. package/src/ts/index.ts +13 -7
  51. package/src/ts/interaction/raw-event-forwarder.ts +7 -0
  52. package/src/ts/interaction/zoom-controller.ts +8 -0
  53. package/src/ts/layout/ticks.ts +9 -0
  54. package/src/ts/plugin/charts.ts +20 -4
  55. package/src/ts/plugin/plugin.ts +86 -16
  56. package/src/ts/render/scheduler.ts +39 -13
  57. package/src/ts/transport/protocol.ts +33 -0
  58. package/src/ts/transport/renderer-transport.ts +185 -9
  59. package/src/ts/webgl/instanced-attrs.ts +15 -1
  60. package/src/ts/worker/dispatch.ts +4 -1
  61. package/src/ts/worker/renderer.worker.ts +32 -3
@@ -52,21 +52,19 @@ export declare class CandlestickChart extends CategoricalYChart {
52
52
  max: number;
53
53
  };
54
54
  /**
55
- * `domain_mode: "expand"` accumulators. Hold the running union of
56
- * the value-axis (and, in numeric-category mode, category-axis)
57
- * extent across data loads. Cleared in `resetExpandedDomain` —
58
- * wired from the worker's `resetAllZooms` and from view-config
59
- * mutations on `AbstractChart`. `null` whenever the option is
60
- * `"fit"` or the accumulator has just been cleared.
55
+ * `domain_mode: "expand"` accumulator — the VALUE (Y) axis only.
56
+ * The category axis has NO accumulator on band charts (see
57
+ * `SeriesChart._expandedLeftDomain`): a numeric/datetime `group_by`
58
+ * axis always fits the current data. Cleared in
59
+ * `resetExpandedDomain` — wired from the worker's `resetAllZooms`
60
+ * and from view-config mutations on `AbstractChart`. `null`
61
+ * whenever the option is `"fit"` or the accumulator has just been
62
+ * cleared.
61
63
  */
62
64
  _expandedYDomain: {
63
65
  min: number;
64
66
  max: number;
65
67
  } | null;
66
- _expandedCategoryDomain: {
67
- min: number;
68
- max: number;
69
- } | null;
70
68
  /**
71
69
  * Numeric category-axis state (single non-string group_by).
72
70
  */
@@ -57,6 +57,22 @@ export declare abstract class AbstractChart implements ChartImplementation {
57
57
  _glManager: WebGLContextManager | null;
58
58
  _gridlineCanvas: HTMLCanvasElement | OffscreenCanvas | null;
59
59
  _chromeCanvas: HTMLCanvasElement | OffscreenCanvas | null;
60
+ /**
61
+ * 2D-canvas draw closures collected during `_fullRender` (the GL
62
+ * pass) and flushed by {@link _flush2D} *after* the scheduler's
63
+ * `awaitGpuFence`. Deferring the gridline/chrome draws past the GPU
64
+ * fence stops the 2D placeholder canvases from pushing a resized
65
+ * frame to the compositor a frame ahead of the GL present — the
66
+ * visible GL/2D misalignment on resize.
67
+ *
68
+ * Renderers wrap their 2D drawing in {@link _defer2D}; the closures
69
+ * capture the exact per-frame locals (layout, ticks, theme) so no
70
+ * state needs to be recomputed or replayed from cache at flush time.
71
+ * Reset at the start of every GL pass (see {@link requestRender} /
72
+ * {@link renderFrameSync}) so a `_fullRender` that throws mid-way
73
+ * can't leak stale closures into the next frame.
74
+ */
75
+ _deferred2D: Array<() => void>;
60
76
  /**
61
77
  * Host-supplied CSS-variable map. The host snapshots its DOM via
62
78
  * `snapshotThemeVars(el)` and ships it over the control channel;
@@ -299,6 +315,17 @@ export declare abstract class AbstractChart implements ChartImplementation {
299
315
  * (pivot / filter / sort changes can all reshuffle rows).
300
316
  */
301
317
  setView(view: View): void;
318
+ /**
319
+ * Silently dismiss any pinned tooltip (the chart's selection visual)
320
+ * WITHOUT emitting `perspective-global-filter` — unlike the `setView`
321
+ * implicit dismiss above, the HOST initiated this (its global filter
322
+ * bar removed the clause this selection contributed), so an unselect
323
+ * emit would double-mutate the host's filter set.
324
+ * `TooltipController.dismiss()` posts the host-side pin teardown but
325
+ * never fires `onUnpin` (that emit belongs to `dispatchClick`'s
326
+ * click-to-unpin gesture), so this is emit-free by construction.
327
+ */
328
+ deselect(): void;
302
329
  /**
303
330
  * Build the chart-specific {@link TooltipCallbacks} object — the
304
331
  * `onHover` / `onLeave` / `onClickPre` / `onPin` / `onDblClick`
@@ -382,16 +409,43 @@ export declare abstract class AbstractChart implements ChartImplementation {
382
409
  * scheduler so concurrent calls collapse to one `_fullRender` per
383
410
  * RAF and the host blitter receives one bitmap per frame. The
384
411
  * returned promise resolves after this chart's `awaitGpuFence` +
385
- * `endFrame` chain — independent of other charts in the same
386
- * RAF, which run their fence waits in parallel.
412
+ * `render2D` + `endFrame` chain — independent of other charts in the
413
+ * same RAF, which run their fence waits in parallel.
414
+ *
415
+ * `_fullRender` submits the GL commands and stashes the frame's 2D
416
+ * draws (gridlines + chrome) via {@link _defer2D}; the scheduler
417
+ * flushes them with {@link _flush2D} *after* the GPU fence, so the
418
+ * 2D canvases don't present ahead of the GL frame. The GL pass
419
+ * resets `_deferred2D` first so a mid-pass throw can't leak stale
420
+ * closures into the next frame.
387
421
  *
388
422
  * Every render-triggering caller — upload chunks, zoom / pan,
389
423
  * resize, theme invalidation, host-driven redraws — calls this.
390
- * The only sanctioned bypass is `snapshotPng`, which calls
391
- * `_fullRender` directly to keep the GL backbuffer intact for
424
+ * The only sanctioned bypass is `snapshotPng`, which uses
425
+ * {@link renderFrameSync} to keep the GL backbuffer intact for
392
426
  * `gl.readPixels`.
393
427
  */
394
428
  requestRender(glManager: WebGLContextManager): Promise<void>;
429
+ /**
430
+ * Queue a 2D-canvas draw (gridline or chrome) to run after the
431
+ * GPU fence. Called from the chart renderers in place of drawing to
432
+ * the gridline/chrome canvases inline during the GL pass. See
433
+ * {@link _deferred2D}.
434
+ */
435
+ _defer2D(fn: () => void): void;
436
+ /**
437
+ * Run and clear the queued 2D-canvas draws. Invoked by the scheduler
438
+ * after `awaitGpuFence` (Phase 2), and by {@link renderFrameSync}.
439
+ */
440
+ _flush2D(): void;
441
+ /**
442
+ * Synchronous full frame (GL + 2D) for the `snapshotPng` bypass,
443
+ * which sits outside the scheduler and needs the gridline/chrome
444
+ * canvases painted before it composites them. Resets the deferred
445
+ * queue, runs the GL pass, then flushes the 2D draws immediately
446
+ * (no fence split — a snapshot isn't presented to the compositor).
447
+ */
448
+ renderFrameSync(glManager: WebGLContextManager): void;
395
449
  destroy(): void;
396
450
  abstract uploadAndRender(glManager: WebGLContextManager, columns: ColumnDataMap, startRow: number, endRow: number): Promise<void>;
397
451
  abstract _fullRender(glManager: WebGLContextManager): void;
@@ -24,12 +24,21 @@ export interface ChartImplementation {
24
24
  */
25
25
  requestRender(glManager: WebGLContextManager): Promise<void>;
26
26
  /**
27
- * The chart-specific frame builder. The scheduler wraps this with
28
- * fence + `endFrame`; callers must not invoke it directly except
29
- * for `snapshotPng`, which needs an intact GL backbuffer for
30
- * `gl.readPixels` and so must skip the `endFrame` pair.
27
+ * The chart-specific GL frame builder. Submits the GL draw commands
28
+ * and stashes the frame's 2D-canvas draws (gridlines + chrome) for
29
+ * the scheduler to flush after `awaitGpuFence`. The scheduler wraps
30
+ * this with fence + 2D flush + `endFrame`; callers must not invoke
31
+ * it directly — `snapshotPng` uses {@link renderFrameSync} instead.
31
32
  */
32
33
  _fullRender(glManager: WebGLContextManager): void;
34
+ /**
35
+ * Synchronous full frame (GL + 2D) for the `snapshotPng` bypass,
36
+ * which sits outside the scheduler and needs the gridline/chrome
37
+ * canvases painted before compositing. Runs the GL pass then flushes
38
+ * the deferred 2D draws immediately, skipping the fence split and
39
+ * `endFrame` so the GL backbuffer stays intact for `gl.readPixels`.
40
+ */
41
+ renderFrameSync(glManager: WebGLContextManager): void;
33
42
  /**
34
43
  * Hand the current View to the chart so it can make on-demand
35
44
  * per-row queries (for lazy tooltip column lookups). Called on
@@ -137,6 +146,14 @@ export interface ChartImplementation {
137
146
  * hook internally.
138
147
  */
139
148
  resetExpandedDomain?(): void;
149
+ /**
150
+ * Silently clear any active selection state (pinned tooltip) WITHOUT
151
+ * emitting selection events. Driven from the host's `deselect`
152
+ * message — its global filter bar removed a clause this chart's
153
+ * selection contributed, so an unselect emit would double-mutate the
154
+ * host's filter set.
155
+ */
156
+ deselect?(): void;
140
157
  destroy(): void;
141
158
  }
142
159
  export interface FacetConfig {
@@ -193,8 +210,19 @@ export interface PluginConfig {
193
210
  * Faceting strategy when `split_by` is non-empty.
194
211
  *
195
212
  * - `"grid"` — one small-multiple sub-plot per split group.
196
- * - `"overlay"` — single plot with split groups differentiated by
197
- * color. Synced into `_facetConfig.facet_mode`.
213
+ * - `"overlay"` — a single plot: cartesian charts differentiate
214
+ * split groups by color; the categorical band pipeline (series
215
+ * charts) stacks splits within each aggregate's band slot.
216
+ * Synced into `_facetConfig.facet_mode`.
217
+ *
218
+ * The default differs by family via
219
+ * `ChartTypeConfig.plugin_field_defaults`: cartesian / density /
220
+ * map chart types default to `"grid"`, the series / financial
221
+ * band-pipeline types to `"overlay"` (their historical split
222
+ * rendering). Series charts REBUILD on a mode change — grid mode
223
+ * keys the stack ladder per split (`facetSplits` in
224
+ * `buildSeriesPipeline`) so each facet grows from its own
225
+ * baseline.
198
226
  */
199
227
  facet_mode: "grid" | "overlay";
200
228
  /**
@@ -226,15 +254,23 @@ export interface PluginConfig {
226
254
  /**
227
255
  * Domain accumulation policy across successive `View` updates.
228
256
  *
229
- * - `"fit"` — every update recomputes the rendered domain (and on
230
- * cartesian charts, the X/Y range and color/size scales) from
257
+ * - `"fit"` — every update recomputes the affected domains from
231
258
  * the current data extent. Can grow or shrink frame-to-frame.
232
- * - `"expand"` — the rendered domain monotonically *grows*: each
259
+ * - `"expand"` — the affected domains monotonically *grow*: each
233
260
  * update unions the new data extent with the previously rendered
234
261
  * extent, so once a value is in scope it stays in scope. Reset
235
262
  * by the "Reset Zoom" button, view-config changes (group_by /
236
263
  * split_by / column-slot / column-type), or toggling back to
237
264
  * `"fit"`.
265
+ *
266
+ * AXIS SCOPE differs by family: cartesian charts (X/Y Scatter,
267
+ * X/Y Line, Density, Maps) apply it to BOTH axes plus the
268
+ * color/size scales (categorical string axes opt out — slot
269
+ * indices are frame-local); the categorical band pipeline (series
270
+ * / financial) applies it to the VALUE axis only — Y for the
271
+ * Y-family, X for X Bar — while the category axis always fits, so
272
+ * a streaming numeric/datetime `group_by` axis releases departed
273
+ * categories instead of pinning to its history.
238
274
  */
239
275
  domain_mode: "fit" | "expand";
240
276
  /**
@@ -22,9 +22,10 @@ export declare class AreaGlyph {
22
22
  rebuildBuffers(chart: SeriesChart, glManager: WebGLContextManager): void;
23
23
  /**
24
24
  * Bind persistent strip buffers and dispatch one TRIANGLE_STRIP per
25
- * series-run. Skips hidden series.
25
+ * series-run. Skips hidden series. `splitFilter` (faceted frames)
26
+ * draws only the series whose `splitIdx` matches.
26
27
  */
27
- draw(chart: SeriesChart, gl: GL, _glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array, opacity: number): void;
28
+ draw(chart: SeriesChart, gl: GL, _glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array, opacity: number, splitFilter?: number): void;
28
29
  destroy(chart: SeriesChart): void;
29
30
  }
30
31
  export {};
@@ -10,6 +10,13 @@ type GL = WebGL2RenderingContext | WebGLRenderingContext;
10
10
  /**
11
11
  * Draw the bar-typed subset of `chart._bars` as instanced quads. Assumes
12
12
  * the caller has already `useProgram`'d the bar shader and set uniforms.
13
+ *
14
+ * `range` draws a contiguous instance sub-range instead of the full
15
+ * uploaded set — the faceted renderer uploads instances split-major
16
+ * (see `uploadBarInstances`) and dispatches one range per facet.
13
17
  */
14
- export declare function drawBars(chart: SeriesChart, gl: GL, glManager: WebGLContextManager): void;
18
+ export declare function drawBars(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, range?: {
19
+ start: number;
20
+ count: number;
21
+ }): void;
15
22
  export {};
@@ -33,9 +33,10 @@ export declare class LineGlyph {
33
33
  * Bind the persistent vertex buffers and dispatch one instanced draw
34
34
  * per series. Skips hidden series via `_hiddenSeries`. Gap /
35
35
  * transparency rendering is governed by `u_interp_alpha`, set per
36
- * series.
36
+ * series. `splitFilter` (faceted frames) draws only the series
37
+ * whose `splitIdx` matches — one call per facet.
37
38
  */
38
- draw(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array): void;
39
+ draw(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array, splitFilter?: number): void;
39
40
  destroy(chart: SeriesChart): void;
40
41
  }
41
42
  export {};
@@ -25,9 +25,12 @@ export declare class ScatterGlyph {
25
25
  rebuildBuffers(chart: SeriesChart, glManager: WebGLContextManager): void;
26
26
  /**
27
27
  * Bind the persistent left/right buffers and issue up to two draw
28
- * calls. No per-frame allocations or buffer uploads.
28
+ * calls. No per-frame allocations or buffer uploads. `splitFilter`
29
+ * (faceted frames) instead draws each matching series' contiguous
30
+ * bucket sub-range — one `drawArrays(POINTS, first, count)` per
31
+ * series of the facet.
29
32
  */
30
- draw(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array): void;
33
+ draw(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array, splitFilter?: number): void;
31
34
  destroy(chart: SeriesChart): void;
32
35
  }
33
36
  export {};
@@ -168,6 +168,17 @@ export interface SeriesPipelineInput {
168
168
  * variation. Maps directly to `PluginConfig.include_zero`.
169
169
  */
170
170
  includeZero: boolean;
171
+ /**
172
+ * `facet_mode: "grid"` geometry: key the stack ladder by
173
+ * `(catIdx, aggIdx, splitIdx)` instead of `(catIdx, aggIdx)`, so
174
+ * splits never stack on each other — each facet's bars / areas
175
+ * grow from their own zero baseline. Slot geometry (`xCenter` /
176
+ * `halfWidth`) is unchanged: facets separate splits by projection
177
+ * + scissor, not by X offset, so every split's records share the
178
+ * same band-slot coordinates. Value extents still accumulate
179
+ * across ALL splits — the facet grid renders a shared value axis.
180
+ */
181
+ facetSplits?: boolean;
171
182
  /**
172
183
  * Reusable scratch — pipeline writes records into these in place
173
184
  * and zero-fills the stack ladder. Pass the previous build's
@@ -11,11 +11,21 @@ export declare function getHoveredBar(chart: SeriesChart): SeriesChartRecord | n
11
11
  * Handle mouse-move across all glyph types. Tests (in reverse paint order
12
12
  * so top glyphs win): scatter points → line points → bars → areas.
13
13
  * Updates `_hoveredBarIdx` or `_hoveredSample` and re-renders on change.
14
+ *
15
+ * Faceted frames first resolve the cell under the cursor: that cell's
16
+ * layout supplies the pixel→data mapping and its index becomes the
17
+ * `splitFilter` — records of other splits share the same band-slot
18
+ * coordinates by construction, so an unfiltered hit-test would match
19
+ * a bar from a different facet.
14
20
  */
15
21
  export declare function handleBarHover(chart: SeriesChart, mx: number, my: number): void;
16
22
  /**
17
23
  * Handle a click on the legend area. Returns true when the click hit a
18
24
  * legend entry (the caller should then treat the event as consumed).
25
+ *
26
+ * An entry may own several series (facet-grid mode groups the legend
27
+ * by aggregate): the toggle is all-or-nothing — any visible member
28
+ * hides the whole group, a fully-hidden group shows every member.
19
29
  */
20
30
  export declare function handleBarLegendClick(chart: SeriesChart, mx: number, my: number): boolean;
21
31
  /**
@@ -1,7 +1,14 @@
1
1
  import type { WebGLContextManager } from "../../webgl/context-manager";
2
2
  import { type SeriesChart } from "./series";
3
+ import { PlotLayout } from "../../layout/plot-layout";
3
4
  /**
4
5
  * Upload bar instance buffers from the columnar `_bars` storage.
6
+ *
7
+ * Overlay mode emits instances in `_bars` order. Facet-grid mode
8
+ * (`_facetActive`) emits them split-major via a counting sort so each
9
+ * facet's instances form a contiguous range — recorded on
10
+ * `chart._facetBarRanges` and drawn per facet with an instance-offset
11
+ * `drawBars` call.
5
12
  */
6
13
  export declare function uploadBarInstances(chart: SeriesChart, glManager: WebGLContextManager): void;
7
14
  /**
@@ -28,11 +35,20 @@ export declare function rebuildGlyphBuffers(chart: SeriesChart, glManager: WebGL
28
35
  * Full-frame render: gridlines → WebGL bars (instanced) → chrome overlay.
29
36
  */
30
37
  export declare function renderBarFrame(chart: SeriesChart, glManager: WebGLContextManager): void;
38
+ /**
39
+ * Resolve the `PlotLayout` a record renders in: the record's facet
40
+ * cell in faceted frames, the single plot layout otherwise. Tooltip /
41
+ * pin positioning must map through this so faceted anchors land in the
42
+ * record's own cell.
43
+ */
44
+ export declare function layoutForRecord(chart: SeriesChart, b: {
45
+ splitIdx: number;
46
+ }): PlotLayout | null;
31
47
  /**
32
48
  * Draw axes chrome + legend + tooltip onto the overlay canvas.
33
49
  */
34
50
  export declare function renderBarChromeOverlay(chart: SeriesChart): void;
35
- export declare function rightAxisDataToPixel(chart: SeriesChart, x: number, y: number): {
51
+ export declare function rightAxisDataToPixel(chart: SeriesChart, x: number, y: number, layoutOverride?: PlotLayout): {
36
52
  px: number;
37
53
  py: number;
38
54
  };
@@ -1,6 +1,7 @@
1
1
  import type { ColumnDataMap } from "../../data/view-reader";
2
2
  import type { WebGLContextManager } from "../../webgl/context-manager";
3
3
  import type { ZoomConfig } from "../../interaction/zoom-controller";
4
+ import type { FacetGrid } from "../../layout/facet-grid";
4
5
  import { CategoricalYChart } from "../common/categorical-y-chart";
5
6
  import { type PlotRect } from "../../layout/plot-layout";
6
7
  import { type AxisDomain } from "../../axis/numeric-axis";
@@ -68,6 +69,35 @@ export declare class SeriesChart extends CategoricalYChart {
68
69
  _aggregates: string[];
69
70
  _splitPrefixes: string[];
70
71
  _series: SeriesInfo[];
72
+ /**
73
+ * Whether the CURRENT build's geometry was produced for facet-grid
74
+ * mode (`facet_mode: "grid"` with real splits). The render / hover
75
+ * paths branch on this — never on the live `_facetConfig` — so a
76
+ * `setPluginConfig` that flips `facet_mode` between a build and a
77
+ * redraw can't pair grid-mode rendering with overlay-built stack
78
+ * geometry (or vice versa). The host always follows a
79
+ * `plugin.restore` with an `update` → `loadAndRender`, so the flag
80
+ * catches up one build later.
81
+ */
82
+ _facetActive: boolean;
83
+ /**
84
+ * Facet grid of the most recent faceted frame — one cell per
85
+ * split group. `null` in overlay / no-split frames. Read by the
86
+ * chrome overlay, hover hit-test, and tooltip pin to resolve the
87
+ * per-facet `PlotLayout`.
88
+ */
89
+ _facetGrid: FacetGrid | null;
90
+ /**
91
+ * Per-split contiguous instance ranges in the uploaded bar
92
+ * buffers, indexed by `splitIdx`. Populated by
93
+ * `uploadBarInstances` when `_facetActive` (instances are emitted
94
+ * split-major); `null` in overlay mode. Each facet's draw binds
95
+ * the instance attributes at `start` and draws `count` instances.
96
+ */
97
+ _facetBarRanges: {
98
+ start: number;
99
+ count: number;
100
+ }[] | null;
71
101
  /**
72
102
  * Columnar bar/area record storage. Indexed by bar slot in
73
103
  * `[0, _bars.count)`. Replaces the legacy `SeriesChartRecord[]` to
@@ -148,8 +178,12 @@ export declare class SeriesChart extends CategoricalYChart {
148
178
  _hasRightAxis: boolean;
149
179
  /**
150
180
  * `domain_mode: "expand"` accumulators. Hold the running union of
151
- * every prior build's value-axis (and, in numeric-category mode,
152
- * category-axis) extent for as long as the option is active.
181
+ * every prior build's VALUE-axis extents (primary + alt) for as
182
+ * long as the option is active. The category axis deliberately has
183
+ * NO accumulator: `domain_mode` scopes to the value axis only on
184
+ * band charts (Y for the Y-family, X for X Bar) — a numeric or
185
+ * datetime `group_by` axis always fits the current data, so a
186
+ * streaming chart's time axis releases departed categories.
153
187
  * Cleared in `resetExpandedDomain` — wired from the worker's
154
188
  * `resetAllZooms` and from view-config mutations on the base
155
189
  * class. `null` whenever the option is `"fit"` or the accumulator
@@ -163,10 +197,6 @@ export declare class SeriesChart extends CategoricalYChart {
163
197
  min: number;
164
198
  max: number;
165
199
  } | null;
166
- _expandedCategoryDomain: {
167
- min: number;
168
- max: number;
169
- } | null;
170
200
  /**
171
201
  * Numeric category-axis state. Populated only when `group_by` has
172
202
  * exactly one level and that level is `date | datetime | integer |
@@ -233,8 +263,14 @@ export declare class SeriesChart extends CategoricalYChart {
233
263
  * capacity is reused across data reloads / legend toggles.
234
264
  */
235
265
  _visibleBarIndices: Int32Array;
266
+ /**
267
+ * Hit-test rects for legend entries. Overlay mode: one entry per
268
+ * series (`seriesIds.length === 1`). Facet-grid mode: one entry
269
+ * per AGGREGATE, carrying every split's seriesId — a legend
270
+ * toggle hides/shows the aggregate across all facets at once.
271
+ */
236
272
  _legendRects: {
237
- seriesId: number;
273
+ seriesIds: number[];
238
274
  rect: PlotRect;
239
275
  }[];
240
276
  /**
@@ -318,9 +354,16 @@ export declare class SeriesChart extends CategoricalYChart {
318
354
  /**
319
355
  * Resolve the per-series palette and stamp it onto `_series[i].color`.
320
356
  * Cached on `_paletteCache` keyed by reference identity of the theme
321
- * inputs + series count — only `restyle()` (which clears `_paletteCache`
322
- * via `invalidateTheme`) or a data load (which clears it explicitly)
323
- * forces re-resolution.
357
+ * inputs + resolved color count — only `restyle()` (which clears
358
+ * `_paletteCache` via `invalidateTheme`) or a data load (which clears
359
+ * it explicitly) forces re-resolution.
360
+ *
361
+ * Facet-grid mode colors by AGGREGATE, not by (agg × split) series:
362
+ * the facet is the axis of splitting, so aggregate `k` renders the
363
+ * same color in every facet and the legend lists aggregates once.
364
+ * Overlay mode keeps the per-series palette (splits are distinguished
365
+ * by color there). `_facetActive` only changes at build time, which
366
+ * clears the cache, so the stamp mode can't go stale.
324
367
  *
325
368
  * Returns true when the cache changed (caller invalidates color upload).
326
369
  */
@@ -31,5 +31,14 @@ export interface TypedArrayWindowOptions {
31
31
  * `Promise`, the underlying `with_typed_arrays` call awaits it before
32
32
  * releasing the backing Arrow buffer. Callers must not retain any
33
33
  * `ColumnData` reference past `render`'s resolution.
34
+ *
35
+ * `render` also receives the DELIVERED row count — the shortest
36
+ * column's length, which may be less than the requested `end_row`:
37
+ * the engine clamps the window to the view's size at fetch time, and
38
+ * on a fast-updating table with deletions that size can shrink after
39
+ * the caller's `num_rows()` round-trip. Build pipelines must iterate
40
+ * to THIS count, never the requested one — a typed array indexed past
41
+ * its length returns `undefined`, which no amount of validity-bitmap
42
+ * checking catches (the bitmap read is out-of-bounds too).
34
43
  */
35
- export declare function viewToColumnDataMap(view: View, render: (data: ColumnDataMap) => void | Promise<void>, options?: TypedArrayWindowOptions): Promise<void>;
44
+ export declare function viewToColumnDataMap(view: View, render: (data: ColumnDataMap, numRows: number) => void | Promise<void>, options?: TypedArrayWindowOptions): Promise<void>;
@@ -24,5 +24,12 @@ export interface PerspectiveClickDetail {
24
24
  * `[<col>, "==", <value>]` clauses.
25
25
  */
26
26
  config: Partial<ViewConfig>;
27
+ /**
28
+ * The source panel's `slot` name in a multi-panel `<perspective-viewer>`,
29
+ * so the host can attribute the click to the originating panel. `undefined`
30
+ * for a lone, unslotted viewer. Mirrors `PerspectiveSelectDetail.panel` and
31
+ * the datagrid `PerspectiveClickDetail`.
32
+ */
33
+ panel?: string;
27
34
  }
28
35
  export { PerspectiveSelectDetail } from "@perspective-dev/viewer/src/ts/extensions.js";
@@ -8,6 +8,11 @@ export declare function computeNiceTicks(min: number, max: number, targetCount:
8
8
  /**
9
9
  * Format a numeric tick value for display.
10
10
  * Uses K/M/B suffixes for large numbers, fixed decimals for small.
11
+ *
12
+ * Total over any input: label formatters run inside render passes, so a
13
+ * non-finite value (or `undefined` smuggled in by an upstream
14
+ * out-of-bounds read) must degrade to a placeholder, never throw the
15
+ * frame away.
11
16
  */
12
17
  export declare function formatTickValue(val: number): string;
13
18
  /**