@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.
Files changed (49) hide show
  1. package/dist/cdn/perspective-viewer-charts.js +2 -2
  2. package/dist/cdn/perspective-viewer-charts.js.map +4 -4
  3. package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
  4. package/dist/esm/charts/cartesian/cartesian.d.ts +1 -0
  5. package/dist/esm/charts/chart-base.d.ts +22 -9
  6. package/dist/esm/charts/chart.d.ts +4 -0
  7. package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
  8. package/dist/esm/charts/common/tree-chart.d.ts +1 -0
  9. package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
  10. package/dist/esm/charts/series/series-type.d.ts +18 -0
  11. package/dist/esm/charts/series/series.d.ts +4 -1
  12. package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
  13. package/dist/esm/perspective-viewer-charts.js +2 -2
  14. package/dist/esm/perspective-viewer-charts.js.map +4 -4
  15. package/dist/esm/plugin/plugin.d.ts +8 -1
  16. package/dist/esm/theme/gradient.d.ts +31 -0
  17. package/dist/esm/theme/overrides.d.ts +19 -0
  18. package/dist/esm/transport/protocol.d.ts +13 -2
  19. package/dist/esm/utils/css.d.ts +4 -0
  20. package/dist/esm/worker/renderer.worker.d.ts +27 -0
  21. package/package.json +1 -1
  22. package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
  23. package/src/ts/charts/candlestick/candlestick-render.ts +8 -0
  24. package/src/ts/charts/candlestick/candlestick.ts +4 -3
  25. package/src/ts/charts/cartesian/cartesian-render.ts +5 -0
  26. package/src/ts/charts/cartesian/cartesian.ts +4 -0
  27. package/src/ts/charts/chart-base.ts +42 -10
  28. package/src/ts/charts/chart.ts +5 -0
  29. package/src/ts/charts/common/category-axis-resolver.ts +44 -5
  30. package/src/ts/charts/common/tree-chart.ts +4 -0
  31. package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
  32. package/src/ts/charts/heatmap/heatmap-interact.ts +10 -1
  33. package/src/ts/charts/heatmap/heatmap-render.ts +5 -0
  34. package/src/ts/charts/heatmap/heatmap.ts +8 -3
  35. package/src/ts/charts/series/series-build.ts +1 -0
  36. package/src/ts/charts/series/series-interact.ts +2 -13
  37. package/src/ts/charts/series/series-render.ts +5 -0
  38. package/src/ts/charts/series/series-type.ts +65 -0
  39. package/src/ts/charts/series/series.ts +21 -8
  40. package/src/ts/charts/sunburst/sunburst-render.ts +9 -0
  41. package/src/ts/charts/treemap/treemap-render.ts +5 -0
  42. package/src/ts/layout/ticks.ts +36 -12
  43. package/src/ts/plugin/plugin.ts +95 -10
  44. package/src/ts/theme/gradient.ts +241 -1
  45. package/src/ts/theme/overrides.ts +76 -0
  46. package/src/ts/transport/protocol.ts +14 -2
  47. package/src/ts/transport/renderer-transport.ts +24 -10
  48. package/src/ts/utils/css.ts +12 -0
  49. 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
- gridlinesCanvas: OffscreenCanvas;
104
- chromeCanvas: OffscreenCanvas;
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). `glOC` is omitted in blit mode (the
369
- // renderer allocates its own offscreen) — only include the
370
- // GL canvas in the transfer list when present.
371
- const transfer: Transferable[] = [
372
- gridlinesOC,
373
- chromeOC,
374
- this._proxyChannel!.port2,
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
  }
@@ -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.post({ kind: "frameBitmap", bitmap }, [bitmap]);
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?.(msg.gridlinesCanvas);
198
- this.chartImpl.setChromeCanvas?.(msg.chromeCanvas);
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
  }