@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
|
@@ -84,7 +84,11 @@ interface RendererHandle {
|
|
|
84
84
|
terminate(): void;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
type PendingRenderType =
|
|
87
|
+
type PendingRenderType =
|
|
88
|
+
| "saveZoom"
|
|
89
|
+
| "loadAndRender"
|
|
90
|
+
| "snapshotPng"
|
|
91
|
+
| "resize";
|
|
88
92
|
interface PendingRenderRequest {
|
|
89
93
|
kind: PendingRenderType;
|
|
90
94
|
resolve: (v: any) => void;
|
|
@@ -110,11 +114,22 @@ interface PendingRenderRequest {
|
|
|
110
114
|
*/
|
|
111
115
|
export class RendererTransport {
|
|
112
116
|
private _handle: RendererHandle | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Set by {@link destroy}. Requests allocated after teardown settle
|
|
120
|
+
* immediately in `_allocPending` — `_post` is already a no-op once
|
|
121
|
+
* `_handle` is null, so a late request would otherwise never
|
|
122
|
+
* receive its reply and pend forever.
|
|
123
|
+
*/
|
|
124
|
+
private _destroyed = false;
|
|
113
125
|
private _proxyChannel: MessageChannel | null = null;
|
|
114
126
|
private _proxySession: any = null;
|
|
115
127
|
private _client: Client;
|
|
116
128
|
private _view: View;
|
|
117
129
|
private _tableName: string | undefined;
|
|
130
|
+
/** Source panel `slot`, tagged onto dispatched interaction events so the
|
|
131
|
+
* host can route them (master/detail, global filter) to the right panel. */
|
|
132
|
+
private _panel: string | undefined;
|
|
118
133
|
private _clientWorkerURL: URL;
|
|
119
134
|
private _clientWasm: WebAssembly.Module;
|
|
120
135
|
private _chartTag: string;
|
|
@@ -158,6 +173,37 @@ export class RendererTransport {
|
|
|
158
173
|
*/
|
|
159
174
|
private _displayCtx: CanvasRenderingContext2D | null = null;
|
|
160
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Present-hold state for the staged-present presize protocol
|
|
178
|
+
* ({@link presize}). While holding, inbound `frameBitmap`s are
|
|
179
|
+
* STAGED (latest wins) instead of blitted, so the on-screen frame
|
|
180
|
+
* — old dimensions in the old, unchanged box — stays put until the
|
|
181
|
+
* host invokes the present closure `presize` resolved to, in the
|
|
182
|
+
* same task as its layout commit. Entered only by `presize`;
|
|
183
|
+
* exited by the present closure, by `presize`'s rejection path, or
|
|
184
|
+
* by `destroy` — every settle path releases the hold or hands the
|
|
185
|
+
* caller the release.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* The last `(cssWidth, cssHeight, dpr)` posted to the worker — at init
|
|
189
|
+
* or any resize — i.e. the dimensions the worker has already rendered
|
|
190
|
+
* (or has an in-flight render for). {@link resize} short-circuits when
|
|
191
|
+
* its measurement matches (±0.5px, the presize protocol's own
|
|
192
|
+
* threshold): reactive resizes fan in from several host paths per
|
|
193
|
+
* layout transition (the post-commit fan-out, activation nudges, the
|
|
194
|
+
* fresh-restore tail), and every worker resize is an unconditional
|
|
195
|
+
* full re-render.
|
|
196
|
+
*/
|
|
197
|
+
private _lastPostedSize: {
|
|
198
|
+
cssWidth: number;
|
|
199
|
+
cssHeight: number;
|
|
200
|
+
dpr: number;
|
|
201
|
+
} | null = null;
|
|
202
|
+
|
|
203
|
+
private _holdPresent = false;
|
|
204
|
+
|
|
205
|
+
private _stagedFrame: ImageBitmap | null = null;
|
|
206
|
+
|
|
161
207
|
/**
|
|
162
208
|
* Host-side sink for tooltip + cursor side-effects. The chart
|
|
163
209
|
* inside the renderer calls into a `MessageHostSink` that posts
|
|
@@ -182,6 +228,7 @@ export class RendererTransport {
|
|
|
182
228
|
client: Client;
|
|
183
229
|
view: View;
|
|
184
230
|
tableName?: string;
|
|
231
|
+
panel?: string;
|
|
185
232
|
clientWasm: WebAssembly.Module;
|
|
186
233
|
clientWorkerURL: URL;
|
|
187
234
|
chartTag: string;
|
|
@@ -192,6 +239,7 @@ export class RendererTransport {
|
|
|
192
239
|
this._client = opts.client;
|
|
193
240
|
this._view = opts.view;
|
|
194
241
|
this._tableName = opts.tableName;
|
|
242
|
+
this._panel = opts.panel;
|
|
195
243
|
this._clientWorkerURL = opts.clientWorkerURL;
|
|
196
244
|
this._clientWasm = opts.clientWasm;
|
|
197
245
|
this._chartTag = opts.chartTag;
|
|
@@ -210,6 +258,7 @@ export class RendererTransport {
|
|
|
210
258
|
chrome: HTMLCanvasElement;
|
|
211
259
|
facetConfig: FacetConfig;
|
|
212
260
|
pluginConfig: PluginConfig;
|
|
261
|
+
columnsConfig?: Record<string, any>;
|
|
213
262
|
defaultChartType?: string;
|
|
214
263
|
renderBlitMode: "blit" | "direct";
|
|
215
264
|
}): Promise<void> {
|
|
@@ -291,6 +340,7 @@ export class RendererTransport {
|
|
|
291
340
|
tableName: this._tableName,
|
|
292
341
|
facetConfig: opts.facetConfig,
|
|
293
342
|
pluginConfig: opts.pluginConfig,
|
|
343
|
+
columnsConfig: opts.columnsConfig,
|
|
294
344
|
defaultChartType: opts.defaultChartType,
|
|
295
345
|
themeVars,
|
|
296
346
|
fontFaces,
|
|
@@ -301,6 +351,11 @@ export class RendererTransport {
|
|
|
301
351
|
precompileShaders: this._precompileShaders,
|
|
302
352
|
};
|
|
303
353
|
|
|
354
|
+
this._lastPostedSize = {
|
|
355
|
+
cssWidth: rect.width,
|
|
356
|
+
cssHeight: rect.height,
|
|
357
|
+
dpr,
|
|
358
|
+
};
|
|
304
359
|
this._handle = await this._createHandle(workerURL, initMsg);
|
|
305
360
|
this._handle.addMessageListener((msg) =>
|
|
306
361
|
this._handleRendererMsg(msg as WorkerMsg),
|
|
@@ -438,7 +493,8 @@ export class RendererTransport {
|
|
|
438
493
|
* `loadAndRenderAck`. Per the worker's "resolve on stale"
|
|
439
494
|
* contract, a mid-flight cancellation (a newer `loadAndRender`
|
|
440
495
|
* superseding this one) still acks — the host's awaiter just
|
|
441
|
-
* resolves quietly.
|
|
496
|
+
* resolves quietly. Calls issued after `destroy()` resolve
|
|
497
|
+
* immediately (see `_allocPending`).
|
|
442
498
|
*/
|
|
443
499
|
loadAndRender(opts: {
|
|
444
500
|
viewerConfig: {
|
|
@@ -464,19 +520,107 @@ export class RendererTransport {
|
|
|
464
520
|
this._post({ kind: "redraw" });
|
|
465
521
|
}
|
|
466
522
|
|
|
467
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Silently clear the worker chart's selection state (pinned tooltip) —
|
|
525
|
+
* no selection events are emitted; the worker echoes `dismissTooltip`
|
|
526
|
+
* to drop the host-side pinned artifact. See `DeselectMsg`.
|
|
527
|
+
*/
|
|
528
|
+
deselect(): void {
|
|
529
|
+
this._post({ kind: "deselect" });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Resize the chart to the GL canvas's CURRENT CSS box and resolve
|
|
534
|
+
* once the resized frame has PRESENTED (the worker's `resizeAck`) —
|
|
535
|
+
* not at message-post, which would let callers proceed while the
|
|
536
|
+
* old-dimensions bitmap is still on screen. Reactive geometry
|
|
537
|
+
* changes only; anticipated ones go through {@link presize}.
|
|
538
|
+
*
|
|
539
|
+
* A measurement matching {@link _lastPostedSize} resolves immediately
|
|
540
|
+
* WITHOUT a worker round trip: the worker has already rendered (or is
|
|
541
|
+
* presenting) that exact frame, and re-posting would be a full
|
|
542
|
+
* re-render of an identical bitmap.
|
|
543
|
+
*/
|
|
544
|
+
resize(): Promise<void> {
|
|
468
545
|
if (!this._hostGlCanvas) {
|
|
469
|
-
return;
|
|
546
|
+
return Promise.resolve();
|
|
470
547
|
}
|
|
471
548
|
|
|
472
549
|
const rect = this._hostGlCanvas.getBoundingClientRect();
|
|
473
550
|
const dpr = window.devicePixelRatio || 1;
|
|
551
|
+
const last = this._lastPostedSize;
|
|
552
|
+
if (
|
|
553
|
+
last &&
|
|
554
|
+
Math.abs(last.cssWidth - rect.width) <= 0.5 &&
|
|
555
|
+
Math.abs(last.cssHeight - rect.height) <= 0.5 &&
|
|
556
|
+
last.dpr === dpr
|
|
557
|
+
) {
|
|
558
|
+
return Promise.resolve();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return this._postResize(rect.width, rect.height);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Staged-present variant of {@link resize}: render at the given
|
|
566
|
+
* TARGET canvas CSS box (no measurement — the caller supplies the
|
|
567
|
+
* box the pending layout commit will produce), HOLDING the
|
|
568
|
+
* resulting frame (and any others that arrive meanwhile) offscreen
|
|
569
|
+
* instead of blitting, so nothing on screen changes. Resolves,
|
|
570
|
+
* once the resized frame is staged, to a present closure: calling
|
|
571
|
+
* it blits the latest staged frame and exits the hold — the host
|
|
572
|
+
* runs it in the same task as its layout commit so geometry and
|
|
573
|
+
* pixels land in one paint. On rejection (teardown) the hold is
|
|
574
|
+
* released here, so every settle path either frees the display or
|
|
575
|
+
* hands the caller the release.
|
|
576
|
+
*/
|
|
577
|
+
presize(cssWidth: number, cssHeight: number): Promise<(() => void) | void> {
|
|
578
|
+
if (!this._hostGlCanvas) {
|
|
579
|
+
return Promise.resolve();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const dpr = window.devicePixelRatio || 1;
|
|
583
|
+
const last = this._lastPostedSize;
|
|
584
|
+
if (
|
|
585
|
+
last &&
|
|
586
|
+
Math.abs(last.cssWidth - cssWidth) <= 0.5 &&
|
|
587
|
+
Math.abs(last.cssHeight - cssHeight) <= 0.5 &&
|
|
588
|
+
last.dpr === dpr
|
|
589
|
+
) {
|
|
590
|
+
return Promise.resolve();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
this._holdPresent = true;
|
|
594
|
+
return this._postResize(cssWidth, cssHeight).then(
|
|
595
|
+
() => () => this._presentStaged(),
|
|
596
|
+
(err) => {
|
|
597
|
+
this._presentStaged();
|
|
598
|
+
throw err;
|
|
599
|
+
},
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private _postResize(cssWidth: number, cssHeight: number): Promise<void> {
|
|
604
|
+
const dpr = window.devicePixelRatio || 1;
|
|
605
|
+
const { id, promise } = this._allocPending<void>("resize");
|
|
606
|
+
this._lastPostedSize = { cssWidth, cssHeight, dpr };
|
|
474
607
|
this._post({
|
|
475
608
|
kind: "resize",
|
|
476
|
-
|
|
477
|
-
|
|
609
|
+
msgId: id,
|
|
610
|
+
cssWidth,
|
|
611
|
+
cssHeight,
|
|
478
612
|
dpr,
|
|
479
613
|
});
|
|
614
|
+
return promise;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private _presentStaged(): void {
|
|
618
|
+
this._holdPresent = false;
|
|
619
|
+
const staged = this._stagedFrame;
|
|
620
|
+
this._stagedFrame = null;
|
|
621
|
+
if (staged) {
|
|
622
|
+
this._drawFrameBitmap(staged);
|
|
623
|
+
}
|
|
480
624
|
}
|
|
481
625
|
|
|
482
626
|
clear() {
|
|
@@ -501,12 +645,39 @@ export class RendererTransport {
|
|
|
501
645
|
* Allocate a pending request slot of the given `kind`. Returns the
|
|
502
646
|
* id (encoded into the outgoing `ControlMsg`) and a promise that
|
|
503
647
|
* resolves / rejects when the matching reply arrives or
|
|
504
|
-
* `destroy()` drains the table.
|
|
648
|
+
* `destroy()` drains the table. Requests issued *after* `destroy()`
|
|
649
|
+
* settle immediately with the same per-kind semantics as the drain.
|
|
505
650
|
*/
|
|
506
651
|
private _allocPending<T>(kind: PendingRenderType): {
|
|
507
652
|
id: number;
|
|
508
653
|
promise: Promise<T>;
|
|
509
654
|
} {
|
|
655
|
+
// A request issued after `destroy()` would never settle: `_post`
|
|
656
|
+
// no-ops with no handle, so no reply ever lands — and the host
|
|
657
|
+
// awaits `loadAndRender` while holding its per-renderer draw
|
|
658
|
+
// lock, so a pending-forever promise wedges that panel's lock
|
|
659
|
+
// permanently. (Reachable when an external DOM disconnect
|
|
660
|
+
// `delete()`s the element mid-draw; the host's own teardown
|
|
661
|
+
// paths are serialized behind the same lock.) Mirror the
|
|
662
|
+
// `destroy()` drain: `loadAndRender` resolves silently,
|
|
663
|
+
// reply-bearing kinds reject. The pre-attached no-op `catch`
|
|
664
|
+
// marks the rejection handled for fire-and-forget callers
|
|
665
|
+
// (`saveZoom`) without affecting real awaiters.
|
|
666
|
+
if (this._destroyed) {
|
|
667
|
+
if (kind === "loadAndRender") {
|
|
668
|
+
return {
|
|
669
|
+
id: -1,
|
|
670
|
+
promise: Promise.resolve(undefined as unknown as T),
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const promise = Promise.reject<T>(
|
|
675
|
+
new Error("RendererTransport destroyed"),
|
|
676
|
+
);
|
|
677
|
+
promise.catch(() => {});
|
|
678
|
+
return { id: -1, promise };
|
|
679
|
+
}
|
|
680
|
+
|
|
510
681
|
const id = ++this._pendingCounter;
|
|
511
682
|
const promise = new Promise<T>((resolve, reject) => {
|
|
512
683
|
this._pending.set(id, { kind, resolve, reject });
|
|
@@ -548,6 +719,7 @@ export class RendererTransport {
|
|
|
548
719
|
}
|
|
549
720
|
|
|
550
721
|
destroy(): void {
|
|
722
|
+
this._destroyed = true;
|
|
551
723
|
this._post({ kind: "destroy" });
|
|
552
724
|
if (this._proxySession) {
|
|
553
725
|
this._proxySession.close().catch(() => {});
|
|
@@ -572,6 +744,9 @@ export class RendererTransport {
|
|
|
572
744
|
// them, and so the GPU-backed 2D context can release earlier.
|
|
573
745
|
this._hostGlCanvas = null;
|
|
574
746
|
this._displayCtx = null;
|
|
747
|
+
this._holdPresent = false;
|
|
748
|
+
this._stagedFrame?.close();
|
|
749
|
+
this._stagedFrame = null;
|
|
575
750
|
|
|
576
751
|
// Drain pending request promises with kind-aware semantics:
|
|
577
752
|
// - `loadAndRender` resolves silently (the host's awaited draw
|
|
@@ -633,7 +808,9 @@ export class RendererTransport {
|
|
|
633
808
|
{
|
|
634
809
|
bubbles: true,
|
|
635
810
|
composed: true,
|
|
636
|
-
|
|
811
|
+
// Tag with the source panel so a multi-panel host
|
|
812
|
+
// can attribute the click to the right panel.
|
|
813
|
+
detail: { ...msg.detail, panel: this._panel },
|
|
637
814
|
},
|
|
638
815
|
),
|
|
639
816
|
);
|
|
@@ -658,6 +835,9 @@ export class RendererTransport {
|
|
|
658
835
|
removeConfigs as any,
|
|
659
836
|
insertConfigs as any,
|
|
660
837
|
);
|
|
838
|
+
// Tag with the source panel so the host's master/detail +
|
|
839
|
+
// global-filter routing can identify the originating panel.
|
|
840
|
+
detail.panel = this._panel;
|
|
661
841
|
this._dispatchOnViewer(
|
|
662
842
|
new CustomEvent<PerspectiveSelectDetail>(
|
|
663
843
|
"perspective-global-filter",
|
|
@@ -672,7 +852,13 @@ export class RendererTransport {
|
|
|
672
852
|
}
|
|
673
853
|
|
|
674
854
|
case "frameBitmap":
|
|
675
|
-
this.
|
|
855
|
+
if (this._holdPresent) {
|
|
856
|
+
this._stagedFrame?.close();
|
|
857
|
+
this._stagedFrame = msg.bitmap;
|
|
858
|
+
} else {
|
|
859
|
+
this._drawFrameBitmap(msg.bitmap);
|
|
860
|
+
}
|
|
861
|
+
|
|
676
862
|
break;
|
|
677
863
|
case "error":
|
|
678
864
|
this._rejectReady(new Error(msg.message));
|
|
@@ -680,6 +866,9 @@ export class RendererTransport {
|
|
|
680
866
|
case "loadAndRenderAck":
|
|
681
867
|
this._resolvePending(msg.msgId, "loadAndRender", undefined);
|
|
682
868
|
break;
|
|
869
|
+
case "resizeAck":
|
|
870
|
+
this._resolvePending(msg.msgId, "resize", undefined);
|
|
871
|
+
break;
|
|
683
872
|
case "snapshotPngReply":
|
|
684
873
|
this._resolvePending(msg.requestId, "snapshotPng", msg.blob);
|
|
685
874
|
break;
|
|
@@ -108,6 +108,12 @@ export function createQuadCornerBuffer(gl: GL): WebGLBuffer {
|
|
|
108
108
|
* paint zero data). No-op return `true` when `attr` is negative
|
|
109
109
|
* (optimized-out attribute — drawing the rest is still valid).
|
|
110
110
|
*
|
|
111
|
+
* `firstInstance` byte-offsets the attribute pointer so the draw's
|
|
112
|
+
* instance 0 reads element `firstInstance` of the buffer — the WebGL
|
|
113
|
+
* substitute for a `baseInstance` draw parameter. Faceted renderers
|
|
114
|
+
* use it to draw a contiguous per-facet sub-range of a shared
|
|
115
|
+
* instance buffer.
|
|
116
|
+
*
|
|
111
117
|
* Render-path uses `peek`, never `getOrCreate`: the latter recreates
|
|
112
118
|
* with zero-initialized contents when `_totalCapacity` has grown past
|
|
113
119
|
* the current buffer, which would wipe the previous draw's vertex
|
|
@@ -120,6 +126,7 @@ export function bindInstancedFloatAttr(
|
|
|
120
126
|
attr: number,
|
|
121
127
|
name: string,
|
|
122
128
|
components: number,
|
|
129
|
+
firstInstance = 0,
|
|
123
130
|
): boolean {
|
|
124
131
|
if (attr < 0) {
|
|
125
132
|
return true;
|
|
@@ -133,7 +140,14 @@ export function bindInstancedFloatAttr(
|
|
|
133
140
|
const gl: GL = glManager.gl;
|
|
134
141
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf.buffer);
|
|
135
142
|
gl.enableVertexAttribArray(attr);
|
|
136
|
-
gl.vertexAttribPointer(
|
|
143
|
+
gl.vertexAttribPointer(
|
|
144
|
+
attr,
|
|
145
|
+
components,
|
|
146
|
+
gl.FLOAT,
|
|
147
|
+
false,
|
|
148
|
+
0,
|
|
149
|
+
firstInstance * components * Float32Array.BYTES_PER_ELEMENT,
|
|
150
|
+
);
|
|
137
151
|
instancing.setDivisor(attr, 1);
|
|
138
152
|
return true;
|
|
139
153
|
}
|
|
@@ -40,9 +40,12 @@ export function dispatch(r: WorkerRenderer, msg: ControlMsg): void {
|
|
|
40
40
|
case "redraw":
|
|
41
41
|
r.redraw();
|
|
42
42
|
break;
|
|
43
|
+
case "deselect":
|
|
44
|
+
r.chartImpl.deselect?.();
|
|
45
|
+
break;
|
|
43
46
|
case "resize":
|
|
44
47
|
r.resize(msg.cssWidth, msg.cssHeight, msg.dpr);
|
|
45
|
-
r.
|
|
48
|
+
r.redrawAck(msg.msgId);
|
|
46
49
|
break;
|
|
47
50
|
case "clear":
|
|
48
51
|
r.clear();
|
|
@@ -204,6 +204,9 @@ export class WorkerRenderer {
|
|
|
204
204
|
|
|
205
205
|
this.chartImpl.setFacetConfig?.(msg.facetConfig);
|
|
206
206
|
this.chartImpl.setPluginConfig?.(msg.pluginConfig);
|
|
207
|
+
if (msg.columnsConfig) {
|
|
208
|
+
this.chartImpl.setColumnsConfig?.(msg.columnsConfig);
|
|
209
|
+
}
|
|
207
210
|
|
|
208
211
|
if (this.chartImpl.setZoomController) {
|
|
209
212
|
this.zoomController = new ZoomController();
|
|
@@ -295,16 +298,23 @@ export class WorkerRenderer {
|
|
|
295
298
|
try {
|
|
296
299
|
await viewToColumnDataMap(
|
|
297
300
|
this.view,
|
|
298
|
-
async (cols) => {
|
|
301
|
+
async (cols, deliveredRows) => {
|
|
299
302
|
if (this._renderGen !== myGen) {
|
|
300
303
|
throw new StaleGenerationError();
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
// `totalRows` came from a SEPARATE `num_rows()`
|
|
307
|
+
// round-trip; a fast-updating table with deletions
|
|
308
|
+
// can shrink the view before the Arrow fetch, which
|
|
309
|
+
// clamps its window silently. Iterating to the stale
|
|
310
|
+
// count reads typed arrays out-of-bounds (`undefined`
|
|
311
|
+
// values no validity bitmap covers) — clamp to what
|
|
312
|
+
// was actually delivered.
|
|
303
313
|
await this.chartImpl.uploadAndRender(
|
|
304
314
|
this.glManager,
|
|
305
315
|
cols,
|
|
306
316
|
0,
|
|
307
|
-
totalRows,
|
|
317
|
+
Math.min(totalRows, deliveredRows),
|
|
308
318
|
);
|
|
309
319
|
},
|
|
310
320
|
{ end_row: totalRows, float32: msg.options.float32 },
|
|
@@ -327,6 +337,24 @@ export class WorkerRenderer {
|
|
|
327
337
|
this.chartImpl.requestRender(this.glManager);
|
|
328
338
|
}
|
|
329
339
|
|
|
340
|
+
/**
|
|
341
|
+
* `redraw`, acked to the host AFTER the frame's present completes —
|
|
342
|
+
* the reply backing the viewer's presize contract (`ResizeAckMsg`):
|
|
343
|
+
* `plugin.resize()` must not resolve until the resized frame is
|
|
344
|
+
* actually on screen. A resize landing mid-present defers its
|
|
345
|
+
* canvas mutation (`deferIfDraining`), but the redraw's own frame
|
|
346
|
+
* applies pending dimensions in `beginFrame`, so the present this
|
|
347
|
+
* awaits IS at the new size. Always acks — a failed present or a
|
|
348
|
+
* torn-down chart resolves the host's await rather than stranding
|
|
349
|
+
* it (completion, not success, is the contract).
|
|
350
|
+
*/
|
|
351
|
+
redrawAck(msgId: number): void {
|
|
352
|
+
this.chartImpl
|
|
353
|
+
.requestRender(this.glManager)
|
|
354
|
+
.catch(() => {})
|
|
355
|
+
.finally(() => this.post({ kind: "resizeAck", msgId }));
|
|
356
|
+
}
|
|
357
|
+
|
|
330
358
|
resize(cssWidth: number, cssHeight: number, dpr: number): void {
|
|
331
359
|
// `glManager.resize` would set `canvas.width = N`, which the
|
|
332
360
|
// spec mandates clears the drawing buffer immediately. In
|
|
@@ -572,7 +600,11 @@ export class WorkerRenderer {
|
|
|
572
600
|
// chart left the shared canvas at its own size) would render at
|
|
573
601
|
// the wrong dimensions.
|
|
574
602
|
this.glManager.beginFrame();
|
|
575
|
-
|
|
603
|
+
// `renderFrameSync` (not `_fullRender`) so the gridline + chrome
|
|
604
|
+
// 2D canvases are actually painted here — the scheduler normally
|
|
605
|
+
// defers those to a post-fence flush, but the snapshot path
|
|
606
|
+
// bypasses the scheduler and composites the 2D layers below.
|
|
607
|
+
this.chartImpl.renderFrameSync(this.glManager);
|
|
576
608
|
const gl = this.glManager.gl;
|
|
577
609
|
const glCanvas = gl.canvas as OffscreenCanvas;
|
|
578
610
|
const w = glCanvas.width;
|