@voila.dev/chart 4.0.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 (377) hide show
  1. package/README.md +85 -0
  2. package/dist/brush/brush-layer.d.ts +16 -0
  3. package/dist/brush/brush-layer.d.ts.map +1 -0
  4. package/dist/brush/brush-layer.js +137 -0
  5. package/dist/brush/brush-layer.js.map +7 -0
  6. package/dist/brush/index.d.ts +10 -0
  7. package/dist/brush/index.d.ts.map +1 -0
  8. package/dist/brush/index.js +8 -0
  9. package/dist/brush/index.js.map +7 -0
  10. package/dist/canvas/canvas-renderer.d.ts +11 -0
  11. package/dist/canvas/canvas-renderer.d.ts.map +1 -0
  12. package/dist/canvas/canvas-renderer.js +49 -0
  13. package/dist/canvas/canvas-renderer.js.map +7 -0
  14. package/dist/canvas/index.d.ts +2 -0
  15. package/dist/canvas/index.d.ts.map +1 -0
  16. package/dist/canvas/index.js +5 -0
  17. package/dist/canvas/index.js.map +7 -0
  18. package/dist/canvas/paint-scene.d.ts +4 -0
  19. package/dist/canvas/paint-scene.d.ts.map +1 -0
  20. package/dist/canvas/paint-scene.js +130 -0
  21. package/dist/canvas/paint-scene.js.map +7 -0
  22. package/dist/canvas/resolve-color.d.ts +18 -0
  23. package/dist/canvas/resolve-color.d.ts.map +1 -0
  24. package/dist/canvas/resolve-color.js +52 -0
  25. package/dist/canvas/resolve-color.js.map +7 -0
  26. package/dist/contour/density.d.ts +20 -0
  27. package/dist/contour/density.d.ts.map +1 -0
  28. package/dist/contour/density.js +59 -0
  29. package/dist/contour/density.js.map +7 -0
  30. package/dist/contour/index.d.ts +2 -0
  31. package/dist/contour/index.d.ts.map +1 -0
  32. package/dist/contour/index.js +5 -0
  33. package/dist/contour/index.js.map +7 -0
  34. package/dist/core/channel.d.ts +11 -0
  35. package/dist/core/channel.d.ts.map +1 -0
  36. package/dist/core/channel.js +44 -0
  37. package/dist/core/channel.js.map +7 -0
  38. package/dist/core/compile.d.ts +22 -0
  39. package/dist/core/compile.d.ts.map +1 -0
  40. package/dist/core/compile.js +295 -0
  41. package/dist/core/compile.js.map +7 -0
  42. package/dist/core/define-chart.d.ts +96 -0
  43. package/dist/core/define-chart.d.ts.map +1 -0
  44. package/dist/core/define-chart.js +7 -0
  45. package/dist/core/define-chart.js.map +7 -0
  46. package/dist/core/facet.d.ts +7 -0
  47. package/dist/core/facet.d.ts.map +1 -0
  48. package/dist/core/facet.js +134 -0
  49. package/dist/core/facet.js.map +7 -0
  50. package/dist/core/format.d.ts +37 -0
  51. package/dist/core/format.d.ts.map +1 -0
  52. package/dist/core/format.js +108 -0
  53. package/dist/core/format.js.map +7 -0
  54. package/dist/core/guides/axes.d.ts +25 -0
  55. package/dist/core/guides/axes.d.ts.map +1 -0
  56. package/dist/core/guides/axes.js +204 -0
  57. package/dist/core/guides/axes.js.map +7 -0
  58. package/dist/core/marks/area.d.ts +23 -0
  59. package/dist/core/marks/area.d.ts.map +1 -0
  60. package/dist/core/marks/area.js +187 -0
  61. package/dist/core/marks/area.js.map +7 -0
  62. package/dist/core/marks/bar.d.ts +26 -0
  63. package/dist/core/marks/bar.d.ts.map +1 -0
  64. package/dist/core/marks/bar.js +200 -0
  65. package/dist/core/marks/bar.js.map +7 -0
  66. package/dist/core/marks/cell.d.ts +16 -0
  67. package/dist/core/marks/cell.d.ts.map +1 -0
  68. package/dist/core/marks/cell.js +98 -0
  69. package/dist/core/marks/cell.js.map +7 -0
  70. package/dist/core/marks/dot.d.ts +17 -0
  71. package/dist/core/marks/dot.d.ts.map +1 -0
  72. package/dist/core/marks/dot.js +125 -0
  73. package/dist/core/marks/dot.js.map +7 -0
  74. package/dist/core/marks/funnel-layout.d.ts +37 -0
  75. package/dist/core/marks/funnel-layout.d.ts.map +1 -0
  76. package/dist/core/marks/funnel-layout.js +61 -0
  77. package/dist/core/marks/funnel-layout.js.map +7 -0
  78. package/dist/core/marks/funnel.d.ts +19 -0
  79. package/dist/core/marks/funnel.d.ts.map +1 -0
  80. package/dist/core/marks/funnel.js +143 -0
  81. package/dist/core/marks/funnel.js.map +7 -0
  82. package/dist/core/marks/line.d.ts +17 -0
  83. package/dist/core/marks/line.d.ts.map +1 -0
  84. package/dist/core/marks/line.js +150 -0
  85. package/dist/core/marks/line.js.map +7 -0
  86. package/dist/core/marks/polar.d.ts +61 -0
  87. package/dist/core/marks/polar.d.ts.map +1 -0
  88. package/dist/core/marks/polar.js +436 -0
  89. package/dist/core/marks/polar.js.map +7 -0
  90. package/dist/core/marks/rect.d.ts +22 -0
  91. package/dist/core/marks/rect.d.ts.map +1 -0
  92. package/dist/core/marks/rect.js +119 -0
  93. package/dist/core/marks/rect.js.map +7 -0
  94. package/dist/core/marks/rule.d.ts +17 -0
  95. package/dist/core/marks/rule.d.ts.map +1 -0
  96. package/dist/core/marks/rule.js +73 -0
  97. package/dist/core/marks/rule.js.map +7 -0
  98. package/dist/core/marks/shared.d.ts +41 -0
  99. package/dist/core/marks/shared.d.ts.map +1 -0
  100. package/dist/core/marks/shared.js +74 -0
  101. package/dist/core/marks/shared.js.map +7 -0
  102. package/dist/core/marks/stack.d.ts +13 -0
  103. package/dist/core/marks/stack.d.ts.map +1 -0
  104. package/dist/core/marks/stack.js +28 -0
  105. package/dist/core/marks/stack.js.map +7 -0
  106. package/dist/core/marks/stats/difference.d.ts +20 -0
  107. package/dist/core/marks/stats/difference.d.ts.map +1 -0
  108. package/dist/core/marks/stats/difference.js +168 -0
  109. package/dist/core/marks/stats/difference.js.map +7 -0
  110. package/dist/core/marks/stats/distribution.d.ts +41 -0
  111. package/dist/core/marks/stats/distribution.d.ts.map +1 -0
  112. package/dist/core/marks/stats/distribution.js +354 -0
  113. package/dist/core/marks/stats/distribution.js.map +7 -0
  114. package/dist/core/marks/stats/dodge.d.ts +16 -0
  115. package/dist/core/marks/stats/dodge.d.ts.map +1 -0
  116. package/dist/core/marks/stats/dodge.js +104 -0
  117. package/dist/core/marks/stats/dodge.js.map +7 -0
  118. package/dist/core/marks/stats/hexbin.d.ts +26 -0
  119. package/dist/core/marks/stats/hexbin.d.ts.map +1 -0
  120. package/dist/core/marks/stats/hexbin.js +113 -0
  121. package/dist/core/marks/stats/hexbin.js.map +7 -0
  122. package/dist/core/marks/stats/histogram.d.ts +27 -0
  123. package/dist/core/marks/stats/histogram.d.ts.map +1 -0
  124. package/dist/core/marks/stats/histogram.js +70 -0
  125. package/dist/core/marks/stats/histogram.js.map +7 -0
  126. package/dist/core/marks/stats/regression.d.ts +13 -0
  127. package/dist/core/marks/stats/regression.d.ts.map +1 -0
  128. package/dist/core/marks/stats/regression.js +80 -0
  129. package/dist/core/marks/stats/regression.js.map +7 -0
  130. package/dist/core/marks/stats/statistics.d.ts +34 -0
  131. package/dist/core/marks/stats/statistics.d.ts.map +1 -0
  132. package/dist/core/marks/stats/statistics.js +111 -0
  133. package/dist/core/marks/stats/statistics.js.map +7 -0
  134. package/dist/core/marks/stats/waffle.d.ts +18 -0
  135. package/dist/core/marks/stats/waffle.d.ts.map +1 -0
  136. package/dist/core/marks/stats/waffle.js +117 -0
  137. package/dist/core/marks/stats/waffle.js.map +7 -0
  138. package/dist/core/marks/text.d.ts +19 -0
  139. package/dist/core/marks/text.d.ts.map +1 -0
  140. package/dist/core/marks/text.js +60 -0
  141. package/dist/core/marks/text.js.map +7 -0
  142. package/dist/core/motion/tween.d.ts +6 -0
  143. package/dist/core/motion/tween.d.ts.map +1 -0
  144. package/dist/core/motion/tween.js +98 -0
  145. package/dist/core/motion/tween.js.map +7 -0
  146. package/dist/core/nearest.d.ts +25 -0
  147. package/dist/core/nearest.d.ts.map +1 -0
  148. package/dist/core/nearest.js +151 -0
  149. package/dist/core/nearest.js.map +7 -0
  150. package/dist/core/paths.d.ts +65 -0
  151. package/dist/core/paths.d.ts.map +1 -0
  152. package/dist/core/paths.js +192 -0
  153. package/dist/core/paths.js.map +7 -0
  154. package/dist/core/scales/color.d.ts +11 -0
  155. package/dist/core/scales/color.d.ts.map +1 -0
  156. package/dist/core/scales/color.js +35 -0
  157. package/dist/core/scales/color.js.map +7 -0
  158. package/dist/core/scales/continuous.d.ts +18 -0
  159. package/dist/core/scales/continuous.d.ts.map +1 -0
  160. package/dist/core/scales/continuous.js +82 -0
  161. package/dist/core/scales/continuous.js.map +7 -0
  162. package/dist/core/scales/discrete.d.ts +22 -0
  163. package/dist/core/scales/discrete.d.ts.map +1 -0
  164. package/dist/core/scales/discrete.js +103 -0
  165. package/dist/core/scales/discrete.js.map +7 -0
  166. package/dist/core/scales/resolve.d.ts +18 -0
  167. package/dist/core/scales/resolve.d.ts.map +1 -0
  168. package/dist/core/scales/resolve.js +112 -0
  169. package/dist/core/scales/resolve.js.map +7 -0
  170. package/dist/core/scales/time.d.ts +4 -0
  171. package/dist/core/scales/time.d.ts.map +1 -0
  172. package/dist/core/scales/time.js +140 -0
  173. package/dist/core/scales/time.js.map +7 -0
  174. package/dist/core/text.d.ts +3 -0
  175. package/dist/core/text.d.ts.map +1 -0
  176. package/dist/core/text.js +29 -0
  177. package/dist/core/text.js.map +7 -0
  178. package/dist/core/theme.d.ts +8 -0
  179. package/dist/core/theme.d.ts.map +1 -0
  180. package/dist/core/theme.js +21 -0
  181. package/dist/core/theme.js.map +7 -0
  182. package/dist/core/ticks.d.ts +27 -0
  183. package/dist/core/ticks.d.ts.map +1 -0
  184. package/dist/core/ticks.js +76 -0
  185. package/dist/core/ticks.js.map +7 -0
  186. package/dist/core/types.d.ts +305 -0
  187. package/dist/core/types.d.ts.map +1 -0
  188. package/dist/core/types.js +1 -0
  189. package/dist/core/types.js.map +7 -0
  190. package/dist/force/force.d.ts +30 -0
  191. package/dist/force/force.d.ts.map +1 -0
  192. package/dist/force/force.js +143 -0
  193. package/dist/force/force.js.map +7 -0
  194. package/dist/force/index.d.ts +2 -0
  195. package/dist/force/index.d.ts.map +1 -0
  196. package/dist/force/index.js +7 -0
  197. package/dist/force/index.js.map +7 -0
  198. package/dist/geo/geo.d.ts +59 -0
  199. package/dist/geo/geo.d.ts.map +1 -0
  200. package/dist/geo/geo.js +231 -0
  201. package/dist/geo/geo.js.map +7 -0
  202. package/dist/geo/index.d.ts +2 -0
  203. package/dist/geo/index.d.ts.map +1 -0
  204. package/dist/geo/index.js +13 -0
  205. package/dist/geo/index.js.map +7 -0
  206. package/dist/hierarchy/hierarchy.d.ts +24 -0
  207. package/dist/hierarchy/hierarchy.d.ts.map +1 -0
  208. package/dist/hierarchy/hierarchy.js +279 -0
  209. package/dist/hierarchy/hierarchy.js.map +7 -0
  210. package/dist/hierarchy/index.d.ts +2 -0
  211. package/dist/hierarchy/index.d.ts.map +1 -0
  212. package/dist/hierarchy/index.js +11 -0
  213. package/dist/hierarchy/index.js.map +7 -0
  214. package/dist/index.d.ts +29 -0
  215. package/dist/index.d.ts.map +1 -0
  216. package/dist/index.js +96 -0
  217. package/dist/index.js.map +7 -0
  218. package/dist/react/brush-contract.d.ts +23 -0
  219. package/dist/react/brush-contract.d.ts.map +1 -0
  220. package/dist/react/brush-contract.js +7 -0
  221. package/dist/react/brush-contract.js.map +7 -0
  222. package/dist/react/chart-data-table.d.ts +18 -0
  223. package/dist/react/chart-data-table.d.ts.map +1 -0
  224. package/dist/react/chart-data-table.js +55 -0
  225. package/dist/react/chart-data-table.js.map +7 -0
  226. package/dist/react/chart-legend.d.ts +17 -0
  227. package/dist/react/chart-legend.d.ts.map +1 -0
  228. package/dist/react/chart-legend.js +126 -0
  229. package/dist/react/chart-legend.js.map +7 -0
  230. package/dist/react/chart-tooltip.d.ts +18 -0
  231. package/dist/react/chart-tooltip.d.ts.map +1 -0
  232. package/dist/react/chart-tooltip.js +95 -0
  233. package/dist/react/chart-tooltip.js.map +7 -0
  234. package/dist/react/chart.d.ts +45 -0
  235. package/dist/react/chart.d.ts.map +1 -0
  236. package/dist/react/chart.js +383 -0
  237. package/dist/react/chart.js.map +7 -0
  238. package/dist/react/focus-overlay.d.ts +14 -0
  239. package/dist/react/focus-overlay.d.ts.map +1 -0
  240. package/dist/react/focus-overlay.js +147 -0
  241. package/dist/react/focus-overlay.js.map +7 -0
  242. package/dist/react/index.d.ts +7 -0
  243. package/dist/react/index.d.ts.map +1 -0
  244. package/dist/react/index.js +11 -0
  245. package/dist/react/index.js.map +7 -0
  246. package/dist/react/messages.d.ts +16 -0
  247. package/dist/react/messages.d.ts.map +1 -0
  248. package/dist/react/messages.js +33 -0
  249. package/dist/react/messages.js.map +7 -0
  250. package/dist/react/renderer.d.ts +14 -0
  251. package/dist/react/renderer.d.ts.map +1 -0
  252. package/dist/react/renderer.js +1 -0
  253. package/dist/react/renderer.js.map +7 -0
  254. package/dist/react/sr-only.d.ts +4 -0
  255. package/dist/react/sr-only.d.ts.map +1 -0
  256. package/dist/react/sr-only.js +15 -0
  257. package/dist/react/sr-only.js.map +7 -0
  258. package/dist/react/svg-renderer.d.ts +4 -0
  259. package/dist/react/svg-renderer.d.ts.map +1 -0
  260. package/dist/react/svg-renderer.js +20 -0
  261. package/dist/react/svg-renderer.js.map +7 -0
  262. package/dist/react/svg-scene.d.ts +8 -0
  263. package/dist/react/svg-scene.d.ts.map +1 -0
  264. package/dist/react/svg-scene.js +121 -0
  265. package/dist/react/svg-scene.js.map +7 -0
  266. package/dist/react/use-animated-scene.d.ts +10 -0
  267. package/dist/react/use-animated-scene.d.ts.map +1 -0
  268. package/dist/react/use-animated-scene.js +41 -0
  269. package/dist/react/use-animated-scene.js.map +7 -0
  270. package/dist/react/use-chart-focus.d.ts +29 -0
  271. package/dist/react/use-chart-focus.d.ts.map +1 -0
  272. package/dist/react/use-chart-focus.js +180 -0
  273. package/dist/react/use-chart-focus.js.map +7 -0
  274. package/dist/react/use-chart-width.d.ts +12 -0
  275. package/dist/react/use-chart-width.d.ts.map +1 -0
  276. package/dist/react/use-chart-width.js +23 -0
  277. package/dist/react/use-chart-width.js.map +7 -0
  278. package/dist/react/use-chart-zoom.d.ts +13 -0
  279. package/dist/react/use-chart-zoom.d.ts.map +1 -0
  280. package/dist/react/use-chart-zoom.js +52 -0
  281. package/dist/react/use-chart-zoom.js.map +7 -0
  282. package/dist/react/use-text-measurer.d.ts +8 -0
  283. package/dist/react/use-text-measurer.d.ts.map +1 -0
  284. package/dist/react/use-text-measurer.js +44 -0
  285. package/dist/react/use-text-measurer.js.map +7 -0
  286. package/dist/sankey/index.d.ts +2 -0
  287. package/dist/sankey/index.d.ts.map +1 -0
  288. package/dist/sankey/index.js +7 -0
  289. package/dist/sankey/index.js.map +7 -0
  290. package/dist/sankey/sankey.d.ts +29 -0
  291. package/dist/sankey/sankey.d.ts.map +1 -0
  292. package/dist/sankey/sankey.js +127 -0
  293. package/dist/sankey/sankey.js.map +7 -0
  294. package/dist/voronoi/index.d.ts +2 -0
  295. package/dist/voronoi/index.d.ts.map +1 -0
  296. package/dist/voronoi/index.js +5 -0
  297. package/dist/voronoi/index.js.map +7 -0
  298. package/dist/voronoi/voronoi.d.ts +16 -0
  299. package/dist/voronoi/voronoi.d.ts.map +1 -0
  300. package/dist/voronoi/voronoi.js +62 -0
  301. package/dist/voronoi/voronoi.js.map +7 -0
  302. package/package.json +74 -0
  303. package/src/brush/brush-layer.tsx +174 -0
  304. package/src/brush/index.ts +14 -0
  305. package/src/canvas/canvas-renderer.tsx +56 -0
  306. package/src/canvas/index.ts +1 -0
  307. package/src/canvas/paint-scene.ts +152 -0
  308. package/src/canvas/resolve-color.ts +65 -0
  309. package/src/contour/density.ts +90 -0
  310. package/src/contour/index.ts +1 -0
  311. package/src/core/channel.ts +53 -0
  312. package/src/core/compile.ts +436 -0
  313. package/src/core/define-chart.ts +128 -0
  314. package/src/core/facet.ts +189 -0
  315. package/src/core/format.ts +163 -0
  316. package/src/core/guides/axes.ts +283 -0
  317. package/src/core/marks/area.ts +238 -0
  318. package/src/core/marks/bar.ts +278 -0
  319. package/src/core/marks/cell.ts +123 -0
  320. package/src/core/marks/dot.ts +157 -0
  321. package/src/core/marks/funnel-layout.ts +113 -0
  322. package/src/core/marks/funnel.ts +172 -0
  323. package/src/core/marks/line.ts +206 -0
  324. package/src/core/marks/polar.ts +568 -0
  325. package/src/core/marks/rect.ts +156 -0
  326. package/src/core/marks/rule.ts +104 -0
  327. package/src/core/marks/shared.ts +134 -0
  328. package/src/core/marks/stack.ts +42 -0
  329. package/src/core/marks/stats/difference.ts +205 -0
  330. package/src/core/marks/stats/distribution.ts +425 -0
  331. package/src/core/marks/stats/dodge.ts +136 -0
  332. package/src/core/marks/stats/hexbin.ts +152 -0
  333. package/src/core/marks/stats/histogram.ts +108 -0
  334. package/src/core/marks/stats/regression.ts +108 -0
  335. package/src/core/marks/stats/statistics.ts +153 -0
  336. package/src/core/marks/stats/waffle.ts +141 -0
  337. package/src/core/marks/text.ts +97 -0
  338. package/src/core/motion/tween.ts +131 -0
  339. package/src/core/nearest.ts +223 -0
  340. package/src/core/paths.ts +323 -0
  341. package/src/core/scales/color.ts +51 -0
  342. package/src/core/scales/continuous.ts +111 -0
  343. package/src/core/scales/discrete.ts +156 -0
  344. package/src/core/scales/resolve.ts +158 -0
  345. package/src/core/scales/time.ts +180 -0
  346. package/src/core/text.ts +42 -0
  347. package/src/core/theme.ts +26 -0
  348. package/src/core/ticks.ts +118 -0
  349. package/src/core/types.ts +358 -0
  350. package/src/force/force.ts +200 -0
  351. package/src/force/index.ts +6 -0
  352. package/src/geo/geo.ts +346 -0
  353. package/src/geo/index.ts +10 -0
  354. package/src/hierarchy/hierarchy.ts +377 -0
  355. package/src/hierarchy/index.ts +6 -0
  356. package/src/index.ts +70 -0
  357. package/src/react/brush-contract.ts +35 -0
  358. package/src/react/chart-data-table.tsx +98 -0
  359. package/src/react/chart-legend.tsx +130 -0
  360. package/src/react/chart-tooltip.tsx +111 -0
  361. package/src/react/chart.tsx +489 -0
  362. package/src/react/focus-overlay.tsx +164 -0
  363. package/src/react/index.ts +9 -0
  364. package/src/react/messages.ts +49 -0
  365. package/src/react/renderer.ts +15 -0
  366. package/src/react/sr-only.ts +14 -0
  367. package/src/react/svg-renderer.tsx +18 -0
  368. package/src/react/svg-scene.tsx +146 -0
  369. package/src/react/use-animated-scene.ts +57 -0
  370. package/src/react/use-chart-focus.ts +245 -0
  371. package/src/react/use-chart-width.ts +27 -0
  372. package/src/react/use-chart-zoom.ts +67 -0
  373. package/src/react/use-text-measurer.ts +49 -0
  374. package/src/sankey/index.ts +6 -0
  375. package/src/sankey/sankey.ts +179 -0
  376. package/src/voronoi/index.ts +1 -0
  377. package/src/voronoi/voronoi.ts +84 -0
@@ -0,0 +1,377 @@
1
+ import {
2
+ type HierarchyNode,
3
+ type HierarchyRectangularNode,
4
+ hierarchy,
5
+ partition,
6
+ tree,
7
+ treemap,
8
+ } from "d3-hierarchy";
9
+ import { truncate } from "#/core/guides/axes.ts";
10
+ import { markId } from "#/core/marks/shared.ts";
11
+ import { arcPath, polarToCartesian } from "#/core/paths.ts";
12
+ import type {
13
+ ChartMark,
14
+ ChartMarkContext,
15
+ ChartPoint,
16
+ SceneNode,
17
+ } from "#/core/types.ts";
18
+
19
+ /**
20
+ * Nested data: a treemap, a sunburst, a tree. Each takes one root and reads
21
+ * its children, value and name through accessors, so any nested shape works.
22
+ * Colours follow the root's direct children (the branches the legend lists);
23
+ * deeper levels keep their branch's colour.
24
+ */
25
+
26
+ export interface HierarchyOptions<TNode> {
27
+ readonly id?: string;
28
+ readonly children?: (node: TNode) => ReadonlyArray<TNode> | undefined;
29
+ /** A leaf's size. Branches sum their leaves. */
30
+ readonly value?: (node: TNode) => number;
31
+ readonly name: (node: TNode) => string;
32
+ /** What the values are, for the table heading. */
33
+ readonly label?: string;
34
+ readonly tip?: boolean;
35
+ }
36
+
37
+ function rooted<TNode>(
38
+ root: TNode,
39
+ options: HierarchyOptions<TNode>,
40
+ ): HierarchyNode<TNode> {
41
+ const node = hierarchy(
42
+ root,
43
+ (datum) =>
44
+ (options.children?.(datum) as TNode[] | undefined) ??
45
+ (datum as { children?: TNode[] }).children,
46
+ );
47
+ return node
48
+ .sum((datum) => (options.value ? Math.max(0, options.value(datum)) : 0))
49
+ .sort((left, right) => (right.value ?? 0) - (left.value ?? 0));
50
+ }
51
+
52
+ function branchOf<TNode>(node: HierarchyNode<TNode>): HierarchyNode<TNode> {
53
+ let current = node;
54
+ while (current.parent?.parent) {
55
+ current = current.parent;
56
+ }
57
+ return current;
58
+ }
59
+
60
+ function pathName<TNode>(
61
+ node: HierarchyNode<TNode>,
62
+ options: HierarchyOptions<TNode>,
63
+ ): string {
64
+ return node
65
+ .ancestors()
66
+ .reverse()
67
+ .slice(1)
68
+ .map((ancestor) => options.name(ancestor.data))
69
+ .join(" › ");
70
+ }
71
+
72
+ function branchNames<TNode>(
73
+ root: TNode,
74
+ options: HierarchyOptions<TNode>,
75
+ ): string[] {
76
+ return (rooted(root, options).children ?? []).map((child) =>
77
+ options.name(child.data),
78
+ );
79
+ }
80
+
81
+ function nodeKey<TNode>(
82
+ node: HierarchyNode<TNode>,
83
+ options: HierarchyOptions<TNode>,
84
+ ): string {
85
+ return node
86
+ .ancestors()
87
+ .map((ancestor) => options.name(ancestor.data))
88
+ .reverse()
89
+ .join("/");
90
+ }
91
+
92
+ function colorFor<TNode>(
93
+ node: HierarchyNode<TNode>,
94
+ options: HierarchyOptions<TNode>,
95
+ context: ChartMarkContext,
96
+ ): string {
97
+ return node.depth === 0
98
+ ? context.theme.muted
99
+ : context.colorOf(options.name(branchOf(node).data));
100
+ }
101
+
102
+ function pointFor<TNode>(
103
+ id: string,
104
+ node: HierarchyNode<TNode>,
105
+ options: HierarchyOptions<TNode>,
106
+ context: ChartMarkContext,
107
+ index: number,
108
+ at: { x: number; y: number },
109
+ hit?: ChartPoint["hit"],
110
+ ): ChartPoint {
111
+ return {
112
+ key: `${id}:${nodeKey(node, options)}`,
113
+ markId: id,
114
+ index,
115
+ datum: node.data,
116
+ x: at.x,
117
+ y: at.y,
118
+ xValue: options.name(node.data),
119
+ yValue: node.value ?? 0,
120
+ series: options.name(branchOf(node).data),
121
+ seriesLabel: options.label,
122
+ color: colorFor(node, options, context),
123
+ title: pathName(node, options) || options.name(node.data),
124
+ value: context.formatY(node.value ?? 0),
125
+ hit,
126
+ };
127
+ }
128
+
129
+ const LABEL_PADDING = 4;
130
+ const MIN_LABEL_HEIGHT = 16;
131
+
132
+ /** Rectangles nested inside rectangles, each leaf as large as its value. */
133
+ export function treemapMark<TNode>(
134
+ root: TNode,
135
+ options: HierarchyOptions<TNode>,
136
+ ): ChartMark {
137
+ return {
138
+ id: options.id,
139
+ coordinate: "frame",
140
+ focusOrder: "point",
141
+ titles: { value: options.label },
142
+ channels: { color: { values: branchNames(root, options) } },
143
+ render(context) {
144
+ const id = markId(options.id, "treemap", context);
145
+ const { plot, theme } = context;
146
+ const laid = treemap<TNode>()
147
+ .size([plot.width, plot.height])
148
+ .paddingInner(2)
149
+ .round(true)(rooted(root, options));
150
+ const nodes: SceneNode[] = [];
151
+ const points: ChartPoint[] = [];
152
+ for (const [index, leaf] of laid.leaves().entries()) {
153
+ const rect = {
154
+ x: plot.x + leaf.x0,
155
+ y: plot.y + leaf.y0,
156
+ width: Math.max(0, leaf.x1 - leaf.x0),
157
+ height: Math.max(0, leaf.y1 - leaf.y0),
158
+ };
159
+ const key = nodeKey(leaf, options);
160
+ const series = options.name(branchOf(leaf).data);
161
+ nodes.push({
162
+ kind: "rect",
163
+ key: `${id}:${key}`,
164
+ series,
165
+ role: "mark",
166
+ ...rect,
167
+ corners: [3, 3, 3, 3],
168
+ paint: {
169
+ fill: colorFor(leaf, options, context),
170
+ fillOpacity: 0.55 + 0.45 / leaf.depth,
171
+ },
172
+ });
173
+ if (
174
+ rect.height >= MIN_LABEL_HEIGHT &&
175
+ rect.width > theme.fontSize * 2
176
+ ) {
177
+ nodes.push({
178
+ kind: "text",
179
+ key: `${id}:label:${key}`,
180
+ series,
181
+ role: "label",
182
+ x: rect.x + LABEL_PADDING,
183
+ y: rect.y + LABEL_PADDING,
184
+ text: truncate(
185
+ options.name(leaf.data),
186
+ rect.width - LABEL_PADDING * 2,
187
+ theme.fontSize,
188
+ context.measureText,
189
+ ),
190
+ paint: {
191
+ fill: theme.background,
192
+ fontSize: theme.fontSize,
193
+ fontWeight: 600,
194
+ baseline: "top",
195
+ },
196
+ });
197
+ }
198
+ if (options.tip !== false) {
199
+ points.push(
200
+ pointFor(
201
+ id,
202
+ leaf,
203
+ options,
204
+ context,
205
+ index,
206
+ { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },
207
+ { kind: "rect", rect },
208
+ ),
209
+ );
210
+ }
211
+ }
212
+ return { nodes, points };
213
+ },
214
+ };
215
+ }
216
+
217
+ /** Rings of arcs: the root's children on the first ring, theirs on the next. */
218
+ export function sunburstMark<TNode>(
219
+ root: TNode,
220
+ options: HierarchyOptions<TNode>,
221
+ ): ChartMark {
222
+ return {
223
+ id: options.id,
224
+ coordinate: "frame",
225
+ focusOrder: "point",
226
+ titles: { value: options.label },
227
+ channels: { color: { values: branchNames(root, options) } },
228
+ render(context) {
229
+ const id = markId(options.id, "sunburst", context);
230
+ const { plot } = context;
231
+ const radius = Math.max(0, Math.min(plot.width, plot.height) / 2 - 4);
232
+ const cx = plot.x + plot.width / 2;
233
+ const cy = plot.y + plot.height / 2;
234
+ const laid = partition<TNode>().size([360, radius])(
235
+ rooted(root, options),
236
+ );
237
+ // The root's ring would be a disc in the middle: leave it empty.
238
+ const ringWidth = radius / (laid.height + 1);
239
+ const nodes: SceneNode[] = [];
240
+ const points: ChartPoint[] = [];
241
+ const descendants = laid
242
+ .descendants()
243
+ .filter((node) => node.depth > 0 && node.x1 - node.x0 > 0.2);
244
+ for (const [index, node] of descendants.entries()) {
245
+ const shape = {
246
+ cx,
247
+ cy,
248
+ innerRadius: node.depth * ringWidth,
249
+ outerRadius: (node.depth + 1) * ringWidth - 1,
250
+ startAngle: node.x0 + 0.25,
251
+ endAngle: node.x1 - 0.25,
252
+ };
253
+ const series = options.name(branchOf(node).data);
254
+ nodes.push({
255
+ kind: "path",
256
+ key: `${id}:${nodeKey(node, options)}`,
257
+ series,
258
+ role: "mark",
259
+ d: arcPath(shape),
260
+ paint: {
261
+ fill: colorFor(node, options, context),
262
+ fillOpacity: 1 - (node.depth - 1) * 0.18,
263
+ },
264
+ });
265
+ if (options.tip !== false) {
266
+ const anchor = polarToCartesian(
267
+ cx,
268
+ cy,
269
+ (shape.innerRadius + shape.outerRadius) / 2,
270
+ (shape.startAngle + shape.endAngle) / 2,
271
+ );
272
+ points.push(
273
+ pointFor(id, node, options, context, index, anchor, {
274
+ kind: "arc",
275
+ ...shape,
276
+ }),
277
+ );
278
+ }
279
+ }
280
+ return { nodes, points };
281
+ },
282
+ };
283
+ }
284
+
285
+ const NODE_RADIUS = 4;
286
+ const LABEL_GAP = 6;
287
+
288
+ /** A node-link tree, root on the left, leaves on the right, every node named. */
289
+ export function treeMark<TNode>(
290
+ root: TNode,
291
+ options: HierarchyOptions<TNode>,
292
+ ): ChartMark {
293
+ return {
294
+ id: options.id,
295
+ coordinate: "frame",
296
+ focusOrder: "point",
297
+ titles: { value: options.label },
298
+ channels: {},
299
+ render(context) {
300
+ const id = markId(options.id, "tree", context);
301
+ const { plot, theme, measureText } = context;
302
+ const rootNode = rooted(root, options);
303
+ const leafLabel = Math.max(
304
+ 0,
305
+ ...rootNode
306
+ .leaves()
307
+ .map((leaf) => measureText(options.name(leaf.data), theme.fontSize)),
308
+ );
309
+ const width = Math.max(
310
+ 0,
311
+ plot.width - leafLabel - LABEL_GAP - NODE_RADIUS * 2,
312
+ );
313
+ const laid = tree<TNode>().size([plot.height, width])(
314
+ rootNode,
315
+ ) as HierarchyRectangularNode<TNode> & HierarchyNode<TNode>;
316
+ const position = (
317
+ node: HierarchyNode<TNode> & { x?: number; y?: number },
318
+ ) => ({
319
+ x: plot.x + NODE_RADIUS + (node.y ?? 0),
320
+ y: plot.y + (node.x ?? 0),
321
+ });
322
+ const nodes: SceneNode[] = [];
323
+ const points: ChartPoint[] = [];
324
+ for (const link of laid.links()) {
325
+ const from = position(link.source);
326
+ const to = position(link.target);
327
+ const middle = (from.x + to.x) / 2;
328
+ nodes.push({
329
+ kind: "path",
330
+ key: `${id}:link:${nodeKey(link.target, options)}`,
331
+ role: "grid",
332
+ d: `M${from.x},${from.y}C${middle},${from.y} ${middle},${to.y} ${to.x},${to.y}`,
333
+ paint: { fill: "none", stroke: theme.grid, strokeWidth: 1.5 },
334
+ });
335
+ }
336
+ for (const [index, node] of laid.descendants().entries()) {
337
+ const at = position(node);
338
+ const leaf = node.children === undefined;
339
+ nodes.push(
340
+ {
341
+ kind: "circle",
342
+ key: `${id}:${nodeKey(node, options)}`,
343
+ role: "mark",
344
+ cx: at.x,
345
+ cy: at.y,
346
+ r: NODE_RADIUS,
347
+ paint: {
348
+ fill: leaf ? theme.background : colorFor(node, options, context),
349
+ stroke: colorFor(node, options, context),
350
+ strokeWidth: 1.5,
351
+ },
352
+ },
353
+ {
354
+ kind: "text",
355
+ key: `${id}:label:${nodeKey(node, options)}`,
356
+ role: "label",
357
+ x:
358
+ at.x +
359
+ (leaf ? LABEL_GAP + NODE_RADIUS : -(LABEL_GAP + NODE_RADIUS)),
360
+ y: at.y,
361
+ text: options.name(node.data),
362
+ paint: {
363
+ fill: theme.foreground,
364
+ fontSize: theme.fontSize,
365
+ textAnchor: leaf ? "start" : "end",
366
+ baseline: "middle",
367
+ },
368
+ },
369
+ );
370
+ if (options.tip !== false) {
371
+ points.push(pointFor(id, node, options, context, index, at));
372
+ }
373
+ }
374
+ return { nodes, points };
375
+ },
376
+ };
377
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ type HierarchyOptions,
3
+ sunburstMark as sunburst,
4
+ treeMark as tree,
5
+ treemapMark as treemap,
6
+ } from "#/hierarchy/hierarchy.ts";
package/src/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ export { type CompileOptions, compileChart } from "#/core/compile.ts";
2
+ export {
3
+ type ChartAxisOptions,
4
+ type ChartBuildContext,
5
+ type ChartColorOptions,
6
+ type ChartDefinition,
7
+ type ChartFacet,
8
+ type ChartFacetOptions,
9
+ type ChartPositionScaleOptions,
10
+ type ChartSpec,
11
+ defineChart,
12
+ } from "#/core/define-chart.ts";
13
+ export { facet } from "#/core/facet.ts";
14
+ export { formatDate, formatNumber, formatValue } from "#/core/format.ts";
15
+ export { type AreaOptions, areaX, areaY } from "#/core/marks/area.ts";
16
+ export { type BarOptions, barX, barY } from "#/core/marks/bar.ts";
17
+ export { type CellOptions, cell } from "#/core/marks/cell.ts";
18
+ export { type DotOptions, dot } from "#/core/marks/dot.ts";
19
+ export { type FunnelOptions, funnel } from "#/core/marks/funnel.ts";
20
+ export { type LineOptions, lineX, lineY } from "#/core/marks/line.ts";
21
+ export {
22
+ type ArcOptions,
23
+ arc,
24
+ donut,
25
+ type RadarOptions,
26
+ type RadialBarOptions,
27
+ radar,
28
+ radialBar,
29
+ } from "#/core/marks/polar.ts";
30
+ export { type RectOptions, rectY } from "#/core/marks/rect.ts";
31
+ export { type RuleOptions, ruleX, ruleY } from "#/core/marks/rule.ts";
32
+ export type { ChartMarkOptions } from "#/core/marks/shared.ts";
33
+ export {
34
+ type DifferenceOptions,
35
+ differenceY,
36
+ } from "#/core/marks/stats/difference.ts";
37
+ export {
38
+ type BoxOptions,
39
+ boxY,
40
+ type RidgelineOptions,
41
+ ridgeline,
42
+ violinY,
43
+ } from "#/core/marks/stats/distribution.ts";
44
+ export { type DodgeOptions, dodgeY } from "#/core/marks/stats/dodge.ts";
45
+ export { type HexbinOptions, hexbin } from "#/core/marks/stats/hexbin.ts";
46
+ export {
47
+ type HistogramOptions,
48
+ histogram,
49
+ } from "#/core/marks/stats/histogram.ts";
50
+ export {
51
+ type RegressionOptions,
52
+ regressionY,
53
+ } from "#/core/marks/stats/regression.ts";
54
+ export {
55
+ boxSummary,
56
+ kernelDensity,
57
+ linearFit,
58
+ quantile,
59
+ } from "#/core/marks/stats/statistics.ts";
60
+ export { type WaffleOptions, waffleY } from "#/core/marks/stats/waffle.ts";
61
+ export { type TextOptions, text } from "#/core/marks/text.ts";
62
+ export { easeOutCubic, tweenScene } from "#/core/motion/tween.ts";
63
+ export {
64
+ type ChartFocusStop,
65
+ findNearest,
66
+ focusStops,
67
+ } from "#/core/nearest.ts";
68
+ export { estimateTextWidth } from "#/core/text.ts";
69
+ export { DEFAULT_THEME } from "#/core/theme.ts";
70
+ export type * from "#/core/types.ts";
@@ -0,0 +1,35 @@
1
+ import type * as React from "react";
2
+ import type {
3
+ ChartPositionScale,
4
+ ChartRect,
5
+ ChartValue,
6
+ } from "#/core/types.ts";
7
+ import type { ChartMessages } from "#/react/messages.ts";
8
+
9
+ export type ChartBrushRange = readonly [ChartValue, ChartValue];
10
+
11
+ export interface ChartBrushLayerProps {
12
+ readonly scale: ChartPositionScale;
13
+ readonly plot: ChartRect;
14
+ readonly range: ChartBrushRange | null;
15
+ readonly messages: ChartMessages;
16
+ readonly onChange: (range: ChartBrushRange | null) => void;
17
+ }
18
+
19
+ /**
20
+ * What `brushX(…)` from `@voila.dev/chart/brush` returns. The chart only
21
+ * knows this shape, so the brush's selection layer and sliders ship only
22
+ * with the charts that use them.
23
+ */
24
+ export interface ChartBrush {
25
+ readonly onBrush: (range: ChartBrushRange | null) => void;
26
+ readonly Layer: React.ComponentType<ChartBrushLayerProps>;
27
+ }
28
+
29
+ /** The two ends in the scale's own order. */
30
+ export function ordered(
31
+ scale: ChartPositionScale,
32
+ [a, b]: ChartBrushRange,
33
+ ): ChartBrushRange {
34
+ return scale.map(a) <= scale.map(b) ? [a, b] : [b, a];
35
+ }
@@ -0,0 +1,98 @@
1
+ import type { ChartFocusStop } from "#/core/nearest.ts";
2
+ import type { ChartScene } from "#/core/types.ts";
3
+ import { SR_ONLY } from "#/react/sr-only.ts";
4
+
5
+ /**
6
+ * The chart's content as a table, for anyone who cannot read the picture: a
7
+ * screen reader gets the actual numbers, not a one-line summary of them. One
8
+ * row per focus stop, one column per series, the same values the tooltip
9
+ * shows.
10
+ *
11
+ * The hiding goes on a wrapper: a table box ignores `width: 1px` and lays out
12
+ * at its content width, which widened the page on a phone.
13
+ */
14
+ export function ChartDataTable({
15
+ scene,
16
+ stops,
17
+ caption,
18
+ seriesHeading,
19
+ }: {
20
+ scene: ChartScene;
21
+ stops: ReadonlyArray<ChartFocusStop>;
22
+ caption: string;
23
+ seriesHeading: string;
24
+ }) {
25
+ if (stops.length === 0) {
26
+ return null;
27
+ }
28
+ const series = new Map<string, string>();
29
+ for (const point of scene.points) {
30
+ const key = point.series ?? point.markId;
31
+ if (!series.has(key)) {
32
+ series.set(key, point.seriesLabel ?? scene.yLabel);
33
+ }
34
+ }
35
+ // A pie or a scatter: one row per point, the series only when it says
36
+ // something the row heading does not.
37
+ if (scene.focusOrder === "point") {
38
+ const named = scene.points.some(
39
+ (point) =>
40
+ point.seriesLabel !== undefined && point.seriesLabel !== point.title,
41
+ );
42
+ return (
43
+ <div style={SR_ONLY}>
44
+ <table data-slot="chart-data-table">
45
+ <caption>{caption}</caption>
46
+ <thead>
47
+ <tr>
48
+ <th scope="col">{scene.xLabel}</th>
49
+ {named ? <th scope="col">{seriesHeading}</th> : null}
50
+ <th scope="col">{scene.yLabel}</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ {scene.points.map((point) => (
55
+ <tr key={point.key}>
56
+ <th scope="row">{point.title}</th>
57
+ {named ? <td>{point.seriesLabel}</td> : null}
58
+ <td>{point.value}</td>
59
+ </tr>
60
+ ))}
61
+ </tbody>
62
+ </table>
63
+ </div>
64
+ );
65
+ }
66
+ const columns = [...series.entries()];
67
+ return (
68
+ <div style={SR_ONLY}>
69
+ <table data-slot="chart-data-table">
70
+ <caption>{caption}</caption>
71
+ <thead>
72
+ <tr>
73
+ <th scope="col">{scene.xLabel}</th>
74
+ {columns.map(([key, label]) => (
75
+ <th key={key} scope="col">
76
+ {label}
77
+ </th>
78
+ ))}
79
+ </tr>
80
+ </thead>
81
+ <tbody>
82
+ {stops.map((stop) => (
83
+ <tr key={stop.key}>
84
+ <th scope="row">{stop.points[0].title}</th>
85
+ {columns.map(([key]) => (
86
+ <td key={key}>
87
+ {stop.points.find(
88
+ (point) => (point.series ?? point.markId) === key,
89
+ )?.value ?? ""}
90
+ </td>
91
+ ))}
92
+ </tr>
93
+ ))}
94
+ </tbody>
95
+ </table>
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,130 @@
1
+ import type { ChartLegendItem, ChartScene } from "#/core/types.ts";
2
+
3
+ function Swatch({ item }: { item: ChartLegendItem }) {
4
+ const base = { flex: "none" as const, display: "inline-block" };
5
+ if (item.shape === "line" || item.shape === "dashed") {
6
+ return (
7
+ <span
8
+ aria-hidden="true"
9
+ style={{
10
+ ...base,
11
+ width: 14,
12
+ height: 0,
13
+ borderTop: `2px ${item.shape === "dashed" ? "dashed" : "solid"} ${item.color}`,
14
+ }}
15
+ />
16
+ );
17
+ }
18
+ return (
19
+ <span
20
+ aria-hidden="true"
21
+ style={{
22
+ ...base,
23
+ width: 10,
24
+ height: 10,
25
+ borderRadius: item.shape === "dot" ? "50%" : 2,
26
+ background: item.color,
27
+ }}
28
+ />
29
+ );
30
+ }
31
+
32
+ /**
33
+ * One toggle button per series. Turning a series off redraws without it but
34
+ * keeps the axes where they were, so the other series do not jump.
35
+ */
36
+ export function ChartLegend({
37
+ items,
38
+ hidden,
39
+ label,
40
+ onToggle,
41
+ }: {
42
+ items: ReadonlyArray<ChartLegendItem>;
43
+ hidden: ReadonlySet<string>;
44
+ label: string;
45
+ onToggle: (key: string) => void;
46
+ }) {
47
+ return (
48
+ <ul
49
+ aria-label={label}
50
+ data-slot="chart-legend"
51
+ style={{
52
+ display: "flex",
53
+ flexWrap: "wrap",
54
+ justifyContent: "center",
55
+ gap: 4,
56
+ margin: 0,
57
+ padding: 0,
58
+ listStyle: "none",
59
+ fontSize: 12,
60
+ }}
61
+ >
62
+ {items.map((item) => {
63
+ const visible = !hidden.has(item.key);
64
+ return (
65
+ <li key={item.key}>
66
+ <button
67
+ type="button"
68
+ aria-pressed={visible}
69
+ onClick={() => onToggle(item.key)}
70
+ style={{
71
+ display: "inline-flex",
72
+ alignItems: "center",
73
+ gap: 6,
74
+ minHeight: 24,
75
+ padding: "2px 8px",
76
+ border: 0,
77
+ borderRadius: 6,
78
+ background: "transparent",
79
+ color: "inherit",
80
+ font: "inherit",
81
+ cursor: "pointer",
82
+ opacity: visible ? 1 : 0.45,
83
+ textDecoration: visible ? undefined : "line-through",
84
+ }}
85
+ >
86
+ <Swatch item={item} />
87
+ {item.label}
88
+ </button>
89
+ </li>
90
+ );
91
+ })}
92
+ </ul>
93
+ );
94
+ }
95
+
96
+ /** The ramp of a sequential colour scale: a gradient bar between its two ends. */
97
+ export function ChartColorRamp({
98
+ ramp,
99
+ label,
100
+ }: {
101
+ ramp: NonNullable<ChartScene["colorRamp"]>;
102
+ label: string;
103
+ }) {
104
+ return (
105
+ <div
106
+ role="img"
107
+ aria-label={`${label} : ${ramp.low} – ${ramp.high}`}
108
+ data-slot="chart-color-ramp"
109
+ style={{
110
+ display: "flex",
111
+ alignItems: "center",
112
+ justifyContent: "center",
113
+ gap: 8,
114
+ fontSize: 12,
115
+ }}
116
+ >
117
+ <span>{ramp.low}</span>
118
+ <span
119
+ aria-hidden="true"
120
+ style={{
121
+ width: 120,
122
+ height: 8,
123
+ borderRadius: 4,
124
+ background: `linear-gradient(to right, ${ramp.stops.join(", ")})`,
125
+ }}
126
+ />
127
+ <span>{ramp.high}</span>
128
+ </div>
129
+ );
130
+ }