@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,516 @@
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 { ColumnDataMap } from "../../data/view-reader";
14
+ import { buildSplitGroups } from "../../data/split-groups";
15
+ import type { CategoricalLevel } from "../../axis/categorical-axis";
16
+ import {
17
+ resolveAxisMode,
18
+ resolveCategoryAxis,
19
+ resolveNumericCategoryDomain,
20
+ type AxisMode,
21
+ type NumericCategoryDomain,
22
+ } from "../common/category-axis-resolver";
23
+ import { computeSlotGeometry } from "../common/band-layout";
24
+
25
+ export interface CandleSeriesInfo {
26
+ seriesId: number;
27
+ splitIdx: number;
28
+ splitKey: string;
29
+ label: string;
30
+ }
31
+
32
+ /**
33
+ * Logical candle record. Synthesized on demand from {@link CandleColumns}
34
+ * via {@link readCandleRecord} for tooltip / hover paths. The pipeline
35
+ * never materializes these — see `CandleColumns` for the columnar
36
+ * storage that replaces the legacy `CandleRecord[]`.
37
+ */
38
+ export interface CandleRecord {
39
+ catIdx: number;
40
+ splitIdx: number;
41
+ seriesId: number;
42
+ xCenter: number;
43
+ halfWidth: number;
44
+ open: number;
45
+ close: number;
46
+ high: number;
47
+ low: number;
48
+ isUp: boolean;
49
+ }
50
+
51
+ /**
52
+ * Columnar storage for the candle record set. Replaces the legacy
53
+ * `CandleRecord[]` to avoid per-record POJO allocation at scale.
54
+ *
55
+ * Records are appended in `(splitIdx, catIdx)` order as the pipeline
56
+ * loop is structured (outer split, inner category) — both `xCenter` and
57
+ * `catIdx` are monotonically non-decreasing within a split, which the
58
+ * hit-test uses for binary-search narrowing.
59
+ *
60
+ * `count` is the active record count; the underlying typed arrays may
61
+ * be over-allocated for capacity reuse across builds.
62
+ */
63
+ export interface CandleColumns {
64
+ count: number;
65
+ catIdx: Int32Array;
66
+ splitIdx: Int32Array;
67
+ seriesId: Int32Array;
68
+ xCenter: Float64Array;
69
+ halfWidth: Float64Array;
70
+ open: Float64Array;
71
+ close: Float64Array;
72
+ high: Float64Array;
73
+ low: Float64Array;
74
+
75
+ /**
76
+ * 1 = up (close ≥ open), 0 = down.
77
+ */
78
+ isUp: Uint8Array;
79
+ }
80
+
81
+ export function emptyCandleColumns(): CandleColumns {
82
+ return {
83
+ count: 0,
84
+ catIdx: new Int32Array(0),
85
+ splitIdx: new Int32Array(0),
86
+ seriesId: new Int32Array(0),
87
+ xCenter: new Float64Array(0),
88
+ halfWidth: new Float64Array(0),
89
+ open: new Float64Array(0),
90
+ close: new Float64Array(0),
91
+ high: new Float64Array(0),
92
+ low: new Float64Array(0),
93
+ isUp: new Uint8Array(0),
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Reuse `prev`'s typed arrays when capacity is sufficient, else allocate
99
+ * fresh. Resets `count` to 0; pipeline writes from index 0.
100
+ */
101
+ export function ensureCandleColumnsCapacity(
102
+ prev: CandleColumns | null,
103
+ capacity: number,
104
+ ): CandleColumns {
105
+ if (prev && prev.catIdx.length >= capacity) {
106
+ prev.count = 0;
107
+ return prev;
108
+ }
109
+
110
+ return {
111
+ count: 0,
112
+ catIdx: new Int32Array(capacity),
113
+ splitIdx: new Int32Array(capacity),
114
+ seriesId: new Int32Array(capacity),
115
+ xCenter: new Float64Array(capacity),
116
+ halfWidth: new Float64Array(capacity),
117
+ open: new Float64Array(capacity),
118
+ close: new Float64Array(capacity),
119
+ high: new Float64Array(capacity),
120
+ low: new Float64Array(capacity),
121
+ isUp: new Uint8Array(capacity),
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Synthesize a {@link CandleRecord} POJO for record `i`. Used by
127
+ * tooltip / pinned tooltip / hover return paths; not called in any
128
+ * frame-rate hot loop.
129
+ */
130
+ export function readCandleRecord(cols: CandleColumns, i: number): CandleRecord {
131
+ return {
132
+ catIdx: cols.catIdx[i],
133
+ splitIdx: cols.splitIdx[i],
134
+ seriesId: cols.seriesId[i],
135
+ xCenter: cols.xCenter[i],
136
+ halfWidth: cols.halfWidth[i],
137
+ open: cols.open[i],
138
+ close: cols.close[i],
139
+ high: cols.high[i],
140
+ low: cols.low[i],
141
+ isUp: cols.isUp[i] !== 0,
142
+ };
143
+ }
144
+
145
+ export interface CandlestickPipelineInput {
146
+ columns: ColumnDataMap;
147
+ numRows: number;
148
+ columnSlots: (string | null)[];
149
+ groupBy: string[];
150
+ splitBy: string[];
151
+
152
+ /**
153
+ * Source-column types for `group_by` columns. Same shape as the bar
154
+ * pipeline — used to stringify non-string row-paths and to enable
155
+ * numeric-axis mode for a single non-string non-boolean group_by.
156
+ */
157
+ groupByTypes: Record<string, string>;
158
+
159
+ /**
160
+ * Band-slot geometry knobs sourced from
161
+ * {@link PluginConfig.band_inner_frac} / `bar_inner_pad`. Forwarded
162
+ * to `computeSlotGeometry`. Replace the `BAND_INNER_FRAC` /
163
+ * `BAR_INNER_PAD` constants.
164
+ */
165
+ bandInnerFrac: number;
166
+ barInnerPad: number;
167
+
168
+ /**
169
+ * Reusable scratch — pipeline writes records into the typed arrays
170
+ * in place. Pass the previous build's columns to amortize
171
+ * allocation across data reloads.
172
+ */
173
+ scratchCandles?: CandleColumns | null;
174
+ }
175
+
176
+ export type { NumericCategoryDomain };
177
+
178
+ export interface CandlestickPipelineResult {
179
+ splitPrefixes: string[];
180
+ rowPaths: CategoricalLevel[];
181
+ numCategories: number;
182
+ rowOffset: number;
183
+
184
+ /**
185
+ * Axis mode discriminator (see bar-build for semantics).
186
+ */
187
+ axisMode: AxisMode;
188
+ numericCategoryDomain: NumericCategoryDomain | null;
189
+
190
+ /**
191
+ * Per-category X position (real data units) in numeric mode.
192
+ */
193
+ categoryPositions: Float64Array | null;
194
+ series: CandleSeriesInfo[];
195
+ candles: CandleColumns;
196
+ yDomain: { min: number; max: number };
197
+ }
198
+
199
+ const EMPTY_RESULT: Omit<CandlestickPipelineResult, "candles"> = {
200
+ splitPrefixes: [],
201
+ rowPaths: [],
202
+ numCategories: 0,
203
+ rowOffset: 0,
204
+ axisMode: { mode: "category" },
205
+ numericCategoryDomain: null,
206
+ categoryPositions: null,
207
+ series: [],
208
+ yDomain: { min: 0, max: 1 },
209
+ };
210
+
211
+ /**
212
+ * Pure pipeline: turn a raw `ColumnDataMap` into a columnar
213
+ * {@link CandleColumns}. Column slots (Open / Close / High / Low) mirror
214
+ * d3fc's convention:
215
+ * - `Open` is required.
216
+ * - `Close` falls back to the next row's Open (last row: own Open).
217
+ * - `High` falls back to `max(open, close)`.
218
+ * - `Low` falls back to `min(open, close)`.
219
+ *
220
+ * The fallbacks apply per series when `split_by` is active, so each
221
+ * split reads the "next row" from its own series.
222
+ */
223
+ export function buildCandlestickPipeline(
224
+ input: CandlestickPipelineInput,
225
+ ): CandlestickPipelineResult {
226
+ const {
227
+ columns,
228
+ numRows,
229
+ columnSlots,
230
+ groupBy,
231
+ splitBy,
232
+ groupByTypes,
233
+ bandInnerFrac,
234
+ barInnerPad,
235
+ scratchCandles,
236
+ } = input;
237
+ const axisMode = resolveAxisMode(groupBy, groupByTypes);
238
+
239
+ const openBase = columnSlots[0] || "";
240
+ if (!openBase) {
241
+ return { ...EMPTY_RESULT, candles: emptyCandleColumns() };
242
+ }
243
+
244
+ const closeBase = columnSlots[1] || "";
245
+ const highBase = columnSlots[2] || "";
246
+ const lowBase = columnSlots[3] || "";
247
+
248
+ // Split-prefix resolution. Each split provides its own Open column
249
+ // (required) and may provide any subset of Close / High / Low.
250
+ const splitPrefixes: string[] = [];
251
+ if (splitBy.length > 0) {
252
+ const aggregates = [openBase];
253
+ if (closeBase) {
254
+ aggregates.push(closeBase);
255
+ }
256
+
257
+ if (highBase) {
258
+ aggregates.push(highBase);
259
+ }
260
+
261
+ if (lowBase) {
262
+ aggregates.push(lowBase);
263
+ }
264
+
265
+ for (const g of buildSplitGroups(columns, [openBase], aggregates)) {
266
+ if (g.colNames.has(openBase)) {
267
+ splitPrefixes.push(g.prefix);
268
+ }
269
+ }
270
+
271
+ if (splitPrefixes.length === 0) {
272
+ splitPrefixes.push("");
273
+ }
274
+ } else {
275
+ splitPrefixes.push("");
276
+ }
277
+
278
+ const levelTypes = groupBy.map((name) => groupByTypes[name] ?? "string");
279
+ const { rowPaths, numCategories, rowOffset } = resolveCategoryAxis(
280
+ columns,
281
+ numRows,
282
+ groupBy.length,
283
+ levelTypes,
284
+ );
285
+ if (numCategories === 0) {
286
+ return {
287
+ ...EMPTY_RESULT,
288
+ axisMode,
289
+ splitPrefixes,
290
+ rowPaths,
291
+ rowOffset,
292
+ candles: emptyCandleColumns(),
293
+ };
294
+ }
295
+
296
+ const P = splitPrefixes.length;
297
+ const series: CandleSeriesInfo[] = [];
298
+ for (let p = 0; p < P; p++) {
299
+ const splitKey = splitPrefixes[p];
300
+ series.push({
301
+ seriesId: p,
302
+ splitIdx: p,
303
+ splitKey,
304
+ label: splitKey === "" ? openBase : splitKey,
305
+ });
306
+ }
307
+
308
+ // Numeric-mode category positions — read from `__ROW_PATH_0__` so
309
+ // candles anchor at real data values (e.g. ms-since-epoch) instead
310
+ // of logical category indices.
311
+ let categoryPositions: Float64Array | null = null;
312
+ let numericCategoryDomain: NumericCategoryDomain | null = null;
313
+ let numericBandWidth = 1;
314
+ if (axisMode.mode === "numeric" && numCategories > 0) {
315
+ const rp = columns.get("__ROW_PATH_0__");
316
+ const resolved = resolveNumericCategoryDomain(
317
+ rp?.values,
318
+ numCategories,
319
+ rowOffset,
320
+ groupBy[0] ?? "",
321
+ axisMode.numericType === "date" ||
322
+ axisMode.numericType === "datetime",
323
+ );
324
+ if (resolved) {
325
+ categoryPositions = resolved.categoryPositions;
326
+ numericCategoryDomain = resolved.numericCategoryDomain;
327
+ numericBandWidth = resolved.numericCategoryDomain.bandWidth;
328
+ }
329
+ }
330
+
331
+ const baseSlot = computeSlotGeometry(P, bandInnerFrac, barInnerPad);
332
+ const slotWidth = baseSlot.slotWidth * numericBandWidth;
333
+ const halfWidth = baseSlot.halfWidth * numericBandWidth;
334
+ const halfP = (P - 1) / 2;
335
+
336
+ // Per-series column references (resolved once, not per row).
337
+ const seriesCols: {
338
+ openCol: ArrayLike<unknown> | null;
339
+ closeCol: ArrayLike<unknown> | null;
340
+ highCol: ArrayLike<unknown> | null;
341
+ lowCol: ArrayLike<unknown> | null;
342
+ openValid: Uint8Array | null;
343
+ closeValid: Uint8Array | null;
344
+ highValid: Uint8Array | null;
345
+ lowValid: Uint8Array | null;
346
+ }[] = [];
347
+ for (let p = 0; p < P; p++) {
348
+ const prefix = splitPrefixes[p];
349
+ const nm = (base: string) =>
350
+ prefix === "" ? base : `${prefix}|${base}`;
351
+ const openCol = columns.get(nm(openBase));
352
+ if (!openCol?.values) {
353
+ seriesCols.push({
354
+ openCol: null,
355
+ closeCol: null,
356
+ highCol: null,
357
+ lowCol: null,
358
+ openValid: null,
359
+ closeValid: null,
360
+ highValid: null,
361
+ lowValid: null,
362
+ });
363
+ continue;
364
+ }
365
+
366
+ const closeCol = closeBase ? columns.get(nm(closeBase)) : null;
367
+ const highCol = highBase ? columns.get(nm(highBase)) : null;
368
+ const lowCol = lowBase ? columns.get(nm(lowBase)) : null;
369
+ seriesCols.push({
370
+ openCol: openCol.values,
371
+ closeCol: closeCol?.values ?? null,
372
+ highCol: highCol?.values ?? null,
373
+ lowCol: lowCol?.values ?? null,
374
+ openValid: openCol.valid ?? null,
375
+ closeValid: closeCol?.valid ?? null,
376
+ highValid: highCol?.valid ?? null,
377
+ lowValid: lowCol?.valid ?? null,
378
+ });
379
+ }
380
+
381
+ // Pre-allocate columnar candle storage at N*P upper bound. The
382
+ // pipeline emits at most one record per (split, cat) cell.
383
+ const cap = numCategories * P;
384
+ const candles = ensureCandleColumnsCapacity(scratchCandles ?? null, cap);
385
+ let write = 0;
386
+
387
+ let yMin = Infinity;
388
+ let yMax = -Infinity;
389
+
390
+ const N = numCategories;
391
+
392
+ for (let p = 0; p < P; p++) {
393
+ const sc = seriesCols[p];
394
+ const openCol = sc.openCol;
395
+ if (!openCol) {
396
+ continue;
397
+ }
398
+
399
+ const closeCol = sc.closeCol;
400
+ const highCol = sc.highCol;
401
+ const lowCol = sc.lowCol;
402
+ const openValid = sc.openValid;
403
+ const closeValid = sc.closeValid;
404
+ const highValid = sc.highValid;
405
+ const lowValid = sc.lowValid;
406
+ const slotOffset = (p - halfP) * slotWidth;
407
+
408
+ for (let catI = 0; catI < N; catI++) {
409
+ const row = catI + rowOffset;
410
+
411
+ // Inlined valid-bit test (was a closure in the legacy build).
412
+ if (openValid && !((openValid[row >> 3] >> (row & 7)) & 1)) {
413
+ continue;
414
+ }
415
+
416
+ const open = openCol[row] as number;
417
+ if (!isFinite(open)) {
418
+ continue;
419
+ }
420
+
421
+ // Close fallback: explicit close column → next row's open →
422
+ // own open (degenerate). Each branch inlines the validity
423
+ // check to avoid a per-row closure.
424
+ let close: number;
425
+ if (
426
+ closeCol &&
427
+ (!closeValid || ((closeValid[row >> 3] >> (row & 7)) & 1) !== 0)
428
+ ) {
429
+ const v = closeCol[row] as number;
430
+ close = isFinite(v) ? v : open;
431
+ } else {
432
+ const nextRow = catI < N - 1 ? catI + 1 + rowOffset : row;
433
+ if (
434
+ !openValid ||
435
+ ((openValid[nextRow >> 3] >> (nextRow & 7)) & 1) !== 0
436
+ ) {
437
+ const v = openCol[nextRow] as number;
438
+ close = isFinite(v) ? v : open;
439
+ } else {
440
+ close = open;
441
+ }
442
+ }
443
+
444
+ let high: number;
445
+ if (
446
+ highCol &&
447
+ (!highValid || ((highValid[row >> 3] >> (row & 7)) & 1) !== 0)
448
+ ) {
449
+ const v = highCol[row] as number;
450
+ high = isFinite(v) ? v : open > close ? open : close;
451
+ } else {
452
+ high = open > close ? open : close;
453
+ }
454
+
455
+ let low: number;
456
+ if (
457
+ lowCol &&
458
+ (!lowValid || ((lowValid[row >> 3] >> (row & 7)) & 1) !== 0)
459
+ ) {
460
+ const v = lowCol[row] as number;
461
+ low = isFinite(v) ? v : open < close ? open : close;
462
+ } else {
463
+ low = open < close ? open : close;
464
+ }
465
+
466
+ const center = categoryPositions ? categoryPositions[catI] : catI;
467
+ const xCenter = center + slotOffset;
468
+ const isUp = close >= open ? 1 : 0;
469
+
470
+ candles.catIdx[write] = catI;
471
+ candles.splitIdx[write] = p;
472
+ candles.seriesId[write] = p;
473
+ candles.xCenter[write] = xCenter;
474
+ candles.halfWidth[write] = halfWidth;
475
+ candles.open[write] = open;
476
+ candles.close[write] = close;
477
+ candles.high[write] = high;
478
+ candles.low[write] = low;
479
+ candles.isUp[write] = isUp;
480
+ write++;
481
+
482
+ if (low < yMin) {
483
+ yMin = low;
484
+ }
485
+
486
+ if (high > yMax) {
487
+ yMax = high;
488
+ }
489
+ }
490
+ }
491
+
492
+ candles.count = write;
493
+
494
+ if (!isFinite(yMin) || !isFinite(yMax)) {
495
+ yMin = 0;
496
+ yMax = 1;
497
+ } else if (yMin === yMax) {
498
+ // Zero-height domain: pad symmetrically so the axis renders.
499
+ const pad = Math.max(Math.abs(yMin), 1) * 0.05;
500
+ yMin -= pad;
501
+ yMax += pad;
502
+ }
503
+
504
+ return {
505
+ splitPrefixes,
506
+ rowPaths,
507
+ numCategories,
508
+ rowOffset,
509
+ axisMode,
510
+ numericCategoryDomain,
511
+ categoryPositions,
512
+ series,
513
+ candles,
514
+ yDomain: { min: yMin, max: yMax },
515
+ };
516
+ }