@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,886 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import type { Context2D } from "../canvas-types";
14
+ import type { WebGLContextManager } from "../../webgl/context-manager";
15
+ import type { TreemapChart } from "./treemap";
16
+ import { NULL_NODE } from "../common/node-store";
17
+ import {
18
+ squarify,
19
+ collectVisible,
20
+ collectVisibleAppend,
21
+ } from "./treemap-layout";
22
+ import { Theme } from "../../theme/theme";
23
+ import { resolvePalette, type Vec3 } from "../../theme/palette";
24
+ import { type GradientStop } from "../../theme/gradient";
25
+ import { renderLegend, renderCategoricalLegend } from "../../axis/legend";
26
+ import { PlotLayout } from "../../layout/plot-layout";
27
+ import { buildFacetGrid } from "../../layout/facet-grid";
28
+ import { leafColor, leafRGBA, luminance } from "../common/leaf-color";
29
+ import treemapVert from "../../shaders/treemap.vert.glsl";
30
+ import treemapFrag from "../../shaders/treemap.frag.glsl";
31
+ import { withChromeCache } from "../common/chrome-cache";
32
+ import { wrapLabel } from "../../axis/label-geometry";
33
+ import {
34
+ renderBreadcrumbs as renderTreeBreadcrumbs,
35
+ renderTreeTooltip,
36
+ } from "../common/tree-chrome";
37
+
38
+ type GL = WebGL2RenderingContext | WebGLRenderingContext;
39
+
40
+ /**
41
+ * Full-frame treemap render: layout → WebGL rects → chrome overlay.
42
+ *
43
+ * When `_splitBy` is populated the top-level children of `_rootId`
44
+ * become facet roots; each is squarified into its own cell rect via
45
+ * {@link buildFacetGrid}. The visible-node list is concatenated across
46
+ * facets so a single vertex buffer + draw call covers the whole scene.
47
+ */
48
+ export function renderTreemapFrame(
49
+ chart: TreemapChart,
50
+ glManager: WebGLContextManager,
51
+ ): void {
52
+ if (chart._currentRootId === NULL_NODE) {
53
+ return;
54
+ }
55
+
56
+ const gl = glManager.gl;
57
+ const cssWidth = glManager.cssWidth;
58
+ const cssHeight = glManager.cssHeight;
59
+ if (cssWidth <= 0 || cssHeight <= 0) {
60
+ return;
61
+ }
62
+
63
+ const store = chart._nodeStore;
64
+ const hasSplits =
65
+ chart._splitBy.length > 0 && chart._facetConfig.facet_mode === "grid";
66
+
67
+ const breadcrumbH = chart._breadcrumbIds.length > 1 ? 28 : 0;
68
+ const hasLegend =
69
+ chart._colorMode === "series"
70
+ ? chart._uniqueColorLabels.size > 1
71
+ : chart._colorMode === "numeric" &&
72
+ chart._colorMin < chart._colorMax;
73
+ const legendW = hasLegend ? 90 : 0;
74
+
75
+ // Scratch buffer for the ordered-layout child ids. Worst case:
76
+ // active children at every level = store.count. Reuse the chart's
77
+ // visible-id buffer as scratch when large enough.
78
+ const scratch = new Int32Array(Math.max(store.count, 64));
79
+
80
+ if (hasSplits) {
81
+ layoutFaceted(
82
+ chart,
83
+ scratch,
84
+ cssWidth,
85
+ cssHeight,
86
+ breadcrumbH,
87
+ legendW,
88
+ );
89
+ } else {
90
+ chart._facetGrid = null;
91
+ const baseDepth = store.depth[chart._currentRootId];
92
+ squarify(
93
+ store,
94
+ chart._currentRootId,
95
+ 0,
96
+ breadcrumbH,
97
+ cssWidth - legendW,
98
+ cssHeight,
99
+ baseDepth,
100
+ scratch,
101
+ chart._showBranchHeader,
102
+ );
103
+ collectVisible(chart, chart._currentRootId, 100, baseDepth);
104
+ ensureVisibleMetadata(chart);
105
+ const baseArr = chart._visibleBaseDepths!;
106
+ const rootArr = chart._visibleRootIds!;
107
+ for (let k = 0; k < chart._visibleNodeCount; k++) {
108
+ baseArr[k] = baseDepth;
109
+ rootArr[k] = chart._currentRootId;
110
+ }
111
+ }
112
+
113
+ if (!chart._program) {
114
+ chart._program = glManager.shaders.getOrCreate(
115
+ "treemap",
116
+ treemapVert,
117
+ treemapFrag,
118
+ );
119
+ chart._locations = {
120
+ u_resolution: gl.getUniformLocation(chart._program, "u_resolution"),
121
+ a_position: gl.getAttribLocation(chart._program, "a_position"),
122
+ a_color: gl.getAttribLocation(chart._program, "a_color"),
123
+ };
124
+ }
125
+
126
+ const theme = chart._resolveTheme();
127
+ const stops = theme.gradientStops;
128
+ const palette = resolvePalette(
129
+ theme.seriesPalette,
130
+ stops,
131
+ Math.max(1, chart._uniqueColorLabels.size),
132
+ );
133
+
134
+ if (chart._gridlineCanvas) {
135
+ const gCtx = chart._gridlineCanvas.getContext("2d") as Context2D | null;
136
+ if (gCtx) {
137
+ gCtx.clearRect(
138
+ 0,
139
+ 0,
140
+ chart._gridlineCanvas.width,
141
+ chart._gridlineCanvas.height,
142
+ );
143
+ }
144
+ }
145
+
146
+ chart._chromeCacheDirty = true;
147
+
148
+ generateAndUploadTreemap(chart, gl, stops, palette, theme.areaOpacity);
149
+
150
+ gl.clearColor(0, 0, 0, 0);
151
+ gl.clear(gl.COLOR_BUFFER_BIT);
152
+ gl.enable(gl.BLEND);
153
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
154
+ gl.useProgram(chart._program);
155
+ gl.uniform2f(chart._locations!.u_resolution, cssWidth, cssHeight);
156
+
157
+ gl.bindBuffer(gl.ARRAY_BUFFER, chart._positionBuffer);
158
+ gl.enableVertexAttribArray(chart._locations!.a_position);
159
+ gl.vertexAttribPointer(
160
+ chart._locations!.a_position,
161
+ 2,
162
+ gl.FLOAT,
163
+ false,
164
+ 0,
165
+ 0,
166
+ );
167
+
168
+ gl.bindBuffer(gl.ARRAY_BUFFER, chart._colorBuffer);
169
+ gl.enableVertexAttribArray(chart._locations!.a_color);
170
+ gl.vertexAttribPointer(chart._locations!.a_color, 4, gl.FLOAT, false, 0, 0);
171
+
172
+ gl.drawArrays(gl.TRIANGLES, 0, chart._vertexCount);
173
+
174
+ renderTreemapChromeOverlay(chart);
175
+ }
176
+
177
+ /**
178
+ * Faceted layout: each top-level child of `_rootId` is one facet.
179
+ * Squarify per cell into the cell's rect and concatenate visible
180
+ * nodes, so downstream rendering and hit-testing treat the scene as
181
+ * one flat visible list.
182
+ *
183
+ * `_visibleBaseDepths` and `_visibleRootIds` are filled in parallel so
184
+ * render code can compute the relative depth of each node without
185
+ * knowing its owning facet. Non-facet callers leave these as copies of
186
+ * the single `_currentRootId` depth.
187
+ */
188
+ function layoutFaceted(
189
+ chart: TreemapChart,
190
+ scratch: Int32Array,
191
+ cssWidth: number,
192
+ cssHeight: number,
193
+ breadcrumbH: number,
194
+ legendW: number,
195
+ ): void {
196
+ const store = chart._nodeStore;
197
+
198
+ // Collect the facet roots in declaration order (= top-level children
199
+ // of the synthetic root). Skip zero-value facets.
200
+ const facetIds: number[] = [];
201
+ const labels: string[] = [];
202
+ for (
203
+ let c = store.firstChild[chart._rootId];
204
+ c !== NULL_NODE;
205
+ c = store.nextSibling[c]
206
+ ) {
207
+ if (store.value[c] <= 0) {
208
+ continue;
209
+ }
210
+
211
+ facetIds.push(c);
212
+ labels.push(store.name[c]);
213
+ }
214
+
215
+ const gridHeight = Math.max(1, cssHeight - breadcrumbH);
216
+ const gridWidth = Math.max(1, cssWidth - legendW);
217
+ const grid = buildFacetGrid(labels, {
218
+ cssWidth: gridWidth,
219
+ cssHeight: gridHeight,
220
+ hasLegend: false, // legend rect handled separately by the chrome
221
+ // Treemap has no X/Y axes — skip the per-cell axis gutters and
222
+ // let adjacent cell plot rects sit flush.
223
+ xAxis: "none",
224
+ yAxis: "none",
225
+ gap: chart._facetConfig.facet_padding,
226
+ });
227
+ chart._facetGrid = grid;
228
+
229
+ ensureVisibleMetadata(chart);
230
+ const baseArr = chart._visibleBaseDepths!;
231
+
232
+ let outIdx = 0;
233
+ for (let i = 0; i < facetIds.length; i++) {
234
+ const facetId = facetIds[i];
235
+ const cell = grid.cells[i];
236
+ if (!cell) {
237
+ continue;
238
+ }
239
+
240
+ const label = store.name[facetId];
241
+ const drillRoot = chart._facetDrillRoots.get(label) ?? facetId;
242
+ const baseDepth = store.depth[drillRoot];
243
+ const plot = cell.layout.plotRect;
244
+
245
+ // Shift by breadcrumb band — `buildFacetGrid` works in a
246
+ // local coord system starting at (0,0), but we need absolute
247
+ // canvas coords for squarify's rect.
248
+ squarify(
249
+ store,
250
+ drillRoot,
251
+ plot.x,
252
+ plot.y + breadcrumbH,
253
+ plot.x + plot.width,
254
+ plot.y + breadcrumbH + plot.height,
255
+ baseDepth,
256
+ scratch,
257
+ chart._showBranchHeader,
258
+ );
259
+ const nextIdx = collectVisibleAppend(
260
+ chart,
261
+ drillRoot,
262
+ 100,
263
+ baseDepth,
264
+ outIdx,
265
+ );
266
+
267
+ // Ensure metadata arrays are wide enough after the append.
268
+ if (baseArr.length < nextIdx) {
269
+ ensureVisibleMetadata(chart);
270
+ }
271
+
272
+ const baseArr2 = chart._visibleBaseDepths!;
273
+ const rootArr2 = chart._visibleRootIds!;
274
+ for (let k = outIdx; k < nextIdx; k++) {
275
+ baseArr2[k] = baseDepth;
276
+ rootArr2[k] = drillRoot;
277
+ }
278
+
279
+ outIdx = nextIdx;
280
+ }
281
+
282
+ chart._visibleNodeCount = outIdx;
283
+ }
284
+
285
+ function ensureVisibleMetadata(chart: TreemapChart): void {
286
+ const need = chart._visibleNodeIds?.length ?? chart._nodeStore.count;
287
+ if (!chart._visibleBaseDepths || chart._visibleBaseDepths.length < need) {
288
+ chart._visibleBaseDepths = new Int32Array(need);
289
+ }
290
+
291
+ if (!chart._visibleRootIds || chart._visibleRootIds.length < need) {
292
+ chart._visibleRootIds = new Int32Array(need);
293
+ }
294
+ }
295
+
296
+ function generateAndUploadTreemap(
297
+ chart: TreemapChart,
298
+ gl: GL,
299
+ stops: GradientStop[],
300
+ palette: Vec3[],
301
+ negativeAlpha: number,
302
+ ): void {
303
+ const store = chart._nodeStore;
304
+ const ids = chart._visibleNodeIds!;
305
+ const n = chart._visibleNodeCount;
306
+ const baseArr = chart._visibleBaseDepths;
307
+ const rootArr = chart._visibleRootIds;
308
+
309
+ const baseDepthOf = (i: number): number =>
310
+ baseArr ? baseArr[i] : store.depth[chart._currentRootId];
311
+ const rootOf = (i: number): number =>
312
+ rootArr ? rootArr[i] : chart._currentRootId;
313
+
314
+ // Count the rects we'll emit so we can size the buffers exactly.
315
+ let rectCount = 0;
316
+ for (let i = 0; i < n; i++) {
317
+ const id = ids[i];
318
+ if (id === rootOf(i)) {
319
+ continue;
320
+ }
321
+
322
+ const w = store.x1[id] - store.x0[id];
323
+ const h = store.y1[id] - store.y0[id];
324
+ if (w < 1 || h < 1) {
325
+ continue;
326
+ }
327
+
328
+ if (store.firstChild[id] === NULL_NODE) {
329
+ rectCount++;
330
+ } else if (store.depth[id] - baseDepthOf(i) === 1) {
331
+ rectCount += 2;
332
+ }
333
+ }
334
+
335
+ const positions = new Float32Array(rectCount * 6 * 2);
336
+
337
+ // 4 floats per vertex (RGBA) — negative-size leaves emit with a
338
+ // reduced alpha (= `theme.areaOpacity`); everything else is opaque.
339
+ const colors = new Float32Array(rectCount * 6 * 4);
340
+ let vi = 0;
341
+
342
+ for (let i = 0; i < n; i++) {
343
+ const id = ids[i];
344
+ if (id === rootOf(i)) {
345
+ continue;
346
+ }
347
+
348
+ const sx0 = store.x0[id];
349
+ const sy0 = store.y0[id];
350
+ const sx1 = store.x1[id];
351
+ const sy1 = store.y1[id];
352
+ const w = sx1 - sx0;
353
+ const h = sy1 - sy0;
354
+ if (w < 1 || h < 1) {
355
+ continue;
356
+ }
357
+
358
+ if (store.firstChild[id] === NULL_NODE) {
359
+ const color = leafRGBA(chart, id, stops, palette, negativeAlpha);
360
+ vi = emitRect(
361
+ positions,
362
+ colors,
363
+ vi,
364
+ sx0,
365
+ sy0,
366
+ sx1 - 1,
367
+ sy1 - 1,
368
+ color,
369
+ );
370
+ } else {
371
+ const relDepth = store.depth[id] - baseDepthOf(i);
372
+ if (relDepth === 1) {
373
+ // Branch borders are structural; always opaque.
374
+ const borderColor: [number, number, number, number] = [
375
+ 0.25, 0.25, 0.25, 1.0,
376
+ ];
377
+ vi = emitRect(
378
+ positions,
379
+ colors,
380
+ vi,
381
+ sx0,
382
+ sy1 - 1,
383
+ sx1,
384
+ sy1,
385
+ borderColor,
386
+ );
387
+ vi = emitRect(
388
+ positions,
389
+ colors,
390
+ vi,
391
+ sx1 - 1,
392
+ sy0,
393
+ sx1,
394
+ sy1,
395
+ borderColor,
396
+ );
397
+ }
398
+ }
399
+ }
400
+
401
+ chart._vertexCount = vi;
402
+
403
+ if (!chart._positionBuffer) {
404
+ chart._positionBuffer = gl.createBuffer();
405
+ }
406
+
407
+ gl.bindBuffer(gl.ARRAY_BUFFER, chart._positionBuffer);
408
+ gl.bufferData(
409
+ gl.ARRAY_BUFFER,
410
+ positions.subarray(0, vi * 2),
411
+ gl.DYNAMIC_DRAW,
412
+ );
413
+
414
+ if (!chart._colorBuffer) {
415
+ chart._colorBuffer = gl.createBuffer();
416
+ }
417
+
418
+ gl.bindBuffer(gl.ARRAY_BUFFER, chart._colorBuffer);
419
+ gl.bufferData(gl.ARRAY_BUFFER, colors.subarray(0, vi * 4), gl.DYNAMIC_DRAW);
420
+ }
421
+
422
+ function emitRect(
423
+ positions: Float32Array,
424
+ colors: Float32Array,
425
+ vi: number,
426
+ x0: number,
427
+ y0: number,
428
+ x1: number,
429
+ y1: number,
430
+ color: [number, number, number, number],
431
+ ): number {
432
+ const pi = vi * 2;
433
+ const ci = vi * 4;
434
+
435
+ positions[pi + 0] = x0;
436
+ positions[pi + 1] = y0;
437
+ positions[pi + 2] = x1;
438
+ positions[pi + 3] = y0;
439
+ positions[pi + 4] = x0;
440
+ positions[pi + 5] = y1;
441
+
442
+ positions[pi + 6] = x1;
443
+ positions[pi + 7] = y0;
444
+ positions[pi + 8] = x1;
445
+ positions[pi + 9] = y1;
446
+ positions[pi + 10] = x0;
447
+ positions[pi + 11] = y1;
448
+
449
+ for (let v = 0; v < 6; v++) {
450
+ colors[ci + v * 4 + 0] = color[0];
451
+ colors[ci + v * 4 + 1] = color[1];
452
+ colors[ci + v * 4 + 2] = color[2];
453
+ colors[ci + v * 4 + 3] = color[3];
454
+ }
455
+
456
+ return vi + 6;
457
+ }
458
+
459
+ /**
460
+ * Render the chrome overlay. On layout changes, draws static content
461
+ * (labels, breadcrumbs, legend) directly and snapshots it into a cached
462
+ * bitmap. On hover-only updates, blits the cache and draws only the
463
+ * tooltip + highlight on top.
464
+ */
465
+ export function renderTreemapChromeOverlay(chart: TreemapChart): void {
466
+ if (!chart._chromeCanvas || chart._currentRootId === NULL_NODE) {
467
+ return;
468
+ }
469
+
470
+ const glManager = chart._glManager;
471
+ if (!glManager) {
472
+ return;
473
+ }
474
+
475
+ const { dpr, cssWidth, cssHeight } = glManager;
476
+
477
+ const highlightId =
478
+ chart._pinnedNodeId !== NULL_NODE
479
+ ? chart._pinnedNodeId
480
+ : chart._hoveredNodeId;
481
+
482
+ withChromeCache(
483
+ chart,
484
+ chart._chromeCanvas,
485
+ dpr,
486
+ cssWidth,
487
+ cssHeight,
488
+ (ctx) => drawStaticChrome(chart, ctx, dpr, cssWidth, cssHeight),
489
+ highlightId !== NULL_NODE
490
+ ? (ctx) => {
491
+ const theme = chart._resolveTheme();
492
+ const { fontFamily } = theme;
493
+ const store = chart._nodeStore;
494
+
495
+ renderHoverHighlight(ctx, store, highlightId);
496
+
497
+ const ids = chart._visibleNodeIds!;
498
+ const n = chart._visibleNodeCount;
499
+ const baseArr = chart._visibleBaseDepths;
500
+ const rootArr = chart._visibleRootIds;
501
+ for (let i = 0; i < n; i++) {
502
+ const id = ids[i];
503
+ const rootId = rootArr
504
+ ? rootArr[i]
505
+ : chart._currentRootId;
506
+ if (id === rootId || store.firstChild[id] === NULL_NODE) {
507
+ continue;
508
+ }
509
+
510
+ const nw = store.x1[id] - store.x0[id];
511
+ const nh = store.y1[id] - store.y0[id];
512
+ const baseDepth = baseArr
513
+ ? baseArr[i]
514
+ : store.depth[chart._currentRootId];
515
+ const relDepth = store.depth[id] - baseDepth;
516
+ if (relDepth === 1) {
517
+ renderBranchLabel(
518
+ ctx,
519
+ store,
520
+ id,
521
+ nw,
522
+ nh,
523
+ theme,
524
+ !chart._showBranchHeader,
525
+ );
526
+ } else if (relDepth === 2) {
527
+ renderBranchLabel(
528
+ ctx,
529
+ store,
530
+ id,
531
+ nw,
532
+ nh,
533
+ theme,
534
+ true,
535
+ );
536
+ }
537
+ }
538
+
539
+ if (store.firstChild[highlightId] === NULL_NODE) {
540
+ const stops = theme.gradientStops;
541
+ const palette = resolvePalette(
542
+ theme.seriesPalette,
543
+ stops,
544
+ Math.max(1, chart._uniqueColorLabels.size),
545
+ );
546
+ const hw = store.x1[highlightId] - store.x0[highlightId];
547
+ const hh = store.y1[highlightId] - store.y0[highlightId];
548
+ renderNodeLabel(
549
+ chart,
550
+ ctx,
551
+ highlightId,
552
+ hw,
553
+ hh,
554
+ fontFamily,
555
+ stops,
556
+ palette,
557
+ true,
558
+ );
559
+ }
560
+
561
+ if (
562
+ chart._pinnedNodeId === NULL_NODE &&
563
+ chart._hoveredNodeId !== NULL_NODE
564
+ ) {
565
+ renderTreemapTooltip(
566
+ chart,
567
+ ctx,
568
+ chart._hoveredNodeId,
569
+ cssWidth,
570
+ cssHeight,
571
+ fontFamily,
572
+ );
573
+ }
574
+ }
575
+ : null,
576
+ );
577
+ }
578
+
579
+ function drawStaticChrome(
580
+ chart: TreemapChart,
581
+ ctx: Context2D,
582
+ dpr: number,
583
+ cssWidth: number,
584
+ cssHeight: number,
585
+ ): void {
586
+ const canvas = chart._chromeCanvas!;
587
+
588
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
589
+ ctx.save();
590
+ ctx.scale(dpr, dpr);
591
+
592
+ const theme = chart._resolveTheme();
593
+ const { fontFamily, labelColor: textColor } = theme;
594
+ const stops = theme.gradientStops;
595
+ const palette = resolvePalette(
596
+ theme.seriesPalette,
597
+ stops,
598
+ Math.max(1, chart._uniqueColorLabels.size),
599
+ );
600
+
601
+ const store = chart._nodeStore;
602
+ const ids = chart._visibleNodeIds!;
603
+ const n = chart._visibleNodeCount;
604
+ const baseArr = chart._visibleBaseDepths;
605
+ const rootArr = chart._visibleRootIds;
606
+
607
+ for (let i = 0; i < n; i++) {
608
+ const id = ids[i];
609
+ const rootId = rootArr ? rootArr[i] : chart._currentRootId;
610
+ if (id === rootId || store.firstChild[id] !== NULL_NODE) {
611
+ continue;
612
+ }
613
+
614
+ const w = store.x1[id] - store.x0[id];
615
+ const h = store.y1[id] - store.y0[id];
616
+ renderNodeLabel(chart, ctx, id, w, h, fontFamily, stops, palette);
617
+ }
618
+
619
+ for (let i = 0; i < n; i++) {
620
+ const id = ids[i];
621
+ const rootId = rootArr ? rootArr[i] : chart._currentRootId;
622
+ if (id === rootId || store.firstChild[id] === NULL_NODE) {
623
+ continue;
624
+ }
625
+
626
+ const w = store.x1[id] - store.x0[id];
627
+ const h = store.y1[id] - store.y0[id];
628
+ const baseDepth = baseArr
629
+ ? baseArr[i]
630
+ : store.depth[chart._currentRootId];
631
+ const relDepth = store.depth[id] - baseDepth;
632
+ if (relDepth === 1) {
633
+ renderBranchLabel(
634
+ ctx,
635
+ store,
636
+ id,
637
+ w,
638
+ h,
639
+ theme,
640
+ !chart._showBranchHeader,
641
+ );
642
+ } else if (relDepth === 2) {
643
+ renderBranchLabel(ctx, store, id, w, h, theme, true);
644
+ }
645
+ }
646
+
647
+ if (chart._breadcrumbIds.length > 1) {
648
+ renderTreeBreadcrumbs(chart, ctx, cssWidth, fontFamily, textColor);
649
+ }
650
+
651
+ // Legend: numeric mode → gradient bar; series mode with 2+ unique
652
+ // labels → categorical swatches. Empty mode (and single-label series)
653
+ // suppress the legend entirely.
654
+ if (chart._colorMode === "series" && chart._uniqueColorLabels.size > 1) {
655
+ const legendLayout = new PlotLayout(cssWidth, cssHeight, {
656
+ hasXLabel: false,
657
+ hasYLabel: false,
658
+ hasLegend: true,
659
+ });
660
+ renderCategoricalLegend(
661
+ canvas,
662
+ legendLayout,
663
+ chart._uniqueColorLabels,
664
+ palette,
665
+ theme,
666
+ );
667
+ } else if (
668
+ chart._colorMode === "numeric" &&
669
+ chart._colorMin < chart._colorMax
670
+ ) {
671
+ const legendLayout = new PlotLayout(cssWidth, cssHeight, {
672
+ hasXLabel: false,
673
+ hasYLabel: false,
674
+ hasLegend: true,
675
+ });
676
+ renderLegend(
677
+ canvas,
678
+ legendLayout,
679
+ {
680
+ min: chart._colorMin,
681
+ max: chart._colorMax,
682
+ label: chart._colorName,
683
+ },
684
+ stops,
685
+ theme,
686
+ chart.getColumnFormatter(chart._colorName, "value"),
687
+ );
688
+ }
689
+
690
+ // Per-facet titles (rendered over the layout; painted in the static
691
+ // chrome bitmap so they appear alongside leaf labels).
692
+ if (chart._facetGrid) {
693
+ ctx.font = `11px ${fontFamily}`;
694
+ ctx.fillStyle = textColor;
695
+ ctx.textAlign = "center";
696
+ ctx.textBaseline = "top";
697
+ for (const cell of chart._facetGrid.cells) {
698
+ const plot = cell.layout.plotRect;
699
+ ctx.fillText(cell.label, plot.x + plot.width / 2, plot.y - 14);
700
+ }
701
+ }
702
+
703
+ ctx.restore();
704
+ }
705
+
706
+ function renderNodeLabel(
707
+ chart: TreemapChart,
708
+ ctx: Context2D,
709
+ nodeId: number,
710
+ w: number,
711
+ h: number,
712
+ fontFamily: string,
713
+ stops: GradientStop[],
714
+ palette: Vec3[],
715
+ hovered = false,
716
+ ): void {
717
+ const MAX_FONT = 11;
718
+ const PAD = 4;
719
+ const LINE_HEIGHT = 1.3;
720
+
721
+ if (w < 30 || h < 14) {
722
+ return;
723
+ }
724
+
725
+ const store = chart._nodeStore;
726
+ const fillColor = leafColor(chart, nodeId, stops, palette);
727
+
728
+ const lum = luminance(fillColor[0], fillColor[1], fillColor[2]);
729
+ const labelColor = hovered
730
+ ? lum > 0.5
731
+ ? "rgba(0,0,0,0.85)"
732
+ : "rgba(255,255,255,0.9)"
733
+ : lum > 0.5
734
+ ? "rgba(0,0,0,0.5)"
735
+ : "rgba(255,255,255,0.55)";
736
+
737
+ const fontSize = Math.min(MAX_FONT, Math.floor(h / 2));
738
+ if (fontSize < 7) {
739
+ return;
740
+ }
741
+
742
+ ctx.font = `${fontSize}px ${fontFamily}`;
743
+
744
+ const maxW = w - PAD * 2;
745
+ const lineH = fontSize * LINE_HEIGHT;
746
+ const maxLines = Math.max(1, Math.floor((h - PAD * 2) / lineH));
747
+
748
+ const lines = wrapLabel(ctx, store.name[nodeId], maxW, maxLines);
749
+ if (lines.length === 0) {
750
+ return;
751
+ }
752
+
753
+ const blockH = lines.length * lineH;
754
+ const startY = store.y0[nodeId] + (h - blockH) / 2 + lineH / 2;
755
+
756
+ ctx.fillStyle = labelColor;
757
+ ctx.textAlign = "center";
758
+ ctx.textBaseline = "middle";
759
+ const cx = store.x0[nodeId] + w / 2;
760
+ for (let i = 0; i < lines.length; i++) {
761
+ ctx.fillText(lines[i], cx, startY + i * lineH);
762
+ }
763
+ }
764
+
765
+ function renderBranchLabel(
766
+ ctx: Context2D,
767
+ store: import("../common/node-store").NodeStore,
768
+ nodeId: number,
769
+ w: number,
770
+ h: number,
771
+ { fontFamily, labelColor, backgroundColor }: Theme,
772
+ nested: boolean,
773
+ ): void {
774
+ const x0 = store.x0[nodeId];
775
+ const y0 = store.y0[nodeId];
776
+ const name = store.name[nodeId];
777
+
778
+ if (nested) {
779
+ if (w < 60 || h < 30) {
780
+ return;
781
+ }
782
+
783
+ const fontSize = 12;
784
+ ctx.font = `${fontSize}px ${fontFamily}`;
785
+
786
+ let text = name;
787
+ const maxW = w - 16;
788
+ const textW = ctx.measureText(text).width;
789
+ if (textW > maxW) {
790
+ while (text.length > 1) {
791
+ text = text.slice(0, -1);
792
+ if (ctx.measureText(text + "\u2026").width <= maxW) {
793
+ text += "\u2026";
794
+ break;
795
+ }
796
+ }
797
+ }
798
+
799
+ if (text.length <= 3) {
800
+ return;
801
+ }
802
+
803
+ ctx.save();
804
+ ctx.beginPath();
805
+ ctx.rect(x0, y0, w, h);
806
+ ctx.clip();
807
+
808
+ const cx = x0 + w / 2;
809
+ const cy = y0 + h / 2;
810
+ ctx.textAlign = "center";
811
+ ctx.textBaseline = "middle";
812
+ ctx.lineWidth = 2;
813
+ ctx.strokeStyle = labelColor;
814
+ ctx.lineJoin = "round";
815
+ ctx.strokeText(text, cx, cy);
816
+ ctx.fillStyle = backgroundColor;
817
+ ctx.fillText(text, cx, cy);
818
+
819
+ ctx.restore();
820
+ } else {
821
+ if (w < 40 || h < 22) {
822
+ return;
823
+ }
824
+
825
+ const fontSize = 11;
826
+ ctx.font = `${fontSize}px ${fontFamily}`;
827
+
828
+ let text = name;
829
+ const maxW = w - 10;
830
+ const textW = ctx.measureText(text).width;
831
+ if (textW > maxW) {
832
+ while (text.length > 1) {
833
+ text = text.slice(0, -1);
834
+ if (ctx.measureText(text + "\u2026").width <= maxW) {
835
+ text += "\u2026";
836
+ break;
837
+ }
838
+ }
839
+ }
840
+
841
+ ctx.fillStyle = labelColor;
842
+ ctx.globalAlpha = 0.85;
843
+ ctx.textAlign = "left";
844
+ ctx.textBaseline = "top";
845
+ ctx.fillText(text, x0 + 5, y0 + 4);
846
+ ctx.globalAlpha = 1.0;
847
+ }
848
+ }
849
+
850
+ function renderHoverHighlight(
851
+ ctx: Context2D,
852
+ store: import("../common/node-store").NodeStore,
853
+ nodeId: number,
854
+ ): void {
855
+ ctx.strokeStyle = "rgba(255,255,255,0.9)";
856
+ ctx.lineWidth = 2;
857
+ ctx.strokeRect(
858
+ store.x0[nodeId],
859
+ store.y0[nodeId],
860
+ store.x1[nodeId] - store.x0[nodeId],
861
+ store.y1[nodeId] - store.y0[nodeId],
862
+ );
863
+ }
864
+
865
+ function renderTreemapTooltip(
866
+ chart: TreemapChart,
867
+ ctx: Context2D,
868
+ nodeId: number,
869
+ cssWidth: number,
870
+ cssHeight: number,
871
+ fontFamily: string,
872
+ ): void {
873
+ const store = chart._nodeStore;
874
+ const cx = (store.x0[nodeId] + store.x1[nodeId]) / 2;
875
+ const cy = (store.y0[nodeId] + store.y1[nodeId]) / 2;
876
+ renderTreeTooltip(
877
+ chart,
878
+ ctx,
879
+ nodeId,
880
+ cx,
881
+ cy,
882
+ cssWidth,
883
+ cssHeight,
884
+ fontFamily,
885
+ );
886
+ }