@perspective-dev/viewer-charts 4.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/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +3 -0
- package/dist/cdn/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/axis/axis-primitives.d.ts +24 -0
- package/dist/esm/axis/bar-axis.d.ts +51 -0
- package/dist/esm/axis/canvas.d.ts +24 -0
- package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
- package/dist/esm/axis/categorical-axis.d.ts +27 -0
- package/dist/esm/axis/facet-chrome.d.ts +13 -0
- package/dist/esm/axis/label-geometry.d.ts +41 -0
- package/dist/esm/axis/legend.d.ts +44 -0
- package/dist/esm/axis/numeric-axis.d.ts +20 -0
- package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
- package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
- package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
- package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
- package/dist/esm/charts/canvas-types.d.ts +15 -0
- package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
- package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
- package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
- package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
- package/dist/esm/charts/chart-base.d.ts +402 -0
- package/dist/esm/charts/chart.d.ts +338 -0
- package/dist/esm/charts/common/band-layout.d.ts +32 -0
- package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
- package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
- package/dist/esm/charts/common/leaf-color.d.ts +33 -0
- package/dist/esm/charts/common/node-store.d.ts +81 -0
- package/dist/esm/charts/common/tree-chart.d.ts +48 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
- package/dist/esm/charts/common/tree-data.d.ts +54 -0
- package/dist/esm/charts/common/visible-extent.d.ts +51 -0
- package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
- package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
- package/dist/esm/charts/map/map.d.ts +67 -0
- package/dist/esm/charts/registry.d.ts +14 -0
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
- package/dist/esm/charts/series/series-build.d.ts +228 -0
- package/dist/esm/charts/series/series-interact.d.ts +35 -0
- package/dist/esm/charts/series/series-render.d.ts +41 -0
- package/dist/esm/charts/series/series-type.d.ts +49 -0
- package/dist/esm/charts/series/series.d.ts +317 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
- package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
- package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
- package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
- package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
- package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
- package/dist/esm/charts/treemap/treemap.d.ts +74 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/data/lazy-row.d.ts +32 -0
- package/dist/esm/data/split-groups.d.ts +20 -0
- package/dist/esm/data/view-reader.d.ts +35 -0
- package/dist/esm/event-detail.d.ts +28 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/interaction/hit-test.d.ts +30 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +46 -0
- package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
- package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
- package/dist/esm/interaction/spatial-grid.d.ts +15 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
- package/dist/esm/interaction/zoom-controller.d.ts +106 -0
- package/dist/esm/interaction/zoom-router.d.ts +48 -0
- package/dist/esm/layout/facet-grid.d.ts +126 -0
- package/dist/esm/layout/plot-layout.d.ts +104 -0
- package/dist/esm/layout/ticks.d.ts +17 -0
- package/dist/esm/map/mercator.d.ts +102 -0
- package/dist/esm/map/tile-cache.d.ts +38 -0
- package/dist/esm/map/tile-layer.d.ts +66 -0
- package/dist/esm/map/tile-loader.d.ts +52 -0
- package/dist/esm/map/tile-source.d.ts +66 -0
- package/dist/esm/perspective-viewer-charts.js +3 -0
- package/dist/esm/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/plugin/charts.d.ts +40 -0
- package/dist/esm/plugin/plugin.d.ts +95 -0
- package/dist/esm/render/scheduler.d.ts +41 -0
- package/dist/esm/theme/gradient.d.ts +48 -0
- package/dist/esm/theme/palette.d.ts +13 -0
- package/dist/esm/theme/theme-snapshot.d.ts +7 -0
- package/dist/esm/theme/theme.d.ts +53 -0
- package/dist/esm/transport/protocol.d.ts +430 -0
- package/dist/esm/transport/renderer-transport.d.ts +201 -0
- package/dist/esm/utils/css.d.ts +1 -0
- package/dist/esm/utils/font-snapshot.d.ts +50 -0
- package/dist/esm/webgl/buffer-pool.d.ts +62 -0
- package/dist/esm/webgl/context-manager.d.ts +184 -0
- package/dist/esm/webgl/gradient-texture.d.ts +17 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
- package/dist/esm/webgl/plot-frame.d.ts +39 -0
- package/dist/esm/webgl/program-cache.d.ts +13 -0
- package/dist/esm/webgl/shader-manifest.d.ts +53 -0
- package/dist/esm/webgl/shader-registry.d.ts +22 -0
- package/dist/esm/worker/boot.d.ts +0 -0
- package/dist/esm/worker/dispatch.d.ts +9 -0
- package/dist/esm/worker/font-loader.d.ts +2 -0
- package/dist/esm/worker/renderer.worker.d.ts +115 -0
- package/dist/esm/worker/session-host.d.ts +26 -0
- package/package.json +47 -0
- package/src/css/perspective-viewer-charts.css +95 -0
- package/src/ts/axis/axis-primitives.ts +125 -0
- package/src/ts/axis/bar-axis.ts +345 -0
- package/src/ts/axis/canvas.ts +64 -0
- package/src/ts/axis/categorical-axis-core.ts +125 -0
- package/src/ts/axis/categorical-axis.ts +716 -0
- package/src/ts/axis/facet-chrome.ts +42 -0
- package/src/ts/axis/label-geometry.ts +188 -0
- package/src/ts/axis/legend.ts +218 -0
- package/src/ts/axis/numeric-axis.ts +353 -0
- package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
- package/src/ts/charts/candlestick/candlestick.ts +367 -0
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
- package/src/ts/charts/canvas-types.ts +30 -0
- package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
- package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
- package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
- package/src/ts/charts/cartesian/cartesian.ts +469 -0
- package/src/ts/charts/cartesian/glyph.ts +81 -0
- package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
- package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
- package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
- package/src/ts/charts/cartesian/label-interner.ts +56 -0
- package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
- package/src/ts/charts/chart-base.ts +840 -0
- package/src/ts/charts/chart.ts +427 -0
- package/src/ts/charts/common/band-layout.ts +63 -0
- package/src/ts/charts/common/categorical-y-chart.ts +81 -0
- package/src/ts/charts/common/category-axis-resolver.ts +314 -0
- package/src/ts/charts/common/chrome-cache.ts +79 -0
- package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
- package/src/ts/charts/common/leaf-color.ts +92 -0
- package/src/ts/charts/common/node-store.ts +235 -0
- package/src/ts/charts/common/tree-chart.ts +76 -0
- package/src/ts/charts/common/tree-chrome.ts +123 -0
- package/src/ts/charts/common/tree-data.ts +623 -0
- package/src/ts/charts/common/visible-extent.ts +112 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
- package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
- package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
- package/src/ts/charts/heatmap/heatmap.ts +368 -0
- package/src/ts/charts/map/map.ts +201 -0
- package/src/ts/charts/registry.ts +65 -0
- package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
- package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
- package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
- package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
- package/src/ts/charts/series/series-build.ts +848 -0
- package/src/ts/charts/series/series-interact.ts +604 -0
- package/src/ts/charts/series/series-render.ts +1109 -0
- package/src/ts/charts/series/series-type.ts +99 -0
- package/src/ts/charts/series/series.ts +794 -0
- package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
- package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
- package/src/ts/charts/sunburst/sunburst.ts +248 -0
- package/src/ts/charts/treemap/treemap-interact.ts +445 -0
- package/src/ts/charts/treemap/treemap-layout.ts +328 -0
- package/src/ts/charts/treemap/treemap-render.ts +886 -0
- package/src/ts/charts/treemap/treemap.ts +247 -0
- package/src/ts/config.ts +41 -0
- package/src/ts/data/lazy-row.ts +140 -0
- package/src/ts/data/split-groups.ts +97 -0
- package/src/ts/data/view-reader.ts +107 -0
- package/src/ts/event-detail.ts +44 -0
- package/src/ts/index.ts +53 -0
- package/src/ts/interaction/hit-test.ts +106 -0
- package/src/ts/interaction/host-sink-dom.ts +85 -0
- package/src/ts/interaction/host-sink-message.ts +75 -0
- package/src/ts/interaction/lazy-tooltip.ts +102 -0
- package/src/ts/interaction/raw-event-forwarder.ts +175 -0
- package/src/ts/interaction/spatial-grid.ts +100 -0
- package/src/ts/interaction/tooltip-controller.ts +407 -0
- package/src/ts/interaction/zoom-controller.ts +468 -0
- package/src/ts/interaction/zoom-router.ts +230 -0
- package/src/ts/layout/facet-grid.ts +346 -0
- package/src/ts/layout/plot-layout.ts +277 -0
- package/src/ts/layout/ticks.ts +168 -0
- package/src/ts/map/mercator.ts +204 -0
- package/src/ts/map/tile-cache.ts +96 -0
- package/src/ts/map/tile-layer.ts +382 -0
- package/src/ts/map/tile-loader.ts +143 -0
- package/src/ts/map/tile-source.ts +156 -0
- package/src/ts/plugin/charts.ts +286 -0
- package/src/ts/plugin/plugin.ts +668 -0
- package/src/ts/render/scheduler.ts +339 -0
- package/src/ts/shaders/area.frag.glsl +20 -0
- package/src/ts/shaders/area.vert.glsl +19 -0
- package/src/ts/shaders/bar.frag.glsl +25 -0
- package/src/ts/shaders/bar.vert.glsl +60 -0
- package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
- package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
- package/src/ts/shaders/density-extreme.frag.glsl +30 -0
- package/src/ts/shaders/density-mrt.frag.glsl +44 -0
- package/src/ts/shaders/density-mrt.vert.glsl +48 -0
- package/src/ts/shaders/density-resolve.frag.glsl +89 -0
- package/src/ts/shaders/density-resolve.vert.glsl +23 -0
- package/src/ts/shaders/density-splat.frag.glsl +34 -0
- package/src/ts/shaders/density-splat.vert.glsl +52 -0
- package/src/ts/shaders/gridline.frag.glsl +18 -0
- package/src/ts/shaders/gridline.vert.glsl +18 -0
- package/src/ts/shaders/heatmap.frag.glsl +23 -0
- package/src/ts/shaders/heatmap.vert.glsl +42 -0
- package/src/ts/shaders/line-uniform.frag.glsl +26 -0
- package/src/ts/shaders/line-uniform.vert.glsl +54 -0
- package/src/ts/shaders/line.frag.glsl +28 -0
- package/src/ts/shaders/line.vert.glsl +87 -0
- package/src/ts/shaders/scatter.frag.glsl +39 -0
- package/src/ts/shaders/scatter.vert.glsl +67 -0
- package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
- package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
- package/src/ts/shaders/tile.frag.glsl +27 -0
- package/src/ts/shaders/tile.vert.glsl +35 -0
- package/src/ts/shaders/treemap.frag.glsl +19 -0
- package/src/ts/shaders/treemap.vert.glsl +25 -0
- package/src/ts/shaders/y-scatter.frag.glsl +30 -0
- package/src/ts/shaders/y-scatter.vert.glsl +31 -0
- package/src/ts/theme/gradient.ts +312 -0
- package/src/ts/theme/palette.ts +64 -0
- package/src/ts/theme/theme-snapshot.ts +66 -0
- package/src/ts/theme/theme.ts +166 -0
- package/src/ts/transport/protocol.ts +497 -0
- package/src/ts/transport/renderer-transport.ts +788 -0
- package/src/ts/utils/css.ts +36 -0
- package/src/ts/utils/font-snapshot.ts +159 -0
- package/src/ts/webgl/buffer-pool.ts +163 -0
- package/src/ts/webgl/context-manager.ts +414 -0
- package/src/ts/webgl/gradient-texture.ts +84 -0
- package/src/ts/webgl/instanced-attrs.ts +139 -0
- package/src/ts/webgl/plot-frame.ts +91 -0
- package/src/ts/webgl/program-cache.ts +46 -0
- package/src/ts/webgl/shader-manifest.ts +148 -0
- package/src/ts/webgl/shader-registry.ts +97 -0
- package/src/ts/worker/boot.ts +22 -0
- package/src/ts/worker/dispatch.ts +99 -0
- package/src/ts/worker/font-loader.ts +89 -0
- package/src/ts/worker/renderer.worker.ts +734 -0
- package/src/ts/worker/session-host.ts +118 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import "./boot";
|
|
14
|
+
|
|
15
|
+
import type { Client, Table, View } from "@perspective-dev/client";
|
|
16
|
+
|
|
17
|
+
import type * as wasm_module_type from "@perspective-dev/viewer/dist/wasm/perspective-viewer.js";
|
|
18
|
+
import { WebGLContextManager } from "../webgl/context-manager";
|
|
19
|
+
import { ChartImplementation } from "../charts/chart";
|
|
20
|
+
import { ZoomController } from "../interaction/zoom-controller";
|
|
21
|
+
import {
|
|
22
|
+
applyPan,
|
|
23
|
+
applyWheel,
|
|
24
|
+
type ZoomTarget,
|
|
25
|
+
} from "../interaction/zoom-router";
|
|
26
|
+
import { MessageHostSink } from "../interaction/host-sink-message";
|
|
27
|
+
import { CHART_IMPLS } from "../charts/registry";
|
|
28
|
+
import type { PlotLayout } from "../layout/plot-layout";
|
|
29
|
+
import type {
|
|
30
|
+
ControlMsg,
|
|
31
|
+
InitMsg,
|
|
32
|
+
InteractionEvent,
|
|
33
|
+
LoadAndRenderMsg,
|
|
34
|
+
WorkerMsg,
|
|
35
|
+
} from "../transport/protocol";
|
|
36
|
+
import { viewToColumnDataMap } from "../data/view-reader";
|
|
37
|
+
import { loadFontDeduped } from "./font-loader";
|
|
38
|
+
import { dispatch } from "./dispatch";
|
|
39
|
+
import { installSessionHost } from "./session-host";
|
|
40
|
+
import { deferIfDraining } from "../render/scheduler";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sentinel thrown inside the `with_typed_arrays` callback when a newer
|
|
44
|
+
* `loadAndRender` has bumped the generation counter. Lets the wasm-side
|
|
45
|
+
* Arrow buffer release path run (the callback's promise must reject
|
|
46
|
+
* cleanly so `with_typed_arrays` unwinds before the next call) without
|
|
47
|
+
* polluting the worker's error path — caught and swallowed by
|
|
48
|
+
* `loadAndRender`'s try/catch.
|
|
49
|
+
*/
|
|
50
|
+
class StaleGenerationError extends Error {
|
|
51
|
+
constructor() {
|
|
52
|
+
super("StaleGenerationError");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Renderer state. One per host element. In worker mode it lives in
|
|
58
|
+
* the worker; in in-process mode (host loads this module via dynamic
|
|
59
|
+
* `import(workerURL)`) it lives on the main thread. The class itself
|
|
60
|
+
* doesn't care — both modes drive it through a `MessagePort` of
|
|
61
|
+
* `ControlMsg`s.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a chart tag to its impl class via the lazy registry. Eager
|
|
65
|
+
* tags microtask-resolve; map tags trigger a dynamic `import()` that
|
|
66
|
+
* the bundler emits as a separately-fetched chunk.
|
|
67
|
+
*/
|
|
68
|
+
async function resolveChartImpl(
|
|
69
|
+
tag: string,
|
|
70
|
+
): Promise<new () => ChartImplementation> {
|
|
71
|
+
const factory = CHART_IMPLS[tag];
|
|
72
|
+
if (!factory) {
|
|
73
|
+
throw new Error(`Unknown chart tag: ${tag}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return await factory();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class WorkerRenderer {
|
|
80
|
+
chartImpl: ChartImplementation;
|
|
81
|
+
glManager: WebGLContextManager;
|
|
82
|
+
zoomController: ZoomController | null = null;
|
|
83
|
+
gridlines: OffscreenCanvas;
|
|
84
|
+
chrome: OffscreenCanvas;
|
|
85
|
+
cssWidth: number;
|
|
86
|
+
cssHeight: number;
|
|
87
|
+
dpr: number;
|
|
88
|
+
client: Client;
|
|
89
|
+
view: View;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Source `Table` opened once at bootstrap from the host-supplied
|
|
93
|
+
* `tableName`. Used by `loadAndRender` to fetch the source schema
|
|
94
|
+
* for group-by level types — the worker resolves it itself so the
|
|
95
|
+
* host's render path makes zero `Client`/`Table`/`View` awaits.
|
|
96
|
+
* Null when the host had no table loaded at init time.
|
|
97
|
+
*/
|
|
98
|
+
table: Table | null;
|
|
99
|
+
controlPort: MessagePort;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Monotonic counter bumped by every `loadAndRender` entry. Captured
|
|
103
|
+
* locally as `myGen` and re-checked after each await — a stale
|
|
104
|
+
* value means a newer call has superseded this one and we must
|
|
105
|
+
* bail (throwing inside the `with_typed_arrays` callback so the
|
|
106
|
+
* wasm Arrow buffer release runs cleanly).
|
|
107
|
+
*/
|
|
108
|
+
private _renderGen = 0;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Active drag state. `pointerdown` resolves a target via the
|
|
112
|
+
* facet grid and stores it; `pointermove` consults this until
|
|
113
|
+
* `pointerup` clears it. Pointer capture itself is host-side.
|
|
114
|
+
*/
|
|
115
|
+
private _dragTarget: ZoomTarget | null = null;
|
|
116
|
+
private _lastDragX = 0;
|
|
117
|
+
private _lastDragY = 0;
|
|
118
|
+
|
|
119
|
+
constructor(
|
|
120
|
+
msg: InitMsg,
|
|
121
|
+
client: Client,
|
|
122
|
+
view: View,
|
|
123
|
+
table: Table | null,
|
|
124
|
+
controlPort: MessagePort,
|
|
125
|
+
ImplClass: new () => ChartImplementation,
|
|
126
|
+
) {
|
|
127
|
+
this.client = client;
|
|
128
|
+
this.view = view;
|
|
129
|
+
this.table = table;
|
|
130
|
+
this.controlPort = controlPort;
|
|
131
|
+
|
|
132
|
+
this.chartImpl = new ImplClass();
|
|
133
|
+
|
|
134
|
+
// Direct mode hands us the host's transferred `.webgl-canvas`.
|
|
135
|
+
// Blit mode omits it — the renderer owns its own offscreen
|
|
136
|
+
// surface and posts each completed frame back as an
|
|
137
|
+
// `ImageBitmap` via the `endFrame` callback wired below.
|
|
138
|
+
const glCanvas =
|
|
139
|
+
msg.glCanvas ??
|
|
140
|
+
new OffscreenCanvas(
|
|
141
|
+
Math.max(1, Math.round(msg.cssWidth * msg.dpr)),
|
|
142
|
+
Math.max(1, Math.round(msg.cssHeight * msg.dpr)),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
this.glManager = new WebGLContextManager(glCanvas, {
|
|
146
|
+
precompile: msg.precompileShaders ?? false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (msg.renderMode === "blit") {
|
|
150
|
+
this.glManager.setFrameCallback((bitmap) => {
|
|
151
|
+
this.post({ kind: "frameBitmap", bitmap }, [bitmap]);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.gridlines = msg.gridlinesCanvas;
|
|
156
|
+
this.chrome = msg.chromeCanvas;
|
|
157
|
+
this.cssWidth = msg.cssWidth;
|
|
158
|
+
this.cssHeight = msg.cssHeight;
|
|
159
|
+
this.dpr = msg.dpr;
|
|
160
|
+
|
|
161
|
+
this.chartImpl.setGridlineCanvas?.(msg.gridlinesCanvas);
|
|
162
|
+
this.chartImpl.setChromeCanvas?.(msg.chromeCanvas);
|
|
163
|
+
this.chartImpl.setTheme?.(msg.themeVars);
|
|
164
|
+
|
|
165
|
+
if (msg.defaultChartType) {
|
|
166
|
+
this.chartImpl.setDefaultChartType?.(msg.defaultChartType);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.chartImpl.setFacetConfig?.(msg.facetConfig);
|
|
170
|
+
this.chartImpl.setPluginConfig?.(msg.pluginConfig);
|
|
171
|
+
|
|
172
|
+
if (this.chartImpl.setZoomController) {
|
|
173
|
+
this.zoomController = new ZoomController();
|
|
174
|
+
this.chartImpl.setZoomController(this.zoomController);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.chartImpl.setView?.(view);
|
|
178
|
+
this.glManager.bufferPool.maxCapacity = msg.bufferMaxCapacity;
|
|
179
|
+
this.glManager.resize(msg.cssWidth, msg.cssHeight, msg.dpr);
|
|
180
|
+
const hostSink = new MessageHostSink((envelope) => {
|
|
181
|
+
switch (envelope.kind) {
|
|
182
|
+
case "pin":
|
|
183
|
+
this.post({
|
|
184
|
+
kind: "pinTooltip",
|
|
185
|
+
lines: envelope.payload.lines,
|
|
186
|
+
pos: envelope.payload.pos,
|
|
187
|
+
bounds: envelope.payload.bounds,
|
|
188
|
+
});
|
|
189
|
+
break;
|
|
190
|
+
case "dismiss":
|
|
191
|
+
this.post({ kind: "dismissTooltip" });
|
|
192
|
+
break;
|
|
193
|
+
case "setCursor":
|
|
194
|
+
this.post({ kind: "setCursor", cursor: envelope.cursor });
|
|
195
|
+
break;
|
|
196
|
+
case "userClick":
|
|
197
|
+
this.post({
|
|
198
|
+
kind: "userClick",
|
|
199
|
+
detail: envelope.payload as any,
|
|
200
|
+
});
|
|
201
|
+
break;
|
|
202
|
+
case "userSelect":
|
|
203
|
+
this.post({
|
|
204
|
+
kind: "userSelect",
|
|
205
|
+
selected: envelope.payload.selected,
|
|
206
|
+
row: envelope.payload.row,
|
|
207
|
+
column_names: envelope.payload.column_names,
|
|
208
|
+
insertConfig: envelope.payload.insertConfig as any,
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.chartImpl.attachTooltip?.(hostSink);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setViewByName(name: string): void {
|
|
218
|
+
this.view = this.client.__unsafe_open_view(name);
|
|
219
|
+
this.chartImpl.setView?.(this.view);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Full data-fetch + render pipeline. Owns every `Client`/`Table`/
|
|
224
|
+
* `View` await on the render path:
|
|
225
|
+
*
|
|
226
|
+
* 1. Resolve metadata (`view.num_rows`, `view.schema`,
|
|
227
|
+
* `view.expression_schema`, `table.schema`) in parallel.
|
|
228
|
+
* 2. Apply schema + viewer-config to the chart impl (replaces the
|
|
229
|
+
* individual `setColumnTypes` / `setGroupByTypes` /
|
|
230
|
+
* `setViewPivots` / `setColumnSlots` setters that used to
|
|
231
|
+
* stream from the host).
|
|
232
|
+
* 3. Compute `totalRows` from `bufferPool.maxCapacity / numCols`
|
|
233
|
+
* and grow the buffer pool to fit.
|
|
234
|
+
* 4. Run `view.with_typed_arrays`; the inner callback hands the
|
|
235
|
+
* resulting `ColumnDataMap` straight to
|
|
236
|
+
* `chartImpl.uploadAndRender` — no `postMessage`, no transfer.
|
|
237
|
+
*
|
|
238
|
+
* Mid-flight cancellation: each entry bumps `_renderGen` and
|
|
239
|
+
* captures `myGen`. After the metadata await we re-check; if a
|
|
240
|
+
* newer call has superseded this one, ack-and-return so the host
|
|
241
|
+
* promise resolves cleanly. Inside the `with_typed_arrays`
|
|
242
|
+
* callback the same check throws `StaleGenerationError` so the
|
|
243
|
+
* wasm Arrow buffer release path runs (callback's promise must
|
|
244
|
+
* reject for `with_typed_arrays` to unwind) before the next call
|
|
245
|
+
* proceeds — caught and swallowed here.
|
|
246
|
+
*
|
|
247
|
+
* Always sends `loadAndRenderAck` (even on stale drop) per the
|
|
248
|
+
* "resolve on stale" host contract.
|
|
249
|
+
*/
|
|
250
|
+
async loadAndRender(msg: LoadAndRenderMsg): Promise<void> {
|
|
251
|
+
const myGen = ++this._renderGen;
|
|
252
|
+
try {
|
|
253
|
+
const [numRows, schema, exprSchema, tableSchema] =
|
|
254
|
+
await Promise.all([
|
|
255
|
+
this.view.num_rows(),
|
|
256
|
+
this.view.schema() as Promise<Record<string, string>>,
|
|
257
|
+
this.view.expression_schema() as Promise<
|
|
258
|
+
Record<string, string>
|
|
259
|
+
>,
|
|
260
|
+
(this.table?.schema() ?? Promise.resolve({})) as Promise<
|
|
261
|
+
Record<string, string>
|
|
262
|
+
>,
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
if (this._renderGen !== myGen) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Order mirrors the pre-refactor host-side message stream
|
|
270
|
+
// (pivots → types → groupByTypes → slots) — chart impls
|
|
271
|
+
// assume types/groupByTypes are pushed after pivots so
|
|
272
|
+
// axis-builder code paths see consistent state.
|
|
273
|
+
this.chartImpl.setViewPivots?.(
|
|
274
|
+
msg.viewerConfig.group_by,
|
|
275
|
+
msg.viewerConfig.split_by,
|
|
276
|
+
);
|
|
277
|
+
this.chartImpl.setColumnTypes?.(schema);
|
|
278
|
+
this.chartImpl.setGroupByTypes?.({ ...tableSchema, ...exprSchema });
|
|
279
|
+
this.chartImpl.setColumnSlots?.(msg.viewerConfig.columns);
|
|
280
|
+
|
|
281
|
+
const numCols = Object.keys(schema).length || 1;
|
|
282
|
+
const maxRows = Math.floor(
|
|
283
|
+
this.glManager.bufferPool.maxCapacity / numCols,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const totalRows = Math.min(numRows, maxRows);
|
|
287
|
+
this.glManager.ensureBufferCapacity(totalRows);
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await viewToColumnDataMap(
|
|
291
|
+
this.view,
|
|
292
|
+
async (cols) => {
|
|
293
|
+
if (this._renderGen !== myGen) {
|
|
294
|
+
throw new StaleGenerationError();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await this.chartImpl.uploadAndRender(
|
|
298
|
+
this.glManager,
|
|
299
|
+
cols,
|
|
300
|
+
0,
|
|
301
|
+
totalRows,
|
|
302
|
+
);
|
|
303
|
+
},
|
|
304
|
+
{ end_row: totalRows, float32: msg.options.float32 },
|
|
305
|
+
);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
if (!(e instanceof StaleGenerationError)) {
|
|
308
|
+
throw e;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
// Any unexpected throw — proxy hiccup, chart-impl mutation
|
|
313
|
+
// failure, RAF chain rejection — must not leak past the
|
|
314
|
+
// outer fire-and-forget caller (`dispatch` does not await
|
|
315
|
+
// this method). Surface to the worker console; the host's
|
|
316
|
+
// pending promise still gets resolved via the `finally`
|
|
317
|
+
// ack below so `draw()` can't deadlock on a renderer error.
|
|
318
|
+
console.error("loadAndRender failed", err);
|
|
319
|
+
} finally {
|
|
320
|
+
this.post({ kind: "loadAndRenderAck", msgId: msg.msgId });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
redraw(): void {
|
|
325
|
+
this.chartImpl.requestRender(this.glManager);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
resize(cssWidth: number, cssHeight: number, dpr: number): void {
|
|
329
|
+
// `glManager.resize` would set `canvas.width = N`, which the
|
|
330
|
+
// spec mandates clears the drawing buffer immediately. In
|
|
331
|
+
// direct / in-process modes the GL canvas IS the host's
|
|
332
|
+
// visible canvas, so a clear at message-receipt time
|
|
333
|
+
// followed by a paint on the next RAF leaves one full
|
|
334
|
+
// compositor cycle between them displaying an empty buffer
|
|
335
|
+
// — visible flicker.
|
|
336
|
+
//
|
|
337
|
+
// `requestResize` only stores the pending dimensions; the
|
|
338
|
+
// `canvas.width = N` assignment is deferred to the next
|
|
339
|
+
// `drain()` Phase 1, where it runs in the same un-yielded
|
|
340
|
+
// synchronous loop as `_fullRender`. Compositor only
|
|
341
|
+
// observes the post-paint state.
|
|
342
|
+
//
|
|
343
|
+
// Because `requestResize` is a pure JS-state operation (no
|
|
344
|
+
// GL ops, no canvas mutation), it doesn't need
|
|
345
|
+
// `deferIfDraining` — it's safe to call concurrently with
|
|
346
|
+
// an in-flight drain. The drain serialization at the
|
|
347
|
+
// scheduler level ensures the actual `applyPendingResize`
|
|
348
|
+
// happens between drains, never during one.
|
|
349
|
+
//
|
|
350
|
+
// Multiple `requestResize` calls before the next render
|
|
351
|
+
// coalesce: last write wins. Five rapid width changes from
|
|
352
|
+
// a window-drag produce one resize+paint, not five.
|
|
353
|
+
this.cssWidth = cssWidth;
|
|
354
|
+
this.cssHeight = cssHeight;
|
|
355
|
+
this.dpr = dpr;
|
|
356
|
+
this.glManager.requestResize(cssWidth, cssHeight, dpr);
|
|
357
|
+
this.chartImpl.requestRender(this.glManager);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
clear(): void {
|
|
361
|
+
// Same rationale as `resize`: `gl.clear` would queue after
|
|
362
|
+
// Phase 1's draws but could execute before
|
|
363
|
+
// `transferToImageBitmap`, wiping the bitmap. Defer.
|
|
364
|
+
deferIfDraining(this.glManager, () => {
|
|
365
|
+
this.glManager.clear();
|
|
366
|
+
const ctx = this.gridlines.getContext("2d");
|
|
367
|
+
ctx?.clearRect(0, 0, this.gridlines.width, this.gridlines.height);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
saveZoom(): any {
|
|
372
|
+
return this.zoomController?.serialize();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
restoreZoom(state: any): void {
|
|
376
|
+
if (state) {
|
|
377
|
+
this.zoomController?.restore(state);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
allZoomsDefault(): boolean {
|
|
382
|
+
if (this.zoomController && !this.zoomController.isDefault()) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const facets = (this.chartImpl as any)?._facetZoomControllers;
|
|
387
|
+
if (facets) {
|
|
388
|
+
for (const zc of facets) {
|
|
389
|
+
if (zc && !zc.isDefault()) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
resetAllZooms(): void {
|
|
399
|
+
this.zoomController?.reset();
|
|
400
|
+
const facets = (this.chartImpl as any)?._facetZoomControllers;
|
|
401
|
+
if (facets) {
|
|
402
|
+
for (const zc of facets) {
|
|
403
|
+
zc?.reset();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Also drop any `domain_mode: "expand"` accumulator — the user
|
|
408
|
+
// explicitly asked for a clean reset, so the next data load
|
|
409
|
+
// should start from the fresh data extent rather than the
|
|
410
|
+
// previously-grown one.
|
|
411
|
+
this.resetExpandedDomain();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
resetExpandedDomain(): void {
|
|
415
|
+
this.chartImpl.resetExpandedDomain?.();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Hit-test the cursor against the chart's facet grid (in faceted
|
|
420
|
+
* mode) or its current layout (single-plot). Mirrors the resolver
|
|
421
|
+
* `_setupZoomRouter` builds on the host for in-process mode — the
|
|
422
|
+
* worker owns the facet grid and controllers, so the resolution
|
|
423
|
+
* runs here.
|
|
424
|
+
*/
|
|
425
|
+
private _resolveTarget(mx: number, my: number): ZoomTarget | null {
|
|
426
|
+
const chart = this.chartImpl as any;
|
|
427
|
+
const facetGrid = chart?._facetGrid as
|
|
428
|
+
| { cells: { layout: PlotLayout }[] }
|
|
429
|
+
| null
|
|
430
|
+
| undefined;
|
|
431
|
+
if (facetGrid) {
|
|
432
|
+
for (let i = 0; i < facetGrid.cells.length; i++) {
|
|
433
|
+
const cell = facetGrid.cells[i];
|
|
434
|
+
const plot = cell.layout.plotRect;
|
|
435
|
+
if (
|
|
436
|
+
mx >= plot.x &&
|
|
437
|
+
mx <= plot.x + plot.width &&
|
|
438
|
+
my >= plot.y &&
|
|
439
|
+
my <= plot.y + plot.height
|
|
440
|
+
) {
|
|
441
|
+
const zc =
|
|
442
|
+
chart.getZoomControllerForFacet?.(i) ??
|
|
443
|
+
this.zoomController;
|
|
444
|
+
return zc ? { controller: zc, layout: cell.layout } : null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!this.zoomController) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const layout = chart?._lastLayout as PlotLayout | null | undefined;
|
|
456
|
+
if (!layout) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const plot = layout.plotRect;
|
|
461
|
+
if (
|
|
462
|
+
mx < plot.x ||
|
|
463
|
+
mx > plot.x + plot.width ||
|
|
464
|
+
my < plot.y ||
|
|
465
|
+
my > plot.y + plot.height
|
|
466
|
+
) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { controller: this.zoomController, layout };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
onInteraction(event: InteractionEvent): void {
|
|
474
|
+
switch (event.type) {
|
|
475
|
+
case "wheel": {
|
|
476
|
+
const target = this._resolveTarget(event.mx, event.my);
|
|
477
|
+
if (!target) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
applyWheel(target, event.mx, event.my, event.deltaY);
|
|
482
|
+
this.chartImpl.requestRender(this.glManager);
|
|
483
|
+
this.post({
|
|
484
|
+
kind: "zoomChanged",
|
|
485
|
+
isDefault: this.allZoomsDefault(),
|
|
486
|
+
});
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
case "pointerdown": {
|
|
491
|
+
const target = this._resolveTarget(event.mx, event.my);
|
|
492
|
+
if (!target) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
this._dragTarget = target;
|
|
497
|
+
this._lastDragX = event.mx;
|
|
498
|
+
this._lastDragY = event.my;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
case "pointermove": {
|
|
503
|
+
if (this._dragTarget) {
|
|
504
|
+
// Mid-drag: pan only; suppress hover dispatch so
|
|
505
|
+
// the tooltip doesn't chase the cursor across a
|
|
506
|
+
// zoom gesture.
|
|
507
|
+
const dx = event.mx - this._lastDragX;
|
|
508
|
+
const dy = event.my - this._lastDragY;
|
|
509
|
+
this._lastDragX = event.mx;
|
|
510
|
+
this._lastDragY = event.my;
|
|
511
|
+
applyPan(this._dragTarget, dx, dy);
|
|
512
|
+
this.chartImpl.requestRender(this.glManager);
|
|
513
|
+
this.post({
|
|
514
|
+
kind: "zoomChanged",
|
|
515
|
+
isDefault: this.allZoomsDefault(),
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
// Plain hover: route into the chart's
|
|
519
|
+
// `TooltipController` (RAF-coalesced).
|
|
520
|
+
this._tooltip()?.dispatchHover(event.mx, event.my);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
case "pointerup": {
|
|
527
|
+
this._dragTarget = null;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
case "pointerleave": {
|
|
532
|
+
this._tooltip()?.dispatchLeave();
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
case "click": {
|
|
537
|
+
this._tooltip()?.dispatchClick(event.mx, event.my);
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case "dblclick": {
|
|
542
|
+
this._tooltip()?.dispatchDblClick(event.mx, event.my);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Read the chart impl's `TooltipController`. Charts that don't use
|
|
550
|
+
* one (no `attachTooltip` override) yield `null` and the
|
|
551
|
+
* mouse-event branches fall through.
|
|
552
|
+
*/
|
|
553
|
+
private _tooltip(): {
|
|
554
|
+
dispatchHover: (mx: number, my: number) => void;
|
|
555
|
+
dispatchLeave: () => void;
|
|
556
|
+
dispatchClick: (mx: number, my: number) => void;
|
|
557
|
+
dispatchDblClick: (mx: number, my: number) => void;
|
|
558
|
+
} | null {
|
|
559
|
+
const tt = (this.chartImpl as any)?._tooltip;
|
|
560
|
+
return tt ?? null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Composite the three layers into a single PNG `Blob`.
|
|
565
|
+
*/
|
|
566
|
+
async snapshotPng(): Promise<Blob> {
|
|
567
|
+
// Snapshot bypasses the scheduler's drain, so it must
|
|
568
|
+
// mirror Phase 1's "apply pending resize before paint"
|
|
569
|
+
// step itself — otherwise a snapshot taken after a resize
|
|
570
|
+
// message but before the next drain would render at the
|
|
571
|
+
// previous dimensions.
|
|
572
|
+
this.glManager.applyPendingResize();
|
|
573
|
+
this.chartImpl._fullRender(this.glManager);
|
|
574
|
+
const gl = this.glManager.gl;
|
|
575
|
+
const glCanvas = gl.canvas as OffscreenCanvas;
|
|
576
|
+
const w = glCanvas.width;
|
|
577
|
+
const h = glCanvas.height;
|
|
578
|
+
const pixels = new Uint8ClampedArray(w * h * 4);
|
|
579
|
+
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
580
|
+
const composite = new OffscreenCanvas(w, h);
|
|
581
|
+
const ctx = composite.getContext("2d");
|
|
582
|
+
if (!ctx) {
|
|
583
|
+
throw new Error("snapshotPng: 2D context unavailable");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const theme = (this.chartImpl as any)._resolveTheme?.();
|
|
587
|
+
const bg = theme?.backgroundColor ?? "transparent";
|
|
588
|
+
if (bg !== "transparent") {
|
|
589
|
+
ctx.fillStyle = bg;
|
|
590
|
+
ctx.fillRect(0, 0, w, h);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
ctx.drawImage(this.gridlines, 0, 0);
|
|
594
|
+
const glLayer = new OffscreenCanvas(w, h);
|
|
595
|
+
const glCtx = glLayer.getContext("2d");
|
|
596
|
+
if (!glCtx) {
|
|
597
|
+
throw new Error("snapshotPng: 2D context unavailable for GL blit");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
glCtx.putImageData(new ImageData(pixels, w, h), 0, 0);
|
|
601
|
+
ctx.save();
|
|
602
|
+
ctx.scale(1, -1);
|
|
603
|
+
|
|
604
|
+
// `readPixels` returns rows bottom-up; flip on the Y axis
|
|
605
|
+
ctx.drawImage(glLayer, 0, -h);
|
|
606
|
+
ctx.restore();
|
|
607
|
+
ctx.drawImage(this.chrome, 0, 0);
|
|
608
|
+
|
|
609
|
+
return await composite.convertToBlob({ type: "image/png" });
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
destroy(): void {
|
|
613
|
+
this.chartImpl.destroy();
|
|
614
|
+
this.glManager.destroy();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
post(msg: WorkerMsg, transfer?: Transferable[]): void {
|
|
618
|
+
if (transfer && transfer.length > 0) {
|
|
619
|
+
this.controlPort.postMessage(msg, transfer);
|
|
620
|
+
} else {
|
|
621
|
+
this.controlPort.postMessage(msg);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Detect whether this module is loaded in a Web Worker scope.
|
|
628
|
+
*/
|
|
629
|
+
const IS_WORKER_SCOPE = typeof (globalThis as any).importScripts === "function";
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Worker-mode bootstrap: receives the host's `InitMsg`, instantiates
|
|
633
|
+
* wasm, registers fonts, opens a `Client` against the host's
|
|
634
|
+
* `ProxySession`, and constructs a {@link WorkerRenderer} bound to the
|
|
635
|
+
* supplied control port (which in worker scope is `self`).
|
|
636
|
+
*/
|
|
637
|
+
async function bootstrapWorker(
|
|
638
|
+
msg: InitMsg,
|
|
639
|
+
host: MessagePort,
|
|
640
|
+
): Promise<WorkerRenderer> {
|
|
641
|
+
if (!msg.clientWorkerURL || !msg.clientWasm || !msg.proxyPort) {
|
|
642
|
+
throw new Error("Init error");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const module = (await import(
|
|
646
|
+
msg.clientWorkerURL.toString()
|
|
647
|
+
)) as typeof wasm_module_type;
|
|
648
|
+
|
|
649
|
+
await module.initSync({ module: msg.clientWasm });
|
|
650
|
+
|
|
651
|
+
// Register every `@font-face` the host found in its document so
|
|
652
|
+
// Canvas2D `ctx.font` lookups inside this worker resolve correctly.
|
|
653
|
+
if (msg.fontFaces?.length) {
|
|
654
|
+
await Promise.all(msg.fontFaces.map(loadFontDeduped));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const proxyPort = msg.proxyPort;
|
|
658
|
+
const client = new module.Client(
|
|
659
|
+
async (proto: Uint8Array) => {
|
|
660
|
+
const buf = proto.slice().buffer;
|
|
661
|
+
proxyPort.postMessage(buf, [buf]);
|
|
662
|
+
},
|
|
663
|
+
async () => proxyPort.close(),
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
proxyPort.addEventListener("message", (e: MessageEvent) => {
|
|
667
|
+
client.handle_response(new Uint8Array(e.data));
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
proxyPort.start();
|
|
671
|
+
const view = client.__unsafe_open_view(msg.viewName);
|
|
672
|
+
const table = msg.tableName ? await client.open_table(msg.tableName) : null;
|
|
673
|
+
const ImplClass = await resolveChartImpl(msg.chartTag);
|
|
674
|
+
const renderer = new WorkerRenderer(
|
|
675
|
+
msg,
|
|
676
|
+
client,
|
|
677
|
+
view,
|
|
678
|
+
table,
|
|
679
|
+
host,
|
|
680
|
+
ImplClass,
|
|
681
|
+
);
|
|
682
|
+
renderer.post({ kind: "ready" });
|
|
683
|
+
return renderer;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* In-process bootstrap. Used when the host loads this same module via
|
|
688
|
+
* `await import(workerURL)` to run the renderer on the main thread —
|
|
689
|
+
* skips the wasm / font / proxy-port plumbing because the host already
|
|
690
|
+
* owns a live `Client` and the document's `FontFaceSet` is the active
|
|
691
|
+
* one.
|
|
692
|
+
*/
|
|
693
|
+
export async function bootstrapInProcess(opts: {
|
|
694
|
+
msg: InitMsg;
|
|
695
|
+
client: Client;
|
|
696
|
+
controlPort: MessagePort;
|
|
697
|
+
}): Promise<WorkerRenderer> {
|
|
698
|
+
const view = opts.client.__unsafe_open_view(opts.msg.viewName);
|
|
699
|
+
const table = opts.msg.tableName
|
|
700
|
+
? await opts.client.open_table(opts.msg.tableName)
|
|
701
|
+
: null;
|
|
702
|
+
const ImplClass = await resolveChartImpl(opts.msg.chartTag);
|
|
703
|
+
const renderer = new WorkerRenderer(
|
|
704
|
+
opts.msg,
|
|
705
|
+
opts.client,
|
|
706
|
+
view,
|
|
707
|
+
table,
|
|
708
|
+
opts.controlPort,
|
|
709
|
+
ImplClass,
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
// Listen for control messages on the same port so the host's
|
|
713
|
+
// `RendererTransport` shape doesn't need to branch.
|
|
714
|
+
opts.controlPort.addEventListener("message", (e: MessageEvent) => {
|
|
715
|
+
const ctrl = e.data as ControlMsg;
|
|
716
|
+
if (ctrl?.kind === "init") {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
dispatch(renderer, ctrl);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
opts.controlPort.start();
|
|
724
|
+
|
|
725
|
+
renderer.post({ kind: "ready" });
|
|
726
|
+
return renderer;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Worker scope only: install the shared message handler . The same module is
|
|
730
|
+
// dynamic-imported on the main thread (in-process mode) where this branch is
|
|
731
|
+
// skipped.
|
|
732
|
+
if (IS_WORKER_SCOPE) {
|
|
733
|
+
installSessionHost(bootstrapWorker);
|
|
734
|
+
}
|