@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,16 @@
1
+ import type { ChartAccessor, ChartMark, ChartValue } from "../core/types.js";
2
+ export interface VoronoiOptions<TDatum> {
3
+ readonly id?: string;
4
+ readonly x: ChartAccessor<TDatum, ChartValue>;
5
+ readonly y: ChartAccessor<TDatum, ChartValue>;
6
+ readonly stroke?: string;
7
+ /** Fill each cell, by its own colour, instead of drawing the borders only. */
8
+ readonly fill?: (datum: TDatum, index: number) => string;
9
+ }
10
+ /**
11
+ * Each point's cell: the area closer to it than to any other. Clipped to the
12
+ * plot. The borders alone read as a mesh over a scatter; filled, the cells
13
+ * become territories.
14
+ */
15
+ export declare function voronoi<TDatum>(data: ReadonlyArray<TDatum>, options: VoronoiOptions<TDatum>): ChartMark;
16
+ //# sourceMappingURL=voronoi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voronoi.d.ts","sourceRoot":"","sources":["../../src/voronoi/voronoi.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EACT,UAAU,EAEV,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc,CAAC,MAAM;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAC7B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,GAC7B,SAAS,CAwDX"}
@@ -0,0 +1,62 @@
1
+ import { Delaunay } from "d3-delaunay";
2
+ import { isPlaceable, readChannel } from "../core/channel.js";
3
+ import { markId, valueOn } from "../core/marks/shared.js";
4
+ function voronoi(data, options) {
5
+ const xs = readChannel(data, options.x);
6
+ const ys = readChannel(data, options.y);
7
+ return {
8
+ id: options.id,
9
+ coordinate: "cartesian",
10
+ channels: {
11
+ x: { values: xs.filter(isPlaceable) },
12
+ y: { values: ys.filter(isPlaceable) }
13
+ },
14
+ render(context) {
15
+ const id = markId(options.id, "voronoi", context);
16
+ const { x: xScale, y: yScale } = context.scales;
17
+ if (xScale === void 0 || yScale === void 0) {
18
+ return { nodes: [] };
19
+ }
20
+ const { plot } = context;
21
+ const indices = [];
22
+ const pixels = [];
23
+ for (const [index, raw] of xs.entries()) {
24
+ const x = valueOn(xScale, raw);
25
+ const y = valueOn(yScale, ys[index]);
26
+ if (x !== void 0 && y !== void 0) {
27
+ indices.push(index);
28
+ pixels.push([xScale.center(x), yScale.center(y)]);
29
+ }
30
+ }
31
+ if (pixels.length === 0) {
32
+ return { nodes: [] };
33
+ }
34
+ const diagram = Delaunay.from(pixels).voronoi([
35
+ plot.x,
36
+ plot.y,
37
+ plot.x + plot.width,
38
+ plot.y + plot.height
39
+ ]);
40
+ const nodes = indices.map((dataIndex, cell) => ({
41
+ kind: "path",
42
+ key: `${id}:${dataIndex}`,
43
+ role: "mark",
44
+ d: diagram.renderCell(cell) ?? "",
45
+ paint: options.fill ? {
46
+ fill: options.fill(data[dataIndex], dataIndex),
47
+ stroke: context.theme.background,
48
+ strokeWidth: 1
49
+ } : {
50
+ fill: "none",
51
+ stroke: options.stroke ?? context.theme.grid,
52
+ strokeWidth: 1
53
+ }
54
+ }));
55
+ return { nodes };
56
+ }
57
+ };
58
+ }
59
+ export {
60
+ voronoi
61
+ };
62
+ //# sourceMappingURL=voronoi.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/voronoi/voronoi.ts"],
4
+ "sourcesContent": ["import { Delaunay } from \"d3-delaunay\";\nimport { isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport { markId, valueOn } from \"#/core/marks/shared.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface VoronoiOptions<TDatum> {\n\treadonly id?: string;\n\treadonly x: ChartAccessor<TDatum, ChartValue>;\n\treadonly y: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\t/** Fill each cell, by its own colour, instead of drawing the borders only. */\n\treadonly fill?: (datum: TDatum, index: number) => string;\n}\n\n/**\n * Each point's cell: the area closer to it than to any other. Clipped to the\n * plot. The borders alone read as a mesh over a scatter; filled, the cells\n * become territories.\n */\nexport function voronoi<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: VoronoiOptions<TDatum>,\n): ChartMark {\n\tconst xs = readChannel(data, options.x);\n\tconst ys = readChannel(data, options.y);\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tchannels: {\n\t\t\tx: { values: xs.filter(isPlaceable) },\n\t\t\ty: { values: ys.filter(isPlaceable) },\n\t\t},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"voronoi\", context);\n\t\t\tconst { x: xScale, y: yScale } = context.scales;\n\t\t\tif (xScale === undefined || yScale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot } = context;\n\t\t\tconst indices: number[] = [];\n\t\t\tconst pixels: Array<[number, number]> = [];\n\t\t\tfor (const [index, raw] of xs.entries()) {\n\t\t\t\tconst x = valueOn(xScale, raw);\n\t\t\t\tconst y = valueOn(yScale, ys[index]);\n\t\t\t\tif (x !== undefined && y !== undefined) {\n\t\t\t\t\tindices.push(index);\n\t\t\t\t\tpixels.push([xScale.center(x), yScale.center(y)]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (pixels.length === 0) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst diagram = Delaunay.from(pixels).voronoi([\n\t\t\t\tplot.x,\n\t\t\t\tplot.y,\n\t\t\t\tplot.x + plot.width,\n\t\t\t\tplot.y + plot.height,\n\t\t\t]);\n\t\t\tconst nodes: SceneNode[] = indices.map((dataIndex, cell) => ({\n\t\t\t\tkind: \"path\",\n\t\t\t\tkey: `${id}:${dataIndex}`,\n\t\t\t\trole: \"mark\",\n\t\t\t\td: diagram.renderCell(cell) ?? \"\",\n\t\t\t\tpaint: options.fill\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tfill: options.fill(data[dataIndex], dataIndex),\n\t\t\t\t\t\t\tstroke: context.theme.background,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tfill: \"none\",\n\t\t\t\t\t\t\tstroke: options.stroke ?? context.theme.grid,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t},\n\t\t\t}));\n\t\t\treturn { nodes };\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,aAAa,mBAAmB;AACzC,SAAS,QAAQ,eAAe;AAsBzB,SAAS,QACf,MACA,SACY;AACZ,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU;AAAA,MACT,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,MACpC,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,IACrC;AAAA,IACA,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,WAAW,OAAO;AAChD,YAAM,EAAE,GAAG,QAAQ,GAAG,OAAO,IAAI,QAAQ;AACzC,UAAI,WAAW,UAAa,WAAW,QAAW;AACjD,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,EAAE,KAAK,IAAI;AACjB,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAkC,CAAC;AACzC,iBAAW,CAAC,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG;AACxC,cAAM,IAAI,QAAQ,QAAQ,GAAG;AAC7B,cAAM,IAAI,QAAQ,QAAQ,GAAG,KAAK,CAAC;AACnC,YAAI,MAAM,UAAa,MAAM,QAAW;AACvC,kBAAQ,KAAK,KAAK;AAClB,iBAAO,KAAK,CAAC,OAAO,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC;AAAA,QACjD;AAAA,MACD;AACA,UAAI,OAAO,WAAW,GAAG;AACxB,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,UAAU,SAAS,KAAK,MAAM,EAAE,QAAQ;AAAA,QAC7C,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,IAAI,KAAK;AAAA,QACd,KAAK,IAAI,KAAK;AAAA,MACf,CAAC;AACD,YAAM,QAAqB,QAAQ,IAAI,CAAC,WAAW,UAAU;AAAA,QAC5D,MAAM;AAAA,QACN,KAAK,GAAG,EAAE,IAAI,SAAS;AAAA,QACvB,MAAM;AAAA,QACN,GAAG,QAAQ,WAAW,IAAI,KAAK;AAAA,QAC/B,OAAO,QAAQ,OACZ;AAAA,UACA,MAAM,QAAQ,KAAK,KAAK,SAAS,GAAG,SAAS;AAAA,UAC7C,QAAQ,QAAQ,MAAM;AAAA,UACtB,aAAa;AAAA,QACd,IACC;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,QAAQ,UAAU,QAAQ,MAAM;AAAA,UACxC,aAAa;AAAA,QACd;AAAA,MACH,EAAE;AACF,aAAO,EAAE,MAAM;AAAA,IAChB;AAAA,EACD;AACD;",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@voila.dev/chart",
3
+ "version": "4.0.0",
4
+ "type": "module",
5
+ "description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
6
+ "license": "MIT",
7
+ "homepage": "https://ui.voila.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/voila-voila-dev/ui.git",
11
+ "directory": "packages/chart"
12
+ },
13
+ "keywords": [
14
+ "react",
15
+ "charts",
16
+ "svg",
17
+ "canvas",
18
+ "accessibility",
19
+ "dataviz"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "files": [
25
+ "src",
26
+ "dist",
27
+ "!src/**/*.test.ts",
28
+ "!src/**/*.test.tsx"
29
+ ],
30
+ "exports": {
31
+ ".": "./dist/index.js",
32
+ "./react": "./dist/react/index.js",
33
+ "./canvas": "./dist/canvas/index.js",
34
+ "./contour": "./dist/contour/index.js",
35
+ "./voronoi": "./dist/voronoi/index.js",
36
+ "./hierarchy": "./dist/hierarchy/index.js",
37
+ "./sankey": "./dist/sankey/index.js",
38
+ "./force": "./dist/force/index.js",
39
+ "./geo": "./dist/geo/index.js",
40
+ "./brush": "./dist/brush/index.js"
41
+ },
42
+ "imports": {
43
+ "#/*": "./src/*"
44
+ },
45
+ "scripts": {
46
+ "check": "biome check",
47
+ "check-types": "tsc --noEmit",
48
+ "check-bundle": "bun scripts/check-bundle.mjs",
49
+ "format": "biome format",
50
+ "lint": "biome lint",
51
+ "test": "vitest run && bun scripts/check-bundle.mjs"
52
+ },
53
+ "peerDependencies": {
54
+ "react": "^19.0.0",
55
+ "react-dom": "^19.0.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "react": {
59
+ "optional": true
60
+ },
61
+ "react-dom": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "sideEffects": false,
66
+ "dependencies": {
67
+ "d3-contour": "4.0.2",
68
+ "d3-delaunay": "6.0.4",
69
+ "d3-force": "3.0.0",
70
+ "d3-geo": "3.1.1",
71
+ "d3-hierarchy": "3.1.2",
72
+ "d3-sankey": "0.12.3"
73
+ }
74
+ }
@@ -0,0 +1,174 @@
1
+ import type * as React from "react";
2
+ import type { ChartPositionScale, ChartValue } from "#/core/types.ts";
3
+ import {
4
+ type ChartBrushLayerProps,
5
+ type ChartBrushRange,
6
+ ordered,
7
+ } from "#/react/brush-contract.ts";
8
+
9
+ function isDiscrete(scale: ChartPositionScale): boolean {
10
+ return scale.kind === "band" || scale.kind === "point";
11
+ }
12
+
13
+ function numeric(value: ChartValue): number {
14
+ return value instanceof Date ? value.getTime() : Number(value);
15
+ }
16
+
17
+ /** The pixel extent of a selection: whole bands on a band scale. */
18
+ export function brushPixels(
19
+ scale: ChartPositionScale,
20
+ [from, to]: ChartBrushRange,
21
+ ): readonly [number, number] {
22
+ const left = Math.min(scale.map(from), scale.map(to));
23
+ const right = Math.max(scale.map(from), scale.map(to)) + scale.bandwidth;
24
+ return [left, right];
25
+ }
26
+
27
+ /** One keyboard step along the scale: a category, or a fiftieth of the domain. */
28
+ export function stepValue(
29
+ scale: ChartPositionScale,
30
+ value: ChartValue,
31
+ direction: 1 | -1,
32
+ large: boolean,
33
+ ): ChartValue {
34
+ if (isDiscrete(scale)) {
35
+ const index = scale.domain.findIndex(
36
+ (candidate) =>
37
+ numeric(candidate) === numeric(value) || candidate === value,
38
+ );
39
+ const next = Math.min(
40
+ scale.domain.length - 1,
41
+ Math.max(0, index + direction * (large ? 5 : 1)),
42
+ );
43
+ return scale.domain[next];
44
+ }
45
+ const [low, high] = scale.domain.map(numeric);
46
+ const step = ((high - low) / 50) * (large ? 5 : 1);
47
+ const next = Math.min(high, Math.max(low, numeric(value) + direction * step));
48
+ return scale.kind === "time" ? new Date(next) : next;
49
+ }
50
+
51
+ const HANDLE_WIDTH = 8;
52
+
53
+ /**
54
+ * The selection over the plot, and its two ends as sliders. The sliders sit
55
+ * outside the chart's graphic (whose children assistive tech does not see),
56
+ * so a keyboard or screen-reader user can set a range as well as a mouse
57
+ * can: arrows move an end, Page keys move it five steps, Home and End jump,
58
+ * Escape clears.
59
+ */
60
+ export function BrushLayer({
61
+ scale,
62
+ plot,
63
+ range,
64
+ messages,
65
+ onChange,
66
+ }: ChartBrushLayerProps) {
67
+ const ends = range ?? [
68
+ scale.domain[0],
69
+ scale.domain[scale.domain.length - 1],
70
+ ];
71
+ const [left, right] = range
72
+ ? brushPixels(scale, range)
73
+ : [plot.x, plot.x + plot.width];
74
+
75
+ function handleKey(which: 0 | 1) {
76
+ return (event: React.KeyboardEvent<HTMLDivElement>) => {
77
+ const current = ends[which];
78
+ let next: ChartValue | undefined;
79
+ if (event.key === "ArrowRight" || event.key === "ArrowUp")
80
+ next = stepValue(scale, current, 1, false);
81
+ if (event.key === "ArrowLeft" || event.key === "ArrowDown")
82
+ next = stepValue(scale, current, -1, false);
83
+ if (event.key === "PageUp") next = stepValue(scale, current, 1, true);
84
+ if (event.key === "PageDown") next = stepValue(scale, current, -1, true);
85
+ if (event.key === "Home") next = scale.domain[0];
86
+ if (event.key === "End") next = scale.domain[scale.domain.length - 1];
87
+ if (event.key === "Escape") {
88
+ event.preventDefault();
89
+ onChange(null);
90
+ return;
91
+ }
92
+ if (next === undefined) return;
93
+ event.preventDefault();
94
+ const updated: [ChartValue, ChartValue] = [ends[0], ends[1]];
95
+ updated[which] = next;
96
+ onChange(ordered(scale, updated));
97
+ };
98
+ }
99
+
100
+ const [low, high] = isDiscrete(scale)
101
+ ? [0, scale.domain.length - 1]
102
+ : scale.domain.map(numeric);
103
+ const position = (value: ChartValue) =>
104
+ isDiscrete(scale)
105
+ ? scale.domain.findIndex(
106
+ (candidate) =>
107
+ candidate === value || numeric(candidate) === numeric(value),
108
+ )
109
+ : numeric(value);
110
+
111
+ return (
112
+ <>
113
+ {range ? (
114
+ <div
115
+ aria-hidden="true"
116
+ data-slot="chart-brush-selection"
117
+ style={{
118
+ position: "absolute",
119
+ left,
120
+ top: plot.y,
121
+ width: Math.max(0, right - left),
122
+ height: plot.height,
123
+ background: "color-mix(in oklab, currentColor 10%, transparent)",
124
+ borderInline:
125
+ "1px solid color-mix(in oklab, currentColor 40%, transparent)",
126
+ pointerEvents: "none",
127
+ }}
128
+ />
129
+ ) : null}
130
+ {([0, 1] as const).map((which) => (
131
+ <div
132
+ key={which}
133
+ role="slider"
134
+ tabIndex={0}
135
+ aria-label={which === 0 ? messages.brushStart : messages.brushEnd}
136
+ aria-valuemin={low}
137
+ aria-valuemax={high}
138
+ aria-valuenow={position(ends[which])}
139
+ aria-valuetext={scale.format(ends[which])}
140
+ aria-orientation="horizontal"
141
+ data-slot="chart-brush-handle"
142
+ onKeyDown={handleKey(which)}
143
+ onPointerDown={(event) =>
144
+ event.currentTarget.setPointerCapture(event.pointerId)
145
+ }
146
+ onPointerMove={(event) => {
147
+ if (!event.currentTarget.hasPointerCapture(event.pointerId)) return;
148
+ const frame =
149
+ event.currentTarget.parentElement?.getBoundingClientRect();
150
+ if (frame === undefined) return;
151
+ const updated: [ChartValue, ChartValue] = [ends[0], ends[1]];
152
+ updated[which] = scale.invert(event.clientX - frame.left);
153
+ onChange(ordered(scale, updated));
154
+ }}
155
+ style={{
156
+ position: "absolute",
157
+ left: (which === 0 ? left : right) - HANDLE_WIDTH / 2,
158
+ top: plot.y + plot.height / 2 - 14,
159
+ width: HANDLE_WIDTH,
160
+ height: 28,
161
+ borderRadius: 4,
162
+ background: "var(--background, Canvas)",
163
+ border:
164
+ "1px solid color-mix(in oklab, currentColor 50%, transparent)",
165
+ cursor: "ew-resize",
166
+ userSelect: "none",
167
+ WebkitUserSelect: "none",
168
+ opacity: range ? 1 : 0.5,
169
+ }}
170
+ />
171
+ ))}
172
+ </>
173
+ );
174
+ }
@@ -0,0 +1,14 @@
1
+ import { BrushLayer } from "#/brush/brush-layer.tsx";
2
+ import type { ChartBrush, ChartBrushRange } from "#/react/brush-contract.ts";
3
+
4
+ export type { ChartBrush, ChartBrushRange } from "#/react/brush-contract.ts";
5
+
6
+ /**
7
+ * Select a range of x by dragging, or with two sliders from the keyboard.
8
+ * Pass the result as `<Chart brush={…} />`.
9
+ */
10
+ export function brushX(options: {
11
+ readonly onBrush: (range: ChartBrushRange | null) => void;
12
+ }): ChartBrush {
13
+ return { onBrush: options.onBrush, Layer: BrushLayer };
14
+ }
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { paintScene } from "#/canvas/paint-scene.ts";
3
+ import { createColorResolver, watchTheme } from "#/canvas/resolve-color.ts";
4
+ import type { ChartRendererProps } from "#/react/renderer.ts";
5
+
6
+ /**
7
+ * Paints the scene onto a canvas: the renderer for thousands of marks, where
8
+ * one DOM node per mark gets slow. Hit-testing, focus, the tooltip, the legend
9
+ * and the data table do not change, they read the same scene. On the server
10
+ * the canvas is empty and the data table carries the content until the first
11
+ * paint.
12
+ */
13
+ export function CanvasRenderer({ scene }: ChartRendererProps) {
14
+ const canvasRef = React.useRef<HTMLCanvasElement>(null);
15
+ const [themeVersion, setThemeVersion] = React.useState(0);
16
+
17
+ React.useEffect(
18
+ () => watchTheme(() => setThemeVersion((version) => version + 1)),
19
+ [],
20
+ );
21
+
22
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a theme change repaints with fresh colours
23
+ React.useLayoutEffect(() => {
24
+ const canvas = canvasRef.current;
25
+ const host = canvas?.parentElement;
26
+ const context = canvas?.getContext("2d");
27
+ if (!canvas || !host || !context) {
28
+ return;
29
+ }
30
+ const ratio = window.devicePixelRatio || 1;
31
+ canvas.width = Math.round(scene.width * ratio);
32
+ canvas.height = Math.round(scene.height * ratio);
33
+ context.setTransform(ratio, 0, 0, ratio, 0, 0);
34
+ const colors = createColorResolver(host);
35
+ paintScene(
36
+ context,
37
+ scene,
38
+ colors.resolve,
39
+ getComputedStyle(host).fontFamily || "sans-serif",
40
+ );
41
+ colors.dispose();
42
+ }, [scene, themeVersion]);
43
+
44
+ return (
45
+ <canvas
46
+ ref={canvasRef}
47
+ data-slot="chart-canvas"
48
+ style={{
49
+ position: "absolute",
50
+ inset: 0,
51
+ width: scene.width,
52
+ height: scene.height,
53
+ }}
54
+ />
55
+ );
56
+ }
@@ -0,0 +1 @@
1
+ export { CanvasRenderer } from "#/canvas/canvas-renderer.tsx";
@@ -0,0 +1,152 @@
1
+ import { roundedBarPath } from "#/core/paths.ts";
2
+ import type { ChartPaint, ChartScene, SceneNode } from "#/core/types.ts";
3
+
4
+ const CANVAS_BASELINE = {
5
+ top: "top",
6
+ middle: "middle",
7
+ bottom: "bottom",
8
+ alphabetic: "alphabetic",
9
+ } as const satisfies Record<string, CanvasTextBaseline>;
10
+
11
+ const CANVAS_ALIGN = {
12
+ start: "left",
13
+ middle: "center",
14
+ end: "right",
15
+ } as const satisfies Record<string, CanvasTextAlign>;
16
+
17
+ function dashes(dasharray: string | undefined): number[] {
18
+ if (!dasharray) {
19
+ return [];
20
+ }
21
+ return dasharray
22
+ .split(/[\s,]+/)
23
+ .map(Number)
24
+ .filter((value) => Number.isFinite(value));
25
+ }
26
+
27
+ interface PaintEnvironment {
28
+ readonly context: CanvasRenderingContext2D;
29
+ readonly resolve: (color: string) => string;
30
+ readonly fontFamily: string;
31
+ }
32
+
33
+ function fillAndStroke(
34
+ environment: PaintEnvironment,
35
+ paint: ChartPaint,
36
+ draw: (mode: "fill" | "stroke") => void,
37
+ ) {
38
+ const { context, resolve } = environment;
39
+ const opacity = paint.opacity ?? 1;
40
+ if (paint.fill && paint.fill !== "none") {
41
+ context.globalAlpha = opacity * (paint.fillOpacity ?? 1);
42
+ context.fillStyle = resolve(paint.fill);
43
+ draw("fill");
44
+ }
45
+ if (paint.stroke && paint.stroke !== "none") {
46
+ context.globalAlpha = opacity * (paint.strokeOpacity ?? 1);
47
+ context.strokeStyle = resolve(paint.stroke);
48
+ context.lineWidth = paint.strokeWidth ?? 1;
49
+ context.lineCap = paint.strokeLinecap ?? "butt";
50
+ context.lineJoin = paint.strokeLinejoin ?? "miter";
51
+ context.setLineDash(dashes(paint.strokeDasharray));
52
+ draw("stroke");
53
+ context.setLineDash([]);
54
+ }
55
+ context.globalAlpha = 1;
56
+ }
57
+
58
+ function paintNode(environment: PaintEnvironment, node: SceneNode) {
59
+ const { context } = environment;
60
+ switch (node.kind) {
61
+ case "group":
62
+ context.save();
63
+ if (node.translate) {
64
+ context.translate(node.translate.x, node.translate.y);
65
+ }
66
+ if (node.clip) {
67
+ context.beginPath();
68
+ context.rect(
69
+ node.clip.x,
70
+ node.clip.y,
71
+ node.clip.width,
72
+ node.clip.height,
73
+ );
74
+ context.clip();
75
+ }
76
+ for (const child of node.children) {
77
+ paintNode(environment, child);
78
+ }
79
+ context.restore();
80
+ return;
81
+ case "rect": {
82
+ const path = node.corners
83
+ ? new Path2D(roundedBarPath({ ...node, radius: node.corners }))
84
+ : undefined;
85
+ fillAndStroke(environment, node.paint, (mode) => {
86
+ if (path) {
87
+ context[mode](path);
88
+ } else if (mode === "fill") {
89
+ context.fillRect(node.x, node.y, node.width, node.height);
90
+ } else {
91
+ context.strokeRect(node.x, node.y, node.width, node.height);
92
+ }
93
+ });
94
+ return;
95
+ }
96
+ case "path": {
97
+ if (node.d === "") {
98
+ return;
99
+ }
100
+ const path = new Path2D(node.d);
101
+ fillAndStroke(environment, node.paint, (mode) => context[mode](path));
102
+ return;
103
+ }
104
+ case "circle": {
105
+ const path = new Path2D();
106
+ path.arc(node.cx, node.cy, node.r, 0, Math.PI * 2);
107
+ fillAndStroke(environment, node.paint, (mode) => context[mode](path));
108
+ return;
109
+ }
110
+ case "line": {
111
+ const path = new Path2D();
112
+ path.moveTo(node.x1, node.y1);
113
+ path.lineTo(node.x2, node.y2);
114
+ fillAndStroke(environment, { ...node.paint, fill: undefined }, () =>
115
+ context.stroke(path),
116
+ );
117
+ return;
118
+ }
119
+ case "text": {
120
+ const { paint } = node;
121
+ context.save();
122
+ context.translate(node.x, node.y);
123
+ if (node.rotate) {
124
+ context.rotate((node.rotate * Math.PI) / 180);
125
+ }
126
+ context.font = `${paint.fontWeight ?? 400} ${paint.fontSize ?? 12}px ${environment.fontFamily}`;
127
+ context.textAlign = CANVAS_ALIGN[paint.textAnchor ?? "start"];
128
+ context.textBaseline = CANVAS_BASELINE[paint.baseline ?? "alphabetic"];
129
+ fillAndStroke(environment, paint, (mode) =>
130
+ mode === "fill"
131
+ ? context.fillText(node.text, 0, 0)
132
+ : context.strokeText(node.text, 0, 0),
133
+ );
134
+ context.restore();
135
+ return;
136
+ }
137
+ }
138
+ }
139
+
140
+ /** Paints the whole scene. The canvas is already scaled to device pixels. */
141
+ export function paintScene(
142
+ context: CanvasRenderingContext2D,
143
+ scene: ChartScene,
144
+ resolve: (color: string) => string,
145
+ fontFamily: string,
146
+ ) {
147
+ context.clearRect(0, 0, scene.width, scene.height);
148
+ const environment = { context, resolve, fontFamily };
149
+ for (const node of scene.nodes) {
150
+ paintNode(environment, node);
151
+ }
152
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Canvas takes colours as computed values only: no `var()`, and `currentColor`
3
+ * means black. A hidden probe inside the chart resolves each CSS colour
4
+ * against the page, theme and inherited colour included; the answers are
5
+ * cached until the theme can have changed.
6
+ */
7
+ export interface ColorResolver {
8
+ readonly resolve: (color: string) => string;
9
+ readonly dispose: () => void;
10
+ }
11
+
12
+ export function createColorResolver(host: HTMLElement): ColorResolver {
13
+ const probe = document.createElement("span");
14
+ probe.setAttribute("aria-hidden", "true");
15
+ probe.style.cssText =
16
+ "position:absolute;width:0;height:0;overflow:hidden;visibility:hidden";
17
+ host.append(probe);
18
+ const cache = new Map<string, string>();
19
+ return {
20
+ resolve(color) {
21
+ const cached = cache.get(color);
22
+ if (cached !== undefined) {
23
+ return cached;
24
+ }
25
+ probe.style.color = "";
26
+ probe.style.color = color;
27
+ const resolved = getComputedStyle(probe).color || color;
28
+ cache.set(color, resolved);
29
+ return resolved;
30
+ },
31
+ dispose() {
32
+ probe.remove();
33
+ },
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Calls `onChange` whenever the colours a chart resolved may be stale: a
39
+ * class, style or theme attribute change on the root, a colour-scheme or
40
+ * forced-colours switch.
41
+ */
42
+ export function watchTheme(onChange: () => void): () => void {
43
+ const observer = new MutationObserver(onChange);
44
+ for (const node of [document.documentElement, document.body]) {
45
+ if (node) {
46
+ observer.observe(node, {
47
+ attributes: true,
48
+ attributeFilter: ["class", "style", "data-theme"],
49
+ });
50
+ }
51
+ }
52
+ const queries = [
53
+ "(prefers-color-scheme: dark)",
54
+ "(forced-colors: active)",
55
+ ].map((query) => window.matchMedia(query));
56
+ for (const query of queries) {
57
+ query.addEventListener("change", onChange);
58
+ }
59
+ return () => {
60
+ observer.disconnect();
61
+ for (const query of queries) {
62
+ query.removeEventListener("change", onChange);
63
+ }
64
+ };
65
+ }