@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,164 @@
1
+ import type * as React from "react";
2
+ import { arcPath, roundedBarPath } from "#/core/paths.ts";
3
+ import type { ChartPoint, ChartScene, ChartTheme } from "#/core/types.ts";
4
+ import type { ActiveFocus } from "#/react/use-chart-focus.ts";
5
+
6
+ const RING_RADIUS = 4.5;
7
+ const RING_OUTSET = 2;
8
+
9
+ function PointRing({
10
+ point,
11
+ keyboard,
12
+ theme,
13
+ }: {
14
+ point: ChartPoint;
15
+ keyboard: boolean;
16
+ theme: ChartTheme;
17
+ }) {
18
+ const ring = keyboard ? "var(--ring, Highlight)" : theme.background;
19
+ if (point.hit?.kind === "rect" && point.hit.rect.width > RING_RADIUS * 4) {
20
+ if (!keyboard) {
21
+ return null;
22
+ }
23
+ const { rect } = point.hit;
24
+ return (
25
+ <path
26
+ data-slot="chart-focus-ring"
27
+ d={roundedBarPath({
28
+ x: rect.x - RING_OUTSET,
29
+ y: rect.y - RING_OUTSET,
30
+ width: rect.width + RING_OUTSET * 2,
31
+ height: rect.height + RING_OUTSET * 2,
32
+ radius: 4,
33
+ })}
34
+ fill="none"
35
+ stroke={ring}
36
+ strokeWidth={2}
37
+ />
38
+ );
39
+ }
40
+ if (point.hit?.kind === "arc") {
41
+ return (
42
+ <path
43
+ data-slot="chart-focus-ring"
44
+ d={arcPath({
45
+ ...point.hit,
46
+ outerRadius: point.hit.outerRadius + RING_OUTSET,
47
+ })}
48
+ fill="none"
49
+ stroke={keyboard ? ring : theme.foreground}
50
+ strokeWidth={2}
51
+ />
52
+ );
53
+ }
54
+ return (
55
+ <circle
56
+ data-slot="chart-focus-ring"
57
+ cx={point.x}
58
+ cy={point.y}
59
+ r={RING_RADIUS}
60
+ fill={point.color}
61
+ stroke={ring}
62
+ strokeWidth={2}
63
+ />
64
+ );
65
+ }
66
+
67
+ /**
68
+ * The focus layer, drawn over the marks whichever renderer drew them: the
69
+ * column or row band under the pointer, and a ring on the focused point.
70
+ * Hidden from assistive tech: the live region says what it shows.
71
+ */
72
+ export function FocusOverlay({
73
+ scene,
74
+ active,
75
+ theme,
76
+ }: {
77
+ scene: ChartScene;
78
+ active: ActiveFocus | null;
79
+ theme: ChartTheme;
80
+ }) {
81
+ if (active === null) {
82
+ return null;
83
+ }
84
+ const { focusOrder, scales } = scene;
85
+ // On a faceted chart the cursor spans the focused point's own cell.
86
+ const plot =
87
+ scene.cells?.find((cell) => cell.key === active.point.facet)?.plot ??
88
+ scene.plot;
89
+ const keyboard = active.source === "keyboard";
90
+ const along = focusOrder === "y" ? scales.y : scales.x;
91
+ const band = along?.kind === "band" ? along.bandwidth : 0;
92
+ let cursor: React.ReactNode = null;
93
+ if (focusOrder !== "point") {
94
+ cursor =
95
+ band > 0 ? (
96
+ <rect
97
+ data-slot="chart-cursor"
98
+ {...(focusOrder === "y"
99
+ ? {
100
+ x: plot.x,
101
+ y: active.stop.at - band / 2,
102
+ width: plot.width,
103
+ height: band,
104
+ }
105
+ : {
106
+ x: active.stop.at - band / 2,
107
+ y: plot.y,
108
+ width: band,
109
+ height: plot.height,
110
+ })}
111
+ fill="currentColor"
112
+ opacity={0.06}
113
+ />
114
+ ) : (
115
+ <line
116
+ data-slot="chart-cursor"
117
+ {...(focusOrder === "y"
118
+ ? {
119
+ x1: plot.x,
120
+ x2: plot.x + plot.width,
121
+ y1: active.stop.at,
122
+ y2: active.stop.at,
123
+ }
124
+ : {
125
+ x1: active.stop.at,
126
+ x2: active.stop.at,
127
+ y1: plot.y,
128
+ y2: plot.y + plot.height,
129
+ })}
130
+ stroke={theme.muted}
131
+ strokeWidth={1}
132
+ strokeDasharray="3 3"
133
+ />
134
+ );
135
+ }
136
+ const ringed =
137
+ focusOrder === "point" || (keyboard && active.stop.points.length > 1)
138
+ ? [active.point]
139
+ : active.stop.points;
140
+ return (
141
+ <svg
142
+ aria-hidden="true"
143
+ data-slot="chart-focus"
144
+ width={scene.width}
145
+ height={scene.height}
146
+ style={{
147
+ position: "absolute",
148
+ inset: 0,
149
+ overflow: "visible",
150
+ pointerEvents: "none",
151
+ }}
152
+ >
153
+ {cursor}
154
+ {ringed.map((point) => (
155
+ <PointRing
156
+ key={point.key}
157
+ point={point}
158
+ keyboard={keyboard}
159
+ theme={theme}
160
+ />
161
+ ))}
162
+ </svg>
163
+ );
164
+ }
@@ -0,0 +1,9 @@
1
+ export type { ChartBrush, ChartBrushRange } from "#/react/brush-contract.ts";
2
+ export { Chart } from "#/react/chart.tsx";
3
+ export {
4
+ ChartTooltipContent,
5
+ type ChartTooltipRenderProps,
6
+ } from "#/react/chart-tooltip.tsx";
7
+ export type { ChartRenderer, ChartRendererProps } from "#/react/renderer.ts";
8
+ export { SvgRenderer } from "#/react/svg-renderer.tsx";
9
+ export type { ActiveFocus } from "#/react/use-chart-focus.ts";
@@ -0,0 +1,49 @@
1
+ /** The few words the chart says itself. French when the chart's locale is, English otherwise. */
2
+ export interface ChartMessages {
3
+ readonly keyboardHint: string;
4
+ readonly roleDescription: string;
5
+ readonly legend: string;
6
+ readonly series: string;
7
+ readonly resetZoom: string;
8
+ readonly showing: (from: string, to: string) => string;
9
+ readonly zoomHint: string;
10
+ readonly brushStart: string;
11
+ readonly brushEnd: string;
12
+ readonly selected: (from: string, to: string) => string;
13
+ readonly dataTable: (label: string) => string;
14
+ }
15
+
16
+ const FRENCH: ChartMessages = {
17
+ keyboardHint:
18
+ "Flèches pour parcourir les valeurs, Entrée pour épingler, Échap pour quitter.",
19
+ roleDescription: "graphique",
20
+ legend: "Légende",
21
+ series: "Série",
22
+ resetZoom: "Réinitialiser le zoom",
23
+ showing: (from, to) => `Affiché : ${from} – ${to}`,
24
+ zoomHint:
25
+ " + et − pour zoomer, Maj et flèches pour se déplacer, 0 pour revenir.",
26
+ brushStart: "Début de la sélection",
27
+ brushEnd: "Fin de la sélection",
28
+ selected: (from, to) => `Sélection : ${from} – ${to}`,
29
+ dataTable: (label) => `Données : ${label}`,
30
+ };
31
+
32
+ const ENGLISH: ChartMessages = {
33
+ keyboardHint:
34
+ "Arrow keys to move through the values, Enter to pin, Escape to leave.",
35
+ roleDescription: "chart",
36
+ legend: "Legend",
37
+ series: "Series",
38
+ resetZoom: "Reset zoom",
39
+ showing: (from, to) => `Showing ${from} – ${to}`,
40
+ zoomHint: " + and − to zoom, Shift and the arrows to pan, 0 to reset.",
41
+ brushStart: "Selection start",
42
+ brushEnd: "Selection end",
43
+ selected: (from, to) => `Selected ${from} – ${to}`,
44
+ dataTable: (label) => `Data: ${label}`,
45
+ };
46
+
47
+ export function messagesFor(locale: string): ChartMessages {
48
+ return locale.toLowerCase().startsWith("fr") ? FRENCH : ENGLISH;
49
+ }
@@ -0,0 +1,15 @@
1
+ import type * as React from "react";
2
+ import type { ChartScene } from "#/core/types.ts";
3
+
4
+ /**
5
+ * What draws the scene. The SVG renderer is the default; `@voila.dev/chart/canvas`
6
+ * exports one that paints the same scene onto a canvas, for thousands of marks.
7
+ * Either way the focus ring, tooltip, legend and data table are the same.
8
+ */
9
+ export interface ChartRendererProps {
10
+ readonly scene: ChartScene;
11
+ /** Unique per chart, for clip-path ids. */
12
+ readonly chartId: string;
13
+ }
14
+
15
+ export type ChartRenderer = React.ComponentType<ChartRendererProps>;
@@ -0,0 +1,14 @@
1
+ import type * as React from "react";
2
+
3
+ /** Present to assistive tech, invisible on screen, and never wider than one pixel. */
4
+ export const SR_ONLY: React.CSSProperties = {
5
+ position: "absolute",
6
+ width: 1,
7
+ height: 1,
8
+ padding: 0,
9
+ margin: -1,
10
+ overflow: "hidden",
11
+ clip: "rect(0, 0, 0, 0)",
12
+ whiteSpace: "nowrap",
13
+ border: 0,
14
+ };
@@ -0,0 +1,18 @@
1
+ import type { ChartRendererProps } from "#/react/renderer.ts";
2
+ import { SvgSceneNodes } from "#/react/svg-scene.tsx";
3
+
4
+ /** The default renderer: the scene as inline SVG, server-renderable and crisp at any zoom. */
5
+ export function SvgRenderer({ scene, chartId }: ChartRendererProps) {
6
+ return (
7
+ <svg
8
+ data-slot="chart-svg"
9
+ aria-hidden="true"
10
+ width={scene.width}
11
+ height={scene.height}
12
+ viewBox={`0 0 ${scene.width} ${scene.height}`}
13
+ style={{ position: "absolute", inset: 0, overflow: "visible" }}
14
+ >
15
+ <SvgSceneNodes scene={scene} clipPrefix={chartId} />
16
+ </svg>
17
+ );
18
+ }
@@ -0,0 +1,146 @@
1
+ import type * as React from "react";
2
+ import { roundedBarPath } from "#/core/paths.ts";
3
+ import type {
4
+ ChartPaint,
5
+ ChartScene,
6
+ ChartTextPaint,
7
+ SceneNode,
8
+ } from "#/core/types.ts";
9
+
10
+ const BASELINE: Record<
11
+ NonNullable<ChartTextPaint["baseline"]>,
12
+ React.SVGAttributes<SVGTextElement>["dominantBaseline"]
13
+ > = {
14
+ top: "hanging",
15
+ middle: "central",
16
+ bottom: "text-after-edge",
17
+ alphabetic: "alphabetic",
18
+ };
19
+
20
+ function paintProps(paint: ChartPaint): React.SVGAttributes<SVGElement> {
21
+ return {
22
+ fill: paint.fill ?? "none",
23
+ stroke: paint.stroke,
24
+ strokeWidth: paint.strokeWidth,
25
+ strokeDasharray: paint.strokeDasharray,
26
+ strokeLinecap: paint.strokeLinecap,
27
+ strokeLinejoin: paint.strokeLinejoin,
28
+ opacity: paint.opacity,
29
+ fillOpacity: paint.fillOpacity,
30
+ strokeOpacity: paint.strokeOpacity,
31
+ };
32
+ }
33
+
34
+ function SceneElement({
35
+ node,
36
+ clipPrefix,
37
+ }: {
38
+ node: SceneNode;
39
+ clipPrefix: string;
40
+ }) {
41
+ const data = { "data-role": node.role, "data-series": node.series };
42
+ switch (node.kind) {
43
+ case "group": {
44
+ const clipId = node.clip ? `${clipPrefix}-${node.key}` : undefined;
45
+ return (
46
+ <g
47
+ {...data}
48
+ transform={
49
+ node.translate
50
+ ? `translate(${node.translate.x} ${node.translate.y})`
51
+ : undefined
52
+ }
53
+ clipPath={clipId ? `url(#${clipId})` : undefined}
54
+ >
55
+ {node.clip && clipId ? (
56
+ <clipPath id={clipId}>
57
+ <rect {...node.clip} />
58
+ </clipPath>
59
+ ) : null}
60
+ {node.children.map((child) => (
61
+ <SceneElement
62
+ key={child.key}
63
+ node={child}
64
+ clipPrefix={clipPrefix}
65
+ />
66
+ ))}
67
+ </g>
68
+ );
69
+ }
70
+ case "rect":
71
+ return node.corners ? (
72
+ <path
73
+ {...data}
74
+ d={roundedBarPath({ ...node, radius: node.corners })}
75
+ {...paintProps(node.paint)}
76
+ />
77
+ ) : (
78
+ <rect
79
+ {...data}
80
+ x={node.x}
81
+ y={node.y}
82
+ width={node.width}
83
+ height={node.height}
84
+ {...paintProps(node.paint)}
85
+ />
86
+ );
87
+ case "path":
88
+ return <path {...data} d={node.d} {...paintProps(node.paint)} />;
89
+ case "circle":
90
+ return (
91
+ <circle
92
+ {...data}
93
+ cx={node.cx}
94
+ cy={node.cy}
95
+ r={node.r}
96
+ {...paintProps(node.paint)}
97
+ />
98
+ );
99
+ case "line":
100
+ return (
101
+ <line
102
+ {...data}
103
+ x1={node.x1}
104
+ y1={node.y1}
105
+ x2={node.x2}
106
+ y2={node.y2}
107
+ {...paintProps(node.paint)}
108
+ />
109
+ );
110
+ case "text":
111
+ return (
112
+ <text
113
+ {...data}
114
+ x={node.x}
115
+ y={node.y}
116
+ transform={
117
+ node.rotate
118
+ ? `rotate(${node.rotate} ${node.x} ${node.y})`
119
+ : undefined
120
+ }
121
+ fontSize={node.paint.fontSize}
122
+ fontWeight={node.paint.fontWeight}
123
+ textAnchor={node.paint.textAnchor}
124
+ dominantBaseline={
125
+ node.paint.baseline ? BASELINE[node.paint.baseline] : undefined
126
+ }
127
+ {...paintProps(node.paint)}
128
+ >
129
+ {node.text}
130
+ </text>
131
+ );
132
+ }
133
+ }
134
+
135
+ /** The scene's nodes as SVG elements, keyed so React reconciles a data update in place. */
136
+ export function SvgSceneNodes({
137
+ scene,
138
+ clipPrefix,
139
+ }: {
140
+ scene: ChartScene;
141
+ clipPrefix: string;
142
+ }) {
143
+ return scene.nodes.map((node) => (
144
+ <SceneElement key={node.key} node={node} clipPrefix={clipPrefix} />
145
+ ));
146
+ }
@@ -0,0 +1,57 @@
1
+ import * as React from "react";
2
+ import { easeOutCubic, tweenScene } from "#/core/motion/tween.ts";
3
+ import type { ChartScene } from "#/core/types.ts";
4
+
5
+ function prefersReducedMotion(): boolean {
6
+ return (
7
+ typeof window !== "undefined" &&
8
+ window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
9
+ );
10
+ }
11
+
12
+ /**
13
+ * The scene to draw this frame. A data change (`trigger` changed: a new
14
+ * definition, a series toggled) tweens from what is on screen to the new
15
+ * scene; a resize snaps, since watching bars slide while a window is dragged
16
+ * helps no one. Reduced motion always snaps. An update that lands mid-tween
17
+ * starts from the frame already painted, so nothing jumps.
18
+ */
19
+ export function useAnimatedScene(
20
+ scene: ChartScene,
21
+ trigger: unknown,
22
+ duration: number,
23
+ ): ChartScene {
24
+ const [shown, setShown] = React.useState(scene);
25
+ const shownRef = React.useRef(scene);
26
+ const triggerRef = React.useRef(trigger);
27
+
28
+ React.useLayoutEffect(() => {
29
+ const changed = triggerRef.current !== trigger;
30
+ triggerRef.current = trigger;
31
+ if (!changed || duration <= 0 || prefersReducedMotion()) {
32
+ shownRef.current = scene;
33
+ setShown(scene);
34
+ return;
35
+ }
36
+ const from = shownRef.current;
37
+ // Frame zero now, before paint: nodes that left are gone at once.
38
+ const first = tweenScene(from, scene, 0);
39
+ shownRef.current = first;
40
+ setShown(first);
41
+ const start = performance.now();
42
+ let frame = 0;
43
+ function step(now: number) {
44
+ const t = Math.min(1, (now - start) / duration);
45
+ const next = tweenScene(from, scene, easeOutCubic(t));
46
+ shownRef.current = next;
47
+ setShown(next);
48
+ if (t < 1) {
49
+ frame = requestAnimationFrame(step);
50
+ }
51
+ }
52
+ frame = requestAnimationFrame(step);
53
+ return () => cancelAnimationFrame(frame);
54
+ }, [scene, trigger, duration]);
55
+
56
+ return shown;
57
+ }