@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,604 @@
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 { SeriesChart } from "./series";
14
+ import {
15
+ BAR_TYPE_BAR,
16
+ BAR_TYPE_AREA,
17
+ readBarRecord,
18
+ type SeriesChartRecord,
19
+ } from "./series-build";
20
+ import {
21
+ renderBarFrame,
22
+ uploadBarInstances,
23
+ rebuildGlyphBuffers,
24
+ rightAxisDataToPixel,
25
+ } from "./series-render";
26
+
27
+ const POINT_HIT_RADIUS_PX = 10;
28
+
29
+ /**
30
+ * Unified accessor for the currently hovered glyph. Returns either the
31
+ * real {@link SeriesChartRecord} from `_bars` (bar / stacked-area hits) or the
32
+ * synthetic one stored in `_hoveredSample` (line / scatter / non-stacked
33
+ * area hits), or `null`.
34
+ */
35
+ export function getHoveredBar(chart: SeriesChart): SeriesChartRecord | null {
36
+ if (chart._hoveredBarIdx >= 0) {
37
+ return readBarRecord(
38
+ chart._bars,
39
+ chart._hoveredBarIdx,
40
+ chart._splitPrefixes.length,
41
+ chart._samples,
42
+ chart._series.length,
43
+ );
44
+ }
45
+
46
+ return chart._hoveredSample;
47
+ }
48
+
49
+ /**
50
+ * Handle mouse-move across all glyph types. Tests (in reverse paint order
51
+ * so top glyphs win): scatter points → line points → bars → areas.
52
+ * Updates `_hoveredBarIdx` or `_hoveredSample` and re-renders on change.
53
+ */
54
+ export function handleBarHover(
55
+ chart: SeriesChart,
56
+ mx: number,
57
+ my: number,
58
+ ): void {
59
+ if (!chart._lastLayout) {
60
+ return;
61
+ }
62
+
63
+ const layout = chart._lastLayout;
64
+ const plot = layout.plotRect;
65
+
66
+ if (
67
+ mx < plot.x ||
68
+ mx > plot.x + plot.width ||
69
+ my < plot.y ||
70
+ my > plot.y + plot.height
71
+ ) {
72
+ clearHover(chart);
73
+ return;
74
+ }
75
+
76
+ // Convert mouse pixels to *logical* (category, value) coordinates.
77
+ // In horizontal mode the plot's X-axis is numeric and Y-axis is the
78
+ // flipped category index, so the raw pixel→data inversion differs.
79
+ const padXMin = layout.paddedXMin;
80
+ const padXMax = layout.paddedXMax;
81
+ const padYMin = layout.paddedYMin;
82
+ const padYMax = layout.paddedYMax;
83
+ let dataX: number;
84
+ let dataYLeft: number;
85
+ let pxPerDataX: number;
86
+ let pxPerDataYLeft: number;
87
+ if (chart._isHorizontal) {
88
+ // paddedY is flipped (catMax, catMin); undo that when inverting so
89
+ // dataYLeft (= logical category axis) grows from top to bottom.
90
+ const valMin = padXMin;
91
+ const valMax = padXMax;
92
+ const catTop = Math.min(padYMin, padYMax);
93
+ const catBot = Math.max(padYMin, padYMax);
94
+ const valAtMouse =
95
+ valMin + ((mx - plot.x) / plot.width) * (valMax - valMin);
96
+ const catAtMouse =
97
+ catTop + ((my - plot.y) / plot.height) * (catBot - catTop);
98
+ dataX = catAtMouse; // logical category → "dataX" in hit-test
99
+ dataYLeft = valAtMouse; // logical value → "dataYLeft" in hit-test
100
+ pxPerDataX = plot.height / (catBot - catTop);
101
+ pxPerDataYLeft = plot.width / (valMax - valMin);
102
+ } else {
103
+ dataX = padXMin + ((mx - plot.x) / plot.width) * (padXMax - padXMin);
104
+ dataYLeft =
105
+ padYMax - ((my - plot.y) / plot.height) * (padYMax - padYMin);
106
+ pxPerDataX = plot.width / (padXMax - padXMin);
107
+ pxPerDataYLeft = plot.height / (padYMax - padYMin);
108
+ }
109
+
110
+ const dataYRight =
111
+ chart._hasRightAxis && chart._rightDomain && !chart._isHorizontal
112
+ ? chart._rightDomain.max -
113
+ ((my - plot.y) / plot.height) *
114
+ (chart._rightDomain.max - chart._rightDomain.min)
115
+ : dataYLeft;
116
+ const pxPerDataYRight =
117
+ chart._hasRightAxis && chart._rightDomain && !chart._isHorizontal
118
+ ? plot.height / (chart._rightDomain.max - chart._rightDomain.min)
119
+ : pxPerDataYLeft;
120
+
121
+ let nextBarIdx = -1;
122
+ let nextSample: SeriesChartRecord | null = null;
123
+
124
+ // 1. Scatter (top).
125
+ nextSample = hitTestPoints(
126
+ chart,
127
+ "scatter",
128
+ dataX,
129
+ dataYLeft,
130
+ dataYRight,
131
+ pxPerDataX,
132
+ pxPerDataYLeft,
133
+ pxPerDataYRight,
134
+ );
135
+
136
+ // 2. Line points (still above bars; treat as point hits).
137
+ if (!nextSample) {
138
+ nextSample = hitTestPoints(
139
+ chart,
140
+ "line",
141
+ dataX,
142
+ dataYLeft,
143
+ dataYRight,
144
+ pxPerDataX,
145
+ pxPerDataYLeft,
146
+ pxPerDataYRight,
147
+ );
148
+ }
149
+
150
+ // 3. Bars (rect intersect).
151
+ if (!nextSample) {
152
+ const bars = chart._bars;
153
+ const ct = bars.chartType;
154
+ const sid = bars.seriesId;
155
+ const xC = bars.xCenter;
156
+ const hw = bars.halfWidth;
157
+ const by0 = bars.y0;
158
+ const by1 = bars.y1;
159
+ const ax = bars.axis;
160
+ const hidden = chart._hiddenSeries;
161
+ for (let i = 0; i < bars.count; i++) {
162
+ if (ct[i] !== BAR_TYPE_BAR) {
163
+ continue;
164
+ }
165
+
166
+ if (hidden.has(sid[i])) {
167
+ continue;
168
+ }
169
+
170
+ const xc = xC[i];
171
+ const halfW = hw[i];
172
+ if (dataX < xc - halfW || dataX > xc + halfW) {
173
+ continue;
174
+ }
175
+
176
+ const dy = ax[i] === 0 ? dataYLeft : dataYRight;
177
+ const y0 = by0[i];
178
+ const y1 = by1[i];
179
+ const lo = y0 < y1 ? y0 : y1;
180
+ const hi = y0 < y1 ? y1 : y0;
181
+ if (dy >= lo && dy <= hi) {
182
+ nextBarIdx = i;
183
+ break;
184
+ }
185
+ }
186
+ }
187
+
188
+ // 4. Areas (strip hit — stacked records via `_bars`, unstacked via samples).
189
+ if (nextBarIdx < 0 && !nextSample) {
190
+ const areaHit = hitTestAreas(chart, dataX, dataYLeft, dataYRight);
191
+ if (areaHit) {
192
+ if (areaHit.idx >= 0) {
193
+ nextBarIdx = areaHit.idx;
194
+ } else {
195
+ nextSample = areaHit.bar;
196
+ }
197
+ }
198
+ }
199
+
200
+ applyHover(chart, nextBarIdx, nextSample);
201
+ }
202
+
203
+ function hitTestPoints(
204
+ chart: SeriesChart,
205
+ chartType: "scatter" | "line",
206
+ dataX: number,
207
+ dataYLeft: number,
208
+ dataYRight: number,
209
+ pxPerDataX: number,
210
+ pxPerDataYLeft: number,
211
+ pxPerDataYRight: number,
212
+ ): SeriesChartRecord | null {
213
+ const N = chart._numCategories;
214
+ const S = chart._series.length;
215
+ if (N === 0 || S === 0) {
216
+ return null;
217
+ }
218
+
219
+ const samples = chart._samples;
220
+ const valid = chart._sampleValid;
221
+
222
+ const rSq = POINT_HIT_RADIUS_PX * POINT_HIT_RADIUS_PX;
223
+ let bestDistSq = rSq;
224
+ let best: SeriesChartRecord | null = null;
225
+
226
+ const positions = chart._categoryPositions;
227
+ for (const s of chart._series) {
228
+ if (s.chartType !== chartType) {
229
+ continue;
230
+ }
231
+
232
+ if (chart._hiddenSeries.has(s.seriesId)) {
233
+ continue;
234
+ }
235
+
236
+ const dataY = s.axis === 1 ? dataYRight : dataYLeft;
237
+ const pyPerData = s.axis === 1 ? pxPerDataYRight : pxPerDataYLeft;
238
+
239
+ // In numeric mode the per-category X positions aren't dense so
240
+ // the catIdx-based narrowing doesn't apply — fall back to a
241
+ // full sweep. In category mode, narrow to ±radius around dataX.
242
+ const catMin = positions
243
+ ? 0
244
+ : Math.max(0, Math.floor(dataX - POINT_HIT_RADIUS_PX / pxPerDataX));
245
+ const catMax = positions
246
+ ? N - 1
247
+ : Math.min(
248
+ N - 1,
249
+ Math.ceil(dataX + POINT_HIT_RADIUS_PX / pxPerDataX),
250
+ );
251
+
252
+ for (let c = catMin; c <= catMax; c++) {
253
+ const idx = c * S + s.seriesId;
254
+ if (!((valid[idx >> 3] >> (idx & 7)) & 1)) {
255
+ continue;
256
+ }
257
+
258
+ const v = samples[idx];
259
+ const x = positions ? positions[c] : c;
260
+ const dx = (x - dataX) * pxPerDataX;
261
+ const dy = (v - dataY) * pyPerData;
262
+ const distSq = dx * dx + dy * dy;
263
+ if (distSq < bestDistSq) {
264
+ bestDistSq = distSq;
265
+ best = {
266
+ catIdx: c,
267
+ aggIdx: s.aggIdx,
268
+ splitIdx: s.splitIdx,
269
+ seriesId: s.seriesId,
270
+ xCenter: x,
271
+ halfWidth: 0,
272
+ y0: 0,
273
+ y1: v,
274
+ value: v,
275
+ axis: s.axis,
276
+
277
+ // Tag as bar so the tooltip renderer treats it like one.
278
+ chartType: "bar",
279
+ };
280
+ }
281
+ }
282
+ }
283
+
284
+ return best;
285
+ }
286
+
287
+ function hitTestAreas(
288
+ chart: SeriesChart,
289
+ dataX: number,
290
+ dataYLeft: number,
291
+ dataYRight: number,
292
+ ): { idx: number; bar: SeriesChartRecord | null } | null {
293
+ // Closest category to the mouse; an area covers every [cat - 0.5, cat + 0.5]
294
+ // slot, so use `round(dataX)` as the candidate index.
295
+ const cat = Math.round(dataX);
296
+ if (cat < 0 || cat >= chart._numCategories) {
297
+ return null;
298
+ }
299
+
300
+ if (Math.abs(dataX - cat) > 0.5) {
301
+ return null;
302
+ }
303
+
304
+ const S = chart._series.length;
305
+ const samples = chart._samples;
306
+ const valid = chart._sampleValid;
307
+
308
+ // Prefer stacked hits (iterate existing bar records — they carry y0/y1).
309
+ const bars = chart._bars;
310
+ const ct = bars.chartType;
311
+ const ci = bars.catIdx;
312
+ const sid = bars.seriesId;
313
+ const ax = bars.axis;
314
+ const by0 = bars.y0;
315
+ const by1 = bars.y1;
316
+ for (let i = 0; i < bars.count; i++) {
317
+ if (ct[i] !== BAR_TYPE_AREA) {
318
+ continue;
319
+ }
320
+
321
+ if (ci[i] !== cat) {
322
+ continue;
323
+ }
324
+
325
+ if (chart._hiddenSeries.has(sid[i])) {
326
+ continue;
327
+ }
328
+
329
+ const dy = ax[i] === 0 ? dataYLeft : dataYRight;
330
+ const y0 = by0[i];
331
+ const y1 = by1[i];
332
+ const lo = y0 < y1 ? y0 : y1;
333
+ const hi = y0 < y1 ? y1 : y0;
334
+ if (dy >= lo && dy <= hi) {
335
+ return { idx: i, bar: null };
336
+ }
337
+ }
338
+
339
+ // Unstacked area series: synthesise from samples.
340
+ for (const s of chart._series) {
341
+ if (s.chartType !== "area" || s.stack) {
342
+ continue;
343
+ }
344
+
345
+ if (chart._hiddenSeries.has(s.seriesId)) {
346
+ continue;
347
+ }
348
+
349
+ const idx = cat * S + s.seriesId;
350
+ if (!((valid[idx >> 3] >> (idx & 7)) & 1)) {
351
+ continue;
352
+ }
353
+
354
+ const v = samples[idx];
355
+ const dy = s.axis === 1 ? dataYRight : dataYLeft;
356
+ const lo = Math.min(0, v);
357
+ const hi = Math.max(0, v);
358
+ if (dy >= lo && dy <= hi) {
359
+ return {
360
+ idx: -1,
361
+ bar: {
362
+ catIdx: cat,
363
+ aggIdx: s.aggIdx,
364
+ splitIdx: s.splitIdx,
365
+ seriesId: s.seriesId,
366
+ xCenter: cat,
367
+ halfWidth: 0.5,
368
+ y0: 0,
369
+ y1: v,
370
+ value: v,
371
+ axis: s.axis,
372
+ chartType: "area",
373
+ },
374
+ };
375
+ }
376
+ }
377
+
378
+ return null;
379
+ }
380
+
381
+ function clearHover(chart: SeriesChart): void {
382
+ if (chart._hoveredBarIdx !== -1 || chart._hoveredSample !== null) {
383
+ chart._hoveredBarIdx = -1;
384
+ chart._hoveredSample = null;
385
+ if (chart._glManager) {
386
+ renderBarFrame(chart, chart._glManager);
387
+ }
388
+ }
389
+ }
390
+
391
+ function applyHover(
392
+ chart: SeriesChart,
393
+ nextBarIdx: number,
394
+ nextSample: SeriesChartRecord | null,
395
+ ): void {
396
+ const sameBar = chart._hoveredBarIdx === nextBarIdx;
397
+ const sameSample =
398
+ (chart._hoveredSample?.seriesId ?? -1) ===
399
+ (nextSample?.seriesId ?? -1) &&
400
+ (chart._hoveredSample?.catIdx ?? -1) === (nextSample?.catIdx ?? -1);
401
+ if (sameBar && sameSample) {
402
+ return;
403
+ }
404
+
405
+ chart._hoveredBarIdx = nextBarIdx;
406
+ chart._hoveredSample = nextSample;
407
+ if (chart._glManager) {
408
+ renderBarFrame(chart, chart._glManager);
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Handle a click on the legend area. Returns true when the click hit a
414
+ * legend entry (the caller should then treat the event as consumed).
415
+ */
416
+ export function handleBarLegendClick(
417
+ chart: SeriesChart,
418
+ mx: number,
419
+ my: number,
420
+ ): boolean {
421
+ if (chart._legendRects.length === 0) {
422
+ return false;
423
+ }
424
+
425
+ for (const entry of chart._legendRects) {
426
+ const r = entry.rect;
427
+ if (
428
+ mx >= r.x &&
429
+ mx <= r.x + r.width &&
430
+ my >= r.y &&
431
+ my <= r.y + r.height
432
+ ) {
433
+ if (chart._hiddenSeries.has(entry.seriesId)) {
434
+ chart._hiddenSeries.delete(entry.seriesId);
435
+ } else {
436
+ chart._hiddenSeries.add(entry.seriesId);
437
+ }
438
+
439
+ // Hidden-series change affects which bars contribute to
440
+ // the auto-fit extent and which scatter / line points are
441
+ // uploaded. Bars rebuild via `uploadBarInstances` (it
442
+ // filters hidden). Scatter and line rebuild because their
443
+ // bulk-packed per-axis buffers need to drop hidden points
444
+ // — the line glyph used to skip rebuilds (per-series
445
+ // buffers + draw-path filter), but with the bulk pack the
446
+ // hidden filter must apply at upload time. Area still
447
+ // uses per-series buffers; its draw path skips hidden.
448
+ // The per-category extent buckets (`_catExtents`) are
449
+ // also invalidated — they hold a pointer to the hidden
450
+ // set used at build time and rebuild on next read.
451
+ chart._autoFitCache = null;
452
+ chart._legendCacheValid = false;
453
+ chart._catExtentsHidden = null;
454
+ if (chart._glManager) {
455
+ uploadBarInstances(chart, chart._glManager);
456
+ rebuildGlyphBuffers(chart, chart._glManager);
457
+ renderBarFrame(chart, chart._glManager);
458
+ }
459
+
460
+ return true;
461
+ }
462
+ }
463
+
464
+ return false;
465
+ }
466
+
467
+ /**
468
+ * Build the per-bar tooltip content lines.
469
+ */
470
+ export function buildBarTooltipLines(
471
+ chart: SeriesChart,
472
+ b: SeriesChartRecord,
473
+ ): string[] {
474
+ const lines: string[] = [];
475
+ const s = chart._series[b.seriesId];
476
+ const categoryPath = formatBarCategoryPath(chart, b.catIdx);
477
+ if (categoryPath) {
478
+ lines.push(categoryPath);
479
+ }
480
+
481
+ const yFmt = chart.getColumnFormatter(s.aggName, "value");
482
+ lines.push(`${s.aggName}: ${yFmt(b.value)}`);
483
+ if (s.splitKey) {
484
+ lines.push(`Split: ${s.splitKey}`);
485
+ }
486
+
487
+ if (b.y0 !== 0) {
488
+ lines.push(`Base: ${yFmt(b.y0)}`);
489
+ lines.push(`Top: ${yFmt(b.y1)}`);
490
+ }
491
+
492
+ return lines;
493
+ }
494
+
495
+ /**
496
+ * Format the hierarchical path label for a given category index. Used by
497
+ * the tooltip — the axis uses per-level text directly instead.
498
+ */
499
+ export function formatBarCategoryPath(
500
+ chart: SeriesChart,
501
+ catIdx: number,
502
+ ): string {
503
+ // Numeric category mode: resolve from the bar's xCenter (real data
504
+ // value) rather than the row-path label array, which is empty when
505
+ // the single group_by level is non-string.
506
+ if (chart._categoryAxisMode === "numeric" && chart._numericCategoryDomain) {
507
+ const bars = chart._bars;
508
+ let v: number | null = null;
509
+ for (let i = 0; i < bars.count; i++) {
510
+ if (bars.catIdx[i] === catIdx) {
511
+ v = bars.xCenter[i];
512
+ break;
513
+ }
514
+ }
515
+
516
+ if (v == null) {
517
+ return "";
518
+ }
519
+
520
+ const xColumn = chart._groupBy[0];
521
+ return chart.getColumnFormatter(xColumn, "value")(v);
522
+ }
523
+
524
+ if (chart._rowPaths.length === 0) {
525
+ return "";
526
+ }
527
+
528
+ const parts: string[] = [];
529
+ for (const rp of chart._rowPaths) {
530
+ const s = rp.labels[catIdx];
531
+ if (s != null && s !== "") {
532
+ parts.push(s);
533
+ }
534
+ }
535
+
536
+ return parts.join(" / ");
537
+ }
538
+
539
+ export function showBarPinnedTooltip(chart: SeriesChart, barIdx: number): void {
540
+ if (barIdx < 0 || barIdx >= chart._bars.count) {
541
+ return;
542
+ }
543
+
544
+ chart._pinnedBarIdx = barIdx;
545
+ pinTooltip(
546
+ chart,
547
+ readBarRecord(
548
+ chart._bars,
549
+ barIdx,
550
+ chart._splitPrefixes.length,
551
+ chart._samples,
552
+ chart._series.length,
553
+ ),
554
+ );
555
+ }
556
+
557
+ /**
558
+ * Pin a tooltip against a synthetic BarRecord (scatter/line/area hit).
559
+ */
560
+ export function showBarPinnedTooltipForSample(
561
+ chart: SeriesChart,
562
+ bar: SeriesChartRecord,
563
+ ): void {
564
+ chart._pinnedBarIdx = -1;
565
+ pinTooltip(chart, bar);
566
+ }
567
+
568
+ function pinTooltip(chart: SeriesChart, b: SeriesChartRecord): void {
569
+ chart._tooltip.dismiss();
570
+ if (!chart._lastLayout) {
571
+ return;
572
+ }
573
+
574
+ const layout = chart._lastLayout;
575
+
576
+ // Anchor at the bar midpoint for bar glyphs (tooltip reads against
577
+ // the body); at the point itself (`y1`) for line / scatter / area.
578
+ const glyph = chart._series[b.seriesId]?.chartType ?? "bar";
579
+ const anchorV = glyph === "bar" ? (b.y0 + b.y1) / 2 : b.y1;
580
+ const pos =
581
+ b.axis === 0
582
+ ? chart._isHorizontal
583
+ ? layout.dataToPixel(anchorV, b.xCenter)
584
+ : layout.dataToPixel(b.xCenter, anchorV)
585
+ : rightAxisDataToPixel(chart, b.xCenter, anchorV);
586
+
587
+ const lines = buildBarTooltipLines(chart, b);
588
+ if (lines.length === 0) {
589
+ return;
590
+ }
591
+
592
+ chart._tooltip.pin(lines, pos, layout);
593
+
594
+ chart._hoveredBarIdx = -1;
595
+ chart._hoveredSample = null;
596
+ if (chart._glManager) {
597
+ renderBarFrame(chart, chart._glManager);
598
+ }
599
+ }
600
+
601
+ export function dismissBarPinnedTooltip(chart: SeriesChart): void {
602
+ chart._tooltip.dismiss();
603
+ chart._pinnedBarIdx = -1;
604
+ }