@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,623 @@
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
+ * Streaming tree pipeline shared by treemap and sunburst. Rows arrive
15
+ * incrementally; each chunk inserts its rows directly into the SOA
16
+ * tree. Per-leaf tooltip columns are fetched lazily on pin via the
17
+ * chart's `_lazyRows`; the tree only retains `leafRowIdx` per leaf
18
+ * (small, O(leaves)) as the handle back to the source view row.
19
+ * After a chunk is processed, `finalizeTree` recomputes `value`
20
+ * bottom-up.
21
+ *
22
+ * Color mode:
23
+ * - `"numeric"` — `readColor` reads the row's numeric value; the
24
+ * render path maps it through the continuous gradient.
25
+ * - `"series"` — `readColor` reads the row's string value from the
26
+ * color column's dictionary, and `seedColorLabels` pre-populates
27
+ * `_uniqueColorLabels` in dictionary-index order. Render picks
28
+ * `palette[dictIdx % paletteSize]`.
29
+ * - `"empty"` — no color column; every leaf gets `palette[0]`.
30
+ *
31
+ * When `_splitBy` is populated, every row is duplicated — one insertion
32
+ * per split prefix, with that prefix pushed as the top-level path
33
+ * segment so the top-level children of the synthetic root become
34
+ * facet roots. The per-prefix `size` / `color` columns (named
35
+ * `${prefix}|${base}`) feed the facet's values. `seedColorLabels`
36
+ * runs once per split so every split's dictionary contributes to the
37
+ * shared legend.
38
+ */
39
+
40
+ import type { ColumnDataMap, ColumnData } from "../../data/view-reader";
41
+ import { buildSplitGroups } from "../../data/split-groups";
42
+ import { synthesizeStringLevel } from "./category-axis-resolver";
43
+ import { NULL_NODE } from "./node-store";
44
+ import type { TreeChartBase } from "./tree-chart";
45
+
46
+ // Reset
47
+
48
+ /**
49
+ * Reset the shared tree state. Called on the first chunk
50
+ * (`startRow === 0`) of each dataset load.
51
+ */
52
+ export function resetTreeState(chart: TreeChartBase): void {
53
+ chart._nodeStore.reset();
54
+ chart._childLookup.clear();
55
+
56
+ // Allocate the synthetic root (id 0 by convention).
57
+ const rootId = chart._nodeStore.allocate();
58
+ chart._nodeStore.name[rootId] = "Total";
59
+ chart._nodeStore.depth[rootId] = 0;
60
+ chart._nodeStore.parent[rootId] = NULL_NODE;
61
+ chart._childLookup.set(rootId, new Map());
62
+
63
+ chart._rootId = rootId;
64
+ chart._currentRootId = rootId;
65
+ chart._breadcrumbIds = [rootId];
66
+
67
+ chart._rowCount = 0;
68
+
69
+ chart._colorMin = Infinity;
70
+ chart._colorMax = -Infinity;
71
+ chart._uniqueColorLabels.clear();
72
+
73
+ chart._visibleNodeIds = null;
74
+ chart._visibleNodeCount = 0;
75
+ }
76
+
77
+ // Tree insertion
78
+
79
+ /**
80
+ * Find-or-create a child of `parentId` named `segment`. Uses a per-
81
+ * parent `Map<name, childId>` for O(1) lookup.
82
+ */
83
+ function childByName(
84
+ chart: TreeChartBase,
85
+ parentId: number,
86
+ segment: string,
87
+ ): number {
88
+ let lookup = chart._childLookup.get(parentId);
89
+ if (!lookup) {
90
+ lookup = new Map();
91
+ chart._childLookup.set(parentId, lookup);
92
+ }
93
+
94
+ const existing = lookup.get(segment);
95
+ if (existing !== undefined) {
96
+ return existing;
97
+ }
98
+
99
+ const childId = chart._nodeStore.allocate();
100
+ chart._nodeStore.name[childId] = segment;
101
+ chart._nodeStore.appendChild(parentId, childId);
102
+ lookup.set(segment, childId);
103
+ return childId;
104
+ }
105
+
106
+ /**
107
+ * Insert one row into the tree.
108
+ */
109
+ function insertRow(
110
+ chart: TreeChartBase,
111
+ rowPath: string[],
112
+ sizeValue: number,
113
+ colorValue: number,
114
+ colorLabel: string,
115
+ rowIdx: number,
116
+ groupByLen: number,
117
+ ): void {
118
+ let currentId = chart._rootId;
119
+ const depth = rowPath.length;
120
+ for (let d = 0; d < depth; d++) {
121
+ const childId = childByName(chart, currentId, rowPath[d]);
122
+
123
+ if (d === depth - 1) {
124
+ if (groupByLen === 0 || depth === groupByLen) {
125
+ // Store `|size|` for layout; remember the sign so
126
+ // render can dim negative leaves (matches the area
127
+ // chart's `theme.areaOpacity`).
128
+ chart._nodeStore.size[childId] = Math.abs(sizeValue);
129
+ chart._nodeStore.sizeSign[childId] = sizeValue < 0 ? -1 : 1;
130
+ chart._nodeStore.leafRowIdx[childId] = rowIdx;
131
+ }
132
+
133
+ if (!isNaN(colorValue)) {
134
+ chart._nodeStore.colorValue[childId] = colorValue;
135
+ if (colorValue < chart._colorMin) {
136
+ chart._colorMin = colorValue;
137
+ }
138
+
139
+ if (colorValue > chart._colorMax) {
140
+ chart._colorMax = colorValue;
141
+ }
142
+ }
143
+
144
+ if (colorLabel) {
145
+ chart._nodeStore.colorLabel[childId] = colorLabel;
146
+ if (!chart._uniqueColorLabels.has(colorLabel)) {
147
+ chart._uniqueColorLabels.set(
148
+ colorLabel,
149
+ chart._uniqueColorLabels.size,
150
+ );
151
+ }
152
+ }
153
+ }
154
+
155
+ currentId = childId;
156
+ }
157
+ }
158
+
159
+ function readColor(
160
+ chart: TreeChartBase,
161
+ colorCol: ColumnData | null | undefined,
162
+ rowIdx: number,
163
+ ): { colorValue: number; colorLabel: string } {
164
+ let colorValue = NaN;
165
+ let colorLabel = "";
166
+ if (!colorCol) {
167
+ return { colorValue, colorLabel };
168
+ }
169
+
170
+ if (chart._colorMode === "numeric" && colorCol.values) {
171
+ colorValue = colorCol.values[rowIdx] as number;
172
+ } else if (
173
+ chart._colorMode === "series" &&
174
+ colorCol.indices &&
175
+ colorCol.dictionary
176
+ ) {
177
+ // Read the dictionary-decoded string for this row. The palette
178
+ // index that render uses is `_uniqueColorLabels.get(label)`,
179
+ // which `seedColorLabels` seeds in dictionary-index order so
180
+ // the end result is `palette[dictIdx % paletteSize]`.
181
+ colorLabel = colorCol.dictionary[colorCol.indices[rowIdx]];
182
+ }
183
+
184
+ return { colorValue, colorLabel };
185
+ }
186
+
187
+ /**
188
+ * Seed `_uniqueColorLabels` with the color column's dictionary in
189
+ * index order. Using insertion-order-guarded `.set` means later
190
+ * chunks (or later splits in split_by mode) append new entries
191
+ * without disturbing already-assigned indices; for a single stable
192
+ * dictionary this yields `_uniqueColorLabels.get(dict[i]) === i`.
193
+ *
194
+ * No-op outside `"series"` mode or when the column lacks a
195
+ * dictionary.
196
+ */
197
+ function seedColorLabels(
198
+ chart: TreeChartBase,
199
+ colorCol: ColumnData | null | undefined,
200
+ ): void {
201
+ if (chart._colorMode !== "series") {
202
+ return;
203
+ }
204
+
205
+ if (!colorCol?.dictionary) {
206
+ return;
207
+ }
208
+
209
+ const dict = colorCol.dictionary;
210
+ for (let i = 0; i < dict.length; i++) {
211
+ const s = dict[i];
212
+ if (!chart._uniqueColorLabels.has(s)) {
213
+ chart._uniqueColorLabels.set(s, chart._uniqueColorLabels.size);
214
+ }
215
+ }
216
+ }
217
+
218
+ // Chunk processor
219
+
220
+ interface SplitSource {
221
+ prefix: string;
222
+ sizeCol: ColumnData | null;
223
+ colorCol: ColumnData | null;
224
+ }
225
+
226
+ /**
227
+ * Resolve the per-split size / color columns. Returns `null` when
228
+ * `_splitBy` is empty — callers then take the non-split fast path.
229
+ */
230
+ function resolveSplitSources(
231
+ chart: TreeChartBase,
232
+ columns: ColumnDataMap,
233
+ ): SplitSource[] | null {
234
+ if (chart._splitBy.length === 0) {
235
+ return null;
236
+ }
237
+
238
+ const required: string[] = chart._sizeName ? [chart._sizeName] : [];
239
+ const optional: string[] = chart._colorName ? [chart._colorName] : [];
240
+ const groups = buildSplitGroups(columns, required, optional);
241
+ if (groups.length === 0) {
242
+ return null;
243
+ }
244
+
245
+ return groups.map((g) => ({
246
+ prefix: g.prefix,
247
+ sizeCol: chart._sizeName
248
+ ? (columns.get(`${g.prefix}|${chart._sizeName}`) ?? null)
249
+ : null,
250
+ colorCol: chart._colorName
251
+ ? (columns.get(`${g.prefix}|${chart._colorName}`) ?? null)
252
+ : null,
253
+ }));
254
+ }
255
+
256
+ /**
257
+ * Process one incoming chunk: grow row-data buffers, walk every row,
258
+ * capture column values, and insert into the tree.
259
+ */
260
+ export function processTreeChunk(
261
+ chart: TreeChartBase,
262
+ columns: ColumnDataMap,
263
+ ): void {
264
+ const rpCols: { indices: Int32Array; dictionary: string[] }[] = [];
265
+ for (let n = 0; ; n++) {
266
+ const rp = columns.get(`__ROW_PATH_${n}__`);
267
+ if (!rp) {
268
+ break;
269
+ }
270
+
271
+ if (rp.type === "string" && rp.indices && rp.dictionary) {
272
+ rpCols.push({ indices: rp.indices, dictionary: rp.dictionary });
273
+ } else if (rp.values) {
274
+ // Non-string group_by (integer / float / date / datetime /
275
+ // boolean) — synthesize a string-indexed dictionary so the
276
+ // tree insertion loop can treat every level uniformly.
277
+ // Uses the same formatter as `resolveCategoryAxis`.
278
+ const levelName = chart._groupBy[n];
279
+ const levelType = chart._groupByTypes[levelName] ?? "string";
280
+ rpCols.push(synthesizeStringLevel(rp, rp.values.length, levelType));
281
+ } else {
282
+ break;
283
+ }
284
+ }
285
+
286
+ const hasGroupBy = rpCols.length > 0;
287
+ const groupByLen = chart._groupBy.length;
288
+ const splitSources = resolveSplitSources(chart, columns);
289
+ const hasSplits = splitSources !== null;
290
+
291
+ const sizeCol = chart._sizeName ? columns.get(chart._sizeName) : null;
292
+ const colorCol = chart._colorName ? columns.get(chart._colorName) : null;
293
+
294
+ const firstSizeCol = hasSplits ? splitSources![0].sizeCol : sizeCol;
295
+ const numRows = hasGroupBy
296
+ ? rpCols[0].indices.length
297
+ : (firstSizeCol?.values?.length ?? 0);
298
+ if (numRows === 0) {
299
+ return;
300
+ }
301
+
302
+ // Seed palette label indices from the color column's dictionary
303
+ // BEFORE inserting rows, so the first row doesn't assign label 0
304
+ // to whichever dict value it happens to reference. For splits we
305
+ // seed once per split's own color column so every dict value is
306
+ // known to the shared legend.
307
+ if (hasSplits) {
308
+ for (const src of splitSources!) {
309
+ seedColorLabels(chart, src.colorCol);
310
+ }
311
+ } else {
312
+ seedColorLabels(chart, colorCol);
313
+ }
314
+
315
+ // `base` is the source-view row offset the tree should tag its
316
+ // leaves with. `_rowCount` tracks how many rows prior chunks
317
+ // occupied so `leafRowIdx[childId] = base + i` still points back
318
+ // to the correct view row after multiple chunk arrivals.
319
+ const base = chart._rowCount;
320
+
321
+ // The split expansion inserts every row under one extra leading
322
+ // path segment — the split prefix. When `group_by` is also active
323
+ // the path is `[prefix, ...groupByLevels]` (length `groupByLen + 1`)
324
+ // and the leaf store gate inside `insertRow` (`depth === groupByLen`)
325
+ // needs the `+1` to find the leaf. When `group_by` is empty we fall
326
+ // through the no-group-by branch below, which writes paths of the
327
+ // form `[prefix, label]`; that path's leaf is the row label, and
328
+ // sizing it relies on `insertRow`'s `groupByLen === 0`
329
+ // "every leaf gets sized" branch — passing `0` here lets that
330
+ // branch fire so each row's leaf actually receives a size. The
331
+ // historical `+1`-always form quietly produced an effective
332
+ // `groupByLen=1` against a depth-2 path, so the size store was
333
+ // skipped and treemap / sunburst rendered nothing.
334
+ const effectiveGroupLen =
335
+ hasSplits && hasGroupBy ? groupByLen + 1 : groupByLen;
336
+
337
+ if (!hasGroupBy) {
338
+ // Flat fallback: synthesize a single-segment path per row from
339
+ // the first string column (or a "Row N" sentinel).
340
+ //
341
+ // In `split_by` mode every column the view emits is pivoted as
342
+ // `${splitPrefix}|${baseName}`, so two correctness traps need
343
+ // to be avoided here:
344
+ //
345
+ // 1. The size / color column the user picked appears under
346
+ // its prefixed form (`First Class|Region`), so a literal
347
+ // `name === chart._sizeName/_colorName` skip would miss
348
+ // them and the loop would happily pick the color column
349
+ // itself as the label source.
350
+ // 2. Reading a pivoted column by row index returns values
351
+ // keyed to ONE specific split (the prefix that happened
352
+ // to win the iteration order), not to that row's actual
353
+ // split — so even a "different" pivoted dictionary column
354
+ // isn't a legitimate per-row label. And once any pivoted
355
+ // column is used as a label source, `childByName` collapses
356
+ // every row sharing a `(prefix, label)` pair onto a single
357
+ // node — last-write-wins for `size` — which truncates the
358
+ // visible cell count to `cardinality(label) × cardinality(splits)`.
359
+ //
360
+ // The two checks below address both: compare base names (post-`|`)
361
+ // for the size/color skip, and reject any pivoted column outright
362
+ // in split mode. If nothing remains, `labelCol` stays undefined
363
+ // and the per-row loop falls through to the `Row N` sentinel,
364
+ // which gives every row a unique key.
365
+ let labelCol: ColumnData | undefined;
366
+ for (const [name, col] of columns) {
367
+ if (name.startsWith("__")) {
368
+ continue;
369
+ }
370
+
371
+ if (hasSplits && name.includes("|")) {
372
+ continue;
373
+ }
374
+
375
+ const pipeIdx = name.lastIndexOf("|");
376
+ const baseName =
377
+ pipeIdx === -1 ? name : name.substring(pipeIdx + 1);
378
+ if (baseName === chart._sizeName || baseName === chart._colorName) {
379
+ continue;
380
+ }
381
+
382
+ if (col.type === "string" && col.indices && col.dictionary) {
383
+ labelCol = col;
384
+ break;
385
+ }
386
+ }
387
+
388
+ const sources = hasSplits ? splitSources! : null;
389
+ for (let i = 0; i < numRows; i++) {
390
+ const label =
391
+ labelCol?.indices && labelCol?.dictionary
392
+ ? labelCol.dictionary[labelCol.indices[i]]
393
+ : `Row ${base + i}`;
394
+
395
+ if (sources) {
396
+ for (const src of sources) {
397
+ // Pass the signed value through; `insertRow` stores
398
+ // `|size|` and `sizeSign` separately so the
399
+ // render pass can dim negative leaves.
400
+ const sizeValue = src.sizeCol?.values
401
+ ? (src.sizeCol.values[i] as number)
402
+ : 1;
403
+ const { colorValue, colorLabel } = readColor(
404
+ chart,
405
+ src.colorCol,
406
+ i,
407
+ );
408
+ insertRow(
409
+ chart,
410
+ [src.prefix, label],
411
+ sizeValue,
412
+ colorValue,
413
+ colorLabel,
414
+ base + i,
415
+ effectiveGroupLen,
416
+ );
417
+ }
418
+ } else {
419
+ const sizeValue = sizeCol?.values
420
+ ? (sizeCol.values[i] as number)
421
+ : 1;
422
+ const { colorValue, colorLabel } = readColor(
423
+ chart,
424
+ colorCol,
425
+ i,
426
+ );
427
+ insertRow(
428
+ chart,
429
+ [label],
430
+ sizeValue,
431
+ colorValue,
432
+ colorLabel,
433
+ base + i,
434
+ effectiveGroupLen,
435
+ );
436
+ }
437
+ }
438
+
439
+ chart._rowCount = base + numRows;
440
+ return;
441
+ }
442
+
443
+ // Hierarchical (group_by present): reuse a scratch path buffer
444
+ // across rows to avoid per-row array allocation. When splits are
445
+ // active the scratch is one slot longer to hold the leading prefix.
446
+ const extra = hasSplits ? 1 : 0;
447
+ const pathScratch: string[] = new Array(rpCols.length + extra);
448
+ for (let i = 0; i < numRows; i++) {
449
+ let pathLen = 0;
450
+ for (let d = 0; d < rpCols.length; d++) {
451
+ const rp = rpCols[d];
452
+ const label = rp.dictionary[rp.indices[i]];
453
+ if (!label && label !== "0") {
454
+ break;
455
+ }
456
+
457
+ pathScratch[extra + pathLen++] = label;
458
+ }
459
+
460
+ if (pathLen === 0) {
461
+ continue;
462
+ } // skip total row
463
+
464
+ if (hasSplits) {
465
+ for (const src of splitSources!) {
466
+ pathScratch[0] = src.prefix;
467
+ const rowPath = pathScratch.slice(0, pathLen + 1);
468
+ const sizeValue = src.sizeCol?.values
469
+ ? (src.sizeCol.values[i] as number)
470
+ : 1;
471
+ const { colorValue, colorLabel } = readColor(
472
+ chart,
473
+ src.colorCol,
474
+ i,
475
+ );
476
+ insertRow(
477
+ chart,
478
+ rowPath,
479
+ sizeValue,
480
+ colorValue,
481
+ colorLabel,
482
+ base + i,
483
+ effectiveGroupLen,
484
+ );
485
+ }
486
+ } else {
487
+ const rowPath = pathScratch.slice(0, pathLen);
488
+ const sizeValue = sizeCol?.values
489
+ ? (sizeCol.values[i] as number)
490
+ : 1;
491
+ const { colorValue, colorLabel } = readColor(chart, colorCol, i);
492
+ insertRow(
493
+ chart,
494
+ rowPath,
495
+ sizeValue,
496
+ colorValue,
497
+ colorLabel,
498
+ base + i,
499
+ effectiveGroupLen,
500
+ );
501
+ }
502
+ }
503
+
504
+ chart._rowCount = base + numRows;
505
+ }
506
+
507
+ // Finalize
508
+
509
+ /**
510
+ * Post-chunk finalization.
511
+ * 1. Recompute `value` bottom-up from `size` via an iterative
512
+ * post-order walk.
513
+ * 2. Re-resolve `_currentRootId` from the breadcrumb name-path so
514
+ * drill state survives incremental chunk arrivals.
515
+ *
516
+ * `colorLabel` is set at insert time (`readColor`) and needs no
517
+ * post-pass: in `"series"` mode it comes from the color column's
518
+ * dictionary, and in `"numeric"` / `"empty"` modes it's unused.
519
+ */
520
+ export function finalizeTree(chart: TreeChartBase): void {
521
+ const store = chart._nodeStore;
522
+ const value = store.value;
523
+ const size = store.size;
524
+ const firstChild = store.firstChild;
525
+ const nextSibling = store.nextSibling;
526
+
527
+ // Iterative post-order. Stack holds `(id, state)` pairs; state
528
+ // 0 = pre-visit, 1 = post-visit.
529
+ let stack = new Int32Array(128);
530
+ stack[0] = chart._rootId;
531
+ stack[1] = 0;
532
+ let sp = 1;
533
+
534
+ // Reset value accumulators.
535
+ for (let i = 0; i < store.count; i++) {
536
+ value[i] = 0;
537
+ }
538
+
539
+ while (sp > 0) {
540
+ sp--;
541
+ const id = stack[sp * 2];
542
+ const s = stack[sp * 2 + 1];
543
+ if (s === 0) {
544
+ stack[sp * 2 + 1] = 1;
545
+ sp++;
546
+ for (let c = firstChild[id]; c !== NULL_NODE; c = nextSibling[c]) {
547
+ if ((sp + 1) * 2 > stack.length) {
548
+ const bigger = new Int32Array(stack.length * 2);
549
+ bigger.set(stack);
550
+ stack = bigger;
551
+ }
552
+
553
+ stack[sp * 2] = c;
554
+ stack[sp * 2 + 1] = 0;
555
+ sp++;
556
+ }
557
+ } else {
558
+ if (firstChild[id] === NULL_NODE) {
559
+ value[id] = Math.max(0, size[id]);
560
+ } else {
561
+ let sum = 0;
562
+ for (
563
+ let c = firstChild[id];
564
+ c !== NULL_NODE;
565
+ c = nextSibling[c]
566
+ ) {
567
+ sum += value[c];
568
+ }
569
+
570
+ value[id] = sum;
571
+ }
572
+ }
573
+ }
574
+
575
+ // Preserve drill state across incremental chunk arrivals: walk the
576
+ // breadcrumb name-path from the root and re-resolve. If a segment
577
+ // is missing (shouldn't happen with incremental build, but
578
+ // defensively), fall back to the root.
579
+ if (chart._breadcrumbIds.length > 1) {
580
+ const breadcrumbNames: string[] = [];
581
+ for (let i = 1; i < chart._breadcrumbIds.length; i++) {
582
+ breadcrumbNames.push(store.name[chart._breadcrumbIds[i]]);
583
+ }
584
+
585
+ let node = chart._rootId;
586
+ let valid = true;
587
+ for (const seg of breadcrumbNames) {
588
+ const lookup = chart._childLookup.get(node);
589
+ const next = lookup?.get(seg);
590
+ if (next === undefined) {
591
+ valid = false;
592
+ break;
593
+ }
594
+
595
+ node = next;
596
+ }
597
+
598
+ if (valid && store.firstChild[node] !== NULL_NODE) {
599
+ chart._currentRootId = node;
600
+ rebuildBreadcrumbs(chart, node);
601
+ return;
602
+ }
603
+ }
604
+
605
+ chart._currentRootId = chart._rootId;
606
+ chart._breadcrumbIds = [chart._rootId];
607
+ }
608
+
609
+ // Breadcrumbs
610
+
611
+ /**
612
+ * Rebuild `chart._breadcrumbIds` by walking up from `nodeId`.
613
+ */
614
+ export function rebuildBreadcrumbs(chart: TreeChartBase, nodeId: number): void {
615
+ const ids: number[] = [];
616
+ let n = nodeId;
617
+ while (n !== NULL_NODE) {
618
+ ids.unshift(n);
619
+ n = chart._nodeStore.parent[n];
620
+ }
621
+
622
+ chart._breadcrumbIds = ids;
623
+ }