@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,320 @@
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 { WebGLContextManager } from "../../../webgl/context-manager";
14
+ import type { CartesianChart } from "../cartesian";
15
+ import type { Glyph } from "../glyph";
16
+ import { bindGradientTexture } from "../../../webgl/gradient-texture";
17
+ import {
18
+ createLineCornerBuffer,
19
+ getInstancing,
20
+ } from "../../../webgl/instanced-attrs";
21
+ import { formatTickValue, formatDateTickValue } from "../../../layout/ticks";
22
+ import lineVert from "../../../shaders/line.vert.glsl";
23
+ import lineFrag from "../../../shaders/line.frag.glsl";
24
+
25
+ interface LineCache {
26
+ program: WebGLProgram;
27
+ cornerBuffer: WebGLBuffer;
28
+ u_projection: WebGLUniformLocation | null;
29
+ u_resolution: WebGLUniformLocation | null;
30
+ u_line_width: WebGLUniformLocation | null;
31
+ u_color_range: WebGLUniformLocation | null;
32
+ u_gradient_lut: WebGLUniformLocation | null;
33
+ a_start: number;
34
+ a_end: number;
35
+ a_color_start: number;
36
+ a_color_end: number;
37
+ a_corner: number;
38
+ }
39
+
40
+ /**
41
+ * Polyline glyph — instanced triangle-strip segments between adjacent
42
+ * same-series points. Segments are scoped per-series via byte-offset
43
+ * rebinding (see `drawLineSeries`); the shader reads the endpoints'
44
+ * raw color values and samples the gradient LUT via the same sign-
45
+ * aware `(v - cmin) / (cmax - cmin)` mapping the scatter glyph uses.
46
+ */
47
+ export class LineGlyph implements Glyph {
48
+ readonly name = "line" as const;
49
+ private _cache: LineCache | null = null;
50
+
51
+ ensureProgram(
52
+ _chart: CartesianChart,
53
+ glManager: WebGLContextManager,
54
+ ): void {
55
+ if (this._cache) {
56
+ return;
57
+ }
58
+
59
+ const gl = glManager.gl;
60
+ const program = glManager.shaders.getOrCreate(
61
+ "line",
62
+ lineVert,
63
+ lineFrag,
64
+ );
65
+ const cornerBuffer = createLineCornerBuffer(gl);
66
+ this._cache = {
67
+ program,
68
+ cornerBuffer,
69
+ u_projection: gl.getUniformLocation(program, "u_projection"),
70
+ u_resolution: gl.getUniformLocation(program, "u_resolution"),
71
+ u_line_width: gl.getUniformLocation(program, "u_line_width"),
72
+ u_color_range: gl.getUniformLocation(program, "u_color_range"),
73
+ u_gradient_lut: gl.getUniformLocation(program, "u_gradient_lut"),
74
+ a_start: gl.getAttribLocation(program, "a_start"),
75
+ a_end: gl.getAttribLocation(program, "a_end"),
76
+ a_color_start: gl.getAttribLocation(program, "a_color_start"),
77
+ a_color_end: gl.getAttribLocation(program, "a_color_end"),
78
+ a_corner: gl.getAttribLocation(program, "a_corner"),
79
+ };
80
+ }
81
+
82
+ draw(
83
+ chart: CartesianChart,
84
+ glManager: WebGLContextManager,
85
+ projection: Float32Array,
86
+ ): void {
87
+ const cache = this._cache;
88
+ if (!cache) {
89
+ return;
90
+ }
91
+
92
+ const bind = bindLineState(cache, chart, glManager, projection);
93
+ if (!bind) {
94
+ return;
95
+ }
96
+
97
+ const numSeries = Math.max(1, chart._splitGroups.length);
98
+ for (let s = 0; s < numSeries; s++) {
99
+ drawLineSeries(cache, chart, glManager, s);
100
+ }
101
+
102
+ unbindLineDivisors(cache, glManager);
103
+ }
104
+
105
+ drawSeries(
106
+ chart: CartesianChart,
107
+ glManager: WebGLContextManager,
108
+ projection: Float32Array,
109
+ seriesIdx: number,
110
+ ): void {
111
+ const cache = this._cache;
112
+ if (!cache) {
113
+ return;
114
+ }
115
+
116
+ if (!bindLineState(cache, chart, glManager, projection)) {
117
+ return;
118
+ }
119
+
120
+ drawLineSeries(cache, chart, glManager, seriesIdx);
121
+ unbindLineDivisors(cache, glManager);
122
+ }
123
+
124
+ // helpers
125
+
126
+ async buildTooltipLines(
127
+ chart: CartesianChart,
128
+ flatIdx: number,
129
+ ): Promise<string[]> {
130
+ const lines: string[] = [];
131
+ if (!chart._xData || !chart._yData) {
132
+ return lines;
133
+ }
134
+
135
+ if (chart._splitGroups.length > 0 && chart._seriesCapacity > 0) {
136
+ const seriesIdx = Math.floor(flatIdx / chart._seriesCapacity);
137
+ const sg = chart._splitGroups[seriesIdx];
138
+ if (sg) {
139
+ lines.push(sg.prefix);
140
+ }
141
+ }
142
+
143
+ const xVal = chart._xData[flatIdx];
144
+ const yVal = chart._yData[flatIdx];
145
+
146
+ const xType = chart._columnTypes[chart._xLabel] || "";
147
+ const xIsDate = xType === "date" || xType === "datetime";
148
+ const xFormatted = xIsDate
149
+ ? formatDateTickValue(xVal)
150
+ : formatTickValue(xVal);
151
+ lines.push(`${chart._xLabel || "Row"}: ${xFormatted}`);
152
+
153
+ const yType = chart._columnTypes[chart._yLabel] || "";
154
+ const yIsDate = yType === "date" || yType === "datetime";
155
+ const yFormatted = yIsDate
156
+ ? formatDateTickValue(yVal)
157
+ : formatTickValue(yVal);
158
+ lines.push(`${chart._yLabel}: ${yFormatted}`);
159
+
160
+ return lines;
161
+ }
162
+
163
+ tooltipOptions() {
164
+ return { crosshair: true, highlightRadius: 5 };
165
+ }
166
+
167
+ destroy(chart: CartesianChart): void {
168
+ const cache = this._cache;
169
+ if (cache?.cornerBuffer && chart._glManager) {
170
+ chart._glManager.gl.deleteBuffer(cache.cornerBuffer);
171
+ }
172
+
173
+ this._cache = null;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Shared pre-draw state setup for `draw` and `drawSeries`. Binds the
179
+ * program, uploads uniforms + gradient texture, binds the static corner
180
+ * buffer, enables the instanced attributes. Returns false if the
181
+ * gradient cache is missing.
182
+ */
183
+ function bindLineState(
184
+ cache: LineCache,
185
+ chart: CartesianChart,
186
+ glManager: WebGLContextManager,
187
+ projection: Float32Array,
188
+ ): boolean {
189
+ const gl = glManager.gl;
190
+ if (!chart._gradientCache) {
191
+ return false;
192
+ }
193
+
194
+ const dpr = glManager.dpr;
195
+
196
+ gl.useProgram(cache.program);
197
+ gl.uniformMatrix4fv(cache.u_projection, false, projection);
198
+ gl.uniform2f(cache.u_resolution, gl.canvas.width, gl.canvas.height);
199
+ gl.uniform1f(cache.u_line_width, chart._pluginConfig.line_width_px * dpr);
200
+ if (chart._colorMin < chart._colorMax) {
201
+ gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
202
+ } else {
203
+ gl.uniform2f(cache.u_color_range, 0.0, 0.0);
204
+ }
205
+
206
+ bindGradientTexture(
207
+ glManager,
208
+ chart._gradientCache.texture,
209
+ cache.u_gradient_lut,
210
+ 0,
211
+ );
212
+
213
+ const instancing = getInstancing(glManager);
214
+ const { setDivisor } = instancing;
215
+
216
+ gl.bindBuffer(gl.ARRAY_BUFFER, cache.cornerBuffer);
217
+ gl.enableVertexAttribArray(cache.a_corner);
218
+ gl.vertexAttribPointer(cache.a_corner, 1, gl.FLOAT, false, 0, 0);
219
+ setDivisor(cache.a_corner, 0);
220
+
221
+ gl.enableVertexAttribArray(cache.a_start);
222
+ setDivisor(cache.a_start, 1);
223
+ gl.enableVertexAttribArray(cache.a_end);
224
+ setDivisor(cache.a_end, 1);
225
+ gl.enableVertexAttribArray(cache.a_color_start);
226
+ setDivisor(cache.a_color_start, 1);
227
+ gl.enableVertexAttribArray(cache.a_color_end);
228
+ setDivisor(cache.a_color_end, 1);
229
+
230
+ return true;
231
+ }
232
+
233
+ /**
234
+ * Dispatch one instanced draw for series `s`. Rebinds start/end attrib
235
+ * pointers with byte offsets into the slotted buffer so instance 0 is
236
+ * the series' first segment.
237
+ */
238
+ function drawLineSeries(
239
+ cache: LineCache,
240
+ chart: CartesianChart,
241
+ glManager: WebGLContextManager,
242
+ s: number,
243
+ ): void {
244
+ const count = chart._seriesUploadedCounts[s] ?? 0;
245
+ if (count < 2) {
246
+ return;
247
+ }
248
+
249
+ const gl = glManager.gl;
250
+ const cap = chart._seriesCapacity;
251
+ const posStride = 2 * Float32Array.BYTES_PER_ELEMENT;
252
+ const idStride = Float32Array.BYTES_PER_ELEMENT;
253
+
254
+ // Render-path uses `peek`. If buffers haven't been uploaded
255
+ // yet (pan/zoom render landing between a pending draw's
256
+ // `ensureBufferCapacity` and its `uploadChunk`), skip — drawing
257
+ // against a recreated zero-filled buffer would produce one frame
258
+ // of empty plot area.
259
+ const posBuf = glManager.bufferPool.peek("a_position");
260
+ const idBuf = glManager.bufferPool.peek("a_color_value");
261
+ if (!posBuf || !idBuf) {
262
+ return;
263
+ }
264
+
265
+ const posBase = s * cap * posStride;
266
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuf.buffer);
267
+ gl.vertexAttribPointer(
268
+ cache.a_start,
269
+ 2,
270
+ gl.FLOAT,
271
+ false,
272
+ posStride,
273
+ posBase,
274
+ );
275
+ gl.vertexAttribPointer(
276
+ cache.a_end,
277
+ 2,
278
+ gl.FLOAT,
279
+ false,
280
+ posStride,
281
+ posBase + posStride,
282
+ );
283
+
284
+ const idBase = s * cap * idStride;
285
+ gl.bindBuffer(gl.ARRAY_BUFFER, idBuf.buffer);
286
+ gl.vertexAttribPointer(
287
+ cache.a_color_start,
288
+ 1,
289
+ gl.FLOAT,
290
+ false,
291
+ idStride,
292
+ idBase,
293
+ );
294
+ gl.vertexAttribPointer(
295
+ cache.a_color_end,
296
+ 1,
297
+ gl.FLOAT,
298
+ false,
299
+ idStride,
300
+ idBase + idStride,
301
+ );
302
+
303
+ getInstancing(glManager).drawArraysInstanced(
304
+ gl.TRIANGLE_STRIP,
305
+ 0,
306
+ 4,
307
+ count - 1,
308
+ );
309
+ }
310
+
311
+ function unbindLineDivisors(
312
+ cache: LineCache,
313
+ glManager: WebGLContextManager,
314
+ ): void {
315
+ const { setDivisor } = getInstancing(glManager);
316
+ setDivisor(cache.a_start, 0);
317
+ setDivisor(cache.a_end, 0);
318
+ setDivisor(cache.a_color_start, 0);
319
+ setDivisor(cache.a_color_end, 0);
320
+ }
@@ -0,0 +1,239 @@
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 { WebGLContextManager } from "../../../webgl/context-manager";
14
+ import type { CartesianChart } from "../cartesian";
15
+ import type { Glyph } from "../glyph";
16
+ import { bindGradientTexture } from "../../../webgl/gradient-texture";
17
+ import { buildPointRowTooltipLines } from "../tooltip-lines";
18
+ import scatterVert from "../../../shaders/scatter.vert.glsl";
19
+ import scatterFrag from "../../../shaders/scatter.frag.glsl";
20
+
21
+ type GL = WebGL2RenderingContext | WebGLRenderingContext;
22
+
23
+ interface PointCache {
24
+ program: WebGLProgram;
25
+ u_projection: WebGLUniformLocation | null;
26
+ u_point_size: WebGLUniformLocation | null;
27
+ u_color_range: WebGLUniformLocation | null;
28
+ u_gradient_lut: WebGLUniformLocation | null;
29
+ u_size_range: WebGLUniformLocation | null;
30
+ u_point_size_range: WebGLUniformLocation | null;
31
+ a_position: number;
32
+ a_color_value: number;
33
+ a_size_value: number;
34
+ }
35
+
36
+ /**
37
+ * `gl.POINTS` glyph — one squared/antialiased point per data row. Color
38
+ * and size are driven by the shared `a_color_value` / `a_size_value`
39
+ * buffers; the vertex shader does sign-aware color-t mapping and samples
40
+ * the gradient LUT. One draw call per series (the slot layout leaves
41
+ * gaps at each series' tail that we can't safely include in a single
42
+ * draw — dispatching `count[s]` per series skips them).
43
+ */
44
+ export class PointGlyph implements Glyph {
45
+ readonly name = "point" as const;
46
+ private _cache: PointCache | null = null;
47
+
48
+ ensureProgram(
49
+ _chart: CartesianChart,
50
+ glManager: WebGLContextManager,
51
+ ): void {
52
+ if (this._cache) {
53
+ return;
54
+ }
55
+
56
+ const gl = glManager.gl;
57
+ const program = glManager.shaders.getOrCreate(
58
+ "scatter",
59
+ scatterVert,
60
+ scatterFrag,
61
+ );
62
+ this._cache = {
63
+ program,
64
+ u_projection: gl.getUniformLocation(program, "u_projection"),
65
+ u_point_size: gl.getUniformLocation(program, "u_point_size"),
66
+ u_color_range: gl.getUniformLocation(program, "u_color_range"),
67
+ u_gradient_lut: gl.getUniformLocation(program, "u_gradient_lut"),
68
+ u_size_range: gl.getUniformLocation(program, "u_size_range"),
69
+ u_point_size_range: gl.getUniformLocation(
70
+ program,
71
+ "u_point_size_range",
72
+ ),
73
+ a_position: gl.getAttribLocation(program, "a_position"),
74
+ a_color_value: gl.getAttribLocation(program, "a_color_value"),
75
+ a_size_value: gl.getAttribLocation(program, "a_size_value"),
76
+ };
77
+ }
78
+
79
+ draw(
80
+ chart: CartesianChart,
81
+ glManager: WebGLContextManager,
82
+ projection: Float32Array,
83
+ ): void {
84
+ const cache = this._cache;
85
+ if (!cache) {
86
+ return;
87
+ }
88
+
89
+ if (!bindPointState(cache, chart, glManager, projection)) {
90
+ return;
91
+ }
92
+
93
+ // Per-series tight draws: each series `s` occupies slots
94
+ // `[s*cap, s*cap + count[s])`. Dispatching `count[s]` avoids
95
+ // rasterizing unused tail slots. All attribs have divisor=0 so
96
+ // `first` shifts them together.
97
+ const gl = glManager.gl;
98
+ const numSeries = Math.max(1, chart._splitGroups.length);
99
+ const cap = chart._seriesCapacity;
100
+ for (let s = 0; s < numSeries; s++) {
101
+ const count = chart._seriesUploadedCounts[s] ?? 0;
102
+ if (count <= 0) {
103
+ continue;
104
+ }
105
+
106
+ gl.drawArrays(gl.POINTS, s * cap, count);
107
+ }
108
+ }
109
+
110
+ drawSeries(
111
+ chart: CartesianChart,
112
+ glManager: WebGLContextManager,
113
+ projection: Float32Array,
114
+ seriesIdx: number,
115
+ ): void {
116
+ const cache = this._cache;
117
+ if (!cache) {
118
+ return;
119
+ }
120
+
121
+ if (!bindPointState(cache, chart, glManager, projection)) {
122
+ return;
123
+ }
124
+
125
+ const count = chart._seriesUploadedCounts[seriesIdx] ?? 0;
126
+ if (count <= 0) {
127
+ return;
128
+ }
129
+
130
+ const gl = glManager.gl;
131
+ const cap = chart._seriesCapacity;
132
+ gl.drawArrays(gl.POINTS, seriesIdx * cap, count);
133
+ }
134
+
135
+ buildTooltipLines(
136
+ chart: CartesianChart,
137
+ flatIdx: number,
138
+ ): Promise<string[]> {
139
+ return buildPointRowTooltipLines(chart, flatIdx);
140
+ }
141
+
142
+ tooltipOptions() {
143
+ return { crosshair: true, highlightRadius: 6 };
144
+ }
145
+
146
+ destroy(_chart: CartesianChart): void {
147
+ // Program lifetime is owned by the shader registry; just drop
148
+ // the cache reference. No private GPU resources to free.
149
+ this._cache = null;
150
+ }
151
+ }
152
+
153
+ function setUniforms(
154
+ cache: PointCache,
155
+ gl: GL,
156
+ projection: Float32Array,
157
+ chart: CartesianChart,
158
+ dpr: number,
159
+ ): void {
160
+ gl.uniformMatrix4fv(cache.u_projection, false, projection);
161
+ gl.uniform1f(cache.u_point_size, chart._pluginConfig.point_size_px * dpr);
162
+
163
+ if (chart._colorMin < chart._colorMax) {
164
+ gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
165
+ } else {
166
+ gl.uniform2f(cache.u_color_range, 0.0, 0.0);
167
+ }
168
+
169
+ if (chart._sizeMin < chart._sizeMax) {
170
+ gl.uniform2f(cache.u_size_range, chart._sizeMin, chart._sizeMax);
171
+ } else {
172
+ gl.uniform2f(cache.u_size_range, 0.0, 0.0);
173
+ }
174
+
175
+ const size_scale_factor = Math.min(chart._pluginConfig.point_size_px, 3);
176
+
177
+ gl.uniform2f(
178
+ cache.u_point_size_range,
179
+ Math.max(
180
+ 2 * dpr,
181
+ (chart._pluginConfig.point_size_px / size_scale_factor) * dpr,
182
+ ),
183
+ chart._pluginConfig.point_size_px * size_scale_factor * dpr,
184
+ );
185
+ }
186
+
187
+ /**
188
+ * Shared pre-draw state setup for `draw` and `drawSeries`. Binds the
189
+ * program, uploads uniforms + gradient texture, wires the three per-
190
+ * vertex attributes. Returns false if the gradient cache is missing.
191
+ */
192
+ function bindPointState(
193
+ cache: PointCache,
194
+ chart: CartesianChart,
195
+ glManager: WebGLContextManager,
196
+ projection: Float32Array,
197
+ ): boolean {
198
+ const gl = glManager.gl;
199
+ if (!chart._gradientCache) {
200
+ return false;
201
+ }
202
+
203
+ gl.useProgram(cache.program);
204
+ setUniforms(cache, gl, projection, chart, glManager.dpr);
205
+ bindGradientTexture(
206
+ glManager,
207
+ chart._gradientCache.texture,
208
+ cache.u_gradient_lut,
209
+ 0,
210
+ );
211
+
212
+ // Render-path uses `peek` (not `getOrCreate`) so we never
213
+ // recreate buffers from the draw path. If a buffer hasn't been
214
+ // uploaded yet — e.g. pan/zoom render landing between a pending
215
+ // draw's `ensureBufferCapacity` and its `uploadChunk` — return
216
+ // false and let the caller skip `drawArrays`. Painting against
217
+ // a freshly-recreated zero-filled buffer would show one frame
218
+ // of empty plot area while gridlines/chrome remain correct.
219
+ const posBuf = glManager.bufferPool.peek("a_position");
220
+ const colorBuf = glManager.bufferPool.peek("a_color_value");
221
+ const sizeBuf = glManager.bufferPool.peek("a_size_value");
222
+ if (!posBuf || !colorBuf || !sizeBuf) {
223
+ return false;
224
+ }
225
+
226
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuf.buffer);
227
+ gl.enableVertexAttribArray(cache.a_position);
228
+ gl.vertexAttribPointer(cache.a_position, 2, gl.FLOAT, false, 0, 0);
229
+
230
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf.buffer);
231
+ gl.enableVertexAttribArray(cache.a_color_value);
232
+ gl.vertexAttribPointer(cache.a_color_value, 1, gl.FLOAT, false, 0, 0);
233
+
234
+ gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuf.buffer);
235
+ gl.enableVertexAttribArray(cache.a_size_value);
236
+ gl.vertexAttribPointer(cache.a_size_value, 1, gl.FLOAT, false, 0, 0);
237
+
238
+ return true;
239
+ }
@@ -0,0 +1,56 @@
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
+ /**
14
+ * Slot-indexed string store for the scatter "Label" column. Strings
15
+ * are deduplicated across split-by facets so identical labels share a
16
+ * dictionary entry; the per-slot `Int32Array` then holds dictionary
17
+ * indices (`-1` means "no label for this slot").
18
+ */
19
+ export class LabelInterner {
20
+ readonly data: Int32Array;
21
+ readonly dictionary: string[] = [];
22
+ private readonly dictMap: Map<string, number> = new Map();
23
+
24
+ constructor(capacity: number) {
25
+ this.data = new Int32Array(capacity);
26
+ this.data.fill(-1);
27
+ }
28
+
29
+ /**
30
+ * Insert (or look up) `label` and write its dictionary index into
31
+ * the slot at `flatIdx`. Returns the assigned dictionary index.
32
+ */
33
+ set(flatIdx: number, label: string): number {
34
+ let mapped = this.dictMap.get(label);
35
+ if (mapped === undefined) {
36
+ mapped = this.dictionary.length;
37
+ this.dictionary.push(label);
38
+ this.dictMap.set(label, mapped);
39
+ }
40
+
41
+ this.data[flatIdx] = mapped;
42
+ return mapped;
43
+ }
44
+
45
+ /**
46
+ * Resolve a slot's label string, or `null` if unset.
47
+ */
48
+ get(flatIdx: number): string | null {
49
+ const idx = this.data[flatIdx];
50
+ if (idx < 0) {
51
+ return null;
52
+ }
53
+
54
+ return this.dictionary[idx];
55
+ }
56
+ }
@@ -0,0 +1,80 @@
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 { CartesianChart } from "./cartesian";
14
+
15
+ /**
16
+ * Build the per-row tooltip for a point-style glyph (scatter, gradient
17
+ * heatmap). Resolves the source arrow row via the chart's lazy row
18
+ * fetcher, then surfaces every non-null column under the (split-aware)
19
+ * prefix filter formatted by column type.
20
+ *
21
+ * Returns `[]` when the chart has no row-index mirror or no fetcher;
22
+ * callers should fall back to a geometry-only tooltip in that case.
23
+ */
24
+ export async function buildPointRowTooltipLines(
25
+ chart: CartesianChart,
26
+ flatIdx: number,
27
+ ): Promise<string[]> {
28
+ const lines: string[] = [];
29
+ if (!chart._rowIndexData || !chart._lazyRows) {
30
+ return lines;
31
+ }
32
+
33
+ const rowIdx = chart._rowIndexData[flatIdx];
34
+ if (rowIdx < 0) {
35
+ return lines;
36
+ }
37
+
38
+ if (chart._splitGroups.length > 0 && chart._seriesCapacity > 0) {
39
+ const seriesIdx = Math.floor(flatIdx / chart._seriesCapacity);
40
+ const sg = chart._splitGroups[seriesIdx];
41
+ if (sg?.prefix) {
42
+ lines.push(sg.prefix);
43
+ }
44
+ }
45
+
46
+ const row = await chart._lazyRows.fetchRow(rowIdx);
47
+
48
+ const prefixFilter =
49
+ chart._splitGroups.length > 0 && chart._seriesCapacity > 0
50
+ ? (chart._splitGroups[Math.floor(flatIdx / chart._seriesCapacity)]
51
+ ?.prefix ?? null)
52
+ : null;
53
+
54
+ for (const [colName, value] of row) {
55
+ if (value === null || value === undefined) {
56
+ continue;
57
+ }
58
+
59
+ let displayName = colName;
60
+ if (prefixFilter !== null) {
61
+ const expected = `${prefixFilter}|`;
62
+ if (!colName.startsWith(expected)) {
63
+ continue;
64
+ }
65
+
66
+ displayName = colName.substring(expected.length);
67
+ } else if (colName.includes("|")) {
68
+ continue;
69
+ }
70
+
71
+ if (typeof value === "number") {
72
+ const formatted = chart.getColumnFormatter(colName, "value")(value);
73
+ lines.push(`${displayName}: ${formatted}`);
74
+ } else {
75
+ lines.push(`${displayName}: ${value}`);
76
+ }
77
+ }
78
+
79
+ return lines;
80
+ }