@perspective-dev/viewer-charts 4.5.2 → 5.1.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.
- package/dist/cdn/perspective-viewer-charts.js +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/charts/candlestick/candlestick.d.ts +8 -10
- package/dist/esm/charts/chart-base.d.ts +58 -4
- package/dist/esm/charts/chart.d.ts +45 -9
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +8 -2
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +8 -1
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +8 -2
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +8 -2
- package/dist/esm/charts/series/series-build.d.ts +11 -0
- package/dist/esm/charts/series/series-interact.d.ts +13 -2
- package/dist/esm/charts/series/series-render.d.ts +17 -1
- package/dist/esm/charts/series/series.d.ts +104 -10
- package/dist/esm/data/view-reader.d.ts +10 -1
- package/dist/esm/event-detail.d.ts +7 -0
- package/dist/esm/layout/ticks.d.ts +5 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/plugin/charts.d.ts +6 -3
- package/dist/esm/plugin/plugin.d.ts +40 -2
- package/dist/esm/render/scheduler.d.ts +11 -8
- package/dist/esm/transport/protocol.d.ts +39 -2
- package/dist/esm/transport/renderer-transport.d.ts +75 -3
- package/dist/esm/webgl/instanced-attrs.d.ts +7 -1
- package/dist/esm/worker/renderer.worker.d.ts +12 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-render.ts +4 -1
- package/src/ts/charts/candlestick/candlestick.ts +12 -17
- package/src/ts/charts/cartesian/cartesian-render.ts +80 -63
- package/src/ts/charts/chart-base.ts +85 -5
- package/src/ts/charts/chart.ts +47 -9
- package/src/ts/charts/common/category-axis-resolver.ts +5 -1
- package/src/ts/charts/common/tree-data.ts +61 -16
- package/src/ts/charts/heatmap/heatmap-render.ts +6 -2
- package/src/ts/charts/series/glyphs/draw-areas.ts +21 -1
- package/src/ts/charts/series/glyphs/draw-bars.ts +30 -9
- package/src/ts/charts/series/glyphs/draw-lines.ts +21 -1
- package/src/ts/charts/series/glyphs/draw-scatter.ts +68 -3
- package/src/ts/charts/series/series-build.ts +21 -3
- package/src/ts/charts/series/series-interact.ts +210 -78
- package/src/ts/charts/series/series-render.ts +779 -49
- package/src/ts/charts/series/series.ts +209 -26
- package/src/ts/charts/sunburst/sunburst-interact.ts +2 -3
- package/src/ts/charts/sunburst/sunburst-render.ts +3 -1
- package/src/ts/charts/treemap/treemap-interact.ts +2 -5
- package/src/ts/charts/treemap/treemap-render.ts +3 -1
- package/src/ts/charts/treemap/treemap.ts +6 -1
- package/src/ts/data/view-reader.ts +22 -2
- package/src/ts/event-detail.ts +8 -0
- package/src/ts/index.ts +13 -7
- package/src/ts/interaction/raw-event-forwarder.ts +7 -0
- package/src/ts/interaction/zoom-controller.ts +8 -0
- package/src/ts/layout/ticks.ts +9 -0
- package/src/ts/plugin/charts.ts +20 -4
- package/src/ts/plugin/plugin.ts +99 -17
- package/src/ts/render/scheduler.ts +39 -13
- package/src/ts/transport/protocol.ts +42 -0
- package/src/ts/transport/renderer-transport.ts +198 -9
- package/src/ts/webgl/instanced-attrs.ts +15 -1
- package/src/ts/worker/dispatch.ts +4 -1
- package/src/ts/worker/renderer.worker.ts +35 -3
package/src/ts/plugin/charts.ts
CHANGED
|
@@ -48,9 +48,12 @@ export interface ChartTypeConfig {
|
|
|
48
48
|
* Per-chart-type overrides for `DEFAULT_PLUGIN_CONFIG`. Used when a
|
|
49
49
|
* field's sensible default differs by chart family — currently
|
|
50
50
|
* `include_zero` (true for Y Bar / Y Area / X Bar, false for line
|
|
51
|
-
* / scatter / cartesian / financial)
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* / scatter / cartesian / financial) and `facet_mode` ("overlay"
|
|
52
|
+
* for the band-pipeline families — series / financial — whose
|
|
53
|
+
* historical split_by rendering is the single stacked/colored
|
|
54
|
+
* plot; "grid" elsewhere). Applied at schema generation and at
|
|
55
|
+
* `restore({})` so the effective default matches the surfaced UI
|
|
56
|
+
* default.
|
|
54
57
|
*/
|
|
55
58
|
plugin_field_defaults?: Partial<PluginConfig>;
|
|
56
59
|
}
|
|
@@ -85,10 +88,19 @@ const SERIES_FIELDS: readonly PluginConfigField[] = [
|
|
|
85
88
|
"bar_inner_pad",
|
|
86
89
|
];
|
|
87
90
|
|
|
91
|
+
// The band pipeline's historical split_by rendering is a single plot
|
|
92
|
+
// (splits stacked / colored in place), so the series family defaults
|
|
93
|
+
// `facet_mode` to "overlay" — grid faceting is the opt-in. Cartesian
|
|
94
|
+
// charts keep the global "grid" default from `DEFAULT_PLUGIN_CONFIG`.
|
|
95
|
+
const SERIES_DEFAULTS: Partial<PluginConfig> = { facet_mode: "overlay" };
|
|
96
|
+
|
|
88
97
|
// Bar / area series glyphs grow from the zero baseline, so the value
|
|
89
98
|
// axis must enclose 0 to render correctly. Line / scatter glyphs have
|
|
90
99
|
// no such constraint — their default `include_zero` stays `false`.
|
|
91
|
-
const ZERO_ANCHORED_DEFAULTS: Partial<PluginConfig> = {
|
|
100
|
+
const ZERO_ANCHORED_DEFAULTS: Partial<PluginConfig> = {
|
|
101
|
+
...SERIES_DEFAULTS,
|
|
102
|
+
include_zero: true,
|
|
103
|
+
};
|
|
92
104
|
|
|
93
105
|
// Pure Cartesian (X/Y Scatter, X/Y Line) — no categorical axis, so no
|
|
94
106
|
// band geometry; gets the facet-routing variant of zoom_mode.
|
|
@@ -210,9 +222,11 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
210
222
|
}),
|
|
211
223
|
make("Y Line", "y-line", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
212
224
|
default_chart_type: "line",
|
|
225
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
213
226
|
}),
|
|
214
227
|
make("Y Scatter", "y-scatter", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
215
228
|
default_chart_type: "scatter",
|
|
229
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
216
230
|
}),
|
|
217
231
|
make("Y Area", "y-area", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
218
232
|
default_chart_type: "area",
|
|
@@ -250,9 +264,11 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
250
264
|
make("Heatmap", "heatmap", HIER, SELECT, 1, ["Color"], HEATMAP_FIELDS),
|
|
251
265
|
make("Candlestick", "candlestick", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
252
266
|
default_chart_type: "candlestick",
|
|
267
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
253
268
|
}),
|
|
254
269
|
make("OHLC", "ohlc", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
255
270
|
default_chart_type: "ohlc",
|
|
271
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
256
272
|
}),
|
|
257
273
|
make(
|
|
258
274
|
"Map Scatter",
|
package/src/ts/plugin/plugin.ts
CHANGED
|
@@ -154,7 +154,6 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
154
154
|
private _renderer: RendererTransport | null = null;
|
|
155
155
|
private _rendererPromise: Promise<RendererTransport> | null = null;
|
|
156
156
|
private _rawEventForwarder: RawEventForwarder | null = null;
|
|
157
|
-
private _generation = 0;
|
|
158
157
|
private _resetClickAbort: AbortController | null = null;
|
|
159
158
|
|
|
160
159
|
/**
|
|
@@ -172,6 +171,16 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
172
171
|
*/
|
|
173
172
|
private _pluginConfigStore: PluginConfig | null = null;
|
|
174
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Per-column config (`alt_axis`, `chart_type`, formats, …), held on
|
|
176
|
+
* the element for the same reason as `_pluginConfigStore`: the host
|
|
177
|
+
* calls `restore()` BEFORE the first draw builds the renderer, so
|
|
178
|
+
* forwarding only to a live renderer silently drops the initial
|
|
179
|
+
* `columns_config` — `_buildRenderer` ships it in the `InitMsg`
|
|
180
|
+
* instead.
|
|
181
|
+
*/
|
|
182
|
+
private _columnsConfig: Record<string, any> = {};
|
|
183
|
+
|
|
175
184
|
private get _pluginConfig(): PluginConfig {
|
|
176
185
|
if (!this._pluginConfigStore) {
|
|
177
186
|
this._pluginConfigStore = this._effectiveDefaults();
|
|
@@ -285,8 +294,7 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
285
294
|
// is the dispose signal: if `this._rendererPromise` no longer
|
|
286
295
|
// points at *this* build when it resolves, the element was
|
|
287
296
|
// deleted (or a reconnect started a newer build) and we destroy
|
|
288
|
-
// the orphan instead of adopting it. Promise identity
|
|
289
|
-
// `_generation`, which every `draw`/`update` bumps — is the
|
|
297
|
+
// the orphan instead of adopting it. Promise identity is the
|
|
290
298
|
// right token: a rapid draw→update must NOT tear down the
|
|
291
299
|
// single in-flight build it shares via this memoized promise.
|
|
292
300
|
const p: Promise<RendererTransport> = this._buildRenderer(view).then(
|
|
@@ -353,11 +361,16 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
353
361
|
|
|
354
362
|
private async _buildRenderer(view: View): Promise<RendererTransport> {
|
|
355
363
|
const viewer = this.parentElement as HTMLPerspectiveViewerElement;
|
|
356
|
-
|
|
364
|
+
// This chart's own panel (its `slot`). Used to scope the client/table
|
|
365
|
+
// to THIS panel rather than the host's active/seed panel (the bare
|
|
366
|
+
// getClient()/getTable() resolve element-wide in a multi-panel viewer),
|
|
367
|
+
// and to tag the dispatched interaction events with their source panel.
|
|
368
|
+
const panel = this.getAttribute("slot") ?? undefined;
|
|
369
|
+
const client = await viewer.getClient({ panel });
|
|
357
370
|
const viewer_class = customElements.get("perspective-viewer");
|
|
358
371
|
const clientWasm = viewer_class.get_wasm_module();
|
|
359
372
|
const clientWorkerURL = viewer_class.get_worker_url();
|
|
360
|
-
const table = await viewer?.getTable?.();
|
|
373
|
+
const table = await viewer?.getTable?.({ panel });
|
|
361
374
|
const tableName: string | undefined = table
|
|
362
375
|
? await table.get_name()
|
|
363
376
|
: undefined;
|
|
@@ -370,6 +383,7 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
370
383
|
client,
|
|
371
384
|
view,
|
|
372
385
|
tableName,
|
|
386
|
+
panel,
|
|
373
387
|
clientWorkerURL,
|
|
374
388
|
clientWasm,
|
|
375
389
|
chartTag: this._chartType.tag,
|
|
@@ -392,6 +406,7 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
392
406
|
zoom_mode: this._pluginConfig.facet_zoom_mode,
|
|
393
407
|
},
|
|
394
408
|
pluginConfig: this._pluginConfig,
|
|
409
|
+
columnsConfig: this._columnsConfig,
|
|
395
410
|
defaultChartType: this._chartType.default_chart_type,
|
|
396
411
|
renderBlitMode: BLIT_MODE,
|
|
397
412
|
});
|
|
@@ -553,8 +568,16 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
553
568
|
return this._drawImpl(view);
|
|
554
569
|
}
|
|
555
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Shared body of `draw` / `update`. No re-entrancy guard: the host
|
|
573
|
+
* serializes every rendering call (`draw`, `update`, `render`,
|
|
574
|
+
* `resize`) and `delete` on its per-renderer draw lock, so this
|
|
575
|
+
* method never overlaps itself or a teardown. The one exception —
|
|
576
|
+
* an external DOM disconnect `delete()`ing the element mid-draw —
|
|
577
|
+
* is absorbed by `RendererTransport`, whose post-`destroy()`
|
|
578
|
+
* requests settle immediately instead of pending forever.
|
|
579
|
+
*/
|
|
556
580
|
private async _drawImpl(view: View): Promise<void> {
|
|
557
|
-
const gen = ++this._generation;
|
|
558
581
|
let renderer: RendererTransport;
|
|
559
582
|
try {
|
|
560
583
|
renderer = await this._ensureRenderer(view);
|
|
@@ -564,19 +587,17 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
564
587
|
return;
|
|
565
588
|
}
|
|
566
589
|
|
|
567
|
-
if (this._generation !== gen) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
590
|
renderer.setView(view);
|
|
572
591
|
renderer.setBufferMaxCapacity(this._chartType.max_cells);
|
|
592
|
+
const panel = this.getAttribute("slot") ?? undefined;
|
|
573
593
|
const viewer = this
|
|
574
594
|
.parentElement as HTMLPerspectiveViewerElement | null;
|
|
575
|
-
|
|
576
|
-
if (
|
|
595
|
+
|
|
596
|
+
if (viewer === null) {
|
|
577
597
|
return;
|
|
578
598
|
}
|
|
579
599
|
|
|
600
|
+
const viewerConfig = await viewer.getViewConfig({ panel });
|
|
580
601
|
await renderer.loadAndRender({
|
|
581
602
|
viewerConfig: {
|
|
582
603
|
group_by: viewerConfig?.group_by ?? [],
|
|
@@ -588,17 +609,78 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
588
609
|
}
|
|
589
610
|
|
|
590
611
|
async clear(): Promise<void> {
|
|
591
|
-
this._generation++;
|
|
592
612
|
this._renderer?.clear();
|
|
593
613
|
}
|
|
594
614
|
|
|
595
615
|
async resize(): Promise<void> {
|
|
596
|
-
|
|
616
|
+
// Hidden (an unslotted tab-stack panel, a `display: none` host):
|
|
617
|
+
// the 0×0 rect a hidden element reports would resize the worker
|
|
618
|
+
// canvas to zero — CLEARING the retained frame, so the next
|
|
619
|
+
// activation repaints from blank (the two-stage tab-switch
|
|
620
|
+
// artifact). Skip instead and keep the last frame for an instant,
|
|
621
|
+
// single-blit reveal — mirroring the datagrid's guard. The host's
|
|
622
|
+
// activation nudge is also a `resize()`, so a hidden panel's nudge
|
|
623
|
+
// is free by the same check.
|
|
624
|
+
if (!this.isConnected || this.offsetParent == null) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// AWAITED to the resized frame's PRESENT (the worker's
|
|
629
|
+
// `resizeAck`) — the host's presize protocol style-overrides
|
|
630
|
+
// this element to its target box and holds the layout commit on
|
|
631
|
+
// this promise, so resolving at message-post would commit the
|
|
632
|
+
// settings-pane layout against the old-dimensions bitmap (the
|
|
633
|
+
// aspect-ratio warp the datagrid's synchronous resize never
|
|
634
|
+
// shows).
|
|
635
|
+
await this._renderer?.resize();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* OPTIONAL host presize protocol: render at the TARGET element box
|
|
640
|
+
* `(width, height)` — the box the host's pending layout commit will
|
|
641
|
+
* produce — holding the resulting frame offscreen, so nothing on
|
|
642
|
+
* screen changes during the round-trip. Resolves, once the resized
|
|
643
|
+
* frame is staged, to a present closure; the host calls it in the
|
|
644
|
+
* same task as the layout commit, landing geometry and pixels in
|
|
645
|
+
* one paint. Plugins without this method get the host's held
|
|
646
|
+
* style-override presize path instead.
|
|
647
|
+
*/
|
|
648
|
+
async presize(width: number, height: number): Promise<(() => void) | void> {
|
|
649
|
+
if (
|
|
650
|
+
!this.isConnected ||
|
|
651
|
+
this.offsetParent == null ||
|
|
652
|
+
!this._renderer ||
|
|
653
|
+
!this._glCanvas
|
|
654
|
+
) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Target GL-canvas box = its current box shifted by the element's
|
|
659
|
+
// box delta — the chrome between the element edge and the canvas
|
|
660
|
+
// is constant across a resize.
|
|
661
|
+
const hostRect = this.getBoundingClientRect();
|
|
662
|
+
const glRect = this._glCanvas.getBoundingClientRect();
|
|
663
|
+
return await this._renderer.presize(
|
|
664
|
+
Math.max(0, glRect.width + (width - hostRect.width)),
|
|
665
|
+
Math.max(0, glRect.height + (height - hostRect.height)),
|
|
666
|
+
);
|
|
597
667
|
}
|
|
598
668
|
|
|
599
669
|
restyle() {
|
|
600
670
|
this._renderer?.invalidateTheme();
|
|
601
|
-
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Clear any active selection state (pinned tooltip) WITHOUT emitting
|
|
675
|
+
* selection events — the host calls this when a global filter
|
|
676
|
+
* contributed by this panel's selection is removed from the global
|
|
677
|
+
* filter bar, so the pin can't outlive the filter it produced. The
|
|
678
|
+
* transport's `_lastInsertConfig` (remove-set memory) is deliberately
|
|
679
|
+
* RETAINED so a subsequent selection still replaces any leftover
|
|
680
|
+
* clauses rather than accumulating.
|
|
681
|
+
*/
|
|
682
|
+
async deselect(): Promise<void> {
|
|
683
|
+
this._renderer?.deselect();
|
|
602
684
|
}
|
|
603
685
|
|
|
604
686
|
async render(view: View): Promise<Blob> {
|
|
@@ -622,12 +704,12 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
622
704
|
...config,
|
|
623
705
|
};
|
|
624
706
|
|
|
707
|
+
this._columnsConfig = columns_config ?? {};
|
|
625
708
|
this._renderer?.setPluginConfig(this._pluginConfig);
|
|
626
|
-
this._renderer?.setColumnsConfig(
|
|
709
|
+
this._renderer?.setColumnsConfig(this._columnsConfig);
|
|
627
710
|
}
|
|
628
711
|
|
|
629
712
|
delete() {
|
|
630
|
-
this._generation++;
|
|
631
713
|
if (this._rawEventForwarder) {
|
|
632
714
|
this._rawEventForwarder.detach();
|
|
633
715
|
this._rawEventForwarder = null;
|
|
@@ -16,8 +16,8 @@ import type { WebGLContextManager } from "../webgl/context-manager";
|
|
|
16
16
|
* Module-level render scheduler. The single entry point for driving a
|
|
17
17
|
* chart frame. Every render-triggering caller — upload chunks, zoom /
|
|
18
18
|
* pan, resize, theme invalidation, host-driven redraws — calls
|
|
19
|
-
* `requestRender(glManager, fullRender)` and awaits the
|
|
20
|
-
* promise.
|
|
19
|
+
* `requestRender(glManager, fullRender, render2D)` and awaits the
|
|
20
|
+
* returned promise.
|
|
21
21
|
*
|
|
22
22
|
* ## Guarantees
|
|
23
23
|
*
|
|
@@ -51,9 +51,12 @@ import type { WebGLContextManager } from "../webgl/context-manager";
|
|
|
51
51
|
* begins, letting per-context GPU work overlap.
|
|
52
52
|
*
|
|
53
53
|
* - **Phase 2 (parallel):** `Promise.all(snapshot.map(present))`
|
|
54
|
-
* where `present` does `await awaitGpuFence();
|
|
55
|
-
* resolve waiters`.
|
|
56
|
-
*
|
|
54
|
+
* where `present` does `await awaitGpuFence(); render2D();
|
|
55
|
+
* endFrame(); resolve waiters`. The `render2D()` step draws the 2D
|
|
56
|
+
* layers (gridlines + chrome) only after the GPU fence, so the 2D
|
|
57
|
+
* placeholder canvases don't present a frame ahead of the GL frame
|
|
58
|
+
* on resize. Each entry's waiters resolve as soon as its own
|
|
59
|
+
* present completes — independent of other entries.
|
|
57
60
|
*
|
|
58
61
|
* ## Failure modes
|
|
59
62
|
*
|
|
@@ -78,6 +81,15 @@ import type { WebGLContextManager } from "../webgl/context-manager";
|
|
|
78
81
|
interface Entry {
|
|
79
82
|
glManager: WebGLContextManager;
|
|
80
83
|
fullRender: () => void;
|
|
84
|
+
/**
|
|
85
|
+
* The chart's 2D-canvas draws (gridlines + chrome), split out of
|
|
86
|
+
* `fullRender` so they run *after* `awaitGpuFence` — see
|
|
87
|
+
* {@link present}. Deferring the 2D work past the GPU fence keeps
|
|
88
|
+
* the 2D placeholder canvases from pushing a resized frame to the
|
|
89
|
+
* compositor a frame ahead of the GL present (the GL/2D "distortion"
|
|
90
|
+
* on resize). Populated as captured closures during `fullRender`.
|
|
91
|
+
*/
|
|
92
|
+
render2D: () => void;
|
|
81
93
|
waiters: PromiseWithResolvers<void>[];
|
|
82
94
|
}
|
|
83
95
|
|
|
@@ -105,25 +117,30 @@ const inFlight = new Set<WebGLContextManager>();
|
|
|
105
117
|
const deferred = new Map<WebGLContextManager, (() => void)[]>();
|
|
106
118
|
|
|
107
119
|
/**
|
|
108
|
-
* Request a coalesced render of `glManager`
|
|
109
|
-
*
|
|
110
|
-
* Phase 2
|
|
120
|
+
* Request a coalesced render of `glManager`. `fullRender` submits the
|
|
121
|
+
* GL commands (Phase 1, synchronous); `render2D` draws the 2D layers
|
|
122
|
+
* (gridlines + chrome) and runs in Phase 2 *after* `awaitGpuFence`, so
|
|
123
|
+
* the 2D canvases don't present ahead of the GL frame. Returns a
|
|
124
|
+
* promise that resolves when this entry's Phase 2 (`awaitGpuFence` +
|
|
125
|
+
* `render2D` + `endFrame`) completes.
|
|
111
126
|
*
|
|
112
127
|
* If a request is already pending for the same glManager, the new
|
|
113
|
-
* call's `fullRender`
|
|
114
|
-
* wins; closures read chart state lazily so this is
|
|
115
|
-
* no-op, but keeps the
|
|
116
|
-
* resolves alongside the existing waiters.
|
|
128
|
+
* call's `fullRender` / `render2D` closures replace the prior ones
|
|
129
|
+
* (latest call wins; closures read chart state lazily so this is
|
|
130
|
+
* functionally a no-op, but keeps the closures fresh) and the returned
|
|
131
|
+
* promise resolves alongside the existing waiters.
|
|
117
132
|
*/
|
|
118
133
|
export function requestRender(
|
|
119
134
|
glManager: WebGLContextManager,
|
|
120
135
|
fullRender: () => void,
|
|
136
|
+
render2D: () => void,
|
|
121
137
|
): Promise<void> {
|
|
122
138
|
let entry = pending.get(glManager);
|
|
123
139
|
if (entry) {
|
|
124
140
|
entry.fullRender = fullRender;
|
|
141
|
+
entry.render2D = render2D;
|
|
125
142
|
} else {
|
|
126
|
-
entry = { glManager, fullRender, waiters: [] };
|
|
143
|
+
entry = { glManager, fullRender, render2D, waiters: [] };
|
|
127
144
|
pending.set(glManager, entry);
|
|
128
145
|
}
|
|
129
146
|
|
|
@@ -354,6 +371,15 @@ async function present(entry: Entry): Promise<void> {
|
|
|
354
371
|
|
|
355
372
|
try {
|
|
356
373
|
await entry.glManager.awaitGpuFence();
|
|
374
|
+
// Draw the 2D layers (gridlines + chrome) only now that the GL
|
|
375
|
+
// frame's GPU work has completed. Issuing the 2D-canvas commands
|
|
376
|
+
// here — rather than inline in `fullRender` (Phase 1) — keeps the
|
|
377
|
+
// 2D placeholder canvases from pushing their resized frame to the
|
|
378
|
+
// compositor during the fence-wait yields, a frame ahead of the
|
|
379
|
+
// GL present. That early push is the visible GL/2D misalignment
|
|
380
|
+
// on resize. Failures fall through to the same present-failed
|
|
381
|
+
// path as the fence.
|
|
382
|
+
entry.render2D();
|
|
357
383
|
entry.glManager.endFrame();
|
|
358
384
|
for (const w of entry.waiters) {
|
|
359
385
|
w.resolve();
|
|
@@ -31,6 +31,7 @@ export type ControlMsg =
|
|
|
31
31
|
| SetBufferMaxCapacityMsg
|
|
32
32
|
| LoadAndRenderMsg
|
|
33
33
|
| RedrawMsg
|
|
34
|
+
| DeselectMsg
|
|
34
35
|
| ResizeMsg
|
|
35
36
|
| ClearMsg
|
|
36
37
|
| InvalidateThemeMsg
|
|
@@ -53,6 +54,7 @@ export type WorkerMsg =
|
|
|
53
54
|
| UserClickMsg
|
|
54
55
|
| UserSelectMsg
|
|
55
56
|
| LoadAndRenderAckMsg
|
|
57
|
+
| ResizeAckMsg
|
|
56
58
|
| FrameBitmapMsg
|
|
57
59
|
| ErrorMsg;
|
|
58
60
|
|
|
@@ -160,6 +162,15 @@ export interface InitMsg {
|
|
|
160
162
|
* a `setPluginConfig` control msg.
|
|
161
163
|
*/
|
|
162
164
|
pluginConfig: PluginConfig;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Initial per-column config (`alt_axis`, `chart_type`, formats, …).
|
|
168
|
+
* Seeds the chart impl before the first `loadAndRender` — the host
|
|
169
|
+
* calls `plugin.restore` before the renderer exists, so without
|
|
170
|
+
* this the initial `columns_config` never reaches the worker.
|
|
171
|
+
* Later changes arrive as `setColumnsConfig` control msgs.
|
|
172
|
+
*/
|
|
173
|
+
columnsConfig?: Record<string, any>;
|
|
163
174
|
defaultChartType?: string;
|
|
164
175
|
|
|
165
176
|
/**
|
|
@@ -300,13 +311,44 @@ export interface RedrawMsg {
|
|
|
300
311
|
kind: "redraw";
|
|
301
312
|
}
|
|
302
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Host → worker: silently clear the chart's selection state (pinned
|
|
316
|
+
* tooltip) — no `userSelect` reply, so the host filter-bar removal that
|
|
317
|
+
* triggered it can't echo back into the host's filter set. The worker's
|
|
318
|
+
* `TooltipController.dismiss()` posts `dismissTooltip` to tear down the
|
|
319
|
+
* host-side pinned artifact.
|
|
320
|
+
*/
|
|
321
|
+
export interface DeselectMsg {
|
|
322
|
+
kind: "deselect";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Host → worker: resize the chart to a new CSS box. Replied with
|
|
327
|
+
* {@link ResizeAckMsg} AFTER the resized frame PRESENTS — the host's
|
|
328
|
+
* `plugin.resize()` promise is the viewer's presize contract ("the
|
|
329
|
+
* plugin has repainted at the target box when this resolves"), so an
|
|
330
|
+
* ack at message-receipt would let the settings-pane layout commit
|
|
331
|
+
* against the old-dimensions bitmap (the aspect-ratio warp).
|
|
332
|
+
*/
|
|
303
333
|
export interface ResizeMsg {
|
|
304
334
|
kind: "resize";
|
|
335
|
+
msgId: number;
|
|
305
336
|
cssWidth: number;
|
|
306
337
|
cssHeight: number;
|
|
307
338
|
dpr: number;
|
|
308
339
|
}
|
|
309
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Worker → host reply to a `ResizeMsg`, posted after the resized
|
|
343
|
+
* frame's present completes. Always sent — present failures and
|
|
344
|
+
* torn-down charts included — so the host's awaited promise resolves
|
|
345
|
+
* deterministically.
|
|
346
|
+
*/
|
|
347
|
+
export interface ResizeAckMsg {
|
|
348
|
+
kind: "resizeAck";
|
|
349
|
+
msgId: number;
|
|
350
|
+
}
|
|
351
|
+
|
|
310
352
|
export interface ClearMsg {
|
|
311
353
|
kind: "clear";
|
|
312
354
|
}
|