@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,351 @@
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 { PlotLayout } from "../../layout/plot-layout";
15
+ import type { Theme } from "../../theme/theme";
16
+ import type {
17
+ CategoricalDomain,
18
+ CategoricalLevel,
19
+ } from "../../axis/categorical-axis";
20
+ import { runsInRange } from "../../axis/categorical-axis-core";
21
+ import { truncateLabel } from "../../axis/label-geometry";
22
+
23
+ interface LevelTickLayout {
24
+ size: number; // width in CSS pixels consumed by this level
25
+ }
26
+
27
+ export interface CategoricalYAxisOptions {
28
+ /**
29
+ * Drop the innermost level of the domain before rendering, promoting
30
+ * the second-to-last level into the leaf position. Used by the
31
+ * heatmap, whose column names encode `split…|aggregate` — with a
32
+ * single aggregate the leaf level is a redundant constant column,
33
+ * and showing the last split as the leaf reads more naturally.
34
+ *
35
+ * No-op when `levels.length <= 1` (there's nothing left to promote);
36
+ * the axis renders nothing in that case.
37
+ */
38
+ skipLeafLevel?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Apply `skipLeafLevel` to a levels array. Returns the input unchanged
43
+ * when the option is off or when there's only a single level (the leaf
44
+ * itself).
45
+ */
46
+ function effectiveLevels(
47
+ levels: CategoricalLevel[],
48
+ opts?: CategoricalYAxisOptions,
49
+ ): CategoricalLevel[] {
50
+ if (opts?.skipLeafLevel && levels.length > 0) {
51
+ return levels.slice(0, -1);
52
+ }
53
+
54
+ return levels;
55
+ }
56
+
57
+ const LEAF_LEVEL_WIDTH = 80;
58
+ const OUTER_LEVEL_WIDTH = 20;
59
+ const TICK_SIZE = 5;
60
+ const LABEL_FONT_PX = 11;
61
+
62
+ /**
63
+ * Map a Y category index to a pixel y-coordinate. Matches the convention
64
+ * chosen for the heatmap plot: yIdx=0 sits at the *bottom* (math Y-up),
65
+ * so a higher yIdx maps to a lower pixel value via `dataToPixel`'s
66
+ * standard (1 - t) flip.
67
+ */
68
+ export function categoryIndexToPixelY(
69
+ layout: PlotLayout,
70
+ index: number,
71
+ ): number {
72
+ return layout.dataToPixel(0, index).py;
73
+ }
74
+
75
+ /**
76
+ * Per-level widths for a vertical categorical axis. Leaf width is
77
+ * derived from the longest label in that level's dictionary; outer
78
+ * levels get a fixed narrow column for the bracket + short text.
79
+ */
80
+ export function measureCategoricalYLevels(
81
+ domain: CategoricalDomain,
82
+ opts?: CategoricalYAxisOptions,
83
+ ): LevelTickLayout[] {
84
+ const levels = effectiveLevels(domain.levels, opts);
85
+ const L = levels.length;
86
+ const result: LevelTickLayout[] = [];
87
+ for (let l = 0; l < L; l++) {
88
+ const longest = levels[l].maxLabelChars;
89
+ if (l === L - 1) {
90
+ const w = Math.max(LEAF_LEVEL_WIDTH, longest * 6.5 + 16);
91
+ result.push({ size: w });
92
+ } else {
93
+ const w = Math.max(OUTER_LEVEL_WIDTH, longest * 6.5 + 16);
94
+ result.push({ size: w });
95
+ }
96
+ }
97
+
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Total CSS-pixel width required for the categorical tick band (levels),
103
+ * NOT including the per-axis-label allowance. Caller feeds this to
104
+ * `PlotLayout` as `leftExtra`; the axis label is added via `hasYLabel`.
105
+ */
106
+ export function measureCategoricalAxisWidth(
107
+ domain: CategoricalDomain,
108
+ opts?: CategoricalYAxisOptions,
109
+ ): number {
110
+ const levels = effectiveLevels(domain.levels, opts);
111
+ if (domain.numRows === 0 || levels.length === 0) {
112
+ return 55;
113
+ }
114
+
115
+ const levelLayouts = measureCategoricalYLevels(domain, opts);
116
+ let total = 0;
117
+ for (const l of levelLayouts) {
118
+ total += l.size;
119
+ }
120
+
121
+ return total;
122
+ }
123
+
124
+ function getLeafText(level: CategoricalLevel, row: number): string {
125
+ return level.labels[row] ?? "";
126
+ }
127
+
128
+ /**
129
+ * Render hierarchical Y-axis tick marks, leaf labels, and outer-level
130
+ * bracket labels on the chrome canvas. The axis line is drawn by the
131
+ * caller alongside the X axis.
132
+ */
133
+ export function renderCategoricalYTicks(
134
+ ctx: Context2D,
135
+ layout: PlotLayout,
136
+ domain: CategoricalDomain,
137
+ theme: Theme,
138
+ opts?: CategoricalYAxisOptions,
139
+ ): void {
140
+ const levels = effectiveLevels(domain.levels, opts);
141
+ if (domain.numRows === 0 || levels.length === 0) {
142
+ return;
143
+ }
144
+
145
+ const { tickColor, labelColor, fontFamily } = theme;
146
+ const { plotRect: plot } = layout;
147
+
148
+ ctx.strokeStyle = tickColor;
149
+ ctx.fillStyle = tickColor;
150
+ ctx.lineWidth = 1;
151
+ ctx.font = `${LABEL_FONT_PX}px ${fontFamily}`;
152
+
153
+ const levelLayouts = measureCategoricalYLevels(domain, opts);
154
+
155
+ // Visible Y range from the (possibly zoomed) padded Y domain.
156
+ const visMin = Math.max(0, Math.ceil(layout.paddedYMin));
157
+ const visMax = Math.min(domain.numRows - 1, Math.floor(layout.paddedYMax));
158
+ if (visMax < visMin) {
159
+ return;
160
+ }
161
+
162
+ const L = levels.length;
163
+
164
+ // Cursor walks from the plot's left edge leftward, innermost (leaf)
165
+ // level closest to the plot, outer levels further left.
166
+ let xCursor = plot.x;
167
+
168
+ for (let l = L - 1; l >= 0; l--) {
169
+ const level = levels[l];
170
+ const lay = levelLayouts[l];
171
+ const bandRight = xCursor;
172
+ const bandLeft = xCursor - lay.size;
173
+ xCursor = bandLeft;
174
+
175
+ if (l === L - 1) {
176
+ renderLeafLevel(
177
+ ctx,
178
+ layout,
179
+ level,
180
+ visMin,
181
+ visMax,
182
+ bandRight,
183
+ bandLeft,
184
+ fontFamily,
185
+ tickColor,
186
+ );
187
+ } else {
188
+ renderOuterLevel(
189
+ ctx,
190
+ layout,
191
+ level,
192
+ visMin,
193
+ visMax,
194
+ bandLeft,
195
+ bandRight,
196
+ fontFamily,
197
+ tickColor,
198
+ );
199
+ }
200
+ }
201
+
202
+ // Axis label — optional; draw vertical along the leftmost edge.
203
+ const axisLabel = domain.levelLabels.filter((s) => !!s).join(" / ");
204
+ if (axisLabel) {
205
+ ctx.fillStyle = labelColor;
206
+ ctx.font = `13px ${fontFamily}`;
207
+ ctx.save();
208
+ ctx.translate(14, plot.y + plot.height / 2);
209
+ ctx.rotate(-Math.PI / 2);
210
+ ctx.textAlign = "center";
211
+ ctx.textBaseline = "top";
212
+ ctx.fillText(axisLabel, 0, 0);
213
+ ctx.restore();
214
+ }
215
+ }
216
+
217
+ function renderLeafLevel(
218
+ ctx: Context2D,
219
+ layout: PlotLayout,
220
+ level: CategoricalLevel,
221
+ visMin: number,
222
+ visMax: number,
223
+ bandRight: number,
224
+ bandLeft: number,
225
+ fontFamily: string,
226
+ tickColor: string,
227
+ ): void {
228
+ const plot = layout.plotRect;
229
+
230
+ ctx.strokeStyle = tickColor;
231
+ ctx.fillStyle = tickColor;
232
+ ctx.beginPath();
233
+ for (let r = visMin; r <= visMax; r++) {
234
+ const py = categoryIndexToPixelY(layout, r);
235
+ if (py < plot.y - 1 || py > plot.y + plot.height + 1) {
236
+ continue;
237
+ }
238
+
239
+ ctx.moveTo(bandRight, py);
240
+ ctx.lineTo(bandRight - TICK_SIZE, py);
241
+ }
242
+
243
+ ctx.stroke();
244
+
245
+ ctx.font = `${LABEL_FONT_PX}px ${fontFamily}`;
246
+ ctx.textAlign = "right";
247
+ ctx.textBaseline = "middle";
248
+ const labelMaxWidth = bandRight - TICK_SIZE - 4 - bandLeft - 4;
249
+ for (let r = visMin; r <= visMax; r++) {
250
+ const py = categoryIndexToPixelY(layout, r);
251
+ if (py < plot.y - 1 || py > plot.y + plot.height + 1) {
252
+ continue;
253
+ }
254
+
255
+ const text = getLeafText(level, r);
256
+ if (!text) {
257
+ continue;
258
+ }
259
+
260
+ const truncated = truncateLabel(ctx, text, labelMaxWidth);
261
+ if (!truncated) {
262
+ continue;
263
+ }
264
+
265
+ ctx.fillText(truncated, bandRight - TICK_SIZE - 4, py);
266
+ }
267
+ }
268
+
269
+ function renderOuterLevel(
270
+ ctx: Context2D,
271
+ layout: PlotLayout,
272
+ level: CategoricalLevel,
273
+ visMin: number,
274
+ visMax: number,
275
+ bandLeft: number,
276
+ bandRight: number,
277
+ fontFamily: string,
278
+ tickColor: string,
279
+ ): void {
280
+ const plot = layout.plotRect;
281
+ const runs = runsInRange(level.runs, visMin, visMax);
282
+ if (runs.length === 0) {
283
+ return;
284
+ }
285
+
286
+ ctx.strokeStyle = tickColor;
287
+ ctx.fillStyle = tickColor;
288
+
289
+ // Bracket line: vertical line near the right edge of the band, with
290
+ // short horizontal ticks at each run boundary.
291
+ const bracketX = bandRight - 3;
292
+ ctx.beginPath();
293
+ for (const run of runs) {
294
+ const yTop = categoryIndexToPixelY(layout, run.endIdx + 0.5);
295
+ const yBot = categoryIndexToPixelY(layout, run.startIdx - 0.5);
296
+ const yHi = Math.min(yTop, yBot);
297
+ const yLo = Math.max(yTop, yBot);
298
+ const clippedHi = Math.max(plot.y, yHi);
299
+ const clippedLo = Math.min(plot.y + plot.height, yLo);
300
+ if (clippedLo <= clippedHi) {
301
+ continue;
302
+ }
303
+
304
+ ctx.moveTo(bracketX, clippedHi);
305
+ ctx.lineTo(bracketX, clippedLo);
306
+
307
+ // Boundary ticks pointing inward (toward the plot).
308
+ ctx.moveTo(bracketX, clippedHi);
309
+ ctx.lineTo(bracketX + 3, clippedHi);
310
+ ctx.moveTo(bracketX, clippedLo);
311
+ ctx.lineTo(bracketX + 3, clippedLo);
312
+ }
313
+
314
+ ctx.stroke();
315
+
316
+ // Centered run label, rotated -90° so long labels fit in a narrow band.
317
+ ctx.font = `${LABEL_FONT_PX}px ${fontFamily}`;
318
+ for (const run of runs) {
319
+ const yTop = categoryIndexToPixelY(layout, run.endIdx + 0.5);
320
+ const yBot = categoryIndexToPixelY(layout, run.startIdx - 0.5);
321
+ const yHi = Math.min(yTop, yBot);
322
+ const yLo = Math.max(yTop, yBot);
323
+ const clippedHi = Math.max(plot.y, yHi);
324
+ const clippedLo = Math.min(plot.y + plot.height, yLo);
325
+ if (clippedLo <= clippedHi) {
326
+ continue;
327
+ }
328
+
329
+ const cy = (clippedHi + clippedLo) / 2;
330
+ const cx = bandLeft + (bandRight - bandLeft - 3) / 2;
331
+
332
+ const text = run.label;
333
+ if (!text) {
334
+ continue;
335
+ }
336
+
337
+ const span = clippedLo - clippedHi - 4;
338
+ const truncated = truncateLabel(ctx, text, Math.max(0, span));
339
+ if (!truncated) {
340
+ continue;
341
+ }
342
+
343
+ ctx.save();
344
+ ctx.translate(cx, cy);
345
+ ctx.rotate(-Math.PI / 2);
346
+ ctx.textAlign = "center";
347
+ ctx.textBaseline = "middle";
348
+ ctx.fillText(truncated, 0, 0);
349
+ ctx.restore();
350
+ }
351
+ }