@perspective-dev/viewer-charts 5.2.0 → 5.3.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 +4 -4
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +1 -0
- package/dist/esm/charts/chart-base.d.ts +22 -9
- package/dist/esm/charts/chart.d.ts +4 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
- package/dist/esm/charts/common/tree-chart.d.ts +1 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
- package/dist/esm/charts/series/series-type.d.ts +18 -0
- package/dist/esm/charts/series/series.d.ts +4 -1
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/plugin.d.ts +8 -1
- package/dist/esm/theme/gradient.d.ts +31 -0
- package/dist/esm/theme/overrides.d.ts +19 -0
- package/dist/esm/transport/protocol.d.ts +13 -2
- package/dist/esm/utils/css.d.ts +4 -0
- package/dist/esm/worker/renderer.worker.d.ts +27 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +8 -0
- package/src/ts/charts/candlestick/candlestick.ts +4 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +5 -0
- package/src/ts/charts/cartesian/cartesian.ts +4 -0
- package/src/ts/charts/chart-base.ts +42 -10
- package/src/ts/charts/chart.ts +5 -0
- package/src/ts/charts/common/category-axis-resolver.ts +44 -5
- package/src/ts/charts/common/tree-chart.ts +4 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
- package/src/ts/charts/heatmap/heatmap-interact.ts +10 -1
- package/src/ts/charts/heatmap/heatmap-render.ts +5 -0
- package/src/ts/charts/heatmap/heatmap.ts +8 -3
- package/src/ts/charts/series/series-build.ts +1 -0
- package/src/ts/charts/series/series-interact.ts +2 -13
- package/src/ts/charts/series/series-render.ts +5 -0
- package/src/ts/charts/series/series-type.ts +65 -0
- package/src/ts/charts/series/series.ts +21 -8
- package/src/ts/charts/sunburst/sunburst-render.ts +9 -0
- package/src/ts/charts/treemap/treemap-render.ts +5 -0
- package/src/ts/layout/ticks.ts +36 -12
- package/src/ts/plugin/plugin.ts +95 -10
- package/src/ts/theme/gradient.ts +241 -1
- package/src/ts/theme/overrides.ts +76 -0
- package/src/ts/transport/protocol.ts +14 -2
- package/src/ts/transport/renderer-transport.ts +24 -10
- package/src/ts/utils/css.ts +12 -0
- package/src/ts/worker/renderer.worker.ts +113 -5
|
@@ -100,8 +100,20 @@ export interface InitMsg {
|
|
|
100
100
|
* `dpr` and there is no host-side GL drawing buffer.
|
|
101
101
|
*/
|
|
102
102
|
glCanvas?: OffscreenCanvas;
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The gridlines (bottom) and chrome/axes (top) 2D layers.
|
|
106
|
+
* Transferred via `transferControlToOffscreen` on the host, present
|
|
107
|
+
* iff `renderMode === "direct"` — their pixels reach the screen
|
|
108
|
+
* through the compositor. In blit mode the renderer allocates
|
|
109
|
+
* worker-local surfaces instead and composites both layers into the
|
|
110
|
+
* shipped `FrameBitmapMsg`, so every layer of the frame rides the
|
|
111
|
+
* host's staged-present hold (a transferred canvas presents on the
|
|
112
|
+
* compositor's own schedule, unsynchronized with the host's layout
|
|
113
|
+
* commits — the one-frame axes warp on anticipated resizes).
|
|
114
|
+
*/
|
|
115
|
+
gridlinesCanvas?: OffscreenCanvas;
|
|
116
|
+
chromeCanvas?: OffscreenCanvas;
|
|
105
117
|
|
|
106
118
|
/**
|
|
107
119
|
* `MessagePort` to the host's `ProxySession`. Worker mode only —
|
|
@@ -299,15 +299,24 @@ export class RendererTransport {
|
|
|
299
299
|
// `ImageBitmap`. Direct mode transfers the visible canvas's
|
|
300
300
|
// drawing buffer to the renderer so GL paints straight to
|
|
301
301
|
// screen.
|
|
302
|
+
// Blit mode also keeps the gridlines/chrome layers worker-side:
|
|
303
|
+
// a transferred canvas presents on the compositor's schedule,
|
|
304
|
+
// unsynchronized with the host's layout commits, so it can't
|
|
305
|
+
// participate in the staged-present hold. The renderer
|
|
306
|
+
// composites both layers into the shipped frame instead, and
|
|
307
|
+
// the host-side placeholder canvases stay untouched
|
|
308
|
+
// (transparent). Direct mode transfers all three surfaces.
|
|
302
309
|
let glOC: OffscreenCanvas | undefined;
|
|
310
|
+
let gridlinesOC: OffscreenCanvas | undefined;
|
|
311
|
+
let chromeOC: OffscreenCanvas | undefined;
|
|
303
312
|
if (opts.renderBlitMode === "blit") {
|
|
304
313
|
this._displayCtx = opts.gl.getContext("2d");
|
|
305
314
|
} else {
|
|
306
315
|
glOC = opts.gl.transferControlToOffscreen();
|
|
316
|
+
gridlinesOC = opts.gridlines.transferControlToOffscreen();
|
|
317
|
+
chromeOC = opts.chrome.transferControlToOffscreen();
|
|
307
318
|
}
|
|
308
319
|
|
|
309
|
-
const gridlinesOC = opts.gridlines.transferControlToOffscreen();
|
|
310
|
-
const chromeOC = opts.chrome.transferControlToOffscreen();
|
|
311
320
|
const rect = opts.gl.getBoundingClientRect();
|
|
312
321
|
const dpr = window.devicePixelRatio || 1;
|
|
313
322
|
const themeVars = snapshotThemeVars(opts.gl);
|
|
@@ -365,14 +374,19 @@ export class RendererTransport {
|
|
|
365
374
|
// Worker mode: the bootstrap is triggered by posting the
|
|
366
375
|
// init message into the worker's scope (which the
|
|
367
376
|
// `if (IS_WORKER_SCOPE)` block in `renderer.worker.ts`
|
|
368
|
-
// listens for).
|
|
369
|
-
// renderer allocates its own
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
// listens for). All three canvases are omitted in blit mode
|
|
378
|
+
// (the renderer allocates its own offscreens and ships
|
|
379
|
+
// composited frames) — only include the transferred
|
|
380
|
+
// surfaces present on this mode's init message.
|
|
381
|
+
const transfer: Transferable[] = [this._proxyChannel!.port2];
|
|
382
|
+
if (chromeOC) {
|
|
383
|
+
transfer.unshift(chromeOC);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (gridlinesOC) {
|
|
387
|
+
transfer.unshift(gridlinesOC);
|
|
388
|
+
}
|
|
389
|
+
|
|
376
390
|
if (glOC) {
|
|
377
391
|
transfer.unshift(glOC);
|
|
378
392
|
}
|
package/src/ts/utils/css.ts
CHANGED
|
@@ -34,3 +34,15 @@ export function parseCSSColorToVec3(
|
|
|
34
34
|
|
|
35
35
|
return [0.5, 0.5, 0.5];
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Format a `[0, 1]`-range RGB triple as a plain `#rrggbb` string.
|
|
40
|
+
*/
|
|
41
|
+
export function vec3ToHexColor(c: [number, number, number]): string {
|
|
42
|
+
const channel = (x: number) =>
|
|
43
|
+
Math.round(Math.max(0, Math.min(1, x)) * 255)
|
|
44
|
+
.toString(16)
|
|
45
|
+
.padStart(2, "0");
|
|
46
|
+
|
|
47
|
+
return `#${channel(c[0])}${channel(c[1])}${channel(c[2])}`;
|
|
48
|
+
}
|
|
@@ -106,6 +106,20 @@ export class WorkerRenderer {
|
|
|
106
106
|
cssWidth: number;
|
|
107
107
|
cssHeight: number;
|
|
108
108
|
dpr: number;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Blit-mode compose surface: gridlines + GL frame + chrome are
|
|
112
|
+
* drawn here each `endFrame` and shipped as ONE `ImageBitmap`, so
|
|
113
|
+
* every layer rides the host's staged-present hold (see
|
|
114
|
+
* `InitMsg.gridlinesCanvas`). `null` in direct mode.
|
|
115
|
+
*/
|
|
116
|
+
private _composeCanvas: OffscreenCanvas | null = null;
|
|
117
|
+
private _composeCtx: OffscreenCanvasRenderingContext2D | null = null;
|
|
118
|
+
|
|
119
|
+
private _lastPlot: ImageBitmap | null = null;
|
|
120
|
+
|
|
121
|
+
private _frameSerial = 0;
|
|
122
|
+
private _overlayPresentQueued = false;
|
|
109
123
|
client: Client;
|
|
110
124
|
view: View;
|
|
111
125
|
|
|
@@ -182,20 +196,31 @@ export class WorkerRenderer {
|
|
|
182
196
|
});
|
|
183
197
|
}
|
|
184
198
|
|
|
199
|
+
// Blit mode: the 2D layers are worker-local (the host omits
|
|
200
|
+
// them from the init message) and every shipped frame is the
|
|
201
|
+
// full composite — gridlines under the GL plot under chrome,
|
|
202
|
+
// the same stacking as the host's canvas elements and the
|
|
203
|
+
// `snapshotPng` composite. Direct mode draws into the host's
|
|
204
|
+
// transferred surfaces and ships nothing.
|
|
205
|
+
const w = Math.max(1, Math.round(msg.cssWidth * msg.dpr));
|
|
206
|
+
const h = Math.max(1, Math.round(msg.cssHeight * msg.dpr));
|
|
207
|
+
this.gridlines = msg.gridlinesCanvas ?? new OffscreenCanvas(w, h);
|
|
208
|
+
this.chrome = msg.chromeCanvas ?? new OffscreenCanvas(w, h);
|
|
185
209
|
if (msg.renderMode === "blit") {
|
|
186
210
|
this.glManager.setFrameCallback((bitmap) => {
|
|
187
|
-
this.
|
|
211
|
+
this._frameSerial += 1;
|
|
212
|
+
const frame = this._composeFrame(bitmap);
|
|
213
|
+
this.post({ kind: "frameBitmap", bitmap: frame }, [frame]);
|
|
188
214
|
});
|
|
215
|
+
this.chartImpl.setOverlayPresenter?.(() => this._presentOverlay());
|
|
189
216
|
}
|
|
190
217
|
|
|
191
|
-
this.gridlines = msg.gridlinesCanvas;
|
|
192
|
-
this.chrome = msg.chromeCanvas;
|
|
193
218
|
this.cssWidth = msg.cssWidth;
|
|
194
219
|
this.cssHeight = msg.cssHeight;
|
|
195
220
|
this.dpr = msg.dpr;
|
|
196
221
|
|
|
197
|
-
this.chartImpl.setGridlineCanvas?.(
|
|
198
|
-
this.chartImpl.setChromeCanvas?.(
|
|
222
|
+
this.chartImpl.setGridlineCanvas?.(this.gridlines);
|
|
223
|
+
this.chartImpl.setChromeCanvas?.(this.chrome);
|
|
199
224
|
this.chartImpl.setTheme?.(msg.themeVars);
|
|
200
225
|
|
|
201
226
|
if (msg.defaultChartType) {
|
|
@@ -588,6 +613,87 @@ export class WorkerRenderer {
|
|
|
588
613
|
return tt ?? null;
|
|
589
614
|
}
|
|
590
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Blit-mode frame composite: draw the gridlines (bottom), the GL
|
|
618
|
+
* plot bitmap (middle) and the chrome/axes (top) into the compose
|
|
619
|
+
* surface and transfer the result — the single `ImageBitmap` the
|
|
620
|
+
* host blits, and the unit the staged-present hold stages. Runs in
|
|
621
|
+
* `endFrame`, which the scheduler orders after `render2D`'s 2D
|
|
622
|
+
* flush, so both layers hold this frame's content.
|
|
623
|
+
*
|
|
624
|
+
* A layer whose buffer differs from the plot's dimensions is
|
|
625
|
+
* skipped rather than scaled: its content is a stale-sized frame
|
|
626
|
+
* mid-resize (the 2D draws re-size their canvas to `css × dpr` on
|
|
627
|
+
* the next flush), and scaling it would re-introduce exactly the
|
|
628
|
+
* warp this composite exists to prevent.
|
|
629
|
+
*/
|
|
630
|
+
private _composeFrame(plot: ImageBitmap): ImageBitmap {
|
|
631
|
+
const w = plot.width;
|
|
632
|
+
const h = plot.height;
|
|
633
|
+
if (!this._composeCanvas) {
|
|
634
|
+
this._composeCanvas = new OffscreenCanvas(w, h);
|
|
635
|
+
this._composeCtx = this._composeCanvas.getContext("2d");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const canvas = this._composeCanvas;
|
|
639
|
+
const ctx = this._composeCtx;
|
|
640
|
+
if (!ctx) {
|
|
641
|
+
return plot;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (canvas.width !== w || canvas.height !== h) {
|
|
645
|
+
canvas.width = w;
|
|
646
|
+
canvas.height = h;
|
|
647
|
+
} else {
|
|
648
|
+
ctx.clearRect(0, 0, w, h);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (this.gridlines.width === w && this.gridlines.height === h) {
|
|
652
|
+
ctx.drawImage(this.gridlines, 0, 0);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
ctx.drawImage(plot, 0, 0);
|
|
656
|
+
if (this._lastPlot !== plot) {
|
|
657
|
+
this._lastPlot?.close();
|
|
658
|
+
this._lastPlot = plot;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (this.chrome.width === w && this.chrome.height === h) {
|
|
662
|
+
ctx.drawImage(this.chrome, 0, 0);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return canvas.transferToImageBitmap();
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private _presentOverlay(): void {
|
|
669
|
+
if (this._overlayPresentQueued) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
this._overlayPresentQueued = true;
|
|
674
|
+
const mark = this._frameSerial;
|
|
675
|
+
queueMicrotask(() => {
|
|
676
|
+
this._overlayPresentQueued = false;
|
|
677
|
+
const plot = this._lastPlot;
|
|
678
|
+
if (this._frameSerial !== mark || !plot) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const w = Math.max(1, Math.round(this.cssWidth * this.dpr));
|
|
683
|
+
const h = Math.max(1, Math.round(this.cssHeight * this.dpr));
|
|
684
|
+
if (plot.width !== w || plot.height !== h) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const frame = this._composeFrame(plot);
|
|
689
|
+
if (frame === plot) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
this.post({ kind: "frameBitmap", bitmap: frame }, [frame]);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
591
697
|
/**
|
|
592
698
|
* Composite the three layers into a single PNG `Blob`.
|
|
593
699
|
*/
|
|
@@ -649,6 +755,8 @@ export class WorkerRenderer {
|
|
|
649
755
|
// next-RAF `drain()` can't paint/present against a dead
|
|
650
756
|
// context (the "scheduler: present failed" path).
|
|
651
757
|
unregister(this.glManager);
|
|
758
|
+
this._lastPlot?.close();
|
|
759
|
+
this._lastPlot = null;
|
|
652
760
|
this.chartImpl.destroy();
|
|
653
761
|
this.glManager.destroy();
|
|
654
762
|
}
|