@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,42 @@
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 "../charts/canvas-types";
14
+ import type { Theme } from "../theme/theme";
15
+
16
+ /**
17
+ * Paint a single facet's title strip — one line of centered text in the
18
+ * caller-supplied `rect`. Shared by every faceted chart family
19
+ * (cartesian, heatmap, …) so the title typography stays uniform.
20
+ */
21
+ export function drawFacetTitle(
22
+ canvas: Canvas2D,
23
+ label: string,
24
+ rect: { x: number; y: number; width: number; height: number },
25
+ theme: Theme,
26
+ dpr: number,
27
+ ): void {
28
+ const ctx = canvas.getContext("2d") as Context2D | null;
29
+ if (!ctx) {
30
+ return;
31
+ }
32
+
33
+ ctx.save();
34
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
35
+ ctx.scale(dpr, dpr);
36
+ ctx.font = `11px ${theme.fontFamily}`;
37
+ ctx.fillStyle = theme.labelColor;
38
+ ctx.textAlign = "center";
39
+ ctx.textBaseline = "middle";
40
+ ctx.fillText(label, rect.x + rect.width / 2, rect.y + rect.height / 2);
41
+ ctx.restore();
42
+ }
@@ -0,0 +1,188 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ /**
14
+ * Generic pixel-space label geometry helpers shared by the axis,
15
+ * legend, and tooltip overlays.
16
+ *
17
+ * All rectangles are in CSS pixels, origin top-left, Y-axis pointing down.
18
+ */
19
+
20
+ import type { Context2D } from "../charts/canvas-types";
21
+
22
+ export interface Rect {
23
+ x: number;
24
+ y: number;
25
+ width: number;
26
+ height: number;
27
+ }
28
+
29
+ export type Rotation = 0 | 45 | 90;
30
+
31
+ /**
32
+ * Bounding rectangle of a text label anchored at `(cx, cy)`, accounting for
33
+ * rotation. Matches d3fc's approximation of rotated text bounds.
34
+ */
35
+ export function labelRect(
36
+ cx: number,
37
+ cy: number,
38
+ textWidth: number,
39
+ textHeight: number,
40
+ rotation: Rotation,
41
+ ): Rect {
42
+ if (rotation === 0) {
43
+ return {
44
+ x: cx - textWidth / 2,
45
+ y: cy,
46
+ width: textWidth,
47
+ height: textHeight,
48
+ };
49
+ }
50
+
51
+ if (rotation === 90) {
52
+ return {
53
+ x: cx - textHeight / 2,
54
+ y: cy,
55
+ width: textHeight,
56
+ height: textWidth,
57
+ };
58
+ }
59
+
60
+ const w = (textWidth + textHeight) / Math.SQRT2;
61
+ return { x: cx - w, y: cy, width: w, height: w };
62
+ }
63
+
64
+ export function rectsOverlap(a: Rect, b: Rect): boolean {
65
+ return (
66
+ a.x <= b.x + b.width &&
67
+ b.x <= a.x + a.width &&
68
+ a.y <= b.y + b.height &&
69
+ b.y <= a.y + a.height
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Rotated-label overlap heuristic from d3fc: for steeply-rotated labels
75
+ * the right edge of the previous box must precede the right edge of the
76
+ * next (plus a small gap).
77
+ */
78
+ export function rotatedLabelsOverlap(a: Rect, b: Rect): boolean {
79
+ return a.x + a.width + 14 > b.x + b.width;
80
+ }
81
+
82
+ export function rectContained(inner: Rect, outer: Rect): boolean {
83
+ return (
84
+ inner.x >= outer.x &&
85
+ inner.x + inner.width <= outer.x + outer.width &&
86
+ inner.y >= outer.y &&
87
+ inner.y + inner.height <= outer.y + outer.height
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Truncate `label` with a trailing ellipsis so the rendered width fits
93
+ * within `maxWidth`. Returns "" when even one character would overflow.
94
+ */
95
+ export function truncateLabel(
96
+ ctx: Context2D,
97
+ label: string,
98
+ maxWidth: number,
99
+ ): string {
100
+ if (maxWidth <= 0) {
101
+ return "";
102
+ }
103
+
104
+ if (ctx.measureText(label).width <= maxWidth) {
105
+ return label;
106
+ }
107
+
108
+ let lo = 0;
109
+ let hi = label.length;
110
+ while (lo < hi) {
111
+ const mid = (lo + hi + 1) >> 1;
112
+ const candidate = label.slice(0, mid) + "…";
113
+ if (ctx.measureText(candidate).width <= maxWidth) {
114
+ lo = mid;
115
+ } else {
116
+ hi = mid - 1;
117
+ }
118
+ }
119
+
120
+ return lo === 0 ? "" : label.slice(0, lo) + "…";
121
+ }
122
+
123
+ /**
124
+ * Word-wrap `text` into at most `maxLines` lines, each fitting within
125
+ * `maxWidth`. Breaks at the last whitespace before the fit boundary
126
+ * when possible, falls back to a hard character break otherwise. The
127
+ * final line is ellipsis-truncated via {@link truncateLabel} if it
128
+ * still doesn't fit. Returns `[]` when nothing meaningful fits (only
129
+ * one line of ≤ 2 chars after wrapping).
130
+ */
131
+ export function wrapLabel(
132
+ ctx: Context2D,
133
+ text: string,
134
+ maxWidth: number,
135
+ maxLines: number,
136
+ ): string[] {
137
+ if (maxLines <= 0 || maxWidth <= 0) {
138
+ return [];
139
+ }
140
+
141
+ if (ctx.measureText(text).width <= maxWidth) {
142
+ return [text];
143
+ }
144
+
145
+ const lines: string[] = [];
146
+ let remaining = text;
147
+
148
+ while (remaining.length > 0 && lines.length < maxLines) {
149
+ const isLastLine = lines.length === maxLines - 1;
150
+
151
+ let fitLen = remaining.length;
152
+ while (
153
+ fitLen > 0 &&
154
+ ctx.measureText(remaining.slice(0, fitLen)).width > maxWidth
155
+ ) {
156
+ fitLen--;
157
+ }
158
+
159
+ if (fitLen === 0) {
160
+ fitLen = 1;
161
+ }
162
+
163
+ if (fitLen === remaining.length) {
164
+ lines.push(remaining);
165
+ break;
166
+ }
167
+
168
+ let breakAt = fitLen;
169
+ const spaceIdx = remaining.lastIndexOf(" ", fitLen);
170
+ if (spaceIdx > 0) {
171
+ breakAt = spaceIdx;
172
+ }
173
+
174
+ if (isLastLine) {
175
+ lines.push(truncateLabel(ctx, remaining, maxWidth));
176
+ break;
177
+ }
178
+
179
+ lines.push(remaining.slice(0, breakAt));
180
+ remaining = remaining.slice(breakAt).trimStart();
181
+ }
182
+
183
+ if (lines.length === 1 && lines[0].length <= 2) {
184
+ return [];
185
+ }
186
+
187
+ return lines;
188
+ }
@@ -0,0 +1,218 @@
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 "../charts/canvas-types";
14
+ import type { PlotLayout, PlotRect } from "../layout/plot-layout";
15
+ import { formatTickValue } from "../layout/ticks";
16
+ import {
17
+ colorValueToT,
18
+ sampleGradient,
19
+ type GradientStop,
20
+ } from "../theme/gradient";
21
+ import type { Theme } from "../theme/theme";
22
+
23
+ function rgbCss(c: [number, number, number, number]): string {
24
+ return `rgb(${Math.round(c[0] * 255)},${Math.round(c[1] * 255)},${Math.round(c[2] * 255)})`;
25
+ }
26
+
27
+ /**
28
+ * Render a vertical color gradient legend on the Canvas2D overlay.
29
+ * Only call when a color column is active. When `colorDomain` crosses
30
+ * zero the 50% stop (sign pivot) is annotated with a tick + `0` label.
31
+ *
32
+ * Per-facet wrapper; computes the anchor from `layout` and delegates
33
+ * to {@link renderLegendAt}. Facet grids render one shared gradient
34
+ * legend and pass an explicit rect to `renderLegendAt` directly.
35
+ */
36
+ export function renderLegend(
37
+ canvas: Canvas2D,
38
+ layout: PlotLayout,
39
+ colorDomain: { min: number; max: number; label: string },
40
+ stops: GradientStop[],
41
+ theme: Theme,
42
+ formatter?: (v: number) => string,
43
+ ): void {
44
+ const rect: PlotRect = {
45
+ x: layout.plotRect.x + layout.plotRect.width + 12,
46
+ y: layout.margins.top + 20,
47
+ width: Math.max(
48
+ 1,
49
+ layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
50
+ ),
51
+ height: Math.max(1, layout.plotRect.height),
52
+ };
53
+ renderLegendAt(canvas, rect, colorDomain, stops, theme, formatter);
54
+ }
55
+
56
+ /**
57
+ * Render a gradient legend at an explicit canvas-absolute rect.
58
+ * Used by facet grids that paint one legend for the whole grid and
59
+ * by single-plot charts through {@link renderLegend}.
60
+ */
61
+ export function renderLegendAt(
62
+ canvas: Canvas2D,
63
+ rect: PlotRect,
64
+ colorDomain: { min: number; max: number; label: string },
65
+ stops: GradientStop[],
66
+ theme: Theme,
67
+ formatter: (v: number) => string = formatTickValue,
68
+ ): void {
69
+ const ctx = canvas.getContext("2d") as Context2D | null;
70
+ if (!ctx) {
71
+ return;
72
+ }
73
+
74
+ const textColor = theme.legendText;
75
+ const borderColor = theme.legendBorder;
76
+ const fontFamily = theme.fontFamily;
77
+
78
+ const barWidth = 16;
79
+ const barHeight = Math.min(120, rect.height * 0.4);
80
+ const x = rect.x;
81
+ const y = rect.y;
82
+
83
+ ctx.fillStyle = textColor;
84
+ ctx.font = `9px ${fontFamily}`;
85
+ ctx.textAlign = "left";
86
+ ctx.textBaseline = "bottom";
87
+ ctx.fillText(colorDomain.label, x, y - 4);
88
+
89
+ // Paint the gradient by walking `colorDomain.min..max` top→bottom and
90
+ // feeding each value through `colorValueToT` so the legend matches the
91
+ // sign-aware mapping used by the GPU / treemap paths.
92
+ const topVal = colorDomain.max;
93
+ const bottomVal = colorDomain.min;
94
+ const gradient = ctx.createLinearGradient(0, y, 0, y + barHeight);
95
+ const SAMPLES = 16;
96
+ for (let i = 0; i <= SAMPLES; i++) {
97
+ const offset = i / SAMPLES;
98
+ const v = topVal + offset * (bottomVal - topVal);
99
+ const t = colorValueToT(v, colorDomain.min, colorDomain.max);
100
+ const rgba = sampleGradient(stops, t);
101
+ gradient.addColorStop(offset, rgbCss(rgba));
102
+ }
103
+
104
+ ctx.fillStyle = gradient;
105
+ ctx.fillRect(x, y, barWidth, barHeight);
106
+
107
+ ctx.strokeStyle = borderColor;
108
+ ctx.lineWidth = 1;
109
+ ctx.strokeRect(x, y, barWidth, barHeight);
110
+
111
+ ctx.fillStyle = textColor;
112
+ ctx.font = `10px ${fontFamily}`;
113
+ ctx.textAlign = "left";
114
+ ctx.textBaseline = "middle";
115
+
116
+ const labelX = x + barWidth + 5;
117
+ ctx.fillText(formatter(colorDomain.max), labelX, y + 2);
118
+ ctx.fillText(
119
+ formatter((colorDomain.min + colorDomain.max) / 2),
120
+ labelX,
121
+ y + barHeight / 2,
122
+ );
123
+ ctx.fillText(formatter(colorDomain.min), labelX, y + barHeight - 2);
124
+
125
+ // Sign-pivot marker when the data crosses zero: a small tick on the
126
+ // right edge of the bar + a "0" label.
127
+ if (colorDomain.min < 0 && colorDomain.max > 0) {
128
+ const zeroOffset =
129
+ (colorDomain.max - 0) / (colorDomain.max - colorDomain.min);
130
+ const zeroY = y + zeroOffset * barHeight;
131
+ ctx.strokeStyle = textColor;
132
+ ctx.lineWidth = 1;
133
+ ctx.beginPath();
134
+ ctx.moveTo(x + barWidth, zeroY);
135
+ ctx.lineTo(x + barWidth + 4, zeroY);
136
+ ctx.stroke();
137
+ ctx.fillStyle = textColor;
138
+ ctx.fillText("0", labelX, zeroY);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Render a categorical legend with discrete colored swatches.
144
+ * Used when split_by or string color columns produce distinct categories.
145
+ *
146
+ * The per-facet wrapper; computes the anchor from `layout` and delegates
147
+ * to {@link renderCategoricalLegendAt}. Facet grids that render one
148
+ * shared legend pass an explicit rect to `renderCategoricalLegendAt`
149
+ * directly.
150
+ */
151
+ export function renderCategoricalLegend(
152
+ canvas: Canvas2D,
153
+ layout: PlotLayout,
154
+ labels: Map<string, number>,
155
+ palette: [number, number, number][],
156
+ theme: Theme,
157
+ ): void {
158
+ const rect: PlotRect = {
159
+ x: layout.plotRect.x + layout.plotRect.width + 12,
160
+ y: layout.margins.top + 10,
161
+ width: Math.max(
162
+ 1,
163
+ layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
164
+ ),
165
+ height: Math.max(1, layout.plotRect.height),
166
+ };
167
+ renderCategoricalLegendAt(canvas, rect, labels, palette, theme);
168
+ }
169
+
170
+ /**
171
+ * Render a categorical legend at an explicit canvas-absolute rect.
172
+ * Used by facet grids that paint one legend for the whole grid and by
173
+ * single-plot charts through {@link renderCategoricalLegend}.
174
+ */
175
+ export function renderCategoricalLegendAt(
176
+ canvas: Canvas2D,
177
+ rect: PlotRect,
178
+ labels: Map<string, number>,
179
+ palette: [number, number, number][],
180
+ theme: Theme,
181
+ ): void {
182
+ const ctx = canvas.getContext("2d") as Context2D | null;
183
+ if (!ctx) {
184
+ return;
185
+ }
186
+
187
+ if (labels.size === 0) {
188
+ return;
189
+ }
190
+
191
+ const textColor = theme.legendText;
192
+ const fontFamily = theme.fontFamily;
193
+
194
+ const swatchSize = 10;
195
+ const lineHeight = 18;
196
+ const x = rect.x;
197
+ let y = rect.y + lineHeight / 2;
198
+
199
+ ctx.font = `11px ${fontFamily}`;
200
+ ctx.textAlign = "left";
201
+ ctx.textBaseline = "middle";
202
+
203
+ for (const [label, idx] of labels) {
204
+ if (y + swatchSize / 2 > rect.y + rect.height) {
205
+ break;
206
+ }
207
+
208
+ const color = palette[idx] ??
209
+ palette[idx % palette.length] ?? [0, 0, 0];
210
+ ctx.fillStyle = `rgb(${Math.round(color[0] * 255)},${Math.round(color[1] * 255)},${Math.round(color[2] * 255)})`;
211
+ ctx.fillRect(x, y - swatchSize / 2, swatchSize, swatchSize);
212
+
213
+ ctx.fillStyle = textColor;
214
+ ctx.fillText(label, x + swatchSize + 6, y);
215
+
216
+ y += lineHeight;
217
+ }
218
+ }