@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,314 @@
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, ColumnData } from "../../data/view-reader";
14
+ import type { CategoricalLevel } from "../../axis/categorical-axis";
15
+ import { buildGroupRuns } from "../../axis/categorical-axis-core";
16
+ import { formatTickValue, formatDateTickValue } from "../../layout/ticks";
17
+
18
+ export interface CategoryAxisResult {
19
+ /**
20
+ * Fully materialized hierarchical levels — labels and group runs are
21
+ * pre-resolved from the view's `__ROW_PATH_N__` dictionaries (or
22
+ * synthesized for non-string levels) so the chart can retain them
23
+ * past the `with_typed_arrays` callback scope. Empty when `groupBy`
24
+ * is empty.
25
+ */
26
+ rowPaths: CategoricalLevel[];
27
+
28
+ /**
29
+ * Rows that actually contribute a category (post-offset).
30
+ */
31
+ numCategories: number;
32
+
33
+ /**
34
+ * Leading rows skipped; callers use this to rebase per-row indices.
35
+ */
36
+ rowOffset: number;
37
+ }
38
+
39
+ export type AxisMode =
40
+ | { mode: "category" }
41
+ | {
42
+ mode: "numeric";
43
+ numericType: "date" | "datetime" | "integer" | "float";
44
+ };
45
+
46
+ /**
47
+ * Numeric category-axis state. Shared across bar / candlestick / heatmap
48
+ * pipelines: when an axis is driven by exactly one non-string group_by /
49
+ * split_by level, glyphs anchor at real data values via `categoryPositions`
50
+ * and the chrome renders a numeric (date-aware) tick row.
51
+ */
52
+ export interface NumericCategoryDomain {
53
+ min: number;
54
+ max: number;
55
+ isDate: boolean;
56
+ label: string;
57
+
58
+ /**
59
+ * Data-unit width of one category band, from min adjacent delta.
60
+ */
61
+ bandWidth: number;
62
+ }
63
+
64
+ /**
65
+ * Compute `categoryPositions` (per-row real data values) plus a
66
+ * `NumericCategoryDomain` summarizing min/max/bandWidth for a numeric
67
+ * row-path column. `bandWidth` falls back to the full domain when there
68
+ * are <2 distinct positions. Pivot rows for a single group_by come ASC
69
+ * by default, so a forward sweep for `minDelta` is sufficient.
70
+ *
71
+ * Returns `null` when the row-path column is missing or carries no
72
+ * `values` array (e.g. dictionary-encoded string column).
73
+ */
74
+ export function resolveNumericCategoryDomain(
75
+ rpValues: ArrayLike<number> | null | undefined,
76
+ numCategories: number,
77
+ rowOffset: number,
78
+ label: string,
79
+ isDate: boolean,
80
+ ): {
81
+ categoryPositions: Float64Array;
82
+ numericCategoryDomain: NumericCategoryDomain;
83
+ } | null {
84
+ if (!rpValues || numCategories <= 0) {
85
+ return null;
86
+ }
87
+
88
+ const categoryPositions = new Float64Array(numCategories);
89
+ let minVal = Infinity;
90
+ let maxVal = -Infinity;
91
+ for (let catI = 0; catI < numCategories; catI++) {
92
+ const v = rpValues[catI + rowOffset] as number;
93
+ categoryPositions[catI] = v;
94
+ if (v < minVal) {
95
+ minVal = v;
96
+ }
97
+
98
+ if (v > maxVal) {
99
+ maxVal = v;
100
+ }
101
+ }
102
+
103
+ let minDelta = Infinity;
104
+ for (let i = 1; i < numCategories; i++) {
105
+ const d = Math.abs(categoryPositions[i] - categoryPositions[i - 1]);
106
+ if (d > 0 && d < minDelta) {
107
+ minDelta = d;
108
+ }
109
+ }
110
+
111
+ if (!isFinite(minDelta) || minDelta === 0) {
112
+ minDelta = Math.max(1, maxVal - minVal);
113
+ }
114
+
115
+ return {
116
+ categoryPositions,
117
+ numericCategoryDomain: {
118
+ min: minVal - minDelta / 2,
119
+ max: maxVal + minDelta / 2,
120
+ isDate,
121
+ label,
122
+ bandWidth: minDelta,
123
+ },
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Decide whether the categorical axis should render as a stringified
129
+ * category axis or a true numeric axis. Numeric mode is only used when
130
+ * there is exactly one `group_by` level AND that level is a non-string,
131
+ * non-boolean numeric type. Boolean and any multi-level case → category.
132
+ */
133
+ export function resolveAxisMode(
134
+ groupBy: string[],
135
+ groupByTypes: Record<string, string>,
136
+ ): AxisMode {
137
+ if (groupBy.length !== 1) {
138
+ return { mode: "category" };
139
+ }
140
+
141
+ const t = groupByTypes[groupBy[0]];
142
+ if (t === "date" || t === "datetime" || t === "integer" || t === "float") {
143
+ return { mode: "numeric", numericType: t };
144
+ }
145
+
146
+ return { mode: "category" };
147
+ }
148
+
149
+ /**
150
+ * Stringify a single value from a non-string row-path column.
151
+ */
152
+ function formatLevelValue(
153
+ value: number,
154
+ valid: boolean,
155
+ levelType: string,
156
+ ): string {
157
+ if (!valid) {
158
+ return "";
159
+ }
160
+
161
+ if (levelType === "boolean") {
162
+ return value ? "true" : "false";
163
+ }
164
+
165
+ if (levelType === "date" || levelType === "datetime") {
166
+ return formatDateTickValue(value);
167
+ }
168
+
169
+ if (levelType === "integer") {
170
+ return String(value | 0);
171
+ }
172
+
173
+ if (levelType === "float") {
174
+ return formatTickValue(value);
175
+ }
176
+
177
+ return String(value);
178
+ }
179
+
180
+ /**
181
+ * Synthesize a `(indices, dictionary)` pair from a non-string row-path
182
+ * column so the rest of the categorical axis machinery (label
183
+ * pre-resolution, run-length encoding) can run unchanged. The dictionary
184
+ * uses `""` at index 0 as the rollup-row sentinel — this preserves the
185
+ * existing skip-rollup loop's `s !== ""` check.
186
+ */
187
+ export function synthesizeStringLevel(
188
+ rp: ColumnData,
189
+ numRows: number,
190
+ levelType: string,
191
+ ): { indices: Int32Array; dictionary: string[] } {
192
+ const values = rp.values!;
193
+ const valid = rp.valid;
194
+ const indices = new Int32Array(numRows);
195
+ const dictionary: string[] = [""];
196
+ const seen = new Map<string, number>();
197
+ seen.set("", 0);
198
+
199
+ for (let r = 0; r < numRows; r++) {
200
+ const isValid = valid ? !!((valid[r >> 3] >> (r & 7)) & 1) : true;
201
+ const v = values[r] as number;
202
+ const label = formatLevelValue(v, isValid, levelType);
203
+ let dictIdx = seen.get(label);
204
+ if (dictIdx === undefined) {
205
+ dictIdx = dictionary.length;
206
+ dictionary.push(label);
207
+ seen.set(label, dictIdx);
208
+ }
209
+
210
+ indices[r] = dictIdx;
211
+ }
212
+
213
+ return { indices, dictionary };
214
+ }
215
+
216
+ /**
217
+ * Resolve the category axis for a categorical-X chart (bar, candlestick,
218
+ * ohlc, …). Walks the `__ROW_PATH_N__` hierarchy columns, skips the
219
+ * rollup rows at the top ("Total" parent aggregates), and returns fully
220
+ * JS-owned level structures (precomputed labels + runs) plus the
221
+ * trimmed category count.
222
+ *
223
+ * Non-string row-path columns (date / datetime / integer / float /
224
+ * boolean group_by levels) are stringified into a synthetic dictionary
225
+ * so the downstream label / run-length machinery is type-agnostic.
226
+ *
227
+ * When `groupByLen === 0`, there are no row-path columns and the
228
+ * category axis falls back to the raw row index — callers infer that
229
+ * from `rowPaths.length === 0`.
230
+ */
231
+ export function resolveCategoryAxis(
232
+ columns: ColumnDataMap,
233
+ numRows: number,
234
+ groupByLen: number,
235
+ levelTypes: string[] = [],
236
+ ): CategoryAxisResult {
237
+ type RawLevel = { indices: Int32Array; dictionary: string[] };
238
+ const rawRowPaths: RawLevel[] = [];
239
+ for (let n = 0; ; n++) {
240
+ const rp = columns.get(`__ROW_PATH_${n}__`);
241
+ if (!rp) {
242
+ break;
243
+ }
244
+
245
+ if (rp.type === "string" && rp.indices && rp.dictionary) {
246
+ rawRowPaths.push({
247
+ indices: rp.indices,
248
+ dictionary: rp.dictionary,
249
+ });
250
+ } else if (rp.values) {
251
+ const levelType = levelTypes[n] ?? "string";
252
+ rawRowPaths.push(synthesizeStringLevel(rp, numRows, levelType));
253
+ } else {
254
+ break;
255
+ }
256
+ }
257
+
258
+ let rowOffset = 0;
259
+ if (groupByLen > 0 && rawRowPaths.length > 0) {
260
+ while (rowOffset < numRows) {
261
+ let anyNonEmpty = false;
262
+ for (const rp of rawRowPaths) {
263
+ const s = rp.dictionary[rp.indices[rowOffset]];
264
+ if (s != null && s !== "") {
265
+ anyNonEmpty = true;
266
+ break;
267
+ }
268
+ }
269
+
270
+ if (anyNonEmpty) {
271
+ break;
272
+ }
273
+
274
+ rowOffset++;
275
+ }
276
+ }
277
+
278
+ const numCategories = Math.max(0, numRows - rowOffset);
279
+
280
+ const L = rawRowPaths.length;
281
+ const rowPaths: CategoricalLevel[] =
282
+ groupByLen > 0 && L > 0
283
+ ? rawRowPaths.map((rp, levelIdx) => {
284
+ const labels = new Array<string>(numCategories);
285
+ let maxLabelChars = 0;
286
+ for (let r = 0; r < numCategories; r++) {
287
+ const s = rp.dictionary[rp.indices[r + rowOffset]] ?? "";
288
+ labels[r] = s;
289
+ if (s.length > maxLabelChars) {
290
+ maxLabelChars = s.length;
291
+ }
292
+ }
293
+
294
+ // Only outer levels need the run-length encoding for
295
+ // bracket rendering; leaves render per-row.
296
+ const runs =
297
+ levelIdx === L - 1
298
+ ? []
299
+ : buildGroupRuns(
300
+ rp.indices,
301
+ rp.dictionary,
302
+ rowOffset,
303
+ rowOffset + numCategories,
304
+ ).map((run) => ({
305
+ startIdx: run.startIdx - rowOffset,
306
+ endIdx: run.endIdx - rowOffset,
307
+ label: run.label,
308
+ }));
309
+ return { labels, runs, maxLabelChars };
310
+ })
311
+ : [];
312
+
313
+ return { rowPaths, numCategories, rowOffset };
314
+ }
@@ -0,0 +1,79 @@
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 { Canvas2D, Context2D } from "../canvas-types";
14
+
15
+ export interface ChromeCacheChart {
16
+ _chromeCanvas: Canvas2D | null;
17
+ _chromeCache: ImageBitmap | null;
18
+ _chromeCacheDirty: boolean;
19
+ _chromeCacheGen: number;
20
+ }
21
+
22
+ /**
23
+ * Run the static-chrome cache pattern shared by sunburst + treemap.
24
+ * Resizes the canvas, paints the static layer (and snapshots it as an
25
+ * `ImageBitmap`) when dirty, otherwise blits the cache; then runs the
26
+ * caller-provided overlay layer for hover/highlight state.
27
+ *
28
+ * Returns the prepared `ctx` already in DPR-scaled space so the overlay
29
+ * callback can paint in CSS pixels — except `null` if either the canvas
30
+ * is missing a 2D context or the chart has nothing to paint.
31
+ */
32
+ export function withChromeCache(
33
+ chart: ChromeCacheChart,
34
+ canvas: Canvas2D,
35
+ dpr: number,
36
+ cssWidth: number,
37
+ cssHeight: number,
38
+ drawStatic: (ctx: Context2D) => void,
39
+ drawOverlay: ((ctx: Context2D) => void) | null,
40
+ ): void {
41
+ const targetW = Math.round(cssWidth * dpr);
42
+ const targetH = Math.round(cssHeight * dpr);
43
+ if (canvas.width !== targetW || canvas.height !== targetH) {
44
+ canvas.width = targetW;
45
+ canvas.height = targetH;
46
+ chart._chromeCacheDirty = true;
47
+ }
48
+
49
+ const ctx = canvas.getContext("2d") as Context2D | null;
50
+ if (!ctx) {
51
+ return;
52
+ }
53
+
54
+ if (chart._chromeCacheDirty) {
55
+ chart._chromeCache?.close();
56
+ chart._chromeCache = null;
57
+ chart._chromeCacheDirty = false;
58
+ const gen = ++chart._chromeCacheGen;
59
+ drawStatic(ctx);
60
+ createImageBitmap(canvas).then((bmp) => {
61
+ if (chart._chromeCacheGen === gen) {
62
+ chart._chromeCache?.close();
63
+ chart._chromeCache = bmp;
64
+ } else {
65
+ bmp.close();
66
+ }
67
+ });
68
+ } else if (chart._chromeCache) {
69
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
70
+ ctx.drawImage(chart._chromeCache, 0, 0);
71
+ }
72
+
73
+ if (drawOverlay) {
74
+ ctx.save();
75
+ ctx.scale(dpr, dpr);
76
+ drawOverlay(ctx);
77
+ ctx.restore();
78
+ }
79
+ }
@@ -0,0 +1,84 @@
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 { Context2D } from "../canvas-types";
14
+ import type { Theme } from "../../theme/theme";
15
+
16
+ /**
17
+ * Draw a freestanding tooltip box anchored near (cx, cy), measuring
18
+ * lines, sizing/clamping the box, painting bg/border, and laying out
19
+ * text rows. Shared by sunburst + treemap which need a non-PlotLayout
20
+ * anchor.
21
+ */
22
+ export function drawTooltipBox(
23
+ ctx: Context2D,
24
+ theme: Theme,
25
+ lines: string[],
26
+ cx: number,
27
+ cy: number,
28
+ cssWidth: number,
29
+ cssHeight: number,
30
+ fontFamily: string,
31
+ ): void {
32
+ if (lines.length === 0) {
33
+ return;
34
+ }
35
+
36
+ const { tooltipBg, tooltipText, tooltipBorder } = theme;
37
+
38
+ ctx.font = `11px ${fontFamily}`;
39
+ const lineHeight = 16;
40
+ const padding = 8;
41
+ let maxWidth = 0;
42
+ for (const line of lines) {
43
+ const w = ctx.measureText(line).width;
44
+ if (w > maxWidth) {
45
+ maxWidth = w;
46
+ }
47
+ }
48
+
49
+ const boxW = maxWidth + padding * 2;
50
+ const boxH = lines.length * lineHeight + padding * 2 - 4;
51
+
52
+ let tx = cx + 12;
53
+ let ty = cy - boxH - 8;
54
+ if (tx + boxW > cssWidth) {
55
+ tx = cx - boxW - 12;
56
+ }
57
+
58
+ if (tx < 0) {
59
+ tx = 4;
60
+ }
61
+
62
+ if (ty < 0) {
63
+ ty = cy + 12;
64
+ }
65
+
66
+ if (ty + boxH > cssHeight) {
67
+ ty = cssHeight - boxH - 4;
68
+ }
69
+
70
+ ctx.fillStyle = tooltipBg;
71
+ ctx.strokeStyle = tooltipBorder;
72
+ ctx.lineWidth = 1;
73
+ ctx.beginPath();
74
+ ctx.roundRect(tx, ty, boxW, boxH, 4);
75
+ ctx.fill();
76
+ ctx.stroke();
77
+
78
+ ctx.fillStyle = tooltipText;
79
+ ctx.textAlign = "left";
80
+ ctx.textBaseline = "top";
81
+ for (let i = 0; i < lines.length; i++) {
82
+ ctx.fillText(lines[i], tx + padding, ty + padding + i * lineHeight);
83
+ }
84
+ }
@@ -0,0 +1,92 @@
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 { TreeChartBase } from "./tree-chart";
14
+ import type { Vec3 } from "../../theme/palette";
15
+ import {
16
+ colorValueToT,
17
+ sampleGradient,
18
+ type GradientStop,
19
+ } from "../../theme/gradient";
20
+
21
+ /**
22
+ * Perceptual luminance for a 0..1 RGB triple. Used by tree-chart label
23
+ * painters to pick a contrasting text color over each leaf's fill.
24
+ */
25
+ export function luminance(r: number, g: number, b: number): number {
26
+ return 0.299 * r + 0.587 * g + 0.114 * b;
27
+ }
28
+
29
+ /**
30
+ * Sample a gradient and drop the alpha channel. Treemap / sunburst
31
+ * fills carry alpha separately (see {@link leafRGBA}); this is the
32
+ * "just give me the RGB" entry point.
33
+ */
34
+ export function sampleRGB(
35
+ stops: GradientStop[],
36
+ t: number,
37
+ ): [number, number, number] {
38
+ const c = sampleGradient(stops, t);
39
+ return [c[0], c[1], c[2]];
40
+ }
41
+
42
+ /**
43
+ * Resolve a leaf's fill color according to the chart's color mode:
44
+ * - `"numeric"` — sign-aware gradient sample via `colorValueToT`.
45
+ * - `"series"` / `"empty"` — discrete palette lookup keyed by the
46
+ * node's `colorLabel` (composite of group_by levels in series mode;
47
+ * `""` in empty mode, which maps to `palette[0]`).
48
+ *
49
+ * Returns RGB only; the alpha channel is applied separately by
50
+ * {@link leafRGBA} using `negativeAlpha` for leaves whose raw size was
51
+ * negative.
52
+ */
53
+ export function leafColor(
54
+ chart: TreeChartBase,
55
+ nodeId: number,
56
+ stops: GradientStop[],
57
+ palette: Vec3[],
58
+ ): [number, number, number] {
59
+ const store = chart._nodeStore;
60
+ const colorValue = store.colorValue[nodeId];
61
+ if (
62
+ chart._colorMode === "numeric" &&
63
+ !isNaN(colorValue) &&
64
+ chart._colorMax > chart._colorMin
65
+ ) {
66
+ return sampleRGB(
67
+ stops,
68
+ colorValueToT(colorValue, chart._colorMin, chart._colorMax),
69
+ );
70
+ }
71
+
72
+ const idx = chart._uniqueColorLabels.get(store.colorLabel[nodeId]) ?? 0;
73
+ return palette[idx % palette.length] ?? [0, 0, 0];
74
+ }
75
+
76
+ /**
77
+ * `leafColor` + an alpha channel. Negative-size leaves receive
78
+ * `negativeAlpha` (mirrors `theme.areaOpacity` for area charts) so
79
+ * they stay visually distinguishable from positive leaves without
80
+ * disappearing.
81
+ */
82
+ export function leafRGBA(
83
+ chart: TreeChartBase,
84
+ nodeId: number,
85
+ stops: GradientStop[],
86
+ palette: Vec3[],
87
+ negativeAlpha: number,
88
+ ): [number, number, number, number] {
89
+ const rgb = leafColor(chart, nodeId, stops, palette);
90
+ const alpha = chart._nodeStore.sizeSign[nodeId] < 0 ? negativeAlpha : 1.0;
91
+ return [rgb[0], rgb[1], rgb[2], alpha];
92
+ }