@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,112 @@
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
+ * Generic "find the value-axis extent among records whose category
15
+ * position falls inside a visible window" helper. Used by Y Bar,
16
+ * X Bar, Candlestick, and any future categorical-axis chart that
17
+ * wants an auto-fit value axis on zoom.
18
+ *
19
+ * Takes a pre-extracted numeric tuple per record via the `extract`
20
+ * callback instead of reading fields directly, so the same code path
21
+ * handles both Bar's `{ catIdx, y0, y1, axis, seriesId }` shape and
22
+ * Candlestick's `{ xCenter, low, high }` shape.
23
+ *
24
+ * TODO(perf): linear scan. Callers that order records by category
25
+ * index could pre-sort once and binary-search the slice to reduce
26
+ * this to O(log N + K_visible). Deferred until profiling shows the
27
+ * scan in the hot path.
28
+ */
29
+ export interface VisibleExtent {
30
+ min: number;
31
+ max: number;
32
+ hasFit: boolean;
33
+ }
34
+
35
+ export interface VisibleExtentRecord {
36
+ /**
37
+ * Position on the categorical axis — compared to the visible window.
38
+ */
39
+ cat: number;
40
+
41
+ /**
42
+ * Low bound of the value-axis extent for this record.
43
+ */
44
+ lo: number;
45
+
46
+ /**
47
+ * High bound of the value-axis extent for this record.
48
+ */
49
+ hi: number;
50
+
51
+ /**
52
+ * True to skip this record (hidden series / wrong axis / etc.).
53
+ */
54
+ skip: boolean;
55
+ }
56
+
57
+ /**
58
+ * Walk `items`, filter by `visCatMin <= cat <= visCatMax` (and the
59
+ * caller-supplied `skip` flag), and return min/max over `lo`/`hi`.
60
+ *
61
+ * Returns `hasFit: false` when the window matches no records, so
62
+ * callers can fall back to the base domain.
63
+ *
64
+ * Zero-range guard: if every visible record shares a single value
65
+ * (flat run), pad by `±|value|` so the axis doesn't collapse to a
66
+ * single pixel.
67
+ */
68
+ export function computeVisibleExtent<T>(
69
+ items: readonly T[],
70
+ visCatMin: number,
71
+ visCatMax: number,
72
+ extract: (item: T, out: VisibleExtentRecord) => void,
73
+ out: VisibleExtent,
74
+ ): VisibleExtent {
75
+ let min = Infinity;
76
+ let max = -Infinity;
77
+
78
+ // Reuse a single scratch record across the walk. `extract` mutates
79
+ // it in place — zero allocations per iteration.
80
+ const scratch: VisibleExtentRecord = { cat: 0, lo: 0, hi: 0, skip: false };
81
+ for (let i = 0; i < items.length; i++) {
82
+ scratch.skip = false;
83
+ extract(items[i], scratch);
84
+ if (scratch.skip) {
85
+ continue;
86
+ }
87
+
88
+ if (scratch.cat < visCatMin || scratch.cat > visCatMax) {
89
+ continue;
90
+ }
91
+
92
+ if (scratch.lo < min) {
93
+ min = scratch.lo;
94
+ }
95
+
96
+ if (scratch.hi > max) {
97
+ max = scratch.hi;
98
+ }
99
+ }
100
+
101
+ const hasFit = isFinite(min) && isFinite(max);
102
+ if (hasFit && min === max) {
103
+ const pad = Math.abs(min) || 1;
104
+ min -= pad;
105
+ max += pad;
106
+ }
107
+
108
+ out.min = min;
109
+ out.max = max;
110
+ out.hasFit = hasFit;
111
+ return out;
112
+ }
@@ -0,0 +1,426 @@
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 type { CategoricalLevel } from "../../axis/categorical-axis";
15
+ import { buildGroupRuns } from "../../axis/categorical-axis-core";
16
+ import {
17
+ resolveAxisMode,
18
+ resolveCategoryAxis,
19
+ resolveNumericCategoryDomain,
20
+ type AxisMode,
21
+ type NumericCategoryDomain,
22
+ } from "../common/category-axis-resolver";
23
+
24
+ export interface HeatmapCell {
25
+ xIdx: number;
26
+ yIdx: number;
27
+ value: number;
28
+ }
29
+
30
+ export interface HeatmapPipelineInput {
31
+ columns: ColumnDataMap;
32
+ numRows: number;
33
+ groupBy: string[];
34
+ splitBy: string[];
35
+
36
+ /**
37
+ * Source-column types keyed by column name (table.schema() merged
38
+ * with view.expression_schema()). Drives both the X-axis level-type
39
+ * lookup (for non-string row-paths) and the Y-axis numeric-mode
40
+ * decision when there's a single split_by.
41
+ */
42
+ groupByTypes: Record<string, string>;
43
+ }
44
+
45
+ export interface HeatmapPipelineResult {
46
+ /**
47
+ * Hierarchical row_path levels driving the X axis (outermost-first).
48
+ */
49
+ xLevels: CategoricalLevel[];
50
+
51
+ /**
52
+ * Arrow column names in iteration order; `yIdx === index in this list`.
53
+ */
54
+ yColumnNames: string[];
55
+
56
+ /**
57
+ * Hierarchical Y levels derived by splitting each name on `|`.
58
+ */
59
+ yLevels: CategoricalLevel[];
60
+ numX: number;
61
+ numY: number;
62
+ rowOffset: number;
63
+ cells: HeatmapCell[];
64
+
65
+ /**
66
+ * O(1) lookup by `yIdx * numX + xIdx`; `null` means no-data.
67
+ */
68
+ cells2D: (HeatmapCell | null)[];
69
+ colorMin: number;
70
+ colorMax: number;
71
+
72
+ /**
73
+ * X-axis mode. `numeric` fires when the single group_by is
74
+ * date/datetime/integer/float; positions live in `xPositions`
75
+ * and the domain in `xNumericDomain`.
76
+ */
77
+ xAxisMode: AxisMode;
78
+ yAxisMode: AxisMode;
79
+ xPositions: Float64Array | null;
80
+ yPositions: Float64Array | null;
81
+ xNumericDomain: NumericCategoryDomain | null;
82
+ yNumericDomain: NumericCategoryDomain | null;
83
+ }
84
+
85
+ /**
86
+ * Pure heatmap pipeline. Y indexing maps 1:1 to the arrow column iteration
87
+ * order — `yIdx` is the position of a value column in the ordered
88
+ * `ColumnDataMap` (after skipping `__ROW_PATH_N__` metadata). No
89
+ * aggregate/split reconstruction; the column name *is* the Y label.
90
+ *
91
+ * Externally enforced: only one entry sits in the `Color` slot, so every
92
+ * non-metadata column is a splitwise expansion of that single aggregate.
93
+ *
94
+ * Numeric-axis mode (matching bar/candlestick): when there's exactly one
95
+ * non-string group_by, the X axis switches to a real numeric/date axis
96
+ * with `xPositions[xIdx]` carrying the data-space center. Y mirrors this
97
+ * for a single non-string split_by, parsed best-effort out of the column
98
+ * name leaf segment; on parse failure it falls back to category mode.
99
+ */
100
+ export function buildHeatmapPipeline(
101
+ input: HeatmapPipelineInput,
102
+ ): HeatmapPipelineResult {
103
+ const { columns, numRows, groupBy, splitBy, groupByTypes } = input;
104
+
105
+ const xAxisMode = resolveAxisMode(groupBy, groupByTypes);
106
+
107
+ const empty: HeatmapPipelineResult = {
108
+ xLevels: [],
109
+ yColumnNames: [],
110
+ yLevels: [],
111
+ numX: 0,
112
+ numY: 0,
113
+ rowOffset: 0,
114
+ cells: [],
115
+ cells2D: [],
116
+ colorMin: 0,
117
+ colorMax: 1,
118
+ xAxisMode,
119
+ yAxisMode: { mode: "category" },
120
+ xPositions: null,
121
+ yPositions: null,
122
+ xNumericDomain: null,
123
+ yNumericDomain: null,
124
+ };
125
+
126
+ const levelTypes = groupBy.map((name) => groupByTypes[name] ?? "string");
127
+ const {
128
+ rowPaths: xLevels,
129
+ numCategories: numX,
130
+ rowOffset,
131
+ } = resolveCategoryAxis(columns, numRows, groupBy.length, levelTypes);
132
+
133
+ // Numeric X domain: sourced from `__ROW_PATH_0__`'s raw values when
134
+ // the single group_by is non-string.
135
+ let xPositions: Float64Array | null = null;
136
+ let xNumericDomain: NumericCategoryDomain | null = null;
137
+ if (xAxisMode.mode === "numeric" && numX > 0) {
138
+ const rp = columns.get("__ROW_PATH_0__");
139
+ const resolved = resolveNumericCategoryDomain(
140
+ rp?.values,
141
+ numX,
142
+ rowOffset,
143
+ groupBy[0] ?? "",
144
+ xAxisMode.numericType === "date" ||
145
+ xAxisMode.numericType === "datetime",
146
+ );
147
+ if (resolved) {
148
+ xPositions = resolved.categoryPositions;
149
+ xNumericDomain = resolved.numericCategoryDomain;
150
+ }
151
+ }
152
+
153
+ // Enumerate Y columns in arrow iteration order, skipping metadata.
154
+ const yColumnNames: string[] = [];
155
+ for (const name of columns.keys()) {
156
+ if (name.startsWith("__")) {
157
+ continue;
158
+ }
159
+
160
+ const col = columns.get(name);
161
+ if (!col?.values) {
162
+ continue;
163
+ }
164
+
165
+ yColumnNames.push(name);
166
+ }
167
+
168
+ const numY = yColumnNames.length;
169
+
170
+ if (numX === 0 || numY === 0) {
171
+ return { ...empty, xLevels, rowOffset };
172
+ }
173
+
174
+ // Build hierarchical Y levels by splitting each name on `|`, coalescing
175
+ // consecutive equal tokens per level into a shared dictionary entry.
176
+ // Shape mirrors `CategoricalLevel`: one `Int32Array` of dictionary
177
+ // indices (length `numY`) + a string dictionary per level.
178
+ const yLevels = buildYLevelsFromNames(yColumnNames);
179
+
180
+ // Y-numeric mode: only when split_by has exactly one non-string level
181
+ // AND every column name parses into a finite number. The leaf segment
182
+ // is the (split_value, aggregate) `splitVal` token — leading segment
183
+ // when there's a trailing `|aggregate`, or the whole name when there
184
+ // is none.
185
+ const yAxisModeRaw = resolveYAxisMode(splitBy, groupByTypes);
186
+ let yAxisMode: AxisMode = { mode: "category" };
187
+ let yPositions: Float64Array | null = null;
188
+ let yNumericDomain: NumericCategoryDomain | null = null;
189
+ if (yAxisModeRaw.mode === "numeric") {
190
+ const parsed = parseYPositions(yColumnNames, yAxisModeRaw.numericType);
191
+ if (parsed) {
192
+ const resolved = resolveNumericCategoryDomain(
193
+ parsed,
194
+ numY,
195
+ 0,
196
+ splitBy[0] ?? "",
197
+ yAxisModeRaw.numericType === "date" ||
198
+ yAxisModeRaw.numericType === "datetime",
199
+ );
200
+ if (resolved) {
201
+ yAxisMode = yAxisModeRaw;
202
+ yPositions = resolved.categoryPositions;
203
+ yNumericDomain = resolved.numericCategoryDomain;
204
+ }
205
+ }
206
+ }
207
+
208
+ // Walk cells. Per-column loop (outer) lets us exploit arrow-contiguous
209
+ // value arrays; validity checks are bit-mask reads.
210
+ const cells: HeatmapCell[] = [];
211
+ const cells2D: (HeatmapCell | null)[] = new Array(numX * numY).fill(null);
212
+ let colorMin = Infinity;
213
+ let colorMax = -Infinity;
214
+
215
+ for (let yIdx = 0; yIdx < numY; yIdx++) {
216
+ const col = columns.get(yColumnNames[yIdx])!;
217
+ const values = col.values!;
218
+ const valid = col.valid;
219
+ for (let xIdx = 0; xIdx < numX; xIdx++) {
220
+ const row = xIdx + rowOffset;
221
+ if (valid) {
222
+ const bit = (valid[row >> 3] >> (row & 7)) & 1;
223
+ if (!bit) {
224
+ continue;
225
+ }
226
+ }
227
+
228
+ const v = values[row] as number;
229
+ if (!isFinite(v)) {
230
+ continue;
231
+ }
232
+
233
+ const cell: HeatmapCell = { xIdx, yIdx, value: v };
234
+ cells.push(cell);
235
+ cells2D[yIdx * numX + xIdx] = cell;
236
+ if (v < colorMin) {
237
+ colorMin = v;
238
+ }
239
+
240
+ if (v > colorMax) {
241
+ colorMax = v;
242
+ }
243
+ }
244
+ }
245
+
246
+ if (!isFinite(colorMin) || !isFinite(colorMax)) {
247
+ colorMin = 0;
248
+ colorMax = 1;
249
+ } else if (colorMin === colorMax) {
250
+ // Degenerate: all equal — nudge so the normalized t is 0 throughout.
251
+ colorMax = colorMin + 1;
252
+ }
253
+
254
+ return {
255
+ xLevels,
256
+ yColumnNames,
257
+ yLevels,
258
+ numX,
259
+ numY,
260
+ rowOffset,
261
+ cells,
262
+ cells2D,
263
+ colorMin,
264
+ colorMax,
265
+ xAxisMode,
266
+ yAxisMode,
267
+ xPositions,
268
+ yPositions,
269
+ xNumericDomain,
270
+ yNumericDomain,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Y-axis mode for heatmap. Only fires when `splitBy.length === 1` and the
276
+ * split column is non-string non-boolean. Multi-split chains stringify
277
+ * each segment so the numeric round-trip is ambiguous; keep them on the
278
+ * categorical path.
279
+ */
280
+ function resolveYAxisMode(
281
+ splitBy: string[],
282
+ splitByTypes: Record<string, string>,
283
+ ): AxisMode {
284
+ if (splitBy.length !== 1) {
285
+ return { mode: "category" };
286
+ }
287
+
288
+ const t = splitByTypes[splitBy[0]];
289
+ if (t === "date" || t === "datetime" || t === "integer" || t === "float") {
290
+ return { mode: "numeric", numericType: t };
291
+ }
292
+
293
+ return { mode: "category" };
294
+ }
295
+
296
+ /**
297
+ * Best-effort parse of the leading `|`-segment of every column name back
298
+ * into a numeric value. Returns `null` if any name fails to parse —
299
+ * caller falls back to category mode.
300
+ *
301
+ * Date/datetime split values are written by the engine as ISO-ish text;
302
+ * `Date.parse` accepts both `YYYY-MM-DD` and `YYYY-MM-DD HH:MM:SS.fff`.
303
+ * Integer/float go through `Number()`.
304
+ */
305
+ function parseYPositions(
306
+ names: string[],
307
+ numericType: "date" | "datetime" | "integer" | "float",
308
+ ): Float64Array | null {
309
+ const positions = new Float64Array(names.length);
310
+ for (let i = 0; i < names.length; i++) {
311
+ const name = names[i];
312
+ const pipeIdx = name.indexOf("|");
313
+ const seg = pipeIdx === -1 ? name : name.slice(0, pipeIdx);
314
+ let v: number;
315
+ if (numericType === "date" || numericType === "datetime") {
316
+ v = Date.parse(seg);
317
+ if (!isFinite(v)) {
318
+ // Engine sometimes emits `YYYY-MM-DD HH:MM:SS.fff` with a
319
+ // space separator that older browsers reject; retry with
320
+ // `T` substitution.
321
+ v = Date.parse(seg.replace(" ", "T"));
322
+ }
323
+ } else {
324
+ v = Number(seg);
325
+ }
326
+
327
+ if (!isFinite(v)) {
328
+ return null;
329
+ }
330
+
331
+ positions[i] = v;
332
+ }
333
+
334
+ return positions;
335
+ }
336
+
337
+ /**
338
+ * Partition a `ColumnDataMap` into one sub-map per user column. Every
339
+ * arrow value column is assigned to the partition whose user column name
340
+ * matches its terminal segment (everything after the last `|`, which
341
+ * equals the whole name when there's no `split_by`). `__ROW_PATH_N__`
342
+ * and `__GROUPING_ID__` metadata columns are copied into every partition
343
+ * since they describe the shared X axis.
344
+ *
345
+ * Used to render one heatmap per user column in a facet grid.
346
+ */
347
+ export function partitionColumnsPerFacet(
348
+ columns: ColumnDataMap,
349
+ userColumns: string[],
350
+ ): Array<{ label: string; columns: ColumnDataMap }> {
351
+ return userColumns.map((userCol) => {
352
+ const partition: ColumnDataMap = new Map();
353
+ for (const [name, col] of columns) {
354
+ if (name.startsWith("__ROW_PATH_") || name === "__GROUPING_ID__") {
355
+ partition.set(name, col);
356
+ continue;
357
+ }
358
+
359
+ const pipeIdx = name.lastIndexOf("|");
360
+ const leaf = pipeIdx === -1 ? name : name.slice(pipeIdx + 1);
361
+ if (leaf === userCol) {
362
+ partition.set(name, col);
363
+ }
364
+ }
365
+
366
+ return { label: userCol, columns: partition };
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Split each column name on `|` → hierarchical levels. Outermost segment
372
+ * is index 0; leaf (terminal) segment is `levels.length - 1`. Runs of
373
+ * identical consecutive outer tokens naturally coalesce later during
374
+ * render because the Y axis compares `indices[yIdx]` against neighbours.
375
+ */
376
+ function buildYLevelsFromNames(names: string[]): CategoricalLevel[] {
377
+ if (names.length === 0) {
378
+ return [];
379
+ }
380
+
381
+ // Find max depth across all names so every Y entry has a value at
382
+ // every level.
383
+ let maxDepth = 0;
384
+ const segments: string[][] = names.map((n) => n.split("|"));
385
+ for (const s of segments) {
386
+ if (s.length > maxDepth) {
387
+ maxDepth = s.length;
388
+ }
389
+ }
390
+
391
+ if (maxDepth === 0) {
392
+ return [];
393
+ }
394
+
395
+ const levels: CategoricalLevel[] = [];
396
+ for (let d = 0; d < maxDepth; d++) {
397
+ const dictionary: string[] = [];
398
+ const dictIndex = new Map<string, number>();
399
+ const indices = new Int32Array(names.length);
400
+ const labels = new Array<string>(names.length);
401
+ let maxLabelChars = 0;
402
+ for (let i = 0; i < names.length; i++) {
403
+ const seg = segments[i][d] ?? "";
404
+ let idx = dictIndex.get(seg);
405
+ if (idx === undefined) {
406
+ idx = dictionary.length;
407
+ dictionary.push(seg);
408
+ dictIndex.set(seg, idx);
409
+ }
410
+
411
+ indices[i] = idx;
412
+ labels[i] = seg;
413
+ if (seg.length > maxLabelChars) {
414
+ maxLabelChars = seg.length;
415
+ }
416
+ }
417
+
418
+ const isLeaf = d === maxDepth - 1;
419
+ const runs = isLeaf
420
+ ? []
421
+ : buildGroupRuns(indices, dictionary, 0, names.length);
422
+ levels.push({ labels, runs, maxLabelChars });
423
+ }
424
+
425
+ return levels;
426
+ }