@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,180 @@
1
+ import * as React from "react";
2
+ function across(stop, order) {
3
+ return [...stop.points].sort(
4
+ (left, right) => order === "y" ? left.x - right.x : left.y - right.y
5
+ );
6
+ }
7
+ const ALONG_KEYS = {
8
+ x: { next: ["ArrowRight"], previous: ["ArrowLeft"] },
9
+ y: { next: ["ArrowDown"], previous: ["ArrowUp"] },
10
+ point: {
11
+ next: ["ArrowRight", "ArrowDown"],
12
+ previous: ["ArrowLeft", "ArrowUp"]
13
+ }
14
+ };
15
+ const ACROSS_KEYS = {
16
+ x: { next: ["ArrowDown"], previous: ["ArrowUp"] },
17
+ y: { next: ["ArrowRight"], previous: ["ArrowLeft"] },
18
+ point: { next: [], previous: [] }
19
+ };
20
+ function useChartFocus({
21
+ stops,
22
+ order,
23
+ hitTest,
24
+ onFocusChange,
25
+ onSelect
26
+ }) {
27
+ const [state, setState] = React.useState(null);
28
+ const active = React.useMemo(() => {
29
+ if (state === null) {
30
+ return null;
31
+ }
32
+ const stop = stops.find((candidate) => candidate.key === state.stopKey);
33
+ const point = stop?.points.find(
34
+ (candidate) => candidate.key === state.pointKey
35
+ );
36
+ return stop && point ? { stop, point, source: state.source, pinned: state.pinned } : null;
37
+ }, [state, stops]);
38
+ const activeKey = active?.point.key;
39
+ const onFocusChangeRef = React.useRef(onFocusChange);
40
+ onFocusChangeRef.current = onFocusChange;
41
+ React.useEffect(() => {
42
+ onFocusChangeRef.current?.(active?.point ?? null);
43
+ }, [activeKey]);
44
+ function focusStop(index, source, pointIndex = 0) {
45
+ const stop = stops[index];
46
+ if (stop === void 0) {
47
+ return;
48
+ }
49
+ const points = across(stop, order);
50
+ const point = points[Math.min(pointIndex, points.length - 1)];
51
+ setState({ stopKey: stop.key, pointKey: point.key, source, pinned: false });
52
+ }
53
+ function onKeyDown(event) {
54
+ if (stops.length === 0) {
55
+ return;
56
+ }
57
+ const stopIndex = active ? stops.indexOf(active.stop) : -1;
58
+ const along = ALONG_KEYS[order];
59
+ const crossing = ACROSS_KEYS[order];
60
+ const { key } = event;
61
+ let handled = true;
62
+ if (along.next.includes(key)) {
63
+ focusStop(
64
+ stopIndex === -1 ? 0 : Math.min(stops.length - 1, stopIndex + 1),
65
+ "keyboard"
66
+ );
67
+ } else if (along.previous.includes(key)) {
68
+ focusStop(stopIndex === -1 ? 0 : Math.max(0, stopIndex - 1), "keyboard");
69
+ } else if (key === "Home") {
70
+ focusStop(0, "keyboard");
71
+ } else if (key === "End") {
72
+ focusStop(stops.length - 1, "keyboard");
73
+ } else if (active && (crossing.next.includes(key) || crossing.previous.includes(key))) {
74
+ const points = across(active.stop, order);
75
+ const current = points.indexOf(active.point);
76
+ const step = crossing.next.includes(key) ? 1 : -1;
77
+ const next = points[Math.min(points.length - 1, Math.max(0, current + step))];
78
+ setState({
79
+ stopKey: active.stop.key,
80
+ pointKey: next.key,
81
+ source: "keyboard",
82
+ pinned: false
83
+ });
84
+ } else if ((key === "Enter" || key === " ") && active) {
85
+ setState({
86
+ stopKey: active.stop.key,
87
+ pointKey: active.point.key,
88
+ source: "keyboard",
89
+ pinned: !active.pinned
90
+ });
91
+ onSelect?.(active.pinned ? null : active.point);
92
+ } else if (key === "Escape" && active) {
93
+ setState(
94
+ active.pinned ? {
95
+ stopKey: active.stop.key,
96
+ pointKey: active.point.key,
97
+ source: "keyboard",
98
+ pinned: false
99
+ } : null
100
+ );
101
+ } else {
102
+ handled = false;
103
+ }
104
+ if (handled) {
105
+ event.preventDefault();
106
+ }
107
+ }
108
+ function localPoint(event) {
109
+ const box = event.currentTarget.getBoundingClientRect();
110
+ return { x: event.clientX - box.left, y: event.clientY - box.top };
111
+ }
112
+ function onPointerMove(event) {
113
+ if (active?.pinned) {
114
+ return;
115
+ }
116
+ const { x, y } = localPoint(event);
117
+ const hit = hitTest(x, y);
118
+ if (hit === null) {
119
+ setState(null);
120
+ return;
121
+ }
122
+ const stop = stops[hit.stop];
123
+ const point = stop.points[hit.point];
124
+ if (stop.key !== state?.stopKey || point.key !== state?.pointKey) {
125
+ setState({
126
+ stopKey: stop.key,
127
+ pointKey: point.key,
128
+ source: "pointer",
129
+ pinned: false
130
+ });
131
+ }
132
+ }
133
+ function onPointerLeave() {
134
+ if (!active?.pinned) {
135
+ setState(null);
136
+ }
137
+ }
138
+ function onClick(event) {
139
+ const { x, y } = localPoint(event);
140
+ const hit = hitTest(x, y);
141
+ if (hit === null) {
142
+ setState(null);
143
+ onSelect?.(null);
144
+ return;
145
+ }
146
+ const stop = stops[hit.stop];
147
+ const point = stop.points[hit.point];
148
+ const unpin = active?.pinned === true && active.point.key === point.key;
149
+ setState({
150
+ stopKey: stop.key,
151
+ pointKey: point.key,
152
+ source: "pointer",
153
+ pinned: !unpin
154
+ });
155
+ onSelect?.(unpin ? null : point);
156
+ }
157
+ function onFocus(event) {
158
+ if (state === null && event.currentTarget.matches(":focus-visible")) {
159
+ focusStop(0, "keyboard");
160
+ }
161
+ }
162
+ function onBlur() {
163
+ setState(null);
164
+ }
165
+ return {
166
+ active,
167
+ handlers: {
168
+ onKeyDown,
169
+ onPointerMove,
170
+ onPointerLeave,
171
+ onClick,
172
+ onFocus,
173
+ onBlur
174
+ }
175
+ };
176
+ }
177
+ export {
178
+ useChartFocus
179
+ };
180
+ //# sourceMappingURL=use-chart-focus.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/use-chart-focus.ts"],
4
+ "sourcesContent": ["import * as React from \"react\";\nimport type { ChartFocusStop, findNearest } from \"#/core/nearest.ts\";\nimport type { ChartFocusOrder, ChartPoint } from \"#/core/types.ts\";\n\n/**\n * Which point the reader is on, by the pointer or the keyboard. Focus is held\n * by key, not index, so it stays on the same datum when the data updates\n * around it, and drops cleanly when that datum goes away.\n */\ninterface FocusState {\n\treadonly stopKey: string;\n\treadonly pointKey: string;\n\treadonly source: \"pointer\" | \"keyboard\";\n\treadonly pinned: boolean;\n}\n\nexport interface ActiveFocus {\n\treadonly stop: ChartFocusStop;\n\treadonly point: ChartPoint;\n\treadonly source: \"pointer\" | \"keyboard\";\n\treadonly pinned: boolean;\n}\n\ninterface Options {\n\treadonly stops: ReadonlyArray<ChartFocusStop>;\n\treadonly order: ChartFocusOrder;\n\treadonly hitTest: (x: number, y: number) => ReturnType<typeof findNearest>;\n\treadonly onFocusChange?: (point: ChartPoint | null) => void;\n\treadonly onSelect?: (point: ChartPoint | null) => void;\n}\n\n/** Points of a stop in screen order across the stop: top to bottom, or left to right. */\nfunction across(\n\tstop: ChartFocusStop,\n\torder: ChartFocusOrder,\n): ReadonlyArray<ChartPoint> {\n\treturn [...stop.points].sort((left, right) =>\n\t\torder === \"y\" ? left.x - right.x : left.y - right.y,\n\t);\n}\n\nconst ALONG_KEYS: Record<\n\tChartFocusOrder,\n\t{ next: string[]; previous: string[] }\n> = {\n\tx: { next: [\"ArrowRight\"], previous: [\"ArrowLeft\"] },\n\ty: { next: [\"ArrowDown\"], previous: [\"ArrowUp\"] },\n\tpoint: {\n\t\tnext: [\"ArrowRight\", \"ArrowDown\"],\n\t\tprevious: [\"ArrowLeft\", \"ArrowUp\"],\n\t},\n};\n\nconst ACROSS_KEYS: Record<\n\tChartFocusOrder,\n\t{ next: string[]; previous: string[] }\n> = {\n\tx: { next: [\"ArrowDown\"], previous: [\"ArrowUp\"] },\n\ty: { next: [\"ArrowRight\"], previous: [\"ArrowLeft\"] },\n\tpoint: { next: [], previous: [] },\n};\n\nexport function useChartFocus({\n\tstops,\n\torder,\n\thitTest,\n\tonFocusChange,\n\tonSelect,\n}: Options) {\n\tconst [state, setState] = React.useState<FocusState | null>(null);\n\n\tconst active = React.useMemo<ActiveFocus | null>(() => {\n\t\tif (state === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst stop = stops.find((candidate) => candidate.key === state.stopKey);\n\t\tconst point = stop?.points.find(\n\t\t\t(candidate) => candidate.key === state.pointKey,\n\t\t);\n\t\treturn stop && point\n\t\t\t? { stop, point, source: state.source, pinned: state.pinned }\n\t\t\t: null;\n\t}, [state, stops]);\n\n\tconst activeKey = active?.point.key;\n\tconst onFocusChangeRef = React.useRef(onFocusChange);\n\tonFocusChangeRef.current = onFocusChange;\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: fires on a change of point, not on every render\n\tReact.useEffect(() => {\n\t\tonFocusChangeRef.current?.(active?.point ?? null);\n\t}, [activeKey]);\n\n\tfunction focusStop(\n\t\tindex: number,\n\t\tsource: FocusState[\"source\"],\n\t\tpointIndex = 0,\n\t) {\n\t\tconst stop = stops[index];\n\t\tif (stop === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tconst points = across(stop, order);\n\t\tconst point = points[Math.min(pointIndex, points.length - 1)];\n\t\tsetState({ stopKey: stop.key, pointKey: point.key, source, pinned: false });\n\t}\n\n\tfunction onKeyDown(event: React.KeyboardEvent<HTMLElement>) {\n\t\tif (stops.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst stopIndex = active ? stops.indexOf(active.stop) : -1;\n\t\tconst along = ALONG_KEYS[order];\n\t\tconst crossing = ACROSS_KEYS[order];\n\t\tconst { key } = event;\n\t\tlet handled = true;\n\t\tif (along.next.includes(key)) {\n\t\t\tfocusStop(\n\t\t\t\tstopIndex === -1 ? 0 : Math.min(stops.length - 1, stopIndex + 1),\n\t\t\t\t\"keyboard\",\n\t\t\t);\n\t\t} else if (along.previous.includes(key)) {\n\t\t\tfocusStop(stopIndex === -1 ? 0 : Math.max(0, stopIndex - 1), \"keyboard\");\n\t\t} else if (key === \"Home\") {\n\t\t\tfocusStop(0, \"keyboard\");\n\t\t} else if (key === \"End\") {\n\t\t\tfocusStop(stops.length - 1, \"keyboard\");\n\t\t} else if (\n\t\t\tactive &&\n\t\t\t(crossing.next.includes(key) || crossing.previous.includes(key))\n\t\t) {\n\t\t\tconst points = across(active.stop, order);\n\t\t\tconst current = points.indexOf(active.point);\n\t\t\tconst step = crossing.next.includes(key) ? 1 : -1;\n\t\t\tconst next =\n\t\t\t\tpoints[Math.min(points.length - 1, Math.max(0, current + step))];\n\t\t\tsetState({\n\t\t\t\tstopKey: active.stop.key,\n\t\t\t\tpointKey: next.key,\n\t\t\t\tsource: \"keyboard\",\n\t\t\t\tpinned: false,\n\t\t\t});\n\t\t} else if ((key === \"Enter\" || key === \" \") && active) {\n\t\t\tsetState({\n\t\t\t\tstopKey: active.stop.key,\n\t\t\t\tpointKey: active.point.key,\n\t\t\t\tsource: \"keyboard\",\n\t\t\t\tpinned: !active.pinned,\n\t\t\t});\n\t\t\tonSelect?.(active.pinned ? null : active.point);\n\t\t} else if (key === \"Escape\" && active) {\n\t\t\tsetState(\n\t\t\t\tactive.pinned\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tstopKey: active.stop.key,\n\t\t\t\t\t\t\tpointKey: active.point.key,\n\t\t\t\t\t\t\tsource: \"keyboard\",\n\t\t\t\t\t\t\tpinned: false,\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\t);\n\t\t} else {\n\t\t\thandled = false;\n\t\t}\n\t\tif (handled) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n\n\tfunction localPoint(\n\t\tevent: React.PointerEvent<HTMLElement> | React.MouseEvent<HTMLElement>,\n\t) {\n\t\tconst box = event.currentTarget.getBoundingClientRect();\n\t\treturn { x: event.clientX - box.left, y: event.clientY - box.top };\n\t}\n\n\tfunction onPointerMove(event: React.PointerEvent<HTMLElement>) {\n\t\tif (active?.pinned) {\n\t\t\treturn;\n\t\t}\n\t\tconst { x, y } = localPoint(event);\n\t\tconst hit = hitTest(x, y);\n\t\tif (hit === null) {\n\t\t\tsetState(null);\n\t\t\treturn;\n\t\t}\n\t\tconst stop = stops[hit.stop];\n\t\tconst point = stop.points[hit.point];\n\t\tif (stop.key !== state?.stopKey || point.key !== state?.pointKey) {\n\t\t\tsetState({\n\t\t\t\tstopKey: stop.key,\n\t\t\t\tpointKey: point.key,\n\t\t\t\tsource: \"pointer\",\n\t\t\t\tpinned: false,\n\t\t\t});\n\t\t}\n\t}\n\n\tfunction onPointerLeave() {\n\t\tif (!active?.pinned) {\n\t\t\tsetState(null);\n\t\t}\n\t}\n\n\tfunction onClick(event: React.MouseEvent<HTMLElement>) {\n\t\tconst { x, y } = localPoint(event);\n\t\tconst hit = hitTest(x, y);\n\t\tif (hit === null) {\n\t\t\tsetState(null);\n\t\t\tonSelect?.(null);\n\t\t\treturn;\n\t\t}\n\t\tconst stop = stops[hit.stop];\n\t\tconst point = stop.points[hit.point];\n\t\tconst unpin = active?.pinned === true && active.point.key === point.key;\n\t\tsetState({\n\t\t\tstopKey: stop.key,\n\t\t\tpointKey: point.key,\n\t\t\tsource: \"pointer\",\n\t\t\tpinned: !unpin,\n\t\t});\n\t\tonSelect?.(unpin ? null : point);\n\t}\n\n\tfunction onFocus(event: React.FocusEvent<HTMLElement>) {\n\t\tif (state === null && event.currentTarget.matches(\":focus-visible\")) {\n\t\t\tfocusStop(0, \"keyboard\");\n\t\t}\n\t}\n\n\tfunction onBlur() {\n\t\tsetState(null);\n\t}\n\n\treturn {\n\t\tactive,\n\t\thandlers: {\n\t\t\tonKeyDown,\n\t\t\tonPointerMove,\n\t\t\tonPointerLeave,\n\t\t\tonClick,\n\t\t\tonFocus,\n\t\t\tonBlur,\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ /**
3
+ * The container's width. The server and the first client render use
4
+ * `initialWidth`, so the hydrated markup matches; the real width arrives on
5
+ * the first observer callback.
6
+ */
7
+ export declare function useChartWidth(initialWidth: number): {
8
+ ref: React.Dispatch<React.SetStateAction<HTMLDivElement | null>>;
9
+ width: number;
10
+ node: HTMLDivElement | null;
11
+ };
12
+ //# sourceMappingURL=use-chart-width.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chart-width.d.ts","sourceRoot":"","sources":["../../src/react/use-chart-width.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM;;;;EAmBjD"}
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ function useChartWidth(initialWidth) {
3
+ const [node, setNode] = React.useState(null);
4
+ const [width, setWidth] = React.useState(initialWidth);
5
+ React.useEffect(() => {
6
+ if (node === null || typeof ResizeObserver === "undefined") {
7
+ return;
8
+ }
9
+ const observer = new ResizeObserver((entries) => {
10
+ const measured = entries[0]?.contentRect.width;
11
+ if (measured !== void 0 && measured > 0) {
12
+ setWidth(Math.round(measured));
13
+ }
14
+ });
15
+ observer.observe(node);
16
+ return () => observer.disconnect();
17
+ }, [node]);
18
+ return { ref: setNode, width, node };
19
+ }
20
+ export {
21
+ useChartWidth
22
+ };
23
+ //# sourceMappingURL=use-chart-width.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/use-chart-width.ts"],
4
+ "sourcesContent": ["import * as React from \"react\";\n\n/**\n * The container's width. The server and the first client render use\n * `initialWidth`, so the hydrated markup matches; the real width arrives on\n * the first observer callback.\n */\nexport function useChartWidth(initialWidth: number) {\n\tconst [node, setNode] = React.useState<HTMLDivElement | null>(null);\n\tconst [width, setWidth] = React.useState(initialWidth);\n\n\tReact.useEffect(() => {\n\t\tif (node === null || typeof ResizeObserver === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst measured = entries[0]?.contentRect.width;\n\t\t\tif (measured !== undefined && measured > 0) {\n\t\t\t\tsetWidth(Math.round(measured));\n\t\t\t}\n\t\t});\n\t\tobserver.observe(node);\n\t\treturn () => observer.disconnect();\n\t}, [node]);\n\n\treturn { ref: setNode, width, node };\n}\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;AAOhB,SAAS,cAAc,cAAsB;AACnD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAgC,IAAI;AAClE,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,YAAY;AAErD,QAAM,UAAU,MAAM;AACrB,QAAI,SAAS,QAAQ,OAAO,mBAAmB,aAAa;AAC3D;AAAA,IACD;AACA,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAChD,YAAM,WAAW,QAAQ,CAAC,GAAG,YAAY;AACzC,UAAI,aAAa,UAAa,WAAW,GAAG;AAC3C,iBAAS,KAAK,MAAM,QAAQ,CAAC;AAAA,MAC9B;AAAA,IACD,CAAC;AACD,aAAS,QAAQ,IAAI;AACrB,WAAO,MAAM,SAAS,WAAW;AAAA,EAClC,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,KAAK,SAAS,OAAO,KAAK;AACpC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The part of a continuous x axis on show. `null` is the whole of it. Zooming
3
+ * keeps the value under the pointer (or the focused value) where it is on
4
+ * screen; panning never leaves the data.
5
+ */
6
+ export declare function useChartZoom(full: readonly [number, number] | undefined, resetKey: unknown): {
7
+ domain: readonly [number, number] | null;
8
+ zoomAround: (center: number, factor: number) => void;
9
+ pan: (delta: number) => void;
10
+ reset: () => void;
11
+ span: () => number;
12
+ };
13
+ //# sourceMappingURL=use-chart-zoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chart-zoom.d.ts","sourceRoot":"","sources":["../../src/react/use-chart-zoom.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,QAAQ,EAAE,OAAO;;yBAmBW,MAAM,UAAU,MAAM;iBAQ9B,MAAM;;;EAe1B"}
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ const MAX_ZOOM = 100;
3
+ function clampDomain([from, to], [low, high]) {
4
+ const span = Math.min(
5
+ high - low,
6
+ Math.max((high - low) / MAX_ZOOM, to - from)
7
+ );
8
+ const start = Math.min(Math.max(from, low), high - span);
9
+ return [start, start + span];
10
+ }
11
+ function useChartZoom(full, resetKey) {
12
+ const [domain, setDomain] = React.useState(
13
+ null
14
+ );
15
+ const keyRef = React.useRef(resetKey);
16
+ if (keyRef.current !== resetKey) {
17
+ keyRef.current = resetKey;
18
+ if (domain !== null) {
19
+ setDomain(null);
20
+ }
21
+ }
22
+ function apply(next) {
23
+ if (full === void 0) return;
24
+ const clamped = clampDomain(next, full);
25
+ setDomain(clamped[0] <= full[0] && clamped[1] >= full[1] ? null : clamped);
26
+ }
27
+ function zoomAround(center, factor) {
28
+ if (full === void 0) return;
29
+ const [from, to] = domain ?? full;
30
+ const share = to === from ? 0.5 : (center - from) / (to - from);
31
+ const span = (to - from) * factor;
32
+ apply([center - span * share, center - span * share + span]);
33
+ }
34
+ function pan(delta) {
35
+ if (full === void 0 || domain === null) return;
36
+ apply([domain[0] + delta, domain[1] + delta]);
37
+ }
38
+ return {
39
+ domain,
40
+ zoomAround,
41
+ pan,
42
+ reset: () => setDomain(null),
43
+ span: () => {
44
+ const [from, to] = domain ?? full ?? [0, 1];
45
+ return to - from;
46
+ }
47
+ };
48
+ }
49
+ export {
50
+ useChartZoom
51
+ };
52
+ //# sourceMappingURL=use-chart-zoom.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/use-chart-zoom.ts"],
4
+ "sourcesContent": ["import * as React from \"react\";\n\n/** The narrowest view: a hundredth of the data. */\nconst MAX_ZOOM = 100;\n\nfunction clampDomain(\n\t[from, to]: readonly [number, number],\n\t[low, high]: readonly [number, number],\n): readonly [number, number] {\n\tconst span = Math.min(\n\t\thigh - low,\n\t\tMath.max((high - low) / MAX_ZOOM, to - from),\n\t);\n\tconst start = Math.min(Math.max(from, low), high - span);\n\treturn [start, start + span];\n}\n\n/**\n * The part of a continuous x axis on show. `null` is the whole of it. Zooming\n * keeps the value under the pointer (or the focused value) where it is on\n * screen; panning never leaves the data.\n */\nexport function useChartZoom(\n\tfull: readonly [number, number] | undefined,\n\tresetKey: unknown,\n) {\n\tconst [domain, setDomain] = React.useState<readonly [number, number] | null>(\n\t\tnull,\n\t);\n\tconst keyRef = React.useRef(resetKey);\n\tif (keyRef.current !== resetKey) {\n\t\tkeyRef.current = resetKey;\n\t\tif (domain !== null) {\n\t\t\tsetDomain(null);\n\t\t}\n\t}\n\n\tfunction apply(next: readonly [number, number]) {\n\t\tif (full === undefined) return;\n\t\tconst clamped = clampDomain(next, full);\n\t\tsetDomain(clamped[0] <= full[0] && clamped[1] >= full[1] ? null : clamped);\n\t}\n\n\tfunction zoomAround(center: number, factor: number) {\n\t\tif (full === undefined) return;\n\t\tconst [from, to] = domain ?? full;\n\t\tconst share = to === from ? 0.5 : (center - from) / (to - from);\n\t\tconst span = (to - from) * factor;\n\t\tapply([center - span * share, center - span * share + span]);\n\t}\n\n\tfunction pan(delta: number) {\n\t\tif (full === undefined || domain === null) return;\n\t\tapply([domain[0] + delta, domain[1] + delta]);\n\t}\n\n\treturn {\n\t\tdomain,\n\t\tzoomAround,\n\t\tpan,\n\t\treset: () => setDomain(null),\n\t\tspan: () => {\n\t\t\tconst [from, to] = domain ?? full ?? [0, 1];\n\t\t\treturn to - from;\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;AAGvB,MAAM,WAAW;AAEjB,SAAS,YACR,CAAC,MAAM,EAAE,GACT,CAAC,KAAK,IAAI,GACkB;AAC5B,QAAM,OAAO,KAAK;AAAA,IACjB,OAAO;AAAA,IACP,KAAK,KAAK,OAAO,OAAO,UAAU,KAAK,IAAI;AAAA,EAC5C;AACA,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,GAAG,OAAO,IAAI;AACvD,SAAO,CAAC,OAAO,QAAQ,IAAI;AAC5B;AAOO,SAAS,aACf,MACA,UACC;AACD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM;AAAA,IACjC;AAAA,EACD;AACA,QAAM,SAAS,MAAM,OAAO,QAAQ;AACpC,MAAI,OAAO,YAAY,UAAU;AAChC,WAAO,UAAU;AACjB,QAAI,WAAW,MAAM;AACpB,gBAAU,IAAI;AAAA,IACf;AAAA,EACD;AAEA,WAAS,MAAM,MAAiC;AAC/C,QAAI,SAAS,OAAW;AACxB,UAAM,UAAU,YAAY,MAAM,IAAI;AACtC,cAAU,QAAQ,CAAC,KAAK,KAAK,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAK,CAAC,IAAI,OAAO,OAAO;AAAA,EAC1E;AAEA,WAAS,WAAW,QAAgB,QAAgB;AACnD,QAAI,SAAS,OAAW;AACxB,UAAM,CAAC,MAAM,EAAE,IAAI,UAAU;AAC7B,UAAM,QAAQ,OAAO,OAAO,OAAO,SAAS,SAAS,KAAK;AAC1D,UAAM,QAAQ,KAAK,QAAQ;AAC3B,UAAM,CAAC,SAAS,OAAO,OAAO,SAAS,OAAO,QAAQ,IAAI,CAAC;AAAA,EAC5D;AAEA,WAAS,IAAI,OAAe;AAC3B,QAAI,SAAS,UAAa,WAAW,KAAM;AAC3C,UAAM,CAAC,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,MAAM,UAAU,IAAI;AAAA,IAC3B,MAAM,MAAM;AACX,YAAM,CAAC,MAAM,EAAE,IAAI,UAAU,QAAQ,CAAC,GAAG,CAAC;AAC1C,aAAO,KAAK;AAAA,IACb;AAAA,EACD;AACD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,8 @@
1
+ import type { ChartTextMeasurer } from "../core/types.js";
2
+ /**
3
+ * Real text metrics once mounted. Until then, and on the server, the estimate
4
+ * — the same on both sides, so hydration never sees a different layout. A web
5
+ * font that finishes loading later triggers one more measure.
6
+ */
7
+ export declare function useTextMeasurer(node: HTMLElement | null): ChartTextMeasurer;
8
+ //# sourceMappingURL=use-text-measurer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-text-measurer.d.ts","sourceRoot":"","sources":["../../src/react/use-text-measurer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,iBAAiB,CAuC3E"}
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import { estimateTextWidth } from "../core/text.js";
3
+ function useTextMeasurer(node) {
4
+ const [measurer, setMeasurer] = React.useState(
5
+ () => estimateTextWidth
6
+ );
7
+ React.useEffect(() => {
8
+ if (node === null || typeof document === "undefined") {
9
+ return;
10
+ }
11
+ const context = document.createElement("canvas").getContext("2d");
12
+ if (context === null) {
13
+ return;
14
+ }
15
+ function install() {
16
+ if (node === null || context === null) {
17
+ return;
18
+ }
19
+ const family = getComputedStyle(node).fontFamily || "sans-serif";
20
+ const cache = /* @__PURE__ */ new Map();
21
+ const measure = (text, fontSize, fontWeight = 400) => {
22
+ const key = `${fontWeight}:${fontSize}:${text}`;
23
+ const cached = cache.get(key);
24
+ if (cached !== void 0) {
25
+ return cached;
26
+ }
27
+ context.font = `${fontWeight} ${fontSize}px ${family}`;
28
+ const width = context.measureText(text).width;
29
+ cache.set(key, width);
30
+ return width;
31
+ };
32
+ setMeasurer(() => measure);
33
+ }
34
+ install();
35
+ const fonts = document.fonts;
36
+ fonts?.addEventListener?.("loadingdone", install);
37
+ return () => fonts?.removeEventListener?.("loadingdone", install);
38
+ }, [node]);
39
+ return measurer;
40
+ }
41
+ export {
42
+ useTextMeasurer
43
+ };
44
+ //# sourceMappingURL=use-text-measurer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/use-text-measurer.ts"],
4
+ "sourcesContent": ["import * as React from \"react\";\nimport { estimateTextWidth } from \"#/core/text.ts\";\nimport type { ChartTextMeasurer } from \"#/core/types.ts\";\n\n/**\n * Real text metrics once mounted. Until then, and on the server, the estimate\n * \u2014 the same on both sides, so hydration never sees a different layout. A web\n * font that finishes loading later triggers one more measure.\n */\nexport function useTextMeasurer(node: HTMLElement | null): ChartTextMeasurer {\n\tconst [measurer, setMeasurer] = React.useState<ChartTextMeasurer>(\n\t\t() => estimateTextWidth,\n\t);\n\n\tReact.useEffect(() => {\n\t\tif (node === null || typeof document === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\t\tconst context = document.createElement(\"canvas\").getContext(\"2d\");\n\t\tif (context === null) {\n\t\t\treturn;\n\t\t}\n\t\tfunction install() {\n\t\t\tif (node === null || context === null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst family = getComputedStyle(node).fontFamily || \"sans-serif\";\n\t\t\tconst cache = new Map<string, number>();\n\t\t\tconst measure: ChartTextMeasurer = (text, fontSize, fontWeight = 400) => {\n\t\t\t\tconst key = `${fontWeight}:${fontSize}:${text}`;\n\t\t\t\tconst cached = cache.get(key);\n\t\t\t\tif (cached !== undefined) {\n\t\t\t\t\treturn cached;\n\t\t\t\t}\n\t\t\t\tcontext.font = `${fontWeight} ${fontSize}px ${family}`;\n\t\t\t\tconst width = context.measureText(text).width;\n\t\t\t\tcache.set(key, width);\n\t\t\t\treturn width;\n\t\t\t};\n\t\t\tsetMeasurer(() => measure);\n\t\t}\n\t\tinstall();\n\t\tconst fonts = document.fonts;\n\t\tfonts?.addEventListener?.(\"loadingdone\", install);\n\t\treturn () => fonts?.removeEventListener?.(\"loadingdone\", install);\n\t}, [node]);\n\n\treturn measurer;\n}\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;AACvB,SAAS,yBAAyB;AAQ3B,SAAS,gBAAgB,MAA6C;AAC5E,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM;AAAA,IACrC,MAAM;AAAA,EACP;AAEA,QAAM,UAAU,MAAM;AACrB,QAAI,SAAS,QAAQ,OAAO,aAAa,aAAa;AACrD;AAAA,IACD;AACA,UAAM,UAAU,SAAS,cAAc,QAAQ,EAAE,WAAW,IAAI;AAChE,QAAI,YAAY,MAAM;AACrB;AAAA,IACD;AACA,aAAS,UAAU;AAClB,UAAI,SAAS,QAAQ,YAAY,MAAM;AACtC;AAAA,MACD;AACA,YAAM,SAAS,iBAAiB,IAAI,EAAE,cAAc;AACpD,YAAM,QAAQ,oBAAI,IAAoB;AACtC,YAAM,UAA6B,CAAC,MAAM,UAAU,aAAa,QAAQ;AACxE,cAAM,MAAM,GAAG,UAAU,IAAI,QAAQ,IAAI,IAAI;AAC7C,cAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,YAAI,WAAW,QAAW;AACzB,iBAAO;AAAA,QACR;AACA,gBAAQ,OAAO,GAAG,UAAU,IAAI,QAAQ,MAAM,MAAM;AACpD,cAAM,QAAQ,QAAQ,YAAY,IAAI,EAAE;AACxC,cAAM,IAAI,KAAK,KAAK;AACpB,eAAO;AAAA,MACR;AACA,kBAAY,MAAM,OAAO;AAAA,IAC1B;AACA,YAAQ;AACR,UAAM,QAAQ,SAAS;AACvB,WAAO,mBAAmB,eAAe,OAAO;AAChD,WAAO,MAAM,OAAO,sBAAsB,eAAe,OAAO;AAAA,EACjE,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACR;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export { type SankeyLink, type SankeyNode, type SankeyOptions, sankey, } from "./sankey.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sankey/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,MAAM,GACN,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import {
2
+ sankey
3
+ } from "./sankey.js";
4
+ export {
5
+ sankey
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/sankey/index.ts"],
4
+ "sourcesContent": ["export {\n\ttype SankeyLink,\n\ttype SankeyNode,\n\ttype SankeyOptions,\n\tsankey,\n} from \"#/sankey/sankey.ts\";\n"],
5
+ "mappings": "AAAA;AAAA,EAIC;AAAA,OACM;",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ import type { ChartMark } from "../core/types.js";
2
+ export interface SankeyNode {
3
+ readonly id: string;
4
+ /** Display name. Defaults to the id. */
5
+ readonly label?: string;
6
+ }
7
+ export interface SankeyLink {
8
+ readonly source: string;
9
+ readonly target: string;
10
+ readonly value: number;
11
+ }
12
+ export interface SankeyOptions {
13
+ readonly id?: string;
14
+ readonly nodeWidth?: number;
15
+ readonly nodePadding?: number;
16
+ /** What the flows are, for the table heading. */
17
+ readonly label?: string;
18
+ readonly tip?: boolean;
19
+ }
20
+ /**
21
+ * Flows between stages: each node as tall as what passes through it, each
22
+ * link as wide as its flow. Nodes and links are both focusable, so the
23
+ * keyboard reaches "Inscriptions → Missions: 240" as well as each stage.
24
+ */
25
+ export declare function sankey(data: {
26
+ readonly nodes: ReadonlyArray<SankeyNode>;
27
+ readonly links: ReadonlyArray<SankeyLink>;
28
+ }, options?: SankeyOptions): ChartMark;
29
+ //# sourceMappingURL=sankey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sankey.d.ts","sourceRoot":"","sources":["../../src/sankey/sankey.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAC;AAExE,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACvB;AAqBD;;;;GAIG;AACH,wBAAgB,MAAM,CACrB,IAAI,EAAE;IACL,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CAC1C,EACD,OAAO,GAAE,aAAkB,GACzB,SAAS,CA2HX"}
@@ -0,0 +1,127 @@
1
+ import { sankey as sankeyLayout, sankeyLinkHorizontal } from "d3-sankey";
2
+ import { markId } from "../core/marks/shared.js";
3
+ const LABEL_GAP = 6;
4
+ function sankey(data, options = {}) {
5
+ const names = new Map(
6
+ data.nodes.map((node) => [node.id, node.label ?? node.id])
7
+ );
8
+ return {
9
+ id: options.id,
10
+ coordinate: "frame",
11
+ focusOrder: "point",
12
+ titles: { value: options.label },
13
+ channels: {
14
+ color: { values: data.nodes.map((node) => node.label ?? node.id) }
15
+ },
16
+ colorLegend: false,
17
+ render(context) {
18
+ const id = markId(options.id, "sankey", context);
19
+ const { plot, theme, measureText } = context;
20
+ const widest = Math.max(
21
+ 0,
22
+ ...[...names.values()].map((name) => measureText(name, theme.fontSize))
23
+ );
24
+ const layout = sankeyLayout().nodeId((node) => node.id).nodeWidth(options.nodeWidth ?? 12).nodePadding(options.nodePadding ?? 12).extent([
25
+ [plot.x, plot.y],
26
+ [plot.x + plot.width - widest - LABEL_GAP, plot.y + plot.height]
27
+ ]);
28
+ const graph = layout({
29
+ nodes: data.nodes.map((node) => ({ ...node })),
30
+ links: data.links.map((link) => ({ ...link }))
31
+ });
32
+ const path = sankeyLinkHorizontal();
33
+ const nodes = [];
34
+ const points = [];
35
+ for (const [index, link] of graph.links.entries()) {
36
+ const from = names.get(link.source.id) ?? link.source.id;
37
+ const to = names.get(link.target.id) ?? link.target.id;
38
+ nodes.push({
39
+ kind: "path",
40
+ key: `${id}:link:${link.source.id}:${link.target.id}`,
41
+ series: from,
42
+ role: "mark",
43
+ d: path(link) ?? "",
44
+ paint: {
45
+ fill: "none",
46
+ stroke: context.colorOf(from),
47
+ strokeOpacity: 0.35,
48
+ strokeWidth: Math.max(1, link.width ?? 1)
49
+ }
50
+ });
51
+ if (options.tip !== false) {
52
+ points.push({
53
+ key: `${id}:link:${link.source.id}:${link.target.id}`,
54
+ markId: id,
55
+ index,
56
+ datum: link,
57
+ x: ((link.source.x1 ?? 0) + (link.target.x0 ?? 0)) / 2,
58
+ y: ((link.y0 ?? 0) + (link.y1 ?? 0)) / 2,
59
+ xValue: `${from} \u2192 ${to}`,
60
+ yValue: link.value,
61
+ seriesLabel: options.label,
62
+ color: context.colorOf(from),
63
+ title: `${from} \u2192 ${to}`,
64
+ value: context.formatY(link.value)
65
+ });
66
+ }
67
+ }
68
+ for (const [index, node] of graph.nodes.entries()) {
69
+ const name = names.get(node.id) ?? node.id;
70
+ const rect = {
71
+ x: node.x0 ?? 0,
72
+ y: node.y0 ?? 0,
73
+ width: (node.x1 ?? 0) - (node.x0 ?? 0),
74
+ height: Math.max(1, (node.y1 ?? 0) - (node.y0 ?? 0))
75
+ };
76
+ nodes.push(
77
+ {
78
+ kind: "rect",
79
+ key: `${id}:node:${node.id}`,
80
+ series: name,
81
+ role: "mark",
82
+ ...rect,
83
+ paint: { fill: context.colorOf(name) }
84
+ },
85
+ {
86
+ kind: "text",
87
+ key: `${id}:label:${node.id}`,
88
+ role: "label",
89
+ x: rect.x + rect.width + LABEL_GAP,
90
+ y: rect.y + rect.height / 2,
91
+ text: name,
92
+ paint: {
93
+ fill: theme.foreground,
94
+ fontSize: theme.fontSize,
95
+ baseline: "middle"
96
+ }
97
+ }
98
+ );
99
+ if (options.tip !== false) {
100
+ points.push({
101
+ key: `${id}:node:${node.id}`,
102
+ markId: id,
103
+ index: graph.links.length + index,
104
+ datum: node,
105
+ x: rect.x + rect.width / 2,
106
+ y: rect.y + rect.height / 2,
107
+ xValue: name,
108
+ yValue: node.value ?? 0,
109
+ seriesLabel: options.label,
110
+ color: context.colorOf(name),
111
+ title: name,
112
+ value: context.formatY(node.value ?? 0),
113
+ hit: {
114
+ kind: "rect",
115
+ rect: { ...rect, x: rect.x - 4, width: rect.width + 8 }
116
+ }
117
+ });
118
+ }
119
+ }
120
+ return { nodes, points };
121
+ }
122
+ };
123
+ }
124
+ export {
125
+ sankey
126
+ };
127
+ //# sourceMappingURL=sankey.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/sankey/sankey.ts"],
4
+ "sourcesContent": ["import { sankey as sankeyLayout, sankeyLinkHorizontal } from \"d3-sankey\";\nimport { markId } from \"#/core/marks/shared.ts\";\nimport type { ChartMark, ChartPoint, SceneNode } from \"#/core/types.ts\";\n\nexport interface SankeyNode {\n\treadonly id: string;\n\t/** Display name. Defaults to the id. */\n\treadonly label?: string;\n}\n\nexport interface SankeyLink {\n\treadonly source: string;\n\treadonly target: string;\n\treadonly value: number;\n}\n\nexport interface SankeyOptions {\n\treadonly id?: string;\n\treadonly nodeWidth?: number;\n\treadonly nodePadding?: number;\n\t/** What the flows are, for the table heading. */\n\treadonly label?: string;\n\treadonly tip?: boolean;\n}\n\ninterface LaidNode extends SankeyNode {\n\tx0?: number;\n\tx1?: number;\n\ty0?: number;\n\ty1?: number;\n\tvalue?: number;\n}\n\ninterface LaidLink {\n\tsource: LaidNode;\n\ttarget: LaidNode;\n\tvalue: number;\n\twidth?: number;\n\ty0?: number;\n\ty1?: number;\n}\n\nconst LABEL_GAP = 6;\n\n/**\n * Flows between stages: each node as tall as what passes through it, each\n * link as wide as its flow. Nodes and links are both focusable, so the\n * keyboard reaches \"Inscriptions \u2192 Missions: 240\" as well as each stage.\n */\nexport function sankey(\n\tdata: {\n\t\treadonly nodes: ReadonlyArray<SankeyNode>;\n\t\treadonly links: ReadonlyArray<SankeyLink>;\n\t},\n\toptions: SankeyOptions = {},\n): ChartMark {\n\tconst names = new Map(\n\t\tdata.nodes.map((node) => [node.id, node.label ?? node.id]),\n\t);\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: {\n\t\t\tcolor: { values: data.nodes.map((node) => node.label ?? node.id) },\n\t\t},\n\t\tcolorLegend: false,\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"sankey\", context);\n\t\t\tconst { plot, theme, measureText } = context;\n\t\t\tconst widest = Math.max(\n\t\t\t\t0,\n\t\t\t\t...[...names.values()].map((name) => measureText(name, theme.fontSize)),\n\t\t\t);\n\t\t\tconst layout = sankeyLayout<LaidNode, LaidLink>()\n\t\t\t\t.nodeId((node) => node.id)\n\t\t\t\t.nodeWidth(options.nodeWidth ?? 12)\n\t\t\t\t.nodePadding(options.nodePadding ?? 12)\n\t\t\t\t.extent([\n\t\t\t\t\t[plot.x, plot.y],\n\t\t\t\t\t[plot.x + plot.width - widest - LABEL_GAP, plot.y + plot.height],\n\t\t\t\t]);\n\t\t\tconst graph = layout({\n\t\t\t\tnodes: data.nodes.map((node) => ({ ...node })),\n\t\t\t\tlinks: data.links.map((link) => ({ ...link })) as unknown as LaidLink[],\n\t\t\t});\n\t\t\tconst path = sankeyLinkHorizontal<LaidNode, LaidLink>();\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const [index, link] of graph.links.entries()) {\n\t\t\t\tconst from = names.get(link.source.id) ?? link.source.id;\n\t\t\t\tconst to = names.get(link.target.id) ?? link.target.id;\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"path\",\n\t\t\t\t\tkey: `${id}:link:${link.source.id}:${link.target.id}`,\n\t\t\t\t\tseries: from,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\td: path(link as never) ?? \"\",\n\t\t\t\t\tpaint: {\n\t\t\t\t\t\tfill: \"none\",\n\t\t\t\t\t\tstroke: context.colorOf(from),\n\t\t\t\t\t\tstrokeOpacity: 0.35,\n\t\t\t\t\t\tstrokeWidth: Math.max(1, link.width ?? 1),\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push({\n\t\t\t\t\t\tkey: `${id}:link:${link.source.id}:${link.target.id}`,\n\t\t\t\t\t\tmarkId: id,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tdatum: link,\n\t\t\t\t\t\tx: ((link.source.x1 ?? 0) + (link.target.x0 ?? 0)) / 2,\n\t\t\t\t\t\ty: ((link.y0 ?? 0) + (link.y1 ?? 0)) / 2,\n\t\t\t\t\t\txValue: `${from} \u2192 ${to}`,\n\t\t\t\t\t\tyValue: link.value,\n\t\t\t\t\t\tseriesLabel: options.label,\n\t\t\t\t\t\tcolor: context.colorOf(from),\n\t\t\t\t\t\ttitle: `${from} \u2192 ${to}`,\n\t\t\t\t\t\tvalue: context.formatY(link.value),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const [index, node] of graph.nodes.entries()) {\n\t\t\t\tconst name = names.get(node.id) ?? node.id;\n\t\t\t\tconst rect = {\n\t\t\t\t\tx: node.x0 ?? 0,\n\t\t\t\t\ty: node.y0 ?? 0,\n\t\t\t\t\twidth: (node.x1 ?? 0) - (node.x0 ?? 0),\n\t\t\t\t\theight: Math.max(1, (node.y1 ?? 0) - (node.y0 ?? 0)),\n\t\t\t\t};\n\t\t\t\tnodes.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"rect\",\n\t\t\t\t\t\tkey: `${id}:node:${node.id}`,\n\t\t\t\t\t\tseries: name,\n\t\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t\t...rect,\n\t\t\t\t\t\tpaint: { fill: context.colorOf(name) },\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${node.id}`,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx: rect.x + rect.width + LABEL_GAP,\n\t\t\t\t\t\ty: rect.y + rect.height / 2,\n\t\t\t\t\t\ttext: name,\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: theme.foreground,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\tbaseline: \"middle\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push({\n\t\t\t\t\t\tkey: `${id}:node:${node.id}`,\n\t\t\t\t\t\tmarkId: id,\n\t\t\t\t\t\tindex: graph.links.length + index,\n\t\t\t\t\t\tdatum: node,\n\t\t\t\t\t\tx: rect.x + rect.width / 2,\n\t\t\t\t\t\ty: rect.y + rect.height / 2,\n\t\t\t\t\t\txValue: name,\n\t\t\t\t\t\tyValue: node.value ?? 0,\n\t\t\t\t\t\tseriesLabel: options.label,\n\t\t\t\t\t\tcolor: context.colorOf(name),\n\t\t\t\t\t\ttitle: name,\n\t\t\t\t\t\tvalue: context.formatY(node.value ?? 0),\n\t\t\t\t\t\thit: {\n\t\t\t\t\t\t\tkind: \"rect\",\n\t\t\t\t\t\t\trect: { ...rect, x: rect.x - 4, width: rect.width + 8 },\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export { type VoronoiOptions, voronoi } from "./voronoi.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voronoi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { voronoi } from "./voronoi.js";
2
+ export {
3
+ voronoi
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/voronoi/index.ts"],
4
+ "sourcesContent": ["export { type VoronoiOptions, voronoi } from \"#/voronoi/voronoi.ts\";\n"],
5
+ "mappings": "AAAA,SAA8B,eAAe;",
6
+ "names": []
7
+ }