@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,100 @@
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
+ export class SpatialGrid {
14
+ private _cells: Map<number, number[]> = new Map();
15
+ private _xMin: number;
16
+ private _yMin: number;
17
+ private _cellSize: number;
18
+ private _cols: number;
19
+
20
+ constructor(
21
+ xMin: number,
22
+ xMax: number,
23
+ yMin: number,
24
+ yMax: number,
25
+ cellSize: number,
26
+ ) {
27
+ this._xMin = xMin;
28
+ this._yMin = yMin;
29
+ this._cellSize = cellSize;
30
+ this._cols = Math.max(1, Math.ceil((xMax - xMin) / cellSize));
31
+ }
32
+
33
+ private _cellKey(cx: number, cy: number): number {
34
+ return cy * this._cols + cx;
35
+ }
36
+
37
+ insert(index: number, x: number, y: number): void {
38
+ const cx = Math.floor((x - this._xMin) / this._cellSize);
39
+ const cy = Math.floor((y - this._yMin) / this._cellSize);
40
+ const key = this._cellKey(cx, cy);
41
+ let cell = this._cells.get(key);
42
+ if (!cell) {
43
+ cell = [];
44
+ this._cells.set(key, cell);
45
+ }
46
+
47
+ cell.push(index);
48
+ }
49
+
50
+ /**
51
+ * Find the nearest point to (dataX, dataY) within the given radius,
52
+ * measured in pixel distance using the provided scale factors.
53
+ */
54
+ query(
55
+ dataX: number,
56
+ dataY: number,
57
+ radiusPx: number,
58
+ pxPerDataX: number,
59
+ pxPerDataY: number,
60
+ xData: Float32Array,
61
+ yData: Float32Array,
62
+ ): number {
63
+ const cellRadiusX = Math.ceil(radiusPx / pxPerDataX / this._cellSize);
64
+ const cellRadiusY = Math.ceil(radiusPx / pxPerDataY / this._cellSize);
65
+ const centerCX = Math.floor((dataX - this._xMin) / this._cellSize);
66
+ const centerCY = Math.floor((dataY - this._yMin) / this._cellSize);
67
+
68
+ let bestIdx = -1;
69
+ let bestDistSq = radiusPx * radiusPx;
70
+
71
+ for (
72
+ let cy = centerCY - cellRadiusY;
73
+ cy <= centerCY + cellRadiusY;
74
+ cy++
75
+ ) {
76
+ for (
77
+ let cx = centerCX - cellRadiusX;
78
+ cx <= centerCX + cellRadiusX;
79
+ cx++
80
+ ) {
81
+ const cell = this._cells.get(this._cellKey(cx, cy));
82
+ if (!cell) {
83
+ continue;
84
+ }
85
+
86
+ for (const i of cell) {
87
+ const dx = (xData[i] - dataX) * pxPerDataX;
88
+ const dy = (yData[i] - dataY) * pxPerDataY;
89
+ const distSq = dx * dx + dy * dy;
90
+ if (distSq < bestDistSq) {
91
+ bestDistSq = distSq;
92
+ bestIdx = i;
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ return bestIdx;
99
+ }
100
+ }
@@ -0,0 +1,407 @@
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 type { Canvas2D, Context2D } from "../charts/canvas-types";
14
+ import type { PlotLayout } from "../layout/plot-layout";
15
+ import type { Theme } from "../theme/theme";
16
+
17
+ /**
18
+ * Minimal positioning input — PlotLayout satisfies this.
19
+ */
20
+ export interface CssBounds {
21
+ cssWidth: number;
22
+ cssHeight: number;
23
+ }
24
+
25
+ export interface TooltipCallbacks {
26
+ /**
27
+ * RAF-throttled mouse position in CSS pixels, relative to the GL
28
+ * canvas (host already subtracted `getBoundingClientRect`).
29
+ */
30
+ onHover(mx: number, my: number): void;
31
+
32
+ /**
33
+ * Fires on mouseleave; skipped while a pinned tooltip is active.
34
+ */
35
+ onLeave(): void;
36
+
37
+ /**
38
+ * Fires on click with mouse position. Return true to consume the click
39
+ * (skipping the default pin/dismiss flow — used for legend clicks).
40
+ */
41
+ onClickPre?(mx: number, my: number): boolean;
42
+
43
+ /**
44
+ * Fires when a click should pin the current hover target.
45
+ */
46
+ onPin?(mx: number, my: number): void;
47
+
48
+ /**
49
+ * Fires on dblclick (treemap drill-up gesture). Optional — charts
50
+ * that don't bind a handler simply ignore the event.
51
+ */
52
+ onDblClick?(mx: number, my: number): void;
53
+
54
+ /**
55
+ * Fires when an active pin is dismissed by a click on the
56
+ * already-pinned target (the "click again to unpin" gesture in
57
+ * `dispatchClick`). Chart impls hook this to emit a
58
+ * `perspective-global-filter` with `selected: false`. Does *not*
59
+ * fire on the implicit dismiss inside `pin()` that replaces an
60
+ * existing pin — that path is followed by a fresh `onPin` which
61
+ * emits its own `selected: true`.
62
+ */
63
+ onUnpin?(): void;
64
+ }
65
+
66
+ export interface RenderTooltipOptions {
67
+ /**
68
+ * Draw a dashed crosshair at `pos`. Used by scatter/line.
69
+ */
70
+ crosshair?: boolean;
71
+
72
+ /**
73
+ * Draw a ring of `radius` CSS pixels at `pos`. Used to highlight a
74
+ * hovered point. Omit for bars (where the bar itself highlights).
75
+ */
76
+ highlightRadius?: number;
77
+ }
78
+
79
+ /**
80
+ * Side-channel from the chart back to the host's DOM. The chart calls
81
+ * into a sink rather than touching the DOM itself; the host
82
+ * materializes the actual visual (`<div>` for pinned tooltip, cursor
83
+ * mutation on the GL canvas).
84
+ *
85
+ * - `MessageHostSink` (in worker/) — forwards calls over a
86
+ * `postMessage`-shaped channel back to
87
+ * the host.
88
+ * - `DomHostSink` (in host transport) — receives the matching
89
+ * envelopes and applies them to the DOM.
90
+ *
91
+ * The controller's `_pinned` flag is the source of truth for whether
92
+ * hover updates are gated; the sink only owns the visual artifact.
93
+ */
94
+ export interface HostSink {
95
+ pin(
96
+ lines: string[],
97
+ pos: { px: number; py: number },
98
+ bounds: CssBounds,
99
+ ): void;
100
+ dismiss(): void;
101
+ setCursor(cursor: string): void;
102
+
103
+ /**
104
+ * Forward a `perspective-click` to the host. Optional — only the
105
+ * worker-bound `MessageHostSink` implements it; `DomHostSink` (the
106
+ * host-side consumer of pin/dismiss) never sees user-event calls,
107
+ * so omits the implementation.
108
+ */
109
+ emitUserClick?(detail: UserClickPayload): void;
110
+
111
+ /**
112
+ * Forward a `perspective-global-filter` to the host with the
113
+ * `selected: true` / `selected: false` semantics. The host owns the
114
+ * `removeConfigs` history (mirrors datagrid's
115
+ * `model._last_insert_configs`); the sink only ships the new state.
116
+ */
117
+ emitUserSelect?(payload: UserSelectPayload): void;
118
+ }
119
+
120
+ /**
121
+ * Plain-object payload for `HostSink.emitUserClick`. Matches
122
+ * `PerspectiveClickDetail` byte-for-byte; defined locally to avoid a
123
+ * cycle through `event-detail.ts`.
124
+ */
125
+ export interface UserClickPayload {
126
+ row: Record<string, unknown>;
127
+ column_names: string[];
128
+ config: { filter?: unknown[] };
129
+ }
130
+
131
+ /**
132
+ * Plain-object payload for `HostSink.emitUserSelect`. The host
133
+ * transport reconstructs a `PerspectiveSelectDetail` class instance
134
+ * from this plus its cached `_lastInsertConfig`.
135
+ */
136
+ export interface UserSelectPayload {
137
+ selected: boolean;
138
+ row: Record<string, unknown>;
139
+ column_names: string[];
140
+ insertConfig: { filter?: unknown[] };
141
+ }
142
+
143
+ /**
144
+ * Owns the hover/click/dblclick state machine and the pinned-tooltip
145
+ * lifecycle. The renderer drives this purely through
146
+ * `dispatchHover` / `dispatchLeave` / `dispatchClick` /
147
+ * `dispatchDblClick` — the host's `RawEventForwarder` captures DOM
148
+ * events on the GL canvas and posts them as `InteractionEvent`s.
149
+ *
150
+ * Pinning + cursor changes go through a {@link HostSink} so the actual
151
+ * DOM mutations happen host-side regardless of where the chart runs.
152
+ */
153
+ export class TooltipController {
154
+ private _callbacks: TooltipCallbacks | null = null;
155
+ private _hoverRAFId = 0;
156
+ private _hoverTimeoutId: ReturnType<typeof setTimeout> | null = null;
157
+ private _host: HostSink | null = null;
158
+ private _pinned = false;
159
+
160
+ get isPinned(): boolean {
161
+ return this._pinned;
162
+ }
163
+
164
+ /**
165
+ * Replace the active host sink. Dismisses any existing pin via the
166
+ * prior sink so we never leak a pinned artifact across resets —
167
+ * though in practice each chart instance uses one sink for its
168
+ * lifetime.
169
+ */
170
+ setHost(sink: HostSink): void {
171
+ if (this._pinned) {
172
+ this._host?.dismiss();
173
+ this._pinned = false;
174
+ }
175
+
176
+ this._host = sink;
177
+ }
178
+
179
+ /**
180
+ * Forward a cursor change to the host. No-op when no host sink is
181
+ * installed (chart constructed without a transport).
182
+ */
183
+ setCursor(cursor: string): void {
184
+ this._host?.setCursor(cursor);
185
+ }
186
+
187
+ /**
188
+ * Install the chart's tooltip callbacks. The renderer drives the
189
+ * controller via `dispatchHover` / `dispatchLeave` /
190
+ * `dispatchClick` / `dispatchDblClick`; this controller never
191
+ * touches the DOM directly.
192
+ */
193
+ attach(callbacks: TooltipCallbacks): void {
194
+ this.detach();
195
+ this._callbacks = callbacks;
196
+ }
197
+
198
+ detach(): void {
199
+ if (this._hoverRAFId) {
200
+ cancelAnimationFrame(this._hoverRAFId);
201
+ this._hoverRAFId = 0;
202
+ }
203
+
204
+ if (this._hoverTimeoutId !== null) {
205
+ clearTimeout(this._hoverTimeoutId);
206
+ this._hoverTimeoutId = null;
207
+ }
208
+
209
+ this._callbacks = null;
210
+ }
211
+
212
+ /**
213
+ * Schedule an `onHover` callback for the given canvas-relative
214
+ * coords. Coalesces multiple calls within one animation frame so
215
+ * pointer streams don't backlog the chart's hit-test path.
216
+ *
217
+ * Workers ship with `requestAnimationFrame` (DedicatedWorkerGlobalScope
218
+ * exposes it for OffscreenCanvas painting), so the same coalescer
219
+ * works in both modes. We fall back to setTimeout if RAF is missing
220
+ * (e.g. node tests without a polyfill).
221
+ */
222
+ dispatchHover(mx: number, my: number): void {
223
+ if (this._pinned || !this._callbacks) {
224
+ return;
225
+ }
226
+
227
+ if (this._hoverRAFId || this._hoverTimeoutId !== null) {
228
+ return;
229
+ }
230
+
231
+ const fire = () => {
232
+ this._hoverRAFId = 0;
233
+ this._hoverTimeoutId = null;
234
+ this._callbacks?.onHover(mx, my);
235
+ };
236
+
237
+ if (typeof requestAnimationFrame === "function") {
238
+ this._hoverRAFId = requestAnimationFrame(fire);
239
+ } else {
240
+ this._hoverTimeoutId = setTimeout(fire, 16);
241
+ }
242
+ }
243
+
244
+ dispatchLeave(): void {
245
+ if (this._pinned || !this._callbacks) {
246
+ return;
247
+ }
248
+
249
+ this._callbacks.onLeave();
250
+ }
251
+
252
+ dispatchClick(mx: number, my: number): void {
253
+ if (!this._callbacks) {
254
+ return;
255
+ }
256
+
257
+ if (this._callbacks.onClickPre?.(mx, my)) {
258
+ return;
259
+ }
260
+
261
+ if (this._pinned) {
262
+ const cb = this._callbacks;
263
+ this.dismiss();
264
+ cb.onUnpin?.();
265
+ return;
266
+ }
267
+
268
+ this._callbacks.onPin?.(mx, my);
269
+ }
270
+
271
+ dispatchDblClick(mx: number, my: number): void {
272
+ this._callbacks?.onDblClick?.(mx, my);
273
+ }
274
+
275
+ /**
276
+ * Pin a tooltip (or replace an active one). Forwards through the
277
+ * configured sink and flips the controller's pinned flag so hover
278
+ * dispatch is suppressed until dismissal.
279
+ */
280
+ pin(
281
+ lines: string[],
282
+ pos: { px: number; py: number },
283
+ bounds: CssBounds,
284
+ ): void {
285
+ if (lines.length === 0) {
286
+ return;
287
+ }
288
+
289
+ this._host?.pin(lines, pos, bounds);
290
+ this._pinned = true;
291
+ }
292
+
293
+ dismiss(): void {
294
+ this._host?.dismiss();
295
+ this._pinned = false;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Paint a canvas tooltip (crosshair, highlight ring, box + text) onto
301
+ * `canvas`. The helper normalizes the 2D context to a DPR-scaled
302
+ * identity transform on entry and restores prior state on exit, so it
303
+ * composes cleanly with other chrome painters that may have already
304
+ * called `initCanvas` on the same canvas — re-applying `scale(dpr,dpr)`
305
+ * blind would double-scale in that case, misplacing the tooltip
306
+ * proportionally to its distance from the origin.
307
+ */
308
+ export function renderCanvasTooltip(
309
+ canvas: Canvas2D | null,
310
+ pos: { px: number; py: number },
311
+ lines: string[],
312
+ layout: PlotLayout,
313
+ theme: Theme,
314
+ dpr: number,
315
+ options: RenderTooltipOptions = {},
316
+ ): void {
317
+ if (!canvas) {
318
+ return;
319
+ }
320
+
321
+ const ctx = canvas.getContext("2d") as Context2D | null;
322
+ if (!ctx) {
323
+ return;
324
+ }
325
+
326
+ ctx.save();
327
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
328
+ ctx.scale(dpr, dpr);
329
+ ctx.font = `11px ${theme.fontFamily}`;
330
+ const lineHeight = 16;
331
+ const padding = 8;
332
+ let maxWidth = 0;
333
+ for (const line of lines) {
334
+ const w = ctx.measureText(line).width;
335
+ if (w > maxWidth) {
336
+ maxWidth = w;
337
+ }
338
+ }
339
+
340
+ const boxW = maxWidth + padding * 2;
341
+ const boxH = lines.length * lineHeight + padding * 2 - 4;
342
+ let tx = pos.px + 12;
343
+ let ty = pos.py - boxH - 8;
344
+ if (tx + boxW > layout.cssWidth) {
345
+ tx = pos.px - boxW - 12;
346
+ }
347
+
348
+ if (ty < 0) {
349
+ ty = pos.py + 12;
350
+ }
351
+
352
+ if (ty + boxH > layout.cssHeight) {
353
+ ty = layout.cssHeight - boxH - 4;
354
+ }
355
+
356
+ const hasLines = lines.length > 0;
357
+
358
+ // Crosshair
359
+ if (options.crosshair) {
360
+ ctx.strokeStyle = theme.tickColor;
361
+ ctx.globalAlpha = 0.3;
362
+ ctx.lineWidth = 1;
363
+ ctx.setLineDash([4, 4]);
364
+ ctx.beginPath();
365
+ ctx.moveTo(pos.px, layout.plotRect.y);
366
+ ctx.lineTo(pos.px, layout.plotRect.y + layout.plotRect.height);
367
+ ctx.moveTo(layout.plotRect.x, pos.py);
368
+ ctx.lineTo(layout.plotRect.x + layout.plotRect.width, pos.py);
369
+ ctx.stroke();
370
+ ctx.setLineDash([]);
371
+ ctx.globalAlpha = 1.0;
372
+ }
373
+
374
+ // Highlight ring
375
+ if (options.highlightRadius && options.highlightRadius > 0) {
376
+ ctx.strokeStyle = theme.tickColor;
377
+ ctx.globalAlpha = 0.8;
378
+ ctx.lineWidth = 2;
379
+ ctx.beginPath();
380
+ ctx.arc(pos.px, pos.py, options.highlightRadius, 0, Math.PI * 2);
381
+ ctx.stroke();
382
+ ctx.globalAlpha = 1.0;
383
+ }
384
+
385
+ // Box + text are only drawn when we have content. Callers pass an
386
+ // empty `lines` array while a lazy row fetch is still in flight —
387
+ // the crosshair / highlight ring above paint immediately so the
388
+ // hover remains visible, but the tooltip chrome waits for data.
389
+ if (hasLines) {
390
+ ctx.fillStyle = theme.tooltipBg;
391
+ ctx.strokeStyle = theme.tooltipBorder;
392
+ ctx.lineWidth = 1;
393
+ ctx.beginPath();
394
+ ctx.roundRect(tx, ty, boxW, boxH, 4);
395
+ ctx.fill();
396
+ ctx.stroke();
397
+
398
+ ctx.fillStyle = theme.tooltipText;
399
+ ctx.textAlign = "left";
400
+ ctx.textBaseline = "top";
401
+ for (let i = 0; i < lines.length; i++) {
402
+ ctx.fillText(lines[i], tx + padding, ty + padding + i * lineHeight);
403
+ }
404
+ }
405
+
406
+ ctx.restore();
407
+ }