@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,346 @@
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 { PlotLayout, type PlotRect } from "./plot-layout";
14
+
15
+ /**
16
+ * Tri-state axis mode.
17
+ *
18
+ * - `"outer"` — one shared axis band reserved at the grid edge;
19
+ * `outerXAxisRect` / `outerYAxisRect` populated, per-cell gutter
20
+ * collapsed to 0 on that side. Caller paints the shared axis
21
+ * once per frame using the grid's outer rect.
22
+ * - `"cell"` — every cell reserves its own gutter on that side;
23
+ * caller paints one axis per cell. Outer rect is undefined.
24
+ * - `"none"` — no gutter anywhere on that side: neither an outer
25
+ * band nor a per-cell reservation. Intended for chart types with
26
+ * no numeric axis at all (treemap, sunburst). When BOTH axes are
27
+ * `"none"` cells are also made flush on the right so adjacent
28
+ * plot rects share a boundary.
29
+ *
30
+ * Defaults to `"cell"` when undefined.
31
+ */
32
+ export type AxisMode = "outer" | "cell" | "none";
33
+
34
+ export interface FacetGridOptions {
35
+ cssWidth: number;
36
+ cssHeight: number;
37
+
38
+ /**
39
+ * See {@link AxisMode}. Default `"cell"`.
40
+ */
41
+ xAxis?: AxisMode;
42
+
43
+ /**
44
+ * See {@link AxisMode}. Default `"cell"`.
45
+ */
46
+ yAxis?: AxisMode;
47
+
48
+ /**
49
+ * Reserve a right gutter for a single shared legend.
50
+ */
51
+ hasLegend?: boolean;
52
+
53
+ /** Axis-label allowance (consumed only when the corresponding axis
54
+ * mode produces a gutter — outer band or per-cell). */
55
+ hasXLabel?: boolean;
56
+ hasYLabel?: boolean;
57
+
58
+ /**
59
+ * Per-facet title strip height (px). 0 disables.
60
+ */
61
+ titleBand?: number;
62
+
63
+ /**
64
+ * Pixel gap between adjacent cells. Carved out of the grid
65
+ * interior before cell sizing; outer edges of the leftmost /
66
+ * rightmost columns and top / bottom rows are unaffected. Default
67
+ * 0 (flush cells).
68
+ */
69
+ gap?: number;
70
+ }
71
+
72
+ export interface FacetCell {
73
+ index: number;
74
+ label: string;
75
+
76
+ /**
77
+ * Sub-plot layout. Every cell in a grid has *identical*
78
+ * `plotRect.width` and `plotRect.height` — cell internal margins
79
+ * do not vary by edge position. Shared-axis gutters live in
80
+ * `FacetGrid.outerXAxisRect` / `outerYAxisRect` instead, painted
81
+ * once per frame by the caller.
82
+ */
83
+ layout: PlotLayout;
84
+
85
+ /**
86
+ * Title strip above the facet's plot rect, if `titleBand > 0`.
87
+ */
88
+ titleRect?: PlotRect;
89
+ isLeftEdge: boolean;
90
+ isBottomEdge: boolean;
91
+ }
92
+
93
+ export interface FacetGrid {
94
+ cells: FacetCell[];
95
+
96
+ /**
97
+ * Right-gutter rect for the shared legend.
98
+ */
99
+ legendRect?: PlotRect;
100
+
101
+ /**
102
+ * Outer band reserved for the shared X axis (ticks + label). Only
103
+ * set when `xAxis === "outer"`. Spans the grid interior's
104
+ * horizontal extent and sits immediately below the bottom row of
105
+ * cells.
106
+ */
107
+ outerXAxisRect?: PlotRect;
108
+
109
+ /**
110
+ * Outer band reserved for the shared Y axis (ticks + label). Only
111
+ * set when `yAxis === "outer"`. Spans the grid interior's
112
+ * vertical extent and sits immediately left of the leftmost
113
+ * column of cells.
114
+ */
115
+ outerYAxisRect?: PlotRect;
116
+ }
117
+
118
+ /**
119
+ * Collect the bottom-row cells' `PlotLayout`s — i.e. the cells that
120
+ * sit on the grid's bottom edge. Shared-X axis renderers paint X
121
+ * ticks aligned to each of these. Empty when the grid has zero cells.
122
+ */
123
+ export function bottomRowLayouts(grid: FacetGrid): PlotLayout[] {
124
+ return grid.cells.filter((c) => c.isBottomEdge).map((c) => c.layout);
125
+ }
126
+
127
+ /**
128
+ * Collect the left-column cells' `PlotLayout`s — symmetric to
129
+ * {@link bottomRowLayouts} for the shared-Y axis path.
130
+ */
131
+ export function leftColumnLayouts(grid: FacetGrid): PlotLayout[] {
132
+ return grid.cells.filter((c) => c.isLeftEdge).map((c) => c.layout);
133
+ }
134
+
135
+ // Per-cell internal gutter defaults mirror `PlotLayout`'s constants so
136
+ // that a cell with `leftExtra: undefined` reserves the same space the
137
+ // outer band would reserve when the axis is shared. Keep these in sync
138
+ // with `plot-layout.ts`.
139
+ const CELL_LEFT_GUTTER = 55;
140
+ const CELL_BOTTOM_GUTTER = 24;
141
+ const AXIS_LABEL_W = 16;
142
+ const AXIS_LABEL_H = 18;
143
+
144
+ const TITLE_BAND_DEFAULT = 18;
145
+ const LEGEND_GUTTER = 96;
146
+
147
+ /**
148
+ * Pick `(cols, rows)` so that each resulting cell's aspect ratio is as
149
+ * close to 1 as possible given the grid interior. Sweeps `cols ∈ [1,
150
+ * count]` with `rows = ceil(count / cols)` and minimizes
151
+ * `max(cellW/cellH, cellH/cellW)`. Ties break toward fewer total cells
152
+ * (less unused grid area).
153
+ */
154
+ function pickGridShape(
155
+ count: number,
156
+ gridW: number,
157
+ gridH: number,
158
+ gap: number,
159
+ ): { cols: number; rows: number } {
160
+ if (count <= 1) {
161
+ return { cols: 1, rows: 1 };
162
+ }
163
+
164
+ let bestCols = 1;
165
+ let bestRows = count;
166
+ let bestCost = Infinity;
167
+ let bestTotal = count;
168
+ for (let cols = 1; cols <= count; cols++) {
169
+ const rows = Math.ceil(count / cols);
170
+ const cellW = Math.max(1, (gridW - (cols - 1) * gap) / cols);
171
+ const cellH = Math.max(1, (gridH - (rows - 1) * gap) / rows);
172
+ const aspect = cellW / cellH;
173
+ const cost = Math.max(aspect, 1 / aspect);
174
+ const total = cols * rows;
175
+ if (cost < bestCost || (cost === bestCost && total < bestTotal)) {
176
+ bestCols = cols;
177
+ bestRows = rows;
178
+ bestCost = cost;
179
+ bestTotal = total;
180
+ }
181
+ }
182
+
183
+ return { cols: bestCols, rows: bestRows };
184
+ }
185
+
186
+ /**
187
+ * Arrange `labels.length` sub-plots in a row-major grid sized to fit
188
+ * `(cssWidth, cssHeight)`.
189
+ *
190
+ * Grid shape is chosen to minimize cell aspect distance from square
191
+ * given the container's grid interior: `cols ∈ [1, count]`,
192
+ * `rows = ceil(count / cols)`, tie-broken toward fewer total cells.
193
+ *
194
+ * **Invariant:** every `cells[i].layout.plotRect` has the same
195
+ * `width` and `height`. Shared-axis gutters are carved out of the
196
+ * outer canvas BEFORE cell sizing, so a cell's edge position never
197
+ * affects its internal margins. This lets per-facet draws reuse the
198
+ * same projection scale and lets shared ticks line up with the
199
+ * interior cell boundaries exactly.
200
+ *
201
+ * Axis modes — see {@link AxisMode}:
202
+ * - `"outer"` → outer band rect is populated; per-cell gutter is 0.
203
+ * - `"cell"` → outer band is undefined; each cell owns its own gutter.
204
+ * - `"none"` → no gutter anywhere on that side; used by axis-less
205
+ * chart types.
206
+ *
207
+ * Because all cells are identical in size, callers can sample *any*
208
+ * cell's layout (e.g. `cells[0].layout`) for tick / scale
209
+ * computations.
210
+ */
211
+ export function buildFacetGrid(
212
+ labels: string[],
213
+ opts: FacetGridOptions,
214
+ ): FacetGrid {
215
+ const count = labels.length;
216
+ const { cssWidth, cssHeight } = opts;
217
+
218
+ if (count <= 0 || cssWidth <= 0 || cssHeight <= 0) {
219
+ return { cells: [] };
220
+ }
221
+
222
+ const titleBand = opts.titleBand ?? TITLE_BAND_DEFAULT;
223
+ const legendW = opts.hasLegend ? LEGEND_GUTTER : 0;
224
+
225
+ const xMode: AxisMode = opts.xAxis ?? "cell";
226
+ const yMode: AxisMode = opts.yAxis ?? "cell";
227
+
228
+ // Axis-less chart types (trees) benefit from fully-flush cells —
229
+ // no per-cell breathing on the right either, so adjacent plot
230
+ // rects share a boundary instead of leaving a 16 px seam.
231
+ const cellsFlush = xMode === "none" && yMode === "none";
232
+
233
+ // Outer margins: shared-axis gutters + legend gutter live OUTSIDE
234
+ // the per-cell rects.
235
+ const outerLeft =
236
+ yMode === "outer"
237
+ ? CELL_LEFT_GUTTER + (opts.hasYLabel ? AXIS_LABEL_W : 0)
238
+ : 0;
239
+ const outerBottom =
240
+ xMode === "outer"
241
+ ? CELL_BOTTOM_GUTTER + (opts.hasXLabel ? AXIS_LABEL_H : 0)
242
+ : 0;
243
+ const outerTop = 0;
244
+ const outerRight = legendW;
245
+
246
+ const gridX = outerLeft;
247
+ const gridY = outerTop;
248
+ const gridW = Math.max(1, cssWidth - outerLeft - outerRight);
249
+ const gridH = Math.max(1, cssHeight - outerTop - outerBottom);
250
+
251
+ // Carve the total inter-cell gap out of the grid interior before
252
+ // sizing cells so every cell remains identical in size (the
253
+ // grid-uniform invariant). Gaps only sit BETWEEN neighbors — not
254
+ // against the outer edges.
255
+ const gap = Math.max(0, opts.gap ?? 0);
256
+ const { cols, rows } = pickGridShape(count, gridW, gridH, gap);
257
+ const totalGapX = Math.max(0, cols - 1) * gap;
258
+ const totalGapY = Math.max(0, rows - 1) * gap;
259
+ const cellW = Math.max(1, (gridW - totalGapX) / cols);
260
+ const cellH = Math.max(1, (gridH - totalGapY) / rows);
261
+
262
+ const cells: FacetCell[] = [];
263
+ for (let i = 0; i < count; i++) {
264
+ const row = Math.floor(i / cols);
265
+ const col = i - row * cols;
266
+ const isBottomEdge = row === rows - 1 || i + cols >= count;
267
+ const isLeftEdge = col === 0;
268
+
269
+ const cellX = gridX + col * (cellW + gap);
270
+ const cellY = gridY + row * (cellH + gap);
271
+
272
+ // Carve a title strip from the top of each cell. The remaining
273
+ // rect becomes the per-cell `PlotLayout`.
274
+ const plotTop = cellY + titleBand;
275
+ const plotLeft = cellX;
276
+ const plotWidth = cellW;
277
+ const plotHeight = Math.max(1, cellH - titleBand);
278
+
279
+ // Per-cell gutters:
280
+ // - "cell" → keep `PlotLayout` default (undefined).
281
+ // - "outer" / "none" → collapse to 0 (no internal gutter).
282
+ // Per-cell labels only paint when the axis is per-cell.
283
+ const layout = new PlotLayout(cssWidth, cssHeight, {
284
+ hasXLabel: xMode === "cell" && opts.hasXLabel === true,
285
+ hasYLabel: yMode === "cell" && opts.hasYLabel === true,
286
+ hasLegend: false,
287
+ leftExtra: yMode === "cell" ? undefined : 0,
288
+ bottomExtra: xMode === "cell" ? undefined : 0,
289
+ rightExtra: cellsFlush ? 0 : undefined,
290
+ originX: plotLeft,
291
+ originY: plotTop,
292
+ cellWidth: plotWidth,
293
+ cellHeight: plotHeight,
294
+ });
295
+
296
+ const titleRect: PlotRect | undefined =
297
+ titleBand > 0
298
+ ? {
299
+ x: plotLeft,
300
+ y: cellY,
301
+ width: plotWidth,
302
+ height: titleBand,
303
+ }
304
+ : undefined;
305
+
306
+ cells.push({
307
+ index: i,
308
+ label: labels[i],
309
+ layout,
310
+ titleRect,
311
+ isLeftEdge,
312
+ isBottomEdge,
313
+ });
314
+ }
315
+
316
+ const legendRect: PlotRect | undefined = opts.hasLegend
317
+ ? {
318
+ x: gridX + gridW,
319
+ y: outerTop,
320
+ width: legendW,
321
+ height: gridH,
322
+ }
323
+ : undefined;
324
+
325
+ const outerXAxisRect: PlotRect | undefined =
326
+ xMode === "outer"
327
+ ? {
328
+ x: gridX,
329
+ y: gridY + gridH,
330
+ width: gridW,
331
+ height: outerBottom,
332
+ }
333
+ : undefined;
334
+
335
+ const outerYAxisRect: PlotRect | undefined =
336
+ yMode === "outer"
337
+ ? {
338
+ x: 0,
339
+ y: gridY,
340
+ width: outerLeft,
341
+ height: gridH,
342
+ }
343
+ : undefined;
344
+
345
+ return { cells, legendRect, outerXAxisRect, outerYAxisRect };
346
+ }
@@ -0,0 +1,277 @@
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
+ export interface PlotMargins {
14
+ top: number;
15
+ right: number;
16
+ bottom: number;
17
+ left: number;
18
+ }
19
+
20
+ export interface PlotRect {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ }
26
+
27
+ export interface PlotLayoutOptions {
28
+ hasXLabel: boolean;
29
+ hasYLabel: boolean;
30
+ hasLegend: boolean;
31
+
32
+ /**
33
+ * Additional CSS-pixel height reserved at the bottom of the plot for a
34
+ * hierarchical / rotated categorical X axis. Overrides the default 24px
35
+ * tick band. The axis-label allowance from `hasXLabel` is preserved.
36
+ */
37
+ bottomExtra?: number;
38
+
39
+ /**
40
+ * Total CSS-pixel width reserved at the left of the plot for a
41
+ * hierarchical categorical Y axis. Overrides the default `55 +
42
+ * hasYLabel*16` left gutter. The axis-label allowance from `hasYLabel`
43
+ * is preserved.
44
+ */
45
+ leftExtra?: number;
46
+
47
+ /**
48
+ * Total CSS-pixel width reserved at the right of the plot. Overrides
49
+ * the default (`80` when `hasLegend`, else `16`). Faceted cells
50
+ * without axes (treemap / sunburst in grid mode) pass `0` to make
51
+ * adjacent cell plot rects flush; axis-bearing charts leave it
52
+ * unset to keep the default breathing-room margin.
53
+ */
54
+ rightExtra?: number;
55
+
56
+ /**
57
+ * Absolute canvas-coordinate offset for this layout's plot origin.
58
+ * When set, `cssWidth` / `cssHeight` describe the *outer* canvas, and
59
+ * `originX` / `originY` name the top-left corner of the cell this
60
+ * layout represents. The cell's own width/height come from
61
+ * `cellWidth` / `cellHeight`. `margins` are computed relative to the
62
+ * cell then shifted into canvas-absolute space so projection
63
+ * matrices, scissor, and `dataToPixel` all operate in full-canvas
64
+ * coordinates without branching per-facet.
65
+ *
66
+ * When any of these fields is unset, the layout is single-plot: the
67
+ * cell occupies the whole canvas and `originX` / `originY` default
68
+ * to 0.
69
+ */
70
+ originX?: number;
71
+ originY?: number;
72
+ cellWidth?: number;
73
+ cellHeight?: number;
74
+ }
75
+
76
+ /**
77
+ * Coordinates margins and coordinate transforms between WebGL and Canvas2D.
78
+ * All measurements are in CSS pixels (not physical/DPR-scaled pixels).
79
+ */
80
+ export class PlotLayout {
81
+ readonly margins: PlotMargins;
82
+ readonly plotRect: PlotRect;
83
+ readonly cssWidth: number;
84
+ readonly cssHeight: number;
85
+
86
+ // Padded domain set by buildProjectionMatrix, used by dataToPixel
87
+ // and pixelToData for tooltip hit-testing.
88
+ paddedXMin = 0;
89
+ paddedXMax = 1;
90
+ paddedYMin = 0;
91
+ paddedYMax = 1;
92
+
93
+ constructor(
94
+ cssWidth: number,
95
+ cssHeight: number,
96
+ options: PlotLayoutOptions,
97
+ ) {
98
+ this.cssWidth = cssWidth;
99
+ this.cssHeight = cssHeight;
100
+
101
+ const baseLeft = options.leftExtra ?? 55;
102
+ const left = baseLeft + (options.hasYLabel ? 16 : 0);
103
+ const baseBottom = options.bottomExtra ?? 24;
104
+ const bottom = baseBottom + (options.hasXLabel ? 18 : 0);
105
+ const top = 0;
106
+ const right = options.rightExtra ?? (options.hasLegend ? 80 : 16);
107
+
108
+ // Facet cells: the sub-plot lives at `(originX, originY)` within a
109
+ // larger canvas of size `(cssWidth, cssHeight)`. Its own bounds are
110
+ // `cellWidth × cellHeight`. The gutters above are then interpreted
111
+ // inside that cell, and `margins` / `plotRect` are shifted into
112
+ // canvas-absolute coordinates. Single-plot layouts leave these
113
+ // unset, in which case `originX / originY = 0` and the cell
114
+ // occupies the whole canvas — identical to pre-facet semantics.
115
+ const originX = options.originX ?? 0;
116
+ const originY = options.originY ?? 0;
117
+ const cellW = options.cellWidth ?? cssWidth;
118
+ const cellH = options.cellHeight ?? cssHeight;
119
+
120
+ const marginLeftAbs = originX + left;
121
+ const marginTopAbs = originY + top;
122
+ const plotW = Math.max(1, cellW - left - right);
123
+ const plotH = Math.max(1, cellH - top - bottom);
124
+ const marginRightAbs = cssWidth - (marginLeftAbs + plotW);
125
+ const marginBottomAbs = cssHeight - (marginTopAbs + plotH);
126
+
127
+ this.margins = {
128
+ top: marginTopAbs,
129
+ right: marginRightAbs,
130
+ bottom: marginBottomAbs,
131
+ left: marginLeftAbs,
132
+ };
133
+
134
+ this.plotRect = {
135
+ x: marginLeftAbs,
136
+ y: marginTopAbs,
137
+ width: plotW,
138
+ height: plotH,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Build an orthographic projection matrix that maps data coordinates
144
+ * [xMin..xMax, yMin..yMax] to the plot area sub-region of clip space [-1, 1].
145
+ *
146
+ * The matrix bakes margin offsets into the transform so that gl.viewport
147
+ * remains full-canvas and no scissor/sub-viewport is needed.
148
+ *
149
+ * `clamp`, when set, names the axis that carries the *value* (as
150
+ * opposed to categorical / positional) data. Today it only affects
151
+ * `requireZero`; both axes always receive symmetric `padRatio` padding.
152
+ *
153
+ * `requireZero`, when true, guarantees that the unpadded value `0`
154
+ * falls inside the clamped axis's final domain. For all-positive
155
+ * data the axis minimum is pinned at `0` (the baseline sits on the
156
+ * axis line); for all-negative data the maximum is pinned at `0`;
157
+ * for data that already straddles zero, nothing changes. Pairs with
158
+ * `clamp`, and is a no-op when `clamp` is unset.
159
+ *
160
+ * `padRatio` controls the symmetric cosmetic pad on both axes
161
+ * (default 2%). Charts that want plot edges flush with the axes
162
+ * (e.g. heatmap, whose cell rects already span the exact domain)
163
+ * pass `0`.
164
+ */
165
+ buildProjectionMatrix(
166
+ xMin: number,
167
+ xMax: number,
168
+ yMin: number,
169
+ yMax: number,
170
+ clamp?: "x" | "y",
171
+ requireZero?: boolean,
172
+ padRatio: number = 0.02,
173
+ xOrigin: number = 0,
174
+ yOrigin: number = 0,
175
+ ): Float32Array {
176
+ // Symmetric cosmetic padding on both axes (default 2%).
177
+ let xRange = xMax - xMin;
178
+ let yRange = yMax - yMin;
179
+ if (xRange === 0) {
180
+ xRange = 1;
181
+ }
182
+
183
+ if (yRange === 0) {
184
+ yRange = 1;
185
+ }
186
+
187
+ const xPad = xRange * padRatio;
188
+ const yPad = yRange * padRatio;
189
+
190
+ // Evaluate the zero-snap condition against the *pre-pad*
191
+ // values so that an exact-zero boundary (e.g. bar pipelines
192
+ // that snap `valMin` to 0) still qualifies — otherwise the
193
+ // padding step would tip the boundary slightly negative and
194
+ // the snap branch below would miss. Inclusive comparison is
195
+ // deliberate.
196
+ const snapYMin = requireZero && clamp === "y" && yMin >= 0;
197
+ const snapYMax = requireZero && clamp === "y" && yMax <= 0;
198
+ const snapXMin = requireZero && clamp === "x" && xMin >= 0;
199
+ const snapXMax = requireZero && clamp === "x" && xMax <= 0;
200
+
201
+ xMin -= xPad;
202
+ xMax += xPad;
203
+ yMin -= yPad;
204
+ yMax += yPad;
205
+
206
+ // Pin the snapped boundary to exactly zero and give the
207
+ // opposite boundary a second pad for visual headroom above the
208
+ // tallest bar. No-op when data straddles zero (neither flag
209
+ // set) so no boundary collapses onto an in-range value.
210
+ if (snapYMin) {
211
+ yMin = 0;
212
+ yMax += yPad;
213
+ } else if (snapYMax) {
214
+ yMax = 0;
215
+ yMin -= yPad;
216
+ }
217
+
218
+ if (snapXMin) {
219
+ xMin = 0;
220
+ xMax += xPad;
221
+ } else if (snapXMax) {
222
+ xMax = 0;
223
+ xMin -= xPad;
224
+ }
225
+
226
+ // Store padded domain for dataToPixel
227
+ this.paddedXMin = xMin;
228
+ this.paddedXMax = xMax;
229
+ this.paddedYMin = yMin;
230
+ this.paddedYMax = yMax;
231
+
232
+ // Clip-space bounds for the plot area
233
+ const clipLeft = (2 * this.margins.left) / this.cssWidth - 1;
234
+ const clipRight = 1 - (2 * this.margins.right) / this.cssWidth;
235
+ const clipBottom = (2 * this.margins.bottom) / this.cssHeight - 1;
236
+ const clipTop = 1 - (2 * this.margins.top) / this.cssHeight;
237
+
238
+ // Scale and translate: rebased data [(min-origin), (max-origin)]
239
+ // → clip [clipMin, clipMax]. Callers that ship rebased values to
240
+ // the GPU pass the origin they used; for ordinary numeric data
241
+ // both origins default to 0 and the math collapses to the
242
+ // legacy `tx = clipLeft - sx*xMin` form. With a datetime axis
243
+ // the origin lifts ~1.7e12 out of `tx` before the f32
244
+ // narrowing in the matrix below, keeping shader cancellation
245
+ // precision-safe down to sub-millisecond granularity.
246
+ const sx = (clipRight - clipLeft) / (xMax - xMin);
247
+ const sy = (clipTop - clipBottom) / (yMax - yMin);
248
+ const tx = clipLeft - sx * (xMin - xOrigin);
249
+ const ty = clipBottom - sy * (yMin - yOrigin);
250
+
251
+ // Column-major 4x4 matrix
252
+ // prettier-ignore
253
+ return new Float32Array([
254
+ sx, 0, 0, 0,
255
+ 0, sy, 0, 0,
256
+ 0, 0, -1, 0,
257
+ tx, ty, 0, 1,
258
+ ]);
259
+ }
260
+
261
+ /**
262
+ * Convert data coordinates to CSS pixel coordinates on the overlay canvas.
263
+ * Uses the padded domain from the last `buildProjectionMatrix` call so
264
+ * that pixel positions align exactly with the WebGL projection.
265
+ */
266
+ dataToPixel(dataX: number, dataY: number): { px: number; py: number } {
267
+ const { x, y, width, height } = this.plotRect;
268
+ const tx =
269
+ (dataX - this.paddedXMin) / (this.paddedXMax - this.paddedXMin);
270
+ const ty =
271
+ (dataY - this.paddedYMin) / (this.paddedYMax - this.paddedYMin);
272
+ return {
273
+ px: x + tx * width,
274
+ py: y + (1 - ty) * height, // Y is flipped (CSS Y goes down)
275
+ };
276
+ }
277
+ }