@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.
Files changed (258) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/cdn/perspective-viewer-charts.js +3 -0
  3. package/dist/cdn/perspective-viewer-charts.js.map +7 -0
  4. package/dist/esm/axis/axis-primitives.d.ts +24 -0
  5. package/dist/esm/axis/bar-axis.d.ts +51 -0
  6. package/dist/esm/axis/canvas.d.ts +24 -0
  7. package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
  8. package/dist/esm/axis/categorical-axis.d.ts +27 -0
  9. package/dist/esm/axis/facet-chrome.d.ts +13 -0
  10. package/dist/esm/axis/label-geometry.d.ts +41 -0
  11. package/dist/esm/axis/legend.d.ts +44 -0
  12. package/dist/esm/axis/numeric-axis.d.ts +20 -0
  13. package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
  14. package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
  15. package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
  16. package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
  17. package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
  18. package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
  19. package/dist/esm/charts/canvas-types.d.ts +15 -0
  20. package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
  21. package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
  22. package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
  23. package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
  24. package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
  25. package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
  26. package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
  27. package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
  28. package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
  29. package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
  30. package/dist/esm/charts/chart-base.d.ts +402 -0
  31. package/dist/esm/charts/chart.d.ts +338 -0
  32. package/dist/esm/charts/common/band-layout.d.ts +32 -0
  33. package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
  34. package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
  35. package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
  36. package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
  37. package/dist/esm/charts/common/leaf-color.d.ts +33 -0
  38. package/dist/esm/charts/common/node-store.d.ts +81 -0
  39. package/dist/esm/charts/common/tree-chart.d.ts +48 -0
  40. package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
  41. package/dist/esm/charts/common/tree-data.d.ts +54 -0
  42. package/dist/esm/charts/common/visible-extent.d.ts +51 -0
  43. package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
  44. package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
  45. package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
  46. package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
  47. package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
  48. package/dist/esm/charts/map/map.d.ts +67 -0
  49. package/dist/esm/charts/registry.d.ts +14 -0
  50. package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
  51. package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
  52. package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
  53. package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
  54. package/dist/esm/charts/series/series-build.d.ts +228 -0
  55. package/dist/esm/charts/series/series-interact.d.ts +35 -0
  56. package/dist/esm/charts/series/series-render.d.ts +41 -0
  57. package/dist/esm/charts/series/series-type.d.ts +49 -0
  58. package/dist/esm/charts/series/series.d.ts +317 -0
  59. package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
  60. package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
  61. package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
  62. package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
  63. package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
  64. package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
  65. package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
  66. package/dist/esm/charts/treemap/treemap.d.ts +74 -0
  67. package/dist/esm/config.d.ts +27 -0
  68. package/dist/esm/data/lazy-row.d.ts +32 -0
  69. package/dist/esm/data/split-groups.d.ts +20 -0
  70. package/dist/esm/data/view-reader.d.ts +35 -0
  71. package/dist/esm/event-detail.d.ts +28 -0
  72. package/dist/esm/index.d.ts +3 -0
  73. package/dist/esm/interaction/hit-test.d.ts +30 -0
  74. package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
  75. package/dist/esm/interaction/host-sink-message.d.ts +46 -0
  76. package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
  77. package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
  78. package/dist/esm/interaction/spatial-grid.d.ts +15 -0
  79. package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
  80. package/dist/esm/interaction/zoom-controller.d.ts +106 -0
  81. package/dist/esm/interaction/zoom-router.d.ts +48 -0
  82. package/dist/esm/layout/facet-grid.d.ts +126 -0
  83. package/dist/esm/layout/plot-layout.d.ts +104 -0
  84. package/dist/esm/layout/ticks.d.ts +17 -0
  85. package/dist/esm/map/mercator.d.ts +102 -0
  86. package/dist/esm/map/tile-cache.d.ts +38 -0
  87. package/dist/esm/map/tile-layer.d.ts +66 -0
  88. package/dist/esm/map/tile-loader.d.ts +52 -0
  89. package/dist/esm/map/tile-source.d.ts +66 -0
  90. package/dist/esm/perspective-viewer-charts.js +3 -0
  91. package/dist/esm/perspective-viewer-charts.js.map +7 -0
  92. package/dist/esm/plugin/charts.d.ts +40 -0
  93. package/dist/esm/plugin/plugin.d.ts +95 -0
  94. package/dist/esm/render/scheduler.d.ts +41 -0
  95. package/dist/esm/theme/gradient.d.ts +48 -0
  96. package/dist/esm/theme/palette.d.ts +13 -0
  97. package/dist/esm/theme/theme-snapshot.d.ts +7 -0
  98. package/dist/esm/theme/theme.d.ts +53 -0
  99. package/dist/esm/transport/protocol.d.ts +430 -0
  100. package/dist/esm/transport/renderer-transport.d.ts +201 -0
  101. package/dist/esm/utils/css.d.ts +1 -0
  102. package/dist/esm/utils/font-snapshot.d.ts +50 -0
  103. package/dist/esm/webgl/buffer-pool.d.ts +62 -0
  104. package/dist/esm/webgl/context-manager.d.ts +184 -0
  105. package/dist/esm/webgl/gradient-texture.d.ts +17 -0
  106. package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
  107. package/dist/esm/webgl/plot-frame.d.ts +39 -0
  108. package/dist/esm/webgl/program-cache.d.ts +13 -0
  109. package/dist/esm/webgl/shader-manifest.d.ts +53 -0
  110. package/dist/esm/webgl/shader-registry.d.ts +22 -0
  111. package/dist/esm/worker/boot.d.ts +0 -0
  112. package/dist/esm/worker/dispatch.d.ts +9 -0
  113. package/dist/esm/worker/font-loader.d.ts +2 -0
  114. package/dist/esm/worker/renderer.worker.d.ts +115 -0
  115. package/dist/esm/worker/session-host.d.ts +26 -0
  116. package/package.json +47 -0
  117. package/src/css/perspective-viewer-charts.css +95 -0
  118. package/src/ts/axis/axis-primitives.ts +125 -0
  119. package/src/ts/axis/bar-axis.ts +345 -0
  120. package/src/ts/axis/canvas.ts +64 -0
  121. package/src/ts/axis/categorical-axis-core.ts +125 -0
  122. package/src/ts/axis/categorical-axis.ts +716 -0
  123. package/src/ts/axis/facet-chrome.ts +42 -0
  124. package/src/ts/axis/label-geometry.ts +188 -0
  125. package/src/ts/axis/legend.ts +218 -0
  126. package/src/ts/axis/numeric-axis.ts +353 -0
  127. package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
  128. package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
  129. package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
  130. package/src/ts/charts/candlestick/candlestick.ts +367 -0
  131. package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
  132. package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
  133. package/src/ts/charts/canvas-types.ts +30 -0
  134. package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
  135. package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
  136. package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
  137. package/src/ts/charts/cartesian/cartesian.ts +469 -0
  138. package/src/ts/charts/cartesian/glyph.ts +81 -0
  139. package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
  140. package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
  141. package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
  142. package/src/ts/charts/cartesian/label-interner.ts +56 -0
  143. package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
  144. package/src/ts/charts/chart-base.ts +840 -0
  145. package/src/ts/charts/chart.ts +427 -0
  146. package/src/ts/charts/common/band-layout.ts +63 -0
  147. package/src/ts/charts/common/categorical-y-chart.ts +81 -0
  148. package/src/ts/charts/common/category-axis-resolver.ts +314 -0
  149. package/src/ts/charts/common/chrome-cache.ts +79 -0
  150. package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
  151. package/src/ts/charts/common/leaf-color.ts +92 -0
  152. package/src/ts/charts/common/node-store.ts +235 -0
  153. package/src/ts/charts/common/tree-chart.ts +76 -0
  154. package/src/ts/charts/common/tree-chrome.ts +123 -0
  155. package/src/ts/charts/common/tree-data.ts +623 -0
  156. package/src/ts/charts/common/visible-extent.ts +112 -0
  157. package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
  158. package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
  159. package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
  160. package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
  161. package/src/ts/charts/heatmap/heatmap.ts +368 -0
  162. package/src/ts/charts/map/map.ts +201 -0
  163. package/src/ts/charts/registry.ts +65 -0
  164. package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
  165. package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
  166. package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
  167. package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
  168. package/src/ts/charts/series/series-build.ts +848 -0
  169. package/src/ts/charts/series/series-interact.ts +604 -0
  170. package/src/ts/charts/series/series-render.ts +1109 -0
  171. package/src/ts/charts/series/series-type.ts +99 -0
  172. package/src/ts/charts/series/series.ts +794 -0
  173. package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
  174. package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
  175. package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
  176. package/src/ts/charts/sunburst/sunburst.ts +248 -0
  177. package/src/ts/charts/treemap/treemap-interact.ts +445 -0
  178. package/src/ts/charts/treemap/treemap-layout.ts +328 -0
  179. package/src/ts/charts/treemap/treemap-render.ts +886 -0
  180. package/src/ts/charts/treemap/treemap.ts +247 -0
  181. package/src/ts/config.ts +41 -0
  182. package/src/ts/data/lazy-row.ts +140 -0
  183. package/src/ts/data/split-groups.ts +97 -0
  184. package/src/ts/data/view-reader.ts +107 -0
  185. package/src/ts/event-detail.ts +44 -0
  186. package/src/ts/index.ts +53 -0
  187. package/src/ts/interaction/hit-test.ts +106 -0
  188. package/src/ts/interaction/host-sink-dom.ts +85 -0
  189. package/src/ts/interaction/host-sink-message.ts +75 -0
  190. package/src/ts/interaction/lazy-tooltip.ts +102 -0
  191. package/src/ts/interaction/raw-event-forwarder.ts +175 -0
  192. package/src/ts/interaction/spatial-grid.ts +100 -0
  193. package/src/ts/interaction/tooltip-controller.ts +407 -0
  194. package/src/ts/interaction/zoom-controller.ts +468 -0
  195. package/src/ts/interaction/zoom-router.ts +230 -0
  196. package/src/ts/layout/facet-grid.ts +346 -0
  197. package/src/ts/layout/plot-layout.ts +277 -0
  198. package/src/ts/layout/ticks.ts +168 -0
  199. package/src/ts/map/mercator.ts +204 -0
  200. package/src/ts/map/tile-cache.ts +96 -0
  201. package/src/ts/map/tile-layer.ts +382 -0
  202. package/src/ts/map/tile-loader.ts +143 -0
  203. package/src/ts/map/tile-source.ts +156 -0
  204. package/src/ts/plugin/charts.ts +286 -0
  205. package/src/ts/plugin/plugin.ts +668 -0
  206. package/src/ts/render/scheduler.ts +339 -0
  207. package/src/ts/shaders/area.frag.glsl +20 -0
  208. package/src/ts/shaders/area.vert.glsl +19 -0
  209. package/src/ts/shaders/bar.frag.glsl +25 -0
  210. package/src/ts/shaders/bar.vert.glsl +60 -0
  211. package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
  212. package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
  213. package/src/ts/shaders/density-extreme.frag.glsl +30 -0
  214. package/src/ts/shaders/density-mrt.frag.glsl +44 -0
  215. package/src/ts/shaders/density-mrt.vert.glsl +48 -0
  216. package/src/ts/shaders/density-resolve.frag.glsl +89 -0
  217. package/src/ts/shaders/density-resolve.vert.glsl +23 -0
  218. package/src/ts/shaders/density-splat.frag.glsl +34 -0
  219. package/src/ts/shaders/density-splat.vert.glsl +52 -0
  220. package/src/ts/shaders/gridline.frag.glsl +18 -0
  221. package/src/ts/shaders/gridline.vert.glsl +18 -0
  222. package/src/ts/shaders/heatmap.frag.glsl +23 -0
  223. package/src/ts/shaders/heatmap.vert.glsl +42 -0
  224. package/src/ts/shaders/line-uniform.frag.glsl +26 -0
  225. package/src/ts/shaders/line-uniform.vert.glsl +54 -0
  226. package/src/ts/shaders/line.frag.glsl +28 -0
  227. package/src/ts/shaders/line.vert.glsl +87 -0
  228. package/src/ts/shaders/scatter.frag.glsl +39 -0
  229. package/src/ts/shaders/scatter.vert.glsl +67 -0
  230. package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
  231. package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
  232. package/src/ts/shaders/tile.frag.glsl +27 -0
  233. package/src/ts/shaders/tile.vert.glsl +35 -0
  234. package/src/ts/shaders/treemap.frag.glsl +19 -0
  235. package/src/ts/shaders/treemap.vert.glsl +25 -0
  236. package/src/ts/shaders/y-scatter.frag.glsl +30 -0
  237. package/src/ts/shaders/y-scatter.vert.glsl +31 -0
  238. package/src/ts/theme/gradient.ts +312 -0
  239. package/src/ts/theme/palette.ts +64 -0
  240. package/src/ts/theme/theme-snapshot.ts +66 -0
  241. package/src/ts/theme/theme.ts +166 -0
  242. package/src/ts/transport/protocol.ts +497 -0
  243. package/src/ts/transport/renderer-transport.ts +788 -0
  244. package/src/ts/utils/css.ts +36 -0
  245. package/src/ts/utils/font-snapshot.ts +159 -0
  246. package/src/ts/webgl/buffer-pool.ts +163 -0
  247. package/src/ts/webgl/context-manager.ts +414 -0
  248. package/src/ts/webgl/gradient-texture.ts +84 -0
  249. package/src/ts/webgl/instanced-attrs.ts +139 -0
  250. package/src/ts/webgl/plot-frame.ts +91 -0
  251. package/src/ts/webgl/program-cache.ts +46 -0
  252. package/src/ts/webgl/shader-manifest.ts +148 -0
  253. package/src/ts/webgl/shader-registry.ts +97 -0
  254. package/src/ts/worker/boot.ts +22 -0
  255. package/src/ts/worker/dispatch.ts +99 -0
  256. package/src/ts/worker/font-loader.ts +89 -0
  257. package/src/ts/worker/renderer.worker.ts +734 -0
  258. 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
+ }