@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
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @voila.dev/chart
2
+
3
+ Charts as data. A chart is a definition — a list of marks over data — and
4
+ `<Chart>` draws it to SVG (default) or Canvas, with keyboard focus on every
5
+ value, a live region that reads the focused value, a legend that toggles
6
+ series, and the values as a hidden table.
7
+
8
+ ```tsx
9
+ import { barY, defineChart, lineY, ruleY } from "@voila.dev/chart";
10
+ import { Chart } from "@voila.dev/chart/react";
11
+
12
+ const definition = useMemo(
13
+ () =>
14
+ defineChart({
15
+ marks: [
16
+ barY(months, { x: "month", y: "missions", label: "Missions" }),
17
+ ruleY([40], { label: "Objectif", strokeDasharray: "4 4" }),
18
+ ],
19
+ }),
20
+ [months],
21
+ );
22
+
23
+ <Chart definition={definition} ariaLabel="Missions par mois" height={240} />;
24
+ ```
25
+
26
+ For thousands of marks, paint on a canvas; nothing else changes:
27
+
28
+ ```tsx
29
+ import { CanvasRenderer } from "@voila.dev/chart/canvas";
30
+
31
+ <Chart definition={definition} ariaLabel="…" renderer={CanvasRenderer} />;
32
+ ```
33
+
34
+ ## Pipeline
35
+
36
+ `compileChart(definition, { width, height })` is pure: marks declare
37
+ channels, the channels resolve the scales (linear, log, time, band, point,
38
+ ordinal and sequential colour), the axes are measured and laid out, and each
39
+ mark returns scene nodes and focusable points. Both renderers draw that scene;
40
+ hit-testing, keyboard focus, the tooltip and the data table read it.
41
+
42
+ ## Marks
43
+
44
+ Cartesian: `lineY` `lineX` `areaY` `areaX` `barY` `barX` `dot` `cell` `ruleY` `ruleX` `text`
45
+
46
+ Round and frame: `arc` `donut` `radialBar` `radar` `funnel`
47
+
48
+ Statistical (no d3): `histogram` `rectY` `boxY` `violinY` `ridgeline`
49
+ `dodgeY` `waffleY` `differenceY` `regressionY` `hexbin`
50
+
51
+ Behind their own subpath, with the d3 module they need:
52
+ `@voila.dev/chart/contour` (`density2d`, d3-contour),
53
+ `@voila.dev/chart/voronoi` (`voronoi`, d3-delaunay),
54
+ `@voila.dev/chart/hierarchy` (`treemap`, `sunburst`, `tree`, d3-hierarchy),
55
+ `@voila.dev/chart/sankey` (`sankey`, d3-sankey),
56
+ `@voila.dev/chart/force` (`forceGraph`, d3-force, seeded so it settles the same way every time),
57
+ `@voila.dev/chart/geo` (`projection`, `geoShape`, `geoDot`, d3-geo; RFC 7946 rings are rewound for you).
58
+
59
+ ## Keyboard
60
+
61
+ Tab reaches the chart. The arrows move along the values (a column of a
62
+ multi-series chart is one stop, the other arrows move inside it), Home and
63
+ End jump to the ends, Enter pins the tooltip and fires `onSelect`, Escape
64
+ unpins then leaves.
65
+
66
+ ## Small multiples, zoom and brush
67
+
68
+ `defineChart({ facet: facet({ values, marks: (value) => [...] }) })` draws the chart
69
+ once per value on a grid, every cell on the same x, y and colour domains.
70
+ The keyboard walks one cell at a time; the tooltip names the cell.
71
+
72
+ `<Chart zoom />` zooms a continuous x: the wheel while the chart has focus
73
+ (never while the page scrolls past it), + and −, Shift and the arrows to
74
+ pan, a drag to pan when zoomed, 0 or the reset button to go back. The live
75
+ region says what is on show.
76
+
77
+ `<Chart brush={brushX({ onBrush })} />` (from `@voila.dev/chart/brush`) selects a range of x by dragging, or
78
+ with two sliders from the keyboard (arrows, Page keys, Home, End, Escape).
79
+ The sliders sit outside the graphic, where assistive tech can reach them.
80
+
81
+ ## Size budget
82
+
83
+ `bun run check-bundle` builds each entry in `scripts/bundle-entries` and fails
84
+ past its gzip budget, or when the core or the React layer retains a `d3-*`
85
+ module.
@@ -0,0 +1,16 @@
1
+ import type * as React from "react";
2
+ import type { ChartPositionScale, ChartValue } from "../core/types.js";
3
+ import { type ChartBrushLayerProps, type ChartBrushRange } from "../react/brush-contract.js";
4
+ /** The pixel extent of a selection: whole bands on a band scale. */
5
+ export declare function brushPixels(scale: ChartPositionScale, [from, to]: ChartBrushRange): readonly [number, number];
6
+ /** One keyboard step along the scale: a category, or a fiftieth of the domain. */
7
+ export declare function stepValue(scale: ChartPositionScale, value: ChartValue, direction: 1 | -1, large: boolean): ChartValue;
8
+ /**
9
+ * The selection over the plot, and its two ends as sliders. The sliders sit
10
+ * outside the chart's graphic (whose children assistive tech does not see),
11
+ * so a keyboard or screen-reader user can set a range as well as a mouse
12
+ * can: arrows move an end, Page keys move it five steps, Home and End jump,
13
+ * Escape clears.
14
+ */
15
+ export declare function BrushLayer({ scale, plot, range, messages, onChange, }: ChartBrushLayerProps): React.JSX.Element;
16
+ //# sourceMappingURL=brush-layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brush-layer.d.ts","sourceRoot":"","sources":["../../src/brush/brush-layer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EAEpB,MAAM,2BAA2B,CAAC;AAUnC,oEAAoE;AACpE,wBAAgB,WAAW,CAC1B,KAAK,EAAE,kBAAkB,EACzB,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,eAAe,GACzB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAI3B;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CACxB,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,EACjB,KAAK,EAAE,OAAO,GACZ,UAAU,CAgBZ;AAID;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAC1B,KAAK,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,GACR,EAAE,oBAAoB,qBA4GtB"}
@@ -0,0 +1,137 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ ordered
4
+ } from "../react/brush-contract.js";
5
+ function isDiscrete(scale) {
6
+ return scale.kind === "band" || scale.kind === "point";
7
+ }
8
+ function numeric(value) {
9
+ return value instanceof Date ? value.getTime() : Number(value);
10
+ }
11
+ function brushPixels(scale, [from, to]) {
12
+ const left = Math.min(scale.map(from), scale.map(to));
13
+ const right = Math.max(scale.map(from), scale.map(to)) + scale.bandwidth;
14
+ return [left, right];
15
+ }
16
+ function stepValue(scale, value, direction, large) {
17
+ if (isDiscrete(scale)) {
18
+ const index = scale.domain.findIndex(
19
+ (candidate) => numeric(candidate) === numeric(value) || candidate === value
20
+ );
21
+ const next2 = Math.min(
22
+ scale.domain.length - 1,
23
+ Math.max(0, index + direction * (large ? 5 : 1))
24
+ );
25
+ return scale.domain[next2];
26
+ }
27
+ const [low, high] = scale.domain.map(numeric);
28
+ const step = (high - low) / 50 * (large ? 5 : 1);
29
+ const next = Math.min(high, Math.max(low, numeric(value) + direction * step));
30
+ return scale.kind === "time" ? new Date(next) : next;
31
+ }
32
+ const HANDLE_WIDTH = 8;
33
+ function BrushLayer({
34
+ scale,
35
+ plot,
36
+ range,
37
+ messages,
38
+ onChange
39
+ }) {
40
+ const ends = range ?? [
41
+ scale.domain[0],
42
+ scale.domain[scale.domain.length - 1]
43
+ ];
44
+ const [left, right] = range ? brushPixels(scale, range) : [plot.x, plot.x + plot.width];
45
+ function handleKey(which) {
46
+ return (event) => {
47
+ const current = ends[which];
48
+ let next;
49
+ if (event.key === "ArrowRight" || event.key === "ArrowUp")
50
+ next = stepValue(scale, current, 1, false);
51
+ if (event.key === "ArrowLeft" || event.key === "ArrowDown")
52
+ next = stepValue(scale, current, -1, false);
53
+ if (event.key === "PageUp") next = stepValue(scale, current, 1, true);
54
+ if (event.key === "PageDown") next = stepValue(scale, current, -1, true);
55
+ if (event.key === "Home") next = scale.domain[0];
56
+ if (event.key === "End") next = scale.domain[scale.domain.length - 1];
57
+ if (event.key === "Escape") {
58
+ event.preventDefault();
59
+ onChange(null);
60
+ return;
61
+ }
62
+ if (next === void 0) return;
63
+ event.preventDefault();
64
+ const updated = [ends[0], ends[1]];
65
+ updated[which] = next;
66
+ onChange(ordered(scale, updated));
67
+ };
68
+ }
69
+ const [low, high] = isDiscrete(scale) ? [0, scale.domain.length - 1] : scale.domain.map(numeric);
70
+ const position = (value) => isDiscrete(scale) ? scale.domain.findIndex(
71
+ (candidate) => candidate === value || numeric(candidate) === numeric(value)
72
+ ) : numeric(value);
73
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
74
+ range ? /* @__PURE__ */ jsx(
75
+ "div",
76
+ {
77
+ "aria-hidden": "true",
78
+ "data-slot": "chart-brush-selection",
79
+ style: {
80
+ position: "absolute",
81
+ left,
82
+ top: plot.y,
83
+ width: Math.max(0, right - left),
84
+ height: plot.height,
85
+ background: "color-mix(in oklab, currentColor 10%, transparent)",
86
+ borderInline: "1px solid color-mix(in oklab, currentColor 40%, transparent)",
87
+ pointerEvents: "none"
88
+ }
89
+ }
90
+ ) : null,
91
+ [0, 1].map((which) => /* @__PURE__ */ jsx(
92
+ "div",
93
+ {
94
+ role: "slider",
95
+ tabIndex: 0,
96
+ "aria-label": which === 0 ? messages.brushStart : messages.brushEnd,
97
+ "aria-valuemin": low,
98
+ "aria-valuemax": high,
99
+ "aria-valuenow": position(ends[which]),
100
+ "aria-valuetext": scale.format(ends[which]),
101
+ "aria-orientation": "horizontal",
102
+ "data-slot": "chart-brush-handle",
103
+ onKeyDown: handleKey(which),
104
+ onPointerDown: (event) => event.currentTarget.setPointerCapture(event.pointerId),
105
+ onPointerMove: (event) => {
106
+ if (!event.currentTarget.hasPointerCapture(event.pointerId)) return;
107
+ const frame = event.currentTarget.parentElement?.getBoundingClientRect();
108
+ if (frame === void 0) return;
109
+ const updated = [ends[0], ends[1]];
110
+ updated[which] = scale.invert(event.clientX - frame.left);
111
+ onChange(ordered(scale, updated));
112
+ },
113
+ style: {
114
+ position: "absolute",
115
+ left: (which === 0 ? left : right) - HANDLE_WIDTH / 2,
116
+ top: plot.y + plot.height / 2 - 14,
117
+ width: HANDLE_WIDTH,
118
+ height: 28,
119
+ borderRadius: 4,
120
+ background: "var(--background, Canvas)",
121
+ border: "1px solid color-mix(in oklab, currentColor 50%, transparent)",
122
+ cursor: "ew-resize",
123
+ userSelect: "none",
124
+ WebkitUserSelect: "none",
125
+ opacity: range ? 1 : 0.5
126
+ }
127
+ },
128
+ which
129
+ ))
130
+ ] });
131
+ }
132
+ export {
133
+ BrushLayer,
134
+ brushPixels,
135
+ stepValue
136
+ };
137
+ //# sourceMappingURL=brush-layer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/brush/brush-layer.tsx"],
4
+ "sourcesContent": ["import type * as React from \"react\";\nimport type { ChartPositionScale, ChartValue } from \"#/core/types.ts\";\nimport {\n\ttype ChartBrushLayerProps,\n\ttype ChartBrushRange,\n\tordered,\n} from \"#/react/brush-contract.ts\";\n\nfunction isDiscrete(scale: ChartPositionScale): boolean {\n\treturn scale.kind === \"band\" || scale.kind === \"point\";\n}\n\nfunction numeric(value: ChartValue): number {\n\treturn value instanceof Date ? value.getTime() : Number(value);\n}\n\n/** The pixel extent of a selection: whole bands on a band scale. */\nexport function brushPixels(\n\tscale: ChartPositionScale,\n\t[from, to]: ChartBrushRange,\n): readonly [number, number] {\n\tconst left = Math.min(scale.map(from), scale.map(to));\n\tconst right = Math.max(scale.map(from), scale.map(to)) + scale.bandwidth;\n\treturn [left, right];\n}\n\n/** One keyboard step along the scale: a category, or a fiftieth of the domain. */\nexport function stepValue(\n\tscale: ChartPositionScale,\n\tvalue: ChartValue,\n\tdirection: 1 | -1,\n\tlarge: boolean,\n): ChartValue {\n\tif (isDiscrete(scale)) {\n\t\tconst index = scale.domain.findIndex(\n\t\t\t(candidate) =>\n\t\t\t\tnumeric(candidate) === numeric(value) || candidate === value,\n\t\t);\n\t\tconst next = Math.min(\n\t\t\tscale.domain.length - 1,\n\t\t\tMath.max(0, index + direction * (large ? 5 : 1)),\n\t\t);\n\t\treturn scale.domain[next];\n\t}\n\tconst [low, high] = scale.domain.map(numeric);\n\tconst step = ((high - low) / 50) * (large ? 5 : 1);\n\tconst next = Math.min(high, Math.max(low, numeric(value) + direction * step));\n\treturn scale.kind === \"time\" ? new Date(next) : next;\n}\n\nconst HANDLE_WIDTH = 8;\n\n/**\n * The selection over the plot, and its two ends as sliders. The sliders sit\n * outside the chart's graphic (whose children assistive tech does not see),\n * so a keyboard or screen-reader user can set a range as well as a mouse\n * can: arrows move an end, Page keys move it five steps, Home and End jump,\n * Escape clears.\n */\nexport function BrushLayer({\n\tscale,\n\tplot,\n\trange,\n\tmessages,\n\tonChange,\n}: ChartBrushLayerProps) {\n\tconst ends = range ?? [\n\t\tscale.domain[0],\n\t\tscale.domain[scale.domain.length - 1],\n\t];\n\tconst [left, right] = range\n\t\t? brushPixels(scale, range)\n\t\t: [plot.x, plot.x + plot.width];\n\n\tfunction handleKey(which: 0 | 1) {\n\t\treturn (event: React.KeyboardEvent<HTMLDivElement>) => {\n\t\t\tconst current = ends[which];\n\t\t\tlet next: ChartValue | undefined;\n\t\t\tif (event.key === \"ArrowRight\" || event.key === \"ArrowUp\")\n\t\t\t\tnext = stepValue(scale, current, 1, false);\n\t\t\tif (event.key === \"ArrowLeft\" || event.key === \"ArrowDown\")\n\t\t\t\tnext = stepValue(scale, current, -1, false);\n\t\t\tif (event.key === \"PageUp\") next = stepValue(scale, current, 1, true);\n\t\t\tif (event.key === \"PageDown\") next = stepValue(scale, current, -1, true);\n\t\t\tif (event.key === \"Home\") next = scale.domain[0];\n\t\t\tif (event.key === \"End\") next = scale.domain[scale.domain.length - 1];\n\t\t\tif (event.key === \"Escape\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tonChange(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (next === undefined) return;\n\t\t\tevent.preventDefault();\n\t\t\tconst updated: [ChartValue, ChartValue] = [ends[0], ends[1]];\n\t\t\tupdated[which] = next;\n\t\t\tonChange(ordered(scale, updated));\n\t\t};\n\t}\n\n\tconst [low, high] = isDiscrete(scale)\n\t\t? [0, scale.domain.length - 1]\n\t\t: scale.domain.map(numeric);\n\tconst position = (value: ChartValue) =>\n\t\tisDiscrete(scale)\n\t\t\t? scale.domain.findIndex(\n\t\t\t\t\t(candidate) =>\n\t\t\t\t\t\tcandidate === value || numeric(candidate) === numeric(value),\n\t\t\t\t)\n\t\t\t: numeric(value);\n\n\treturn (\n\t\t<>\n\t\t\t{range ? (\n\t\t\t\t<div\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\tdata-slot=\"chart-brush-selection\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tleft,\n\t\t\t\t\t\ttop: plot.y,\n\t\t\t\t\t\twidth: Math.max(0, right - left),\n\t\t\t\t\t\theight: plot.height,\n\t\t\t\t\t\tbackground: \"color-mix(in oklab, currentColor 10%, transparent)\",\n\t\t\t\t\t\tborderInline:\n\t\t\t\t\t\t\t\"1px solid color-mix(in oklab, currentColor 40%, transparent)\",\n\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t{([0, 1] as const).map((which) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={which}\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\taria-label={which === 0 ? messages.brushStart : messages.brushEnd}\n\t\t\t\t\taria-valuemin={low}\n\t\t\t\t\taria-valuemax={high}\n\t\t\t\t\taria-valuenow={position(ends[which])}\n\t\t\t\t\taria-valuetext={scale.format(ends[which])}\n\t\t\t\t\taria-orientation=\"horizontal\"\n\t\t\t\t\tdata-slot=\"chart-brush-handle\"\n\t\t\t\t\tonKeyDown={handleKey(which)}\n\t\t\t\t\tonPointerDown={(event) =>\n\t\t\t\t\t\tevent.currentTarget.setPointerCapture(event.pointerId)\n\t\t\t\t\t}\n\t\t\t\t\tonPointerMove={(event) => {\n\t\t\t\t\t\tif (!event.currentTarget.hasPointerCapture(event.pointerId)) return;\n\t\t\t\t\t\tconst frame =\n\t\t\t\t\t\t\tevent.currentTarget.parentElement?.getBoundingClientRect();\n\t\t\t\t\t\tif (frame === undefined) return;\n\t\t\t\t\t\tconst updated: [ChartValue, ChartValue] = [ends[0], ends[1]];\n\t\t\t\t\t\tupdated[which] = scale.invert(event.clientX - frame.left);\n\t\t\t\t\t\tonChange(ordered(scale, updated));\n\t\t\t\t\t}}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tleft: (which === 0 ? left : right) - HANDLE_WIDTH / 2,\n\t\t\t\t\t\ttop: plot.y + plot.height / 2 - 14,\n\t\t\t\t\t\twidth: HANDLE_WIDTH,\n\t\t\t\t\t\theight: 28,\n\t\t\t\t\t\tborderRadius: 4,\n\t\t\t\t\t\tbackground: \"var(--background, Canvas)\",\n\t\t\t\t\t\tborder:\n\t\t\t\t\t\t\t\"1px solid color-mix(in oklab, currentColor 50%, transparent)\",\n\t\t\t\t\t\tcursor: \"ew-resize\",\n\t\t\t\t\t\tuserSelect: \"none\",\n\t\t\t\t\t\tWebkitUserSelect: \"none\",\n\t\t\t\t\t\topacity: range ? 1 : 0.5,\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["next"]
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { ChartBrush, ChartBrushRange } from "../react/brush-contract.js";
2
+ export type { ChartBrush, ChartBrushRange } from "../react/brush-contract.js";
3
+ /**
4
+ * Select a range of x by dragging, or with two sliders from the keyboard.
5
+ * Pass the result as `<Chart brush={…} />`.
6
+ */
7
+ export declare function brushX(options: {
8
+ readonly onBrush: (range: ChartBrushRange | null) => void;
9
+ }): ChartBrush;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/brush/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE7E,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE7E;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE;IAC/B,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;CAC1D,GAAG,UAAU,CAEb"}
@@ -0,0 +1,8 @@
1
+ import { BrushLayer } from "./brush-layer.js";
2
+ function brushX(options) {
3
+ return { onBrush: options.onBrush, Layer: BrushLayer };
4
+ }
5
+ export {
6
+ brushX
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/brush/index.ts"],
4
+ "sourcesContent": ["import { BrushLayer } from \"#/brush/brush-layer.tsx\";\nimport type { ChartBrush, ChartBrushRange } from \"#/react/brush-contract.ts\";\n\nexport type { ChartBrush, ChartBrushRange } from \"#/react/brush-contract.ts\";\n\n/**\n * Select a range of x by dragging, or with two sliders from the keyboard.\n * Pass the result as `<Chart brush={\u2026} />`.\n */\nexport function brushX(options: {\n\treadonly onBrush: (range: ChartBrushRange | null) => void;\n}): ChartBrush {\n\treturn { onBrush: options.onBrush, Layer: BrushLayer };\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AASpB,SAAS,OAAO,SAER;AACd,SAAO,EAAE,SAAS,QAAQ,SAAS,OAAO,WAAW;AACtD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import type { ChartRendererProps } from "../react/renderer.js";
3
+ /**
4
+ * Paints the scene onto a canvas: the renderer for thousands of marks, where
5
+ * one DOM node per mark gets slow. Hit-testing, focus, the tooltip, the legend
6
+ * and the data table do not change, they read the same scene. On the server
7
+ * the canvas is empty and the data table carries the content until the first
8
+ * paint.
9
+ */
10
+ export declare function CanvasRenderer({ scene }: ChartRendererProps): React.JSX.Element;
11
+ //# sourceMappingURL=canvas-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-renderer.d.ts","sourceRoot":"","sources":["../../src/canvas/canvas-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,kBAAkB,qBA2C3D"}
@@ -0,0 +1,49 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { paintScene } from "./paint-scene.js";
4
+ import { createColorResolver, watchTheme } from "./resolve-color.js";
5
+ function CanvasRenderer({ scene }) {
6
+ const canvasRef = React.useRef(null);
7
+ const [themeVersion, setThemeVersion] = React.useState(0);
8
+ React.useEffect(
9
+ () => watchTheme(() => setThemeVersion((version) => version + 1)),
10
+ []
11
+ );
12
+ React.useLayoutEffect(() => {
13
+ const canvas = canvasRef.current;
14
+ const host = canvas?.parentElement;
15
+ const context = canvas?.getContext("2d");
16
+ if (!canvas || !host || !context) {
17
+ return;
18
+ }
19
+ const ratio = window.devicePixelRatio || 1;
20
+ canvas.width = Math.round(scene.width * ratio);
21
+ canvas.height = Math.round(scene.height * ratio);
22
+ context.setTransform(ratio, 0, 0, ratio, 0, 0);
23
+ const colors = createColorResolver(host);
24
+ paintScene(
25
+ context,
26
+ scene,
27
+ colors.resolve,
28
+ getComputedStyle(host).fontFamily || "sans-serif"
29
+ );
30
+ colors.dispose();
31
+ }, [scene, themeVersion]);
32
+ return /* @__PURE__ */ jsx(
33
+ "canvas",
34
+ {
35
+ ref: canvasRef,
36
+ "data-slot": "chart-canvas",
37
+ style: {
38
+ position: "absolute",
39
+ inset: 0,
40
+ width: scene.width,
41
+ height: scene.height
42
+ }
43
+ }
44
+ );
45
+ }
46
+ export {
47
+ CanvasRenderer
48
+ };
49
+ //# sourceMappingURL=canvas-renderer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/canvas/canvas-renderer.tsx"],
4
+ "sourcesContent": ["import * as React from \"react\";\nimport { paintScene } from \"#/canvas/paint-scene.ts\";\nimport { createColorResolver, watchTheme } from \"#/canvas/resolve-color.ts\";\nimport type { ChartRendererProps } from \"#/react/renderer.ts\";\n\n/**\n * Paints the scene onto a canvas: the renderer for thousands of marks, where\n * one DOM node per mark gets slow. Hit-testing, focus, the tooltip, the legend\n * and the data table do not change, they read the same scene. On the server\n * the canvas is empty and the data table carries the content until the first\n * paint.\n */\nexport function CanvasRenderer({ scene }: ChartRendererProps) {\n\tconst canvasRef = React.useRef<HTMLCanvasElement>(null);\n\tconst [themeVersion, setThemeVersion] = React.useState(0);\n\n\tReact.useEffect(\n\t\t() => watchTheme(() => setThemeVersion((version) => version + 1)),\n\t\t[],\n\t);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: a theme change repaints with fresh colours\n\tReact.useLayoutEffect(() => {\n\t\tconst canvas = canvasRef.current;\n\t\tconst host = canvas?.parentElement;\n\t\tconst context = canvas?.getContext(\"2d\");\n\t\tif (!canvas || !host || !context) {\n\t\t\treturn;\n\t\t}\n\t\tconst ratio = window.devicePixelRatio || 1;\n\t\tcanvas.width = Math.round(scene.width * ratio);\n\t\tcanvas.height = Math.round(scene.height * ratio);\n\t\tcontext.setTransform(ratio, 0, 0, ratio, 0, 0);\n\t\tconst colors = createColorResolver(host);\n\t\tpaintScene(\n\t\t\tcontext,\n\t\t\tscene,\n\t\t\tcolors.resolve,\n\t\t\tgetComputedStyle(host).fontFamily || \"sans-serif\",\n\t\t);\n\t\tcolors.dispose();\n\t}, [scene, themeVersion]);\n\n\treturn (\n\t\t<canvas\n\t\t\tref={canvasRef}\n\t\t\tdata-slot=\"chart-canvas\"\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tinset: 0,\n\t\t\t\twidth: scene.width,\n\t\t\t\theight: scene.height,\n\t\t\t}}\n\t\t/>\n\t);\n}\n"],
5
+ "mappings": "AA4CE;AA5CF,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB,kBAAkB;AAUzC,SAAS,eAAe,EAAE,MAAM,GAAuB;AAC7D,QAAM,YAAY,MAAM,OAA0B,IAAI;AACtD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,CAAC;AAExD,QAAM;AAAA,IACL,MAAM,WAAW,MAAM,gBAAgB,CAAC,YAAY,UAAU,CAAC,CAAC;AAAA,IAChE,CAAC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM;AAC3B,UAAM,SAAS,UAAU;AACzB,UAAM,OAAO,QAAQ;AACrB,UAAM,UAAU,QAAQ,WAAW,IAAI;AACvC,QAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS;AACjC;AAAA,IACD;AACA,UAAM,QAAQ,OAAO,oBAAoB;AACzC,WAAO,QAAQ,KAAK,MAAM,MAAM,QAAQ,KAAK;AAC7C,WAAO,SAAS,KAAK,MAAM,MAAM,SAAS,KAAK;AAC/C,YAAQ,aAAa,OAAO,GAAG,GAAG,OAAO,GAAG,CAAC;AAC7C,UAAM,SAAS,oBAAoB,IAAI;AACvC;AAAA,MACC;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,iBAAiB,IAAI,EAAE,cAAc;AAAA,IACtC;AACA,WAAO,QAAQ;AAAA,EAChB,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,SACC;AAAA,IAAC;AAAA;AAAA,MACA,KAAK;AAAA,MACL,aAAU;AAAA,MACV,OAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO;AAAA,QACP,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM;AAAA,MACf;AAAA;AAAA,EACD;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export { CanvasRenderer } from "./canvas-renderer.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { CanvasRenderer } from "./canvas-renderer.js";
2
+ export {
3
+ CanvasRenderer
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/canvas/index.ts"],
4
+ "sourcesContent": ["export { CanvasRenderer } from \"#/canvas/canvas-renderer.tsx\";\n"],
5
+ "mappings": "AAAA,SAAS,sBAAsB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,4 @@
1
+ import type { ChartScene } from "../core/types.js";
2
+ /** Paints the whole scene. The canvas is already scaled to device pixels. */
3
+ export declare function paintScene(context: CanvasRenderingContext2D, scene: ChartScene, resolve: (color: string) => string, fontFamily: string): void;
4
+ //# sourceMappingURL=paint-scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint-scene.d.ts","sourceRoot":"","sources":["../../src/canvas/paint-scene.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,UAAU,EAAa,MAAM,iBAAiB,CAAC;AA0IzE,6EAA6E;AAC7E,wBAAgB,UAAU,CACzB,OAAO,EAAE,wBAAwB,EACjC,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAClC,UAAU,EAAE,MAAM,QAOlB"}
@@ -0,0 +1,130 @@
1
+ import { roundedBarPath } from "../core/paths.js";
2
+ const CANVAS_BASELINE = {
3
+ top: "top",
4
+ middle: "middle",
5
+ bottom: "bottom",
6
+ alphabetic: "alphabetic"
7
+ };
8
+ const CANVAS_ALIGN = {
9
+ start: "left",
10
+ middle: "center",
11
+ end: "right"
12
+ };
13
+ function dashes(dasharray) {
14
+ if (!dasharray) {
15
+ return [];
16
+ }
17
+ return dasharray.split(/[\s,]+/).map(Number).filter((value) => Number.isFinite(value));
18
+ }
19
+ function fillAndStroke(environment, paint, draw) {
20
+ const { context, resolve } = environment;
21
+ const opacity = paint.opacity ?? 1;
22
+ if (paint.fill && paint.fill !== "none") {
23
+ context.globalAlpha = opacity * (paint.fillOpacity ?? 1);
24
+ context.fillStyle = resolve(paint.fill);
25
+ draw("fill");
26
+ }
27
+ if (paint.stroke && paint.stroke !== "none") {
28
+ context.globalAlpha = opacity * (paint.strokeOpacity ?? 1);
29
+ context.strokeStyle = resolve(paint.stroke);
30
+ context.lineWidth = paint.strokeWidth ?? 1;
31
+ context.lineCap = paint.strokeLinecap ?? "butt";
32
+ context.lineJoin = paint.strokeLinejoin ?? "miter";
33
+ context.setLineDash(dashes(paint.strokeDasharray));
34
+ draw("stroke");
35
+ context.setLineDash([]);
36
+ }
37
+ context.globalAlpha = 1;
38
+ }
39
+ function paintNode(environment, node) {
40
+ const { context } = environment;
41
+ switch (node.kind) {
42
+ case "group":
43
+ context.save();
44
+ if (node.translate) {
45
+ context.translate(node.translate.x, node.translate.y);
46
+ }
47
+ if (node.clip) {
48
+ context.beginPath();
49
+ context.rect(
50
+ node.clip.x,
51
+ node.clip.y,
52
+ node.clip.width,
53
+ node.clip.height
54
+ );
55
+ context.clip();
56
+ }
57
+ for (const child of node.children) {
58
+ paintNode(environment, child);
59
+ }
60
+ context.restore();
61
+ return;
62
+ case "rect": {
63
+ const path = node.corners ? new Path2D(roundedBarPath({ ...node, radius: node.corners })) : void 0;
64
+ fillAndStroke(environment, node.paint, (mode) => {
65
+ if (path) {
66
+ context[mode](path);
67
+ } else if (mode === "fill") {
68
+ context.fillRect(node.x, node.y, node.width, node.height);
69
+ } else {
70
+ context.strokeRect(node.x, node.y, node.width, node.height);
71
+ }
72
+ });
73
+ return;
74
+ }
75
+ case "path": {
76
+ if (node.d === "") {
77
+ return;
78
+ }
79
+ const path = new Path2D(node.d);
80
+ fillAndStroke(environment, node.paint, (mode) => context[mode](path));
81
+ return;
82
+ }
83
+ case "circle": {
84
+ const path = new Path2D();
85
+ path.arc(node.cx, node.cy, node.r, 0, Math.PI * 2);
86
+ fillAndStroke(environment, node.paint, (mode) => context[mode](path));
87
+ return;
88
+ }
89
+ case "line": {
90
+ const path = new Path2D();
91
+ path.moveTo(node.x1, node.y1);
92
+ path.lineTo(node.x2, node.y2);
93
+ fillAndStroke(
94
+ environment,
95
+ { ...node.paint, fill: void 0 },
96
+ () => context.stroke(path)
97
+ );
98
+ return;
99
+ }
100
+ case "text": {
101
+ const { paint } = node;
102
+ context.save();
103
+ context.translate(node.x, node.y);
104
+ if (node.rotate) {
105
+ context.rotate(node.rotate * Math.PI / 180);
106
+ }
107
+ context.font = `${paint.fontWeight ?? 400} ${paint.fontSize ?? 12}px ${environment.fontFamily}`;
108
+ context.textAlign = CANVAS_ALIGN[paint.textAnchor ?? "start"];
109
+ context.textBaseline = CANVAS_BASELINE[paint.baseline ?? "alphabetic"];
110
+ fillAndStroke(
111
+ environment,
112
+ paint,
113
+ (mode) => mode === "fill" ? context.fillText(node.text, 0, 0) : context.strokeText(node.text, 0, 0)
114
+ );
115
+ context.restore();
116
+ return;
117
+ }
118
+ }
119
+ }
120
+ function paintScene(context, scene, resolve, fontFamily) {
121
+ context.clearRect(0, 0, scene.width, scene.height);
122
+ const environment = { context, resolve, fontFamily };
123
+ for (const node of scene.nodes) {
124
+ paintNode(environment, node);
125
+ }
126
+ }
127
+ export {
128
+ paintScene
129
+ };
130
+ //# sourceMappingURL=paint-scene.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/canvas/paint-scene.ts"],
4
+ "sourcesContent": ["import { roundedBarPath } from \"#/core/paths.ts\";\nimport type { ChartPaint, ChartScene, SceneNode } from \"#/core/types.ts\";\n\nconst CANVAS_BASELINE = {\n\ttop: \"top\",\n\tmiddle: \"middle\",\n\tbottom: \"bottom\",\n\talphabetic: \"alphabetic\",\n} as const satisfies Record<string, CanvasTextBaseline>;\n\nconst CANVAS_ALIGN = {\n\tstart: \"left\",\n\tmiddle: \"center\",\n\tend: \"right\",\n} as const satisfies Record<string, CanvasTextAlign>;\n\nfunction dashes(dasharray: string | undefined): number[] {\n\tif (!dasharray) {\n\t\treturn [];\n\t}\n\treturn dasharray\n\t\t.split(/[\\s,]+/)\n\t\t.map(Number)\n\t\t.filter((value) => Number.isFinite(value));\n}\n\ninterface PaintEnvironment {\n\treadonly context: CanvasRenderingContext2D;\n\treadonly resolve: (color: string) => string;\n\treadonly fontFamily: string;\n}\n\nfunction fillAndStroke(\n\tenvironment: PaintEnvironment,\n\tpaint: ChartPaint,\n\tdraw: (mode: \"fill\" | \"stroke\") => void,\n) {\n\tconst { context, resolve } = environment;\n\tconst opacity = paint.opacity ?? 1;\n\tif (paint.fill && paint.fill !== \"none\") {\n\t\tcontext.globalAlpha = opacity * (paint.fillOpacity ?? 1);\n\t\tcontext.fillStyle = resolve(paint.fill);\n\t\tdraw(\"fill\");\n\t}\n\tif (paint.stroke && paint.stroke !== \"none\") {\n\t\tcontext.globalAlpha = opacity * (paint.strokeOpacity ?? 1);\n\t\tcontext.strokeStyle = resolve(paint.stroke);\n\t\tcontext.lineWidth = paint.strokeWidth ?? 1;\n\t\tcontext.lineCap = paint.strokeLinecap ?? \"butt\";\n\t\tcontext.lineJoin = paint.strokeLinejoin ?? \"miter\";\n\t\tcontext.setLineDash(dashes(paint.strokeDasharray));\n\t\tdraw(\"stroke\");\n\t\tcontext.setLineDash([]);\n\t}\n\tcontext.globalAlpha = 1;\n}\n\nfunction paintNode(environment: PaintEnvironment, node: SceneNode) {\n\tconst { context } = environment;\n\tswitch (node.kind) {\n\t\tcase \"group\":\n\t\t\tcontext.save();\n\t\t\tif (node.translate) {\n\t\t\t\tcontext.translate(node.translate.x, node.translate.y);\n\t\t\t}\n\t\t\tif (node.clip) {\n\t\t\t\tcontext.beginPath();\n\t\t\t\tcontext.rect(\n\t\t\t\t\tnode.clip.x,\n\t\t\t\t\tnode.clip.y,\n\t\t\t\t\tnode.clip.width,\n\t\t\t\t\tnode.clip.height,\n\t\t\t\t);\n\t\t\t\tcontext.clip();\n\t\t\t}\n\t\t\tfor (const child of node.children) {\n\t\t\t\tpaintNode(environment, child);\n\t\t\t}\n\t\t\tcontext.restore();\n\t\t\treturn;\n\t\tcase \"rect\": {\n\t\t\tconst path = node.corners\n\t\t\t\t? new Path2D(roundedBarPath({ ...node, radius: node.corners }))\n\t\t\t\t: undefined;\n\t\t\tfillAndStroke(environment, node.paint, (mode) => {\n\t\t\t\tif (path) {\n\t\t\t\t\tcontext[mode](path);\n\t\t\t\t} else if (mode === \"fill\") {\n\t\t\t\t\tcontext.fillRect(node.x, node.y, node.width, node.height);\n\t\t\t\t} else {\n\t\t\t\t\tcontext.strokeRect(node.x, node.y, node.width, node.height);\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tcase \"path\": {\n\t\t\tif (node.d === \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst path = new Path2D(node.d);\n\t\t\tfillAndStroke(environment, node.paint, (mode) => context[mode](path));\n\t\t\treturn;\n\t\t}\n\t\tcase \"circle\": {\n\t\t\tconst path = new Path2D();\n\t\t\tpath.arc(node.cx, node.cy, node.r, 0, Math.PI * 2);\n\t\t\tfillAndStroke(environment, node.paint, (mode) => context[mode](path));\n\t\t\treturn;\n\t\t}\n\t\tcase \"line\": {\n\t\t\tconst path = new Path2D();\n\t\t\tpath.moveTo(node.x1, node.y1);\n\t\t\tpath.lineTo(node.x2, node.y2);\n\t\t\tfillAndStroke(environment, { ...node.paint, fill: undefined }, () =>\n\t\t\t\tcontext.stroke(path),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tcase \"text\": {\n\t\t\tconst { paint } = node;\n\t\t\tcontext.save();\n\t\t\tcontext.translate(node.x, node.y);\n\t\t\tif (node.rotate) {\n\t\t\t\tcontext.rotate((node.rotate * Math.PI) / 180);\n\t\t\t}\n\t\t\tcontext.font = `${paint.fontWeight ?? 400} ${paint.fontSize ?? 12}px ${environment.fontFamily}`;\n\t\t\tcontext.textAlign = CANVAS_ALIGN[paint.textAnchor ?? \"start\"];\n\t\t\tcontext.textBaseline = CANVAS_BASELINE[paint.baseline ?? \"alphabetic\"];\n\t\t\tfillAndStroke(environment, paint, (mode) =>\n\t\t\t\tmode === \"fill\"\n\t\t\t\t\t? context.fillText(node.text, 0, 0)\n\t\t\t\t\t: context.strokeText(node.text, 0, 0),\n\t\t\t);\n\t\t\tcontext.restore();\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/** Paints the whole scene. The canvas is already scaled to device pixels. */\nexport function paintScene(\n\tcontext: CanvasRenderingContext2D,\n\tscene: ChartScene,\n\tresolve: (color: string) => string,\n\tfontFamily: string,\n) {\n\tcontext.clearRect(0, 0, scene.width, scene.height);\n\tconst environment = { context, resolve, fontFamily };\n\tfor (const node of scene.nodes) {\n\t\tpaintNode(environment, node);\n\t}\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
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
+ export declare function createColorResolver(host: HTMLElement): ColorResolver;
12
+ /**
13
+ * Calls `onChange` whenever the colours a chart resolved may be stale: a
14
+ * class, style or theme attribute change on the root, a colour-scheme or
15
+ * forced-colours switch.
16
+ */
17
+ export declare function watchTheme(onChange: () => void): () => void;
18
+ //# sourceMappingURL=resolve-color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-color.d.ts","sourceRoot":"","sources":["../../src/canvas/resolve-color.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAuBpE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAuB3D"}
@@ -0,0 +1,52 @@
1
+ function createColorResolver(host) {
2
+ const probe = document.createElement("span");
3
+ probe.setAttribute("aria-hidden", "true");
4
+ probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;visibility:hidden";
5
+ host.append(probe);
6
+ const cache = /* @__PURE__ */ new Map();
7
+ return {
8
+ resolve(color) {
9
+ const cached = cache.get(color);
10
+ if (cached !== void 0) {
11
+ return cached;
12
+ }
13
+ probe.style.color = "";
14
+ probe.style.color = color;
15
+ const resolved = getComputedStyle(probe).color || color;
16
+ cache.set(color, resolved);
17
+ return resolved;
18
+ },
19
+ dispose() {
20
+ probe.remove();
21
+ }
22
+ };
23
+ }
24
+ function watchTheme(onChange) {
25
+ const observer = new MutationObserver(onChange);
26
+ for (const node of [document.documentElement, document.body]) {
27
+ if (node) {
28
+ observer.observe(node, {
29
+ attributes: true,
30
+ attributeFilter: ["class", "style", "data-theme"]
31
+ });
32
+ }
33
+ }
34
+ const queries = [
35
+ "(prefers-color-scheme: dark)",
36
+ "(forced-colors: active)"
37
+ ].map((query) => window.matchMedia(query));
38
+ for (const query of queries) {
39
+ query.addEventListener("change", onChange);
40
+ }
41
+ return () => {
42
+ observer.disconnect();
43
+ for (const query of queries) {
44
+ query.removeEventListener("change", onChange);
45
+ }
46
+ };
47
+ }
48
+ export {
49
+ createColorResolver,
50
+ watchTheme
51
+ };
52
+ //# sourceMappingURL=resolve-color.js.map