@misoto22/design 0.7.0 → 0.8.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 (369) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +50 -5
  4. package/bin/misoto22-design.mjs +59 -13
  5. package/dist/agent/Accordion.md +24 -1
  6. package/dist/agent/Alert.md +23 -1
  7. package/dist/agent/AppShell.md +30 -2
  8. package/dist/agent/ArchitectureFigure.md +25 -2
  9. package/dist/agent/AreaChart.md +27 -2
  10. package/dist/agent/Article.md +26 -3
  11. package/dist/agent/AspectRatio.md +57 -0
  12. package/dist/agent/Avatar.md +25 -5
  13. package/dist/agent/Badge.md +22 -1
  14. package/dist/agent/BarChart.md +29 -2
  15. package/dist/agent/BarList.md +27 -1
  16. package/dist/agent/BigNumber.md +26 -2
  17. package/dist/agent/BoxPlot.md +24 -1
  18. package/dist/agent/Breadcrumb.md +24 -2
  19. package/dist/agent/BulletChart.md +23 -1
  20. package/dist/agent/Button.md +25 -1
  21. package/dist/agent/Calendar.md +37 -3
  22. package/dist/agent/Card.md +24 -2
  23. package/dist/agent/Checkbox.md +23 -2
  24. package/dist/agent/Code.md +50 -0
  25. package/dist/agent/CodeBlock.md +74 -0
  26. package/dist/agent/Collapsible.md +22 -1
  27. package/dist/agent/Combobox.md +29 -3
  28. package/dist/agent/Command.md +25 -2
  29. package/dist/agent/ComposedChart.md +26 -2
  30. package/dist/agent/ContextMenu.md +37 -4
  31. package/dist/agent/DataflowFigure.md +22 -1
  32. package/dist/agent/DatePicker.md +34 -6
  33. package/dist/agent/DescriptionList.md +68 -0
  34. package/dist/agent/Diagram.md +27 -3
  35. package/dist/agent/DiagramCanvas.md +24 -1
  36. package/dist/agent/DiagramExportMenu.md +29 -3
  37. package/dist/agent/DiagramInspector.md +23 -1
  38. package/dist/agent/DiagramLegend.md +22 -1
  39. package/dist/agent/DiagramMinimap.md +24 -4
  40. package/dist/agent/DiagramToolbar.md +23 -1
  41. package/dist/agent/Dialog.md +26 -3
  42. package/dist/agent/DropdownMenu.md +45 -6
  43. package/dist/agent/EmptyState.md +26 -1
  44. package/dist/agent/ErrorState.md +27 -2
  45. package/dist/agent/Facet.md +25 -1
  46. package/dist/agent/Field.md +45 -4
  47. package/dist/agent/FigureBand.md +24 -1
  48. package/dist/agent/FloatingIconButton.md +23 -1
  49. package/dist/agent/FunnelChart.md +23 -1
  50. package/dist/agent/Heading.md +64 -0
  51. package/dist/agent/Heatmap.md +25 -1
  52. package/dist/agent/Histogram.md +26 -2
  53. package/dist/agent/Input.md +22 -1
  54. package/dist/agent/Kbd.md +19 -1
  55. package/dist/agent/LifecycleFigure.md +22 -1
  56. package/dist/agent/LineChart.md +26 -2
  57. package/dist/agent/LinkArrow.md +20 -1
  58. package/dist/agent/Markdown.md +68 -0
  59. package/dist/agent/NativeSelect.md +23 -2
  60. package/dist/agent/NavItem.md +21 -1
  61. package/dist/agent/Pagination.md +28 -2
  62. package/dist/agent/PieChart.md +26 -2
  63. package/dist/agent/Popover.md +23 -1
  64. package/dist/agent/Progress.md +27 -3
  65. package/dist/agent/RadarChart.md +26 -2
  66. package/dist/agent/RadialChart.md +26 -3
  67. package/dist/agent/RadioGroup.md +23 -2
  68. package/dist/agent/SankeyChart.md +25 -2
  69. package/dist/agent/ScatterChart.md +27 -2
  70. package/dist/agent/ScrollArea.md +24 -3
  71. package/dist/agent/SearchableMenu.md +23 -1
  72. package/dist/agent/Select.md +29 -3
  73. package/dist/agent/Separator.md +28 -4
  74. package/dist/agent/SequenceFigure.md +23 -1
  75. package/dist/agent/Sheet.md +25 -2
  76. package/dist/agent/Skeleton.md +24 -2
  77. package/dist/agent/Slider.md +27 -4
  78. package/dist/agent/Sparkline.md +23 -1
  79. package/dist/agent/Spinner.md +24 -2
  80. package/dist/agent/StatusDot.md +20 -1
  81. package/dist/agent/StatusPill.md +28 -2
  82. package/dist/agent/Steps.md +24 -1
  83. package/dist/agent/Switch.md +21 -1
  84. package/dist/agent/Table.md +26 -3
  85. package/dist/agent/Tabs.md +24 -2
  86. package/dist/agent/Tag.md +40 -9
  87. package/dist/agent/Text.md +65 -0
  88. package/dist/agent/Textarea.md +20 -1
  89. package/dist/agent/Timestamp.md +64 -0
  90. package/dist/agent/Toast.md +26 -2
  91. package/dist/agent/ToggleGroup.md +24 -2
  92. package/dist/agent/Toolbar.md +71 -0
  93. package/dist/agent/Tooltip.md +23 -1
  94. package/dist/agent/TreemapChart.md +24 -1
  95. package/dist/agent/WaterfallChart.md +24 -1
  96. package/dist/agent/WorkflowFigure.md +24 -2
  97. package/dist/agent/catalog.json +5602 -745
  98. package/dist/agent/index.md +13 -4
  99. package/dist/article.css +16 -4
  100. package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
  101. package/dist/charts/AreaChart/AreaChart.js +12 -13
  102. package/dist/charts/AreaChart/AreaChart.js.map +1 -1
  103. package/dist/charts/BarChart/BarChart.d.ts +14 -3
  104. package/dist/charts/BarChart/BarChart.js +20 -12
  105. package/dist/charts/BarChart/BarChart.js.map +1 -1
  106. package/dist/charts/BarList/BarList.d.ts +9 -1
  107. package/dist/charts/BarList/BarList.js +9 -1
  108. package/dist/charts/BarList/BarList.js.map +1 -1
  109. package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
  110. package/dist/charts/BigNumber/BigNumber.js +25 -5
  111. package/dist/charts/BigNumber/BigNumber.js.map +1 -1
  112. package/dist/charts/BoxPlot/BoxPlot.js +4 -2
  113. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
  114. package/dist/charts/BulletChart/BulletChart.js +28 -7
  115. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  116. package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
  117. package/dist/charts/ComposedChart/ComposedChart.js +10 -10
  118. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
  119. package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
  120. package/dist/charts/FunnelChart/FunnelChart.js +6 -16
  121. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
  122. package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
  123. package/dist/charts/Heatmap/Heatmap.js +20 -11
  124. package/dist/charts/Heatmap/Heatmap.js.map +1 -1
  125. package/dist/charts/Histogram/Histogram.d.ts +24 -1
  126. package/dist/charts/Histogram/Histogram.js +43 -11
  127. package/dist/charts/Histogram/Histogram.js.map +1 -1
  128. package/dist/charts/LineChart/LineChart.d.ts +9 -2
  129. package/dist/charts/LineChart/LineChart.js +11 -12
  130. package/dist/charts/LineChart/LineChart.js.map +1 -1
  131. package/dist/charts/PieChart/PieChart.d.ts +15 -3
  132. package/dist/charts/PieChart/PieChart.js +14 -6
  133. package/dist/charts/PieChart/PieChart.js.map +1 -1
  134. package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
  135. package/dist/charts/RadarChart/RadarChart.js +11 -9
  136. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  137. package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
  138. package/dist/charts/RadialChart/RadialChart.js +28 -6
  139. package/dist/charts/RadialChart/RadialChart.js.map +1 -1
  140. package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
  141. package/dist/charts/SankeyChart/SankeyChart.js +14 -6
  142. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
  143. package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
  144. package/dist/charts/ScatterChart/ScatterChart.js +21 -15
  145. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
  146. package/dist/charts/Sparkline/Sparkline.js +5 -3
  147. package/dist/charts/Sparkline/Sparkline.js.map +1 -1
  148. package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
  149. package/dist/charts/TreemapChart/TreemapChart.js +20 -11
  150. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
  151. package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
  152. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
  153. package/dist/charts/lib/export.d.ts +1 -0
  154. package/dist/charts/lib/figure.d.ts +21 -1
  155. package/dist/charts/lib/figure.js +5 -2
  156. package/dist/charts/lib/figure.js.map +1 -1
  157. package/dist/charts/lib/scale.d.ts +23 -0
  158. package/dist/charts/lib/scale.js +13 -0
  159. package/dist/charts/lib/scale.js.map +1 -0
  160. package/dist/charts/lib/selection.d.ts +20 -0
  161. package/dist/charts/lib/selection.js +18 -0
  162. package/dist/charts/lib/selection.js.map +1 -0
  163. package/dist/charts/lib/toolbar.d.ts +1 -0
  164. package/dist/components/Accordion/Accordion.js +8 -1
  165. package/dist/components/Accordion/Accordion.js.map +1 -1
  166. package/dist/components/AppShell/AppShell.d.ts +6 -0
  167. package/dist/components/AppShell/AppShell.js +34 -4
  168. package/dist/components/AppShell/AppShell.js.map +1 -1
  169. package/dist/components/Article/Article.d.ts +11 -4
  170. package/dist/components/Article/Article.js +13 -0
  171. package/dist/components/Article/Article.js.map +1 -1
  172. package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
  173. package/dist/components/AspectRatio/AspectRatio.js +33 -0
  174. package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
  175. package/dist/components/Avatar/Avatar.d.ts +12 -3
  176. package/dist/components/Avatar/Avatar.js +6 -1
  177. package/dist/components/Avatar/Avatar.js.map +1 -1
  178. package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
  179. package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
  180. package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
  181. package/dist/components/Button/Button.js +14 -0
  182. package/dist/components/Button/Button.js.map +1 -1
  183. package/dist/components/Calendar/Calendar.d.ts +29 -3
  184. package/dist/components/Calendar/Calendar.js +60 -12
  185. package/dist/components/Calendar/Calendar.js.map +1 -1
  186. package/dist/components/Card/Card.d.ts +6 -0
  187. package/dist/components/Card/Card.js +1 -1
  188. package/dist/components/Card/Card.js.map +1 -1
  189. package/dist/components/Checkbox/Checkbox.d.ts +5 -2
  190. package/dist/components/Checkbox/Checkbox.js +12 -2
  191. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  192. package/dist/components/Code/Code.d.ts +30 -0
  193. package/dist/components/Code/Code.js +21 -0
  194. package/dist/components/Code/Code.js.map +1 -0
  195. package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
  196. package/dist/components/CodeBlock/CodeBlock.js +123 -0
  197. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  198. package/dist/components/Collapsible/Collapsible.js +10 -2
  199. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  200. package/dist/components/Combobox/Combobox.d.ts +15 -1
  201. package/dist/components/Combobox/Combobox.js +25 -5
  202. package/dist/components/Combobox/Combobox.js.map +1 -1
  203. package/dist/components/Command/Command.d.ts +4 -2
  204. package/dist/components/Command/Command.js +13 -1
  205. package/dist/components/Command/Command.js.map +1 -1
  206. package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
  207. package/dist/components/ContextMenu/ContextMenu.js +32 -2
  208. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  209. package/dist/components/DatePicker/DatePicker.d.ts +36 -4
  210. package/dist/components/DatePicker/DatePicker.js +55 -12
  211. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  212. package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
  213. package/dist/components/DescriptionList/DescriptionList.js +35 -0
  214. package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
  215. package/dist/components/Diagram/Diagram.d.ts +8 -3
  216. package/dist/components/Diagram/Diagram.js +82 -10
  217. package/dist/components/Diagram/Diagram.js.map +1 -1
  218. package/dist/components/Dialog/Dialog.d.ts +18 -3
  219. package/dist/components/Dialog/Dialog.js +20 -2
  220. package/dist/components/Dialog/Dialog.js.map +1 -1
  221. package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
  222. package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
  223. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  224. package/dist/components/EmptyState/EmptyState.d.ts +25 -1
  225. package/dist/components/EmptyState/EmptyState.js +3 -1
  226. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  227. package/dist/components/ErrorState/ErrorState.d.ts +22 -1
  228. package/dist/components/ErrorState/ErrorState.js +11 -2
  229. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  230. package/dist/components/Field/Field.d.ts +70 -3
  231. package/dist/components/Field/Field.js +53 -12
  232. package/dist/components/Field/Field.js.map +1 -1
  233. package/dist/components/Field/field-control.d.ts +31 -0
  234. package/dist/components/Field/field-control.js +12 -0
  235. package/dist/components/Field/field-control.js.map +1 -0
  236. package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
  237. package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
  238. package/dist/components/Heading/Heading.d.ts +62 -0
  239. package/dist/components/Heading/Heading.js +44 -0
  240. package/dist/components/Heading/Heading.js.map +1 -0
  241. package/dist/components/Markdown/Markdown.d.ts +139 -0
  242. package/dist/components/Markdown/Markdown.js +135 -0
  243. package/dist/components/Markdown/Markdown.js.map +1 -0
  244. package/dist/components/Markdown/inline.d.ts +29 -0
  245. package/dist/components/Markdown/inline.js +74 -0
  246. package/dist/components/Markdown/inline.js.map +1 -0
  247. package/dist/components/Markdown/nodes.d.ts +65 -0
  248. package/dist/components/Markdown/nodes.js +1 -0
  249. package/dist/components/Markdown/nodes.js.map +1 -0
  250. package/dist/components/Markdown/parse.d.ts +24 -0
  251. package/dist/components/Markdown/parse.js +110 -0
  252. package/dist/components/Markdown/parse.js.map +1 -0
  253. package/dist/components/Markdown/slug.d.ts +15 -0
  254. package/dist/components/Markdown/slug.js +7 -0
  255. package/dist/components/Markdown/slug.js.map +1 -0
  256. package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
  257. package/dist/components/NativeSelect/NativeSelect.js +2 -3
  258. package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
  259. package/dist/components/Pagination/Pagination.d.ts +24 -1
  260. package/dist/components/Pagination/Pagination.js +6 -3
  261. package/dist/components/Pagination/Pagination.js.map +1 -1
  262. package/dist/components/Progress/Progress.d.ts +17 -5
  263. package/dist/components/Progress/Progress.js +18 -6
  264. package/dist/components/Progress/Progress.js.map +1 -1
  265. package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
  266. package/dist/components/RadioGroup/RadioGroup.js +11 -1
  267. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  268. package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
  269. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  270. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  271. package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
  272. package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
  273. package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
  274. package/dist/components/Select/Select.d.ts +18 -2
  275. package/dist/components/Select/Select.js +20 -3
  276. package/dist/components/Select/Select.js.map +1 -1
  277. package/dist/components/Separator/Separator.d.ts +21 -3
  278. package/dist/components/Separator/Separator.js +9 -1
  279. package/dist/components/Separator/Separator.js.map +1 -1
  280. package/dist/components/Sheet/Sheet.d.ts +3 -0
  281. package/dist/components/Sheet/Sheet.js +11 -2
  282. package/dist/components/Sheet/Sheet.js.map +1 -1
  283. package/dist/components/Skeleton/Skeleton.d.ts +8 -1
  284. package/dist/components/Skeleton/Skeleton.js +1 -1
  285. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  286. package/dist/components/Slider/Slider.d.ts +21 -3
  287. package/dist/components/Slider/Slider.js +54 -27
  288. package/dist/components/Slider/Slider.js.map +1 -1
  289. package/dist/components/Spinner/Spinner.d.ts +8 -0
  290. package/dist/components/Spinner/Spinner.js +3 -2
  291. package/dist/components/Spinner/Spinner.js.map +1 -1
  292. package/dist/components/StatusPill/StatusPill.d.ts +5 -0
  293. package/dist/components/StatusPill/StatusPill.js +6 -0
  294. package/dist/components/StatusPill/StatusPill.js.map +1 -1
  295. package/dist/components/Table/Table.d.ts +6 -0
  296. package/dist/components/Table/Table.js +3 -1
  297. package/dist/components/Table/Table.js.map +1 -1
  298. package/dist/components/Tag/Tag.d.ts +68 -9
  299. package/dist/components/Tag/Tag.js +50 -10
  300. package/dist/components/Tag/Tag.js.map +1 -1
  301. package/dist/components/Text/Text.d.ts +62 -0
  302. package/dist/components/Text/Text.js +30 -0
  303. package/dist/components/Text/Text.js.map +1 -0
  304. package/dist/components/Timestamp/Timestamp.d.ts +59 -0
  305. package/dist/components/Timestamp/Timestamp.js +56 -0
  306. package/dist/components/Timestamp/Timestamp.js.map +1 -0
  307. package/dist/components/Toast/Toast.d.ts +9 -1
  308. package/dist/components/Toast/Toast.js +27 -4
  309. package/dist/components/Toast/Toast.js.map +1 -1
  310. package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
  311. package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
  312. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  313. package/dist/components/Toolbar/Toolbar.d.ts +55 -0
  314. package/dist/components/Toolbar/Toolbar.js +43 -0
  315. package/dist/components/Toolbar/Toolbar.js.map +1 -0
  316. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
  317. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
  318. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
  319. package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
  320. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
  321. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
  322. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
  323. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
  324. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
  325. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
  326. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
  327. package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
  328. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
  329. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
  330. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
  331. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
  332. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
  333. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
  334. package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
  335. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
  336. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
  337. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
  338. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
  339. package/dist/diagrams/index.d.ts +1 -0
  340. package/dist/diagrams/index.js +10 -0
  341. package/dist/diagrams/index.js.map +1 -1
  342. package/dist/diagrams/lib/dev.d.ts +61 -0
  343. package/dist/diagrams/lib/dev.js +96 -0
  344. package/dist/diagrams/lib/dev.js.map +1 -0
  345. package/dist/diagrams/lib/frame.d.ts +48 -7
  346. package/dist/diagrams/lib/frame.js +17 -2
  347. package/dist/diagrams/lib/frame.js.map +1 -1
  348. package/dist/diagrams/lib/wires.js.map +1 -1
  349. package/dist/diagrams/spec.d.ts +64 -12
  350. package/dist/index.d.ts +16 -4
  351. package/dist/index.js +9 -0
  352. package/dist/index.js.map +1 -1
  353. package/dist/keyframes.css +57 -3
  354. package/dist/lib/overlay-container.d.ts +14 -4
  355. package/dist/lib/overlay-container.js.map +1 -1
  356. package/dist/lib/warn.d.ts +73 -0
  357. package/dist/lib/warn.js +38 -0
  358. package/dist/lib/warn.js.map +1 -0
  359. package/dist/semantic.css +17 -6
  360. package/dist/styles.css +1 -1
  361. package/dist/tokens.css +20 -2
  362. package/dist/tokens.d.ts +1 -1
  363. package/dist/tokens.js +39 -36
  364. package/dist/tokens.json +39 -36
  365. package/llms.txt +35 -0
  366. package/package.json +5 -2
  367. package/skills/misoto22-design/evals/evals.json +84 -0
  368. package/skills/misoto22-design/rules/a11y.md +55 -3
  369. package/skills/misoto22-design/rules/forms.md +5 -0
@@ -2,9 +2,9 @@ import * as react from 'react';
2
2
  import { ReactNode, FC } from 'react';
3
3
  import { SankeyData, SankeyProps } from 'recharts';
4
4
  import { ChartConfig } from '../lib/chart.js';
5
+ import { ChartEmptyProps } from '../lib/empty.js';
5
6
  import { ChartBackgroundVariant } from '../lib/background.js';
6
7
  import { ChartTooltipSlotProps } from '../AreaChart/AreaChart.js';
7
- import '../lib/empty.js';
8
8
  import '../lib/values.js';
9
9
  import '../lib/sonify-control.js';
10
10
  import '../lib/sonify.js';
@@ -69,10 +69,17 @@ interface SankeyChartProps {
69
69
  /** How nodes are distributed within a column. */
70
70
  verticalAlign?: 'justify' | 'top';
71
71
  /**
72
- * The node lit on first render. Selecting one dims every flow it does not
73
- * touch.
72
+ * The node lit on first render, when the chart keeps its own selection.
73
+ * Selecting one dims every flow it does not touch.
74
74
  */
75
75
  defaultSelectedNode?: string | null;
76
+ /**
77
+ * The selected node, driven from outside.
78
+ *
79
+ * Give this and the chart follows it; leave it undefined and the chart keeps
80
+ * its own, starting from `defaultSelectedNode`.
81
+ */
82
+ selectedNode?: string | null;
76
83
  /** Fires when the selection changes, and with null when it is cleared. */
77
84
  onSelectionChange?: (selection: {
78
85
  name: string;
@@ -88,6 +95,11 @@ interface SankeyChartProps {
88
95
  * itself.
89
96
  */
90
97
  hideDataTable?: boolean;
98
+ /**
99
+ * What the chart shows when it has nothing to draw. `false` keeps the empty
100
+ * plot, for a chart whose emptiness is itself the reading.
101
+ */
102
+ empty?: ChartEmptyProps | false;
91
103
  }
92
104
  /**
93
105
  * Where a quantity goes as it moves through stages — the shape for a funnel, a
@@ -106,7 +118,7 @@ interface SankeyChartProps {
106
118
  * <SankeyChart.Tooltip />
107
119
  * </SankeyChart>
108
120
  */
109
- declare function SankeyChart({ data, config, title, showTitle, description, children, className, sankeyProps, nodeWidth, nodePadding, linkCurvature, iterations, sort, align, verticalAlign, defaultSelectedNode, onSelectionChange, isLoading, hideDataTable, }: SankeyChartProps): react.JSX.Element;
121
+ declare function SankeyChart({ data, config, title, showTitle, description, children, className, sankeyProps, nodeWidth, nodePadding, linkCurvature, iterations, sort, align, verticalAlign, defaultSelectedNode, selectedNode: controlledNode, onSelectionChange, isLoading, hideDataTable, empty, }: SankeyChartProps): react.JSX.Element;
110
122
  declare namespace SankeyChart {
111
123
  var Node: FC<NodeProps>;
112
124
  var NodeLabel: FC<NodeLabelProps>;
@@ -7,8 +7,7 @@ import {
7
7
  useCallback,
8
8
  useContext,
9
9
  useId,
10
- useMemo,
11
- useState
10
+ useMemo
12
11
  } from "react";
13
12
  import {
14
13
  Layer,
@@ -17,6 +16,8 @@ import {
17
16
  import { motion, useReducedMotion } from "motion/react";
18
17
  import { ChartContainer, colorStops, cssName } from "../lib/chart.js";
19
18
  import { ChartFigure } from "../lib/figure.js";
19
+ import {} from "../lib/empty.js";
20
+ import { useChartSelection } from "../lib/selection.js";
20
21
  import { ChartBackground } from "../lib/background.js";
21
22
  import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
22
23
  import { LoadingIndicator } from "../lib/loading.js";
@@ -45,20 +46,25 @@ function SankeyChart({
45
46
  align = "justify",
46
47
  verticalAlign = "justify",
47
48
  defaultSelectedNode = null,
49
+ selectedNode: controlledNode,
48
50
  onSelectionChange,
49
51
  isLoading = false,
50
- hideDataTable = false
52
+ hideDataTable = false,
53
+ empty
51
54
  }) {
52
55
  const chartId = useId().replace(/:/g, "");
53
- const [selectedNode, setSelectedNode] = useState(defaultSelectedNode);
54
- const selectNode = useCallback(
56
+ const report = useCallback(
55
57
  (name) => {
56
- setSelectedNode(name);
57
58
  if (!onSelectionChange) return;
58
59
  onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) });
59
60
  },
60
61
  [data, onSelectionChange]
61
62
  );
63
+ const [selectedNode, selectNode] = useChartSelection(
64
+ controlledNode,
65
+ defaultSelectedNode,
66
+ report
67
+ );
62
68
  const context = useMemo(
63
69
  () => ({ data, config, chartId, isLoading, selectedNode, selectNode }),
64
70
  [chartId, config, data, isLoading, selectNode, selectedNode]
@@ -86,6 +92,8 @@ function SankeyChart({
86
92
  { key: "value", label: "Value" }
87
93
  ]
88
94
  },
95
+ isEmpty: !isLoading && (data.nodes.length === 0 || data.links.length === 0),
96
+ empty,
89
97
  children: [
90
98
  isLoading ? /* @__PURE__ */ jsx("div", { className: "relative aspect-video w-full", children: /* @__PURE__ */ jsx(
91
99
  "svg",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/SankeyChart/SankeyChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Layer,\n Sankey as RechartsSankey,\n type SankeyData,\n type SankeyLinkProps,\n type SankeyNodeProps,\n type SankeyProps,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** One full skeleton pulse, in seconds. */\nconst LOADING_PULSE = 2\n\n/**\n * How a flow band is coloured.\n *\n * `gradient` fades from the source's colour to the target's, which is the one\n * that actually reads as flow. `source` and `target` attribute the whole band\n * to one end — useful when the question is \"where did this come from\" rather\n * than \"what became of it\". `solid` gives up colour entirely and lets the\n * node rectangles carry identity.\n */\nexport type SankeyLinkVariant = 'gradient' | 'solid' | 'source' | 'target'\n\n/** Where a node's label sits. */\nexport type SankeyLabelPosition = 'inside' | 'outside'\n\ninterface SankeyChartContextValue {\n data: SankeyData\n config: ChartConfig\n chartId: string\n isLoading: boolean\n selectedNode: string | null\n selectNode: (name: string | null) => void\n}\n\nconst SankeyChartContext = createContext<SankeyChartContextValue | null>(null)\n\nfunction useSankeyChart(): SankeyChartContextValue {\n const context = useContext(SankeyChartContext)\n if (!context) throw new Error('A sankey chart part must be rendered inside <SankeyChart>')\n return context\n}\n\nexport interface SankeyChartProps {\n /** The nodes and the links between them, in Recharts' own shape. */\n data: SankeyData\n /** Node names → their label and paint. */\n config: ChartConfig\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts Sankey element. */\n sankeyProps?: Omit<SankeyProps, 'data'>\n /** How wide each node rectangle is, in pixels. */\n nodeWidth?: number\n /** Vertical gap between nodes in the same column, in pixels. */\n nodePadding?: number\n /** 0 draws straight links, 1 the fullest curve. */\n linkCurvature?: number\n /** Layout passes. More is tidier and slower. */\n iterations?: number\n /** Lets the layout reorder nodes for the fewest crossings. */\n sort?: boolean\n /** How nodes are placed along the flow axis. */\n align?: 'left' | 'justify'\n /** How nodes are distributed within a column. */\n verticalAlign?: 'justify' | 'top'\n /**\n * The node lit on first render. Selecting one dims every flow it does not\n * touch.\n */\n defaultSelectedNode?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n}\n\n/**\n * Where a quantity goes as it moves through stages — the shape for a funnel, a\n * budget, an energy or traffic breakdown.\n *\n * The only chart here whose data is a GRAPH rather than a table, so it takes\n * `{ nodes, links }` instead of rows, and the table view lists the flows rather\n * than the nodes.\n *\n * @example\n * <SankeyChart title=\"Traffic by source and outcome\" config={config} data={data}>\n * <SankeyChart.Node radius={2}>\n * <SankeyChart.NodeLabel position=\"outside\" showValues />\n * </SankeyChart.Node>\n * <SankeyChart.Link variant=\"gradient\" />\n * <SankeyChart.Tooltip />\n * </SankeyChart>\n */\nexport function SankeyChart({\n data,\n config,\n title,\n showTitle,\n description,\n children,\n className,\n sankeyProps,\n nodeWidth = 10,\n nodePadding = 10,\n linkCurvature = 0.5,\n iterations = 32,\n sort = true,\n align = 'justify',\n verticalAlign = 'justify',\n defaultSelectedNode = null,\n onSelectionChange,\n isLoading = false,\n hideDataTable = false,\n}: SankeyChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedNode, setSelectedNode] = useState<string | null>(defaultSelectedNode)\n\n const selectNode = useCallback(\n (name: string | null) => {\n setSelectedNode(name)\n if (!onSelectionChange) return\n onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) })\n },\n [data, onSelectionChange],\n )\n\n const context = useMemo<SankeyChartContextValue>(\n () => ({ data, config, chartId, isLoading, selectedNode, selectNode }),\n [chartId, config, data, isLoading, selectNode, selectedNode],\n )\n\n // The flows, not the nodes: a link IS the fact a sankey states, and a table\n // of node totals would lose every \"from → to\" the diagram is drawn to show.\n const flows = useMemo(\n () =>\n data.links.map((link) => ({\n from: String(data.nodes[link.source]?.name ?? link.source),\n to: String(data.nodes[link.target]?.name ?? link.target),\n value: link.value,\n })),\n [data],\n )\n\n return (\n <SankeyChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: flows,\n rowKey: 'from',\n columns: [\n { key: 'to', label: 'To' },\n { key: 'value', label: 'Value' },\n ],\n }\n }\n >\n {isLoading ? (\n <div className=\"relative aspect-video w-full\">\n <svg\n viewBox=\"0 0 500 250\"\n preserveAspectRatio=\"xMidYMid meet\"\n width=\"100%\"\n height=\"100%\"\n aria-hidden\n className=\"absolute inset-0 text-(--ink)\"\n >\n <LoadingSankey />\n </svg>\n </div>\n ) : (\n <ChartContainer config={config}>\n <RechartsSankey\n id={chartId}\n data={data}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n linkCurvature={linkCurvature}\n iterations={iterations}\n sort={sort}\n align={align}\n verticalAlign={verticalAlign}\n {...resolveRenderers(children)}\n {...sankeyProps}\n >\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-node-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsSankey>\n </ChartContainer>\n )}\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </SankeyChartContext.Provider>\n )\n}\n\nexport interface NodeProps {\n radius?: number\n isClickable?: boolean\n /** `<SankeyChart.NodeLabel>`. */\n children?: ReactNode\n}\n\n/** How the node rectangles render. A slot: the root reads it. */\nconst Node: FC<NodeProps> = () => null\n\nexport interface NodeLabelProps {\n position?: SankeyLabelPosition\n /** Prints each node's total flow under its name. */\n showValues?: boolean\n valueFormatter?: (value: number) => string\n}\n\n/** Labels for the nodes. A slot, composed inside `<Node>`. */\nconst NodeLabel: FC<NodeLabelProps> = () => null\n\nexport interface LinkProps {\n variant?: SankeyLinkVariant\n /** Thins each band where it meets a node, so neighbours stay separable. */\n verticalPadding?: number\n}\n\n/** How the flow bands render. A slot: the root reads it. */\nconst Link: FC<LinkProps> = () => null\n\n/** The hover panel. No heading: the node's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n content={\n <ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The decorative plate behind the diagram. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A node's total flow — outgoing, or incoming for a leaf. */\nfunction nodeValue(data: SankeyData, name: string): number {\n const index = data.nodes.findIndex((node) => node.name === name)\n if (index === -1) return 0\n\n const out = data.links\n .filter((link) => link.source === index)\n .reduce((sum, link) => sum + link.value, 0)\n const incoming = data.links\n .filter((link) => link.target === index)\n .reduce((sum, link) => sum + link.value, 0)\n\n return out > 0 ? out : incoming\n}\n\n/** Turns the composed `<Node>` and `<Link>` slots into Recharts renderers. */\nfunction resolveRenderers(children: ReactNode): Pick<SankeyProps, 'node' | 'link'> {\n let node: NodeProps | null = null\n let link: LinkProps | null = null\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n if (child.type === Node) node = (child as ReactElement<NodeProps>).props\n if (child.type === Link) link = (child as ReactElement<LinkProps>).props\n })\n\n return {\n node: (props: SankeyNodeProps) => <SankeyNodeShape {...props} nodeSlot={node} />,\n link: (props: SankeyLinkProps) => <SankeyLinkShape {...props} linkSlot={link} />,\n }\n}\n\n/** Reads the `<NodeLabel>` composed inside a `<Node>`, if any. */\nfunction resolveNodeLabel(children: ReactNode): NodeLabelProps | null {\n let label: NodeLabelProps | null = null\n\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === NodeLabel) {\n label = (child as ReactElement<NodeLabelProps>).props\n }\n })\n\n return label\n}\n\n/** Whether a node is the selected one, or one hop from it. */\nfunction isConnected(data: SankeyData, selected: string | null, name: string): boolean {\n if (selected === null || selected === name) return true\n\n const selectedIndex = data.nodes.findIndex((node) => node.name === selected)\n const nodeIndex = data.nodes.findIndex((node) => node.name === name)\n\n return data.links.some(\n (link) =>\n (link.source === selectedIndex && link.target === nodeIndex) ||\n (link.source === nodeIndex && link.target === selectedIndex),\n )\n}\n\n/** One node rectangle, with its label and value. */\nfunction SankeyNodeShape({\n x,\n y,\n width,\n height,\n payload,\n nodeSlot,\n}: SankeyNodeProps & { nodeSlot: NodeProps | null }) {\n const { config, chartId, data, selectedNode, selectNode } = useSankeyChart()\n\n const radius = nodeSlot?.radius ?? 0\n const isClickable = nodeSlot?.isClickable ?? false\n const label = resolveNodeLabel(nodeSlot?.children)\n\n const name = payload.name\n const value = payload.value\n const icon = (payload as { icon?: ReactNode }).icon\n\n const dimmed = isClickable && !isConnected(data, selectedNode, name)\n const painted = name in config\n const text = config[name]?.label ?? name\n\n const format = label?.valueFormatter ?? ((n: number) => n.toLocaleString())\n const showValues = label?.showValues ?? false\n\n const centreX = x + width / 2\n const centreY = showValues ? y + height / 2 - 8 : y + height / 2\n const valueY = y + height / 2 + 8\n const outsideX = x + width + 8\n const outsideY = y + height / 2\n\n return (\n <Layer>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={painted ? `url(#${chartId}-node-${cssName(name)})` : 'currentColor'}\n fillOpacity={dimmed ? 0.15 : 0.9}\n className=\"text-(--ink) transition-opacity duration-(--duration-base)\"\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectNode(selectedNode === name ? null : name)\n }}\n />\n {label?.position === 'inside' && (\n <>\n {/* A scrim inside the node, so the label reads against whatever the\n node's own gradient happens to be under it. */}\n <rect\n x={x + 1}\n y={y + 1}\n width={width - 2}\n height={height - 2}\n rx={Math.max(0, radius - 1)}\n ry={Math.max(0, radius - 1)}\n opacity={dimmed ? 0.3 : 1}\n className=\"fill-(--chart-surface)/60 transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n />\n {icon && (\n <foreignObject\n x={centreX - 8}\n y={centreY - 30}\n width={16}\n height={16}\n opacity={dimmed ? 0.3 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n >\n <div className=\"flex items-center justify-center text-(--ink-2)\">{icon}</div>\n </foreignObject>\n )}\n <text\n x={centreX}\n y={icon ? centreY - 4 : centreY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-[10px] font-medium transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 1}\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={centreX}\n y={valueY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) font-mono text-xs font-medium tabular-nums transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 0.6}\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n {label?.position === 'outside' && (\n <>\n <text\n x={outsideX}\n y={outsideY - (showValues ? 8 : 0)}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-xs\"\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={outsideX}\n y={outsideY + 8}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n opacity={0.5}\n className=\"fill-(--ink) font-mono text-xs tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n </Layer>\n )\n}\n\n/** One flow band. */\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n index,\n payload,\n linkSlot,\n}: SankeyLinkProps & { linkSlot: LinkProps | null }) {\n const { config, chartId, selectedNode } = useSankeyChart()\n\n const variant = linkSlot?.variant ?? 'gradient'\n const verticalPadding = linkSlot?.verticalPadding ?? 0\n\n const sourceName = payload.source.name\n const targetName = payload.target.name\n const lit =\n selectedNode === null || selectedNode === sourceName || selectedNode === targetName\n\n const half = Math.max(1, linkWidth - verticalPadding) / 2\n\n // Two mirrored cubics closed into a ribbon: the band has to be a filled shape\n // rather than a thick stroke, because its width changes along its length.\n const band = `M${sourceX},${sourceY - half}\n C${sourceControlX},${sourceY - half} ${targetControlX},${targetY - half} ${targetX},${targetY - half}\n L${targetX},${targetY + half}\n C${targetControlX},${targetY + half} ${sourceControlX},${sourceY + half} ${sourceX},${sourceY + half}\n Z`\n\n const sourceColor = sourceName in config ? `var(--color-${cssName(sourceName)}-0)` : 'currentColor'\n const targetColor = targetName in config ? `var(--color-${cssName(targetName)}-0)` : 'currentColor'\n\n return (\n <Layer className=\"text-(--ink)\">\n <defs>\n {variant === 'gradient' && (\n <linearGradient id={`${chartId}-link-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor={sourceColor} stopOpacity={0.2} />\n <stop offset=\"50%\" stopColor={sourceColor} stopOpacity={0.5} />\n <stop offset=\"100%\" stopColor={targetColor} stopOpacity={0.2} />\n </linearGradient>\n )}\n {/* The edge that traces a band connected to the selection. It fades out\n at both ends so it reads as a highlight rather than as an outline. */}\n <linearGradient id={`${chartId}-link-edge-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n <stop offset=\"15%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"50%\" stopColor=\"var(--accent)\" stopOpacity={1} />\n <stop offset=\"85%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"100%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n </linearGradient>\n </defs>\n <path\n d={band}\n fill={linkFill(variant, chartId, index, config, sourceName, targetName)}\n // Read from the ground rather than fixed: a band at 0.4 of ink over\n // paper reads; the same 0.4 of paper-white over near-black does not.\n fillOpacity={lit ? 'calc(var(--chart-fill) * 2.6)' : 'calc(var(--chart-fill) * 0.7)'}\n stroke={selectedNode !== null && lit ? `url(#${chartId}-link-edge-${index})` : 'none'}\n strokeWidth={1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </Layer>\n )\n}\n\n/** The paint reference for a band, given its variant. */\nfunction linkFill(\n variant: SankeyLinkVariant,\n chartId: string,\n index: number,\n config: ChartConfig,\n sourceName: string,\n targetName: string,\n): string {\n switch (variant) {\n case 'gradient':\n return `url(#${chartId}-link-${index})`\n case 'source':\n return sourceName in config ? `url(#${chartId}-node-${cssName(sourceName)})` : 'currentColor'\n case 'target':\n return targetName in config ? `url(#${chartId}-node-${cssName(targetName)})` : 'currentColor'\n default:\n return 'currentColor'\n }\n}\n\n/** The skeleton's node columns. Fixed, so the shape reads as a sankey at once. */\nconst SKELETON_NODES = [\n { x: 30, y: 25, width: 12, height: 65, delay: 0 },\n { x: 30, y: 110, width: 12, height: 50, delay: 0.3 },\n { x: 30, y: 180, width: 12, height: 45, delay: 0.15 },\n { x: 244, y: 20, width: 12, height: 55, delay: 0.45 },\n { x: 244, y: 95, width: 12, height: 75, delay: 0.6 },\n { x: 244, y: 190, width: 12, height: 40, delay: 0.25 },\n { x: 458, y: 35, width: 12, height: 80, delay: 0.5 },\n { x: 458, y: 135, width: 12, height: 90, delay: 0.1 },\n]\n\nconst SKELETON_LINKS = [\n { from: 0, to: 3, width: 26, delay: 0.2 },\n { from: 0, to: 4, width: 18, delay: 0.7 },\n { from: 1, to: 4, width: 24, delay: 0.4 },\n { from: 1, to: 5, width: 12, delay: 0.9 },\n { from: 2, to: 4, width: 16, delay: 0.1 },\n { from: 2, to: 5, width: 14, delay: 0.55 },\n { from: 3, to: 6, width: 22, delay: 0.35 },\n { from: 3, to: 7, width: 18, delay: 0.8 },\n { from: 4, to: 6, width: 28, delay: 0.05 },\n { from: 4, to: 7, width: 32, delay: 0.65 },\n { from: 5, to: 7, width: 16, delay: 0.45 },\n]\n\n/** A bezier from one skeleton node's right edge to another's left. */\nfunction skeletonPath(fromIndex: number, toIndex: number): string {\n const from = SKELETON_NODES[fromIndex]!\n const to = SKELETON_NODES[toIndex]!\n const startX = from.x + from.width\n const startY = from.y + from.height / 2\n const endX = to.x\n const endY = to.y + to.height / 2\n return `M${startX},${startY} C${startX + (endX - startX) * 0.4},${startY} ${startX + (endX - startX) * 0.6},${endY} ${endX},${endY}`\n}\n\n/** The skeleton diagram: a fixed graph whose parts pulse out of phase. */\nfunction LoadingSankey() {\n const reduceMotion = useReducedMotion()\n\n if (reduceMotion) {\n return (\n <>\n {SKELETON_LINKS.map((link) => (\n <path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n opacity={0.09}\n />\n ))}\n {SKELETON_NODES.map((node) => (\n <rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n opacity={0.25}\n />\n ))}\n </>\n )\n }\n\n return (\n <>\n {SKELETON_LINKS.map((link, index) => (\n <motion.path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n initial={{ opacity: 0.04 }}\n animate={{ opacity: [0.04, 0.14, 0.04] }}\n transition={{\n duration: LOADING_PULSE * (0.8 + (index % 3) * 0.2),\n delay: link.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n {SKELETON_NODES.map((node, index) => (\n <motion.rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n initial={{ opacity: 0.15 }}\n animate={{ opacity: [0.15, 0.4, 0.15] }}\n transition={{\n duration: LOADING_PULSE * (0.9 + (index % 4) * 0.1),\n delay: node.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n </>\n )\n}\n\nSankeyChart.Node = Node\nSankeyChart.NodeLabel = NodeLabel\nSankeyChart.Link = Link\nSankeyChart.Tooltip = Tooltip\nSankeyChart.Background = Background\n\nexport default SankeyChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/SankeyChart/SankeyChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useId,\n useMemo,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Layer,\n Sankey as RechartsSankey,\n type SankeyData,\n type SankeyLinkProps,\n type SankeyNodeProps,\n type SankeyProps,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { useChartSelection } from '../lib/selection'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** One full skeleton pulse, in seconds. */\nconst LOADING_PULSE = 2\n\n/**\n * How a flow band is coloured.\n *\n * `gradient` fades from the source's colour to the target's, which is the one\n * that actually reads as flow. `source` and `target` attribute the whole band\n * to one end — useful when the question is \"where did this come from\" rather\n * than \"what became of it\". `solid` gives up colour entirely and lets the\n * node rectangles carry identity.\n */\nexport type SankeyLinkVariant = 'gradient' | 'solid' | 'source' | 'target'\n\n/** Where a node's label sits. */\nexport type SankeyLabelPosition = 'inside' | 'outside'\n\ninterface SankeyChartContextValue {\n data: SankeyData\n config: ChartConfig\n chartId: string\n isLoading: boolean\n selectedNode: string | null\n selectNode: (name: string | null) => void\n}\n\nconst SankeyChartContext = createContext<SankeyChartContextValue | null>(null)\n\nfunction useSankeyChart(): SankeyChartContextValue {\n const context = useContext(SankeyChartContext)\n if (!context) throw new Error('A sankey chart part must be rendered inside <SankeyChart>')\n return context\n}\n\nexport interface SankeyChartProps {\n /** The nodes and the links between them, in Recharts' own shape. */\n data: SankeyData\n /** Node names → their label and paint. */\n config: ChartConfig\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts Sankey element. */\n sankeyProps?: Omit<SankeyProps, 'data'>\n /** How wide each node rectangle is, in pixels. */\n nodeWidth?: number\n /** Vertical gap between nodes in the same column, in pixels. */\n nodePadding?: number\n /** 0 draws straight links, 1 the fullest curve. */\n linkCurvature?: number\n /** Layout passes. More is tidier and slower. */\n iterations?: number\n /** Lets the layout reorder nodes for the fewest crossings. */\n sort?: boolean\n /** How nodes are placed along the flow axis. */\n align?: 'left' | 'justify'\n /** How nodes are distributed within a column. */\n verticalAlign?: 'justify' | 'top'\n /**\n * The node lit on first render, when the chart keeps its own selection.\n * Selecting one dims every flow it does not touch.\n */\n defaultSelectedNode?: string | null\n /**\n * The selected node, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedNode`.\n */\n selectedNode?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the empty\n * plot, for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Where a quantity goes as it moves through stages — the shape for a funnel, a\n * budget, an energy or traffic breakdown.\n *\n * The only chart here whose data is a GRAPH rather than a table, so it takes\n * `{ nodes, links }` instead of rows, and the table view lists the flows rather\n * than the nodes.\n *\n * @example\n * <SankeyChart title=\"Traffic by source and outcome\" config={config} data={data}>\n * <SankeyChart.Node radius={2}>\n * <SankeyChart.NodeLabel position=\"outside\" showValues />\n * </SankeyChart.Node>\n * <SankeyChart.Link variant=\"gradient\" />\n * <SankeyChart.Tooltip />\n * </SankeyChart>\n */\nexport function SankeyChart({\n data,\n config,\n title,\n showTitle,\n description,\n children,\n className,\n sankeyProps,\n nodeWidth = 10,\n nodePadding = 10,\n linkCurvature = 0.5,\n iterations = 32,\n sort = true,\n align = 'justify',\n verticalAlign = 'justify',\n defaultSelectedNode = null,\n selectedNode: controlledNode,\n onSelectionChange,\n isLoading = false,\n hideDataTable = false,\n empty,\n}: SankeyChartProps) {\n const chartId = useId().replace(/:/g, '')\n\n const report = useCallback(\n (name: string | null) => {\n if (!onSelectionChange) return\n onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) })\n },\n [data, onSelectionChange],\n )\n\n const [selectedNode, selectNode] = useChartSelection(\n controlledNode,\n defaultSelectedNode,\n report,\n )\n\n const context = useMemo<SankeyChartContextValue>(\n () => ({ data, config, chartId, isLoading, selectedNode, selectNode }),\n [chartId, config, data, isLoading, selectNode, selectedNode],\n )\n\n // The flows, not the nodes: a link IS the fact a sankey states, and a table\n // of node totals would lose every \"from → to\" the diagram is drawn to show.\n const flows = useMemo(\n () =>\n data.links.map((link) => ({\n from: String(data.nodes[link.source]?.name ?? link.source),\n to: String(data.nodes[link.target]?.name ?? link.target),\n value: link.value,\n })),\n [data],\n )\n\n return (\n <SankeyChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: flows,\n rowKey: 'from',\n columns: [\n { key: 'to', label: 'To' },\n { key: 'value', label: 'Value' },\n ],\n }\n }\n isEmpty={!isLoading && (data.nodes.length === 0 || data.links.length === 0)}\n empty={empty}\n >\n {isLoading ? (\n <div className=\"relative aspect-video w-full\">\n <svg\n viewBox=\"0 0 500 250\"\n preserveAspectRatio=\"xMidYMid meet\"\n width=\"100%\"\n height=\"100%\"\n aria-hidden\n className=\"absolute inset-0 text-(--ink)\"\n >\n <LoadingSankey />\n </svg>\n </div>\n ) : (\n <ChartContainer config={config}>\n <RechartsSankey\n id={chartId}\n data={data}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n linkCurvature={linkCurvature}\n iterations={iterations}\n sort={sort}\n align={align}\n verticalAlign={verticalAlign}\n {...resolveRenderers(children)}\n {...sankeyProps}\n >\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-node-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsSankey>\n </ChartContainer>\n )}\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </SankeyChartContext.Provider>\n )\n}\n\nexport interface NodeProps {\n radius?: number\n isClickable?: boolean\n /** `<SankeyChart.NodeLabel>`. */\n children?: ReactNode\n}\n\n/** How the node rectangles render. A slot: the root reads it. */\nconst Node: FC<NodeProps> = () => null\n\nexport interface NodeLabelProps {\n position?: SankeyLabelPosition\n /** Prints each node's total flow under its name. */\n showValues?: boolean\n valueFormatter?: (value: number) => string\n}\n\n/** Labels for the nodes. A slot, composed inside `<Node>`. */\nconst NodeLabel: FC<NodeLabelProps> = () => null\n\nexport interface LinkProps {\n variant?: SankeyLinkVariant\n /** Thins each band where it meets a node, so neighbours stay separable. */\n verticalPadding?: number\n}\n\n/** How the flow bands render. A slot: the root reads it. */\nconst Link: FC<LinkProps> = () => null\n\n/** The hover panel. No heading: the node's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n content={\n <ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The decorative plate behind the diagram. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A node's total flow — outgoing, or incoming for a leaf. */\nfunction nodeValue(data: SankeyData, name: string): number {\n const index = data.nodes.findIndex((node) => node.name === name)\n if (index === -1) return 0\n\n const out = data.links\n .filter((link) => link.source === index)\n .reduce((sum, link) => sum + link.value, 0)\n const incoming = data.links\n .filter((link) => link.target === index)\n .reduce((sum, link) => sum + link.value, 0)\n\n return out > 0 ? out : incoming\n}\n\n/** Turns the composed `<Node>` and `<Link>` slots into Recharts renderers. */\nfunction resolveRenderers(children: ReactNode): Pick<SankeyProps, 'node' | 'link'> {\n let node: NodeProps | null = null\n let link: LinkProps | null = null\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n if (child.type === Node) node = (child as ReactElement<NodeProps>).props\n if (child.type === Link) link = (child as ReactElement<LinkProps>).props\n })\n\n return {\n node: (props: SankeyNodeProps) => <SankeyNodeShape {...props} nodeSlot={node} />,\n link: (props: SankeyLinkProps) => <SankeyLinkShape {...props} linkSlot={link} />,\n }\n}\n\n/** Reads the `<NodeLabel>` composed inside a `<Node>`, if any. */\nfunction resolveNodeLabel(children: ReactNode): NodeLabelProps | null {\n let label: NodeLabelProps | null = null\n\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === NodeLabel) {\n label = (child as ReactElement<NodeLabelProps>).props\n }\n })\n\n return label\n}\n\n/** Whether a node is the selected one, or one hop from it. */\nfunction isConnected(data: SankeyData, selected: string | null, name: string): boolean {\n if (selected === null || selected === name) return true\n\n const selectedIndex = data.nodes.findIndex((node) => node.name === selected)\n const nodeIndex = data.nodes.findIndex((node) => node.name === name)\n\n return data.links.some(\n (link) =>\n (link.source === selectedIndex && link.target === nodeIndex) ||\n (link.source === nodeIndex && link.target === selectedIndex),\n )\n}\n\n/** One node rectangle, with its label and value. */\nfunction SankeyNodeShape({\n x,\n y,\n width,\n height,\n payload,\n nodeSlot,\n}: SankeyNodeProps & { nodeSlot: NodeProps | null }) {\n const { config, chartId, data, selectedNode, selectNode } = useSankeyChart()\n\n const radius = nodeSlot?.radius ?? 0\n const isClickable = nodeSlot?.isClickable ?? false\n const label = resolveNodeLabel(nodeSlot?.children)\n\n const name = payload.name\n const value = payload.value\n const icon = (payload as { icon?: ReactNode }).icon\n\n const dimmed = isClickable && !isConnected(data, selectedNode, name)\n const painted = name in config\n const text = config[name]?.label ?? name\n\n const format = label?.valueFormatter ?? ((n: number) => n.toLocaleString())\n const showValues = label?.showValues ?? false\n\n const centreX = x + width / 2\n const centreY = showValues ? y + height / 2 - 8 : y + height / 2\n const valueY = y + height / 2 + 8\n const outsideX = x + width + 8\n const outsideY = y + height / 2\n\n return (\n <Layer>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={painted ? `url(#${chartId}-node-${cssName(name)})` : 'currentColor'}\n fillOpacity={dimmed ? 0.15 : 0.9}\n className=\"text-(--ink) transition-opacity duration-(--duration-base)\"\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectNode(selectedNode === name ? null : name)\n }}\n />\n {label?.position === 'inside' && (\n <>\n {/* A scrim inside the node, so the label reads against whatever the\n node's own gradient happens to be under it. */}\n <rect\n x={x + 1}\n y={y + 1}\n width={width - 2}\n height={height - 2}\n rx={Math.max(0, radius - 1)}\n ry={Math.max(0, radius - 1)}\n opacity={dimmed ? 0.3 : 1}\n className=\"fill-(--chart-surface)/60 transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n />\n {icon && (\n <foreignObject\n x={centreX - 8}\n y={centreY - 30}\n width={16}\n height={16}\n opacity={dimmed ? 0.3 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n >\n <div className=\"flex items-center justify-center text-(--ink-2)\">{icon}</div>\n </foreignObject>\n )}\n <text\n x={centreX}\n y={icon ? centreY - 4 : centreY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-[10px] font-medium transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 1}\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={centreX}\n y={valueY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) font-mono text-xs font-medium tabular-nums transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 0.6}\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n {label?.position === 'outside' && (\n <>\n <text\n x={outsideX}\n y={outsideY - (showValues ? 8 : 0)}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-xs\"\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={outsideX}\n y={outsideY + 8}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n opacity={0.5}\n className=\"fill-(--ink) font-mono text-xs tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n </Layer>\n )\n}\n\n/** One flow band. */\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n index,\n payload,\n linkSlot,\n}: SankeyLinkProps & { linkSlot: LinkProps | null }) {\n const { config, chartId, selectedNode } = useSankeyChart()\n\n const variant = linkSlot?.variant ?? 'gradient'\n const verticalPadding = linkSlot?.verticalPadding ?? 0\n\n const sourceName = payload.source.name\n const targetName = payload.target.name\n const lit =\n selectedNode === null || selectedNode === sourceName || selectedNode === targetName\n\n const half = Math.max(1, linkWidth - verticalPadding) / 2\n\n // Two mirrored cubics closed into a ribbon: the band has to be a filled shape\n // rather than a thick stroke, because its width changes along its length.\n const band = `M${sourceX},${sourceY - half}\n C${sourceControlX},${sourceY - half} ${targetControlX},${targetY - half} ${targetX},${targetY - half}\n L${targetX},${targetY + half}\n C${targetControlX},${targetY + half} ${sourceControlX},${sourceY + half} ${sourceX},${sourceY + half}\n Z`\n\n const sourceColor = sourceName in config ? `var(--color-${cssName(sourceName)}-0)` : 'currentColor'\n const targetColor = targetName in config ? `var(--color-${cssName(targetName)}-0)` : 'currentColor'\n\n return (\n <Layer className=\"text-(--ink)\">\n <defs>\n {variant === 'gradient' && (\n <linearGradient id={`${chartId}-link-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor={sourceColor} stopOpacity={0.2} />\n <stop offset=\"50%\" stopColor={sourceColor} stopOpacity={0.5} />\n <stop offset=\"100%\" stopColor={targetColor} stopOpacity={0.2} />\n </linearGradient>\n )}\n {/* The edge that traces a band connected to the selection. It fades out\n at both ends so it reads as a highlight rather than as an outline. */}\n <linearGradient id={`${chartId}-link-edge-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n <stop offset=\"15%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"50%\" stopColor=\"var(--accent)\" stopOpacity={1} />\n <stop offset=\"85%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"100%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n </linearGradient>\n </defs>\n <path\n d={band}\n fill={linkFill(variant, chartId, index, config, sourceName, targetName)}\n // Read from the ground rather than fixed: a band at 0.4 of ink over\n // paper reads; the same 0.4 of paper-white over near-black does not.\n fillOpacity={lit ? 'calc(var(--chart-fill) * 2.6)' : 'calc(var(--chart-fill) * 0.7)'}\n stroke={selectedNode !== null && lit ? `url(#${chartId}-link-edge-${index})` : 'none'}\n strokeWidth={1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </Layer>\n )\n}\n\n/** The paint reference for a band, given its variant. */\nfunction linkFill(\n variant: SankeyLinkVariant,\n chartId: string,\n index: number,\n config: ChartConfig,\n sourceName: string,\n targetName: string,\n): string {\n switch (variant) {\n case 'gradient':\n return `url(#${chartId}-link-${index})`\n case 'source':\n return sourceName in config ? `url(#${chartId}-node-${cssName(sourceName)})` : 'currentColor'\n case 'target':\n return targetName in config ? `url(#${chartId}-node-${cssName(targetName)})` : 'currentColor'\n default:\n return 'currentColor'\n }\n}\n\n/** The skeleton's node columns. Fixed, so the shape reads as a sankey at once. */\nconst SKELETON_NODES = [\n { x: 30, y: 25, width: 12, height: 65, delay: 0 },\n { x: 30, y: 110, width: 12, height: 50, delay: 0.3 },\n { x: 30, y: 180, width: 12, height: 45, delay: 0.15 },\n { x: 244, y: 20, width: 12, height: 55, delay: 0.45 },\n { x: 244, y: 95, width: 12, height: 75, delay: 0.6 },\n { x: 244, y: 190, width: 12, height: 40, delay: 0.25 },\n { x: 458, y: 35, width: 12, height: 80, delay: 0.5 },\n { x: 458, y: 135, width: 12, height: 90, delay: 0.1 },\n]\n\nconst SKELETON_LINKS = [\n { from: 0, to: 3, width: 26, delay: 0.2 },\n { from: 0, to: 4, width: 18, delay: 0.7 },\n { from: 1, to: 4, width: 24, delay: 0.4 },\n { from: 1, to: 5, width: 12, delay: 0.9 },\n { from: 2, to: 4, width: 16, delay: 0.1 },\n { from: 2, to: 5, width: 14, delay: 0.55 },\n { from: 3, to: 6, width: 22, delay: 0.35 },\n { from: 3, to: 7, width: 18, delay: 0.8 },\n { from: 4, to: 6, width: 28, delay: 0.05 },\n { from: 4, to: 7, width: 32, delay: 0.65 },\n { from: 5, to: 7, width: 16, delay: 0.45 },\n]\n\n/** A bezier from one skeleton node's right edge to another's left. */\nfunction skeletonPath(fromIndex: number, toIndex: number): string {\n const from = SKELETON_NODES[fromIndex]!\n const to = SKELETON_NODES[toIndex]!\n const startX = from.x + from.width\n const startY = from.y + from.height / 2\n const endX = to.x\n const endY = to.y + to.height / 2\n return `M${startX},${startY} C${startX + (endX - startX) * 0.4},${startY} ${startX + (endX - startX) * 0.6},${endY} ${endX},${endY}`\n}\n\n/** The skeleton diagram: a fixed graph whose parts pulse out of phase. */\nfunction LoadingSankey() {\n const reduceMotion = useReducedMotion()\n\n if (reduceMotion) {\n return (\n <>\n {SKELETON_LINKS.map((link) => (\n <path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n opacity={0.09}\n />\n ))}\n {SKELETON_NODES.map((node) => (\n <rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n opacity={0.25}\n />\n ))}\n </>\n )\n }\n\n return (\n <>\n {SKELETON_LINKS.map((link, index) => (\n <motion.path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n initial={{ opacity: 0.04 }}\n animate={{ opacity: [0.04, 0.14, 0.04] }}\n transition={{\n duration: LOADING_PULSE * (0.8 + (index % 3) * 0.2),\n delay: link.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n {SKELETON_NODES.map((node, index) => (\n <motion.rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n initial={{ opacity: 0.15 }}\n animate={{ opacity: [0.15, 0.4, 0.15] }}\n transition={{\n duration: LOADING_PULSE * (0.9 + (index % 4) * 0.1),\n delay: node.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n </>\n )\n}\n\nSankeyChart.Node = Node\nSankeyChart.NodeLabel = NodeLabel\nSankeyChart.Link = Link\nSankeyChart.Tooltip = Tooltip\nSankeyChart.Background = Background\n\nexport default SankeyChart\n"],"mappings":"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","names":[]}
@@ -3,9 +3,9 @@ import * as react from 'react';
3
3
  import { ReactNode, ComponentProps } from 'react';
4
4
  import { ScatterChart as ScatterChart$1, Scatter, XAxis, YAxis, ZAxis, CartesianGrid } from 'recharts';
5
5
  import { ChartConfig } from '../lib/chart.js';
6
+ import { ChartEmptyProps } from '../lib/empty.js';
6
7
  import { ChartBackgroundVariant } from '../lib/background.js';
7
8
  import { ReferenceLine, ReferenceBand, Annotation } from '../lib/annotations.js';
8
- import '../lib/empty.js';
9
9
  import '../lib/values.js';
10
10
  import '../lib/sonify-control.js';
11
11
  import '../lib/sonify.js';
@@ -58,8 +58,15 @@ interface ScatterChartProps {
58
58
  className?: string;
59
59
  /** Escape hatch onto the raw Recharts chart element. */
60
60
  chartProps?: ComponentProps<typeof ScatterChart$1>;
61
- /** The series lit on first render. Selection dims every other series. */
61
+ /** The series lit on first render, when the chart keeps its own selection. */
62
62
  defaultSelectedDataKey?: string | null;
63
+ /**
64
+ * The selected series, driven from outside.
65
+ *
66
+ * Give this and the chart follows it; leave it undefined and the chart keeps
67
+ * its own, starting from `defaultSelectedDataKey`.
68
+ */
69
+ selectedDataKey?: string | null;
63
70
  /** Fires when the selection changes, and with null when it is cleared. */
64
71
  onSelectionChange?: (selectedDataKey: string | null) => void;
65
72
  /**
@@ -68,6 +75,20 @@ interface ScatterChartProps {
68
75
  * other chart here the table cannot be inferred — it is declared.
69
76
  */
70
77
  table?: ScatterTable | false;
78
+ /**
79
+ * Swaps the marks for an animated skeleton, keeping the measured height so
80
+ * the page does not jump when the data lands.
81
+ */
82
+ isLoading?: boolean;
83
+ /**
84
+ * What the chart shows when it has nothing to draw. `false` keeps the empty
85
+ * plot, for a chart whose emptiness is itself the reading.
86
+ *
87
+ * Read from the declared `table` rows, for the same reason the table is
88
+ * declared: the observations live on each `<Scatter>`, and the root cannot
89
+ * see them.
90
+ */
91
+ empty?: ChartEmptyProps | false;
71
92
  }
72
93
  /**
73
94
  * Two measures against each other, one mark per observation — the shape for
@@ -89,7 +110,7 @@ interface ScatterChartProps {
89
110
  * <ScatterChart.Scatter dataKey="desktop" data={desktop} />
90
111
  * </ScatterChart>
91
112
  */
92
- declare function ScatterChart({ config, title, showTitle, description, children, className, chartProps, defaultSelectedDataKey, onSelectionChange, table, }: ScatterChartProps): react.JSX.Element;
113
+ declare function ScatterChart({ config, title, showTitle, description, children, className, chartProps, defaultSelectedDataKey, selectedDataKey: controlledDataKey, onSelectionChange, table, isLoading, empty, }: ScatterChartProps): react.JSX.Element;
93
114
  declare namespace ScatterChart {
94
115
  var Scatter: ({ dataKey, data, shape, variant, size, glowing, isClickable, scatterProps, }: ScatterProps) => react.JSX.Element;
95
116
  var XAxis: ({ type, tickLine, axisLine, tickMargin, tickFormatter, label, ...rest }: Omit<ComponentProps<typeof XAxis>, "label"> & {
@@ -2,11 +2,9 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import {
4
4
  createContext,
5
- useCallback,
6
5
  useContext,
7
6
  useId,
8
- useMemo,
9
- useState
7
+ useMemo
10
8
  } from "react";
11
9
  import {
12
10
  CartesianGrid,
@@ -18,9 +16,12 @@ import {
18
16
  } from "recharts";
19
17
  import { ChartContainer } from "../lib/chart.js";
20
18
  import { ChartFigure } from "../lib/figure.js";
19
+ import {} from "../lib/empty.js";
20
+ import { useChartSelection } from "../lib/selection.js";
21
21
  import { ChartBackground } from "../lib/background.js";
22
22
  import { ChartLegend, ChartLegendContent } from "../lib/legend.js";
23
23
  import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
24
+ import { LoadingIndicator } from "../lib/loading.js";
24
25
  import { GlowFilter, SeriesGradient } from "../lib/paint.js";
25
26
  import { Annotation, ReferenceBand, ReferenceLine } from "../lib/annotations.js";
26
27
  import { axisLabel } from "../lib/axis.js";
@@ -41,23 +42,23 @@ function ScatterChart({
41
42
  className,
42
43
  chartProps,
43
44
  defaultSelectedDataKey = null,
45
+ selectedDataKey: controlledDataKey,
44
46
  onSelectionChange,
45
- table
47
+ table,
48
+ isLoading = false,
49
+ empty
46
50
  }) {
47
51
  const chartId = useId().replace(/:/g, "");
48
- const [selectedDataKey, setSelectedDataKey] = useState(defaultSelectedDataKey);
49
- const selectDataKey = useCallback(
50
- (next) => {
51
- setSelectedDataKey(next);
52
- onSelectionChange?.(next);
53
- },
54
- [onSelectionChange]
52
+ const [selectedDataKey, selectDataKey] = useChartSelection(
53
+ controlledDataKey,
54
+ defaultSelectedDataKey,
55
+ onSelectionChange
55
56
  );
56
57
  const context = useMemo(
57
- () => ({ config, isLoading: false, selectedDataKey, selectDataKey }),
58
- [config, selectDataKey, selectedDataKey]
58
+ () => ({ config, isLoading, selectedDataKey, selectDataKey }),
59
+ [config, isLoading, selectDataKey, selectedDataKey]
59
60
  );
60
- return /* @__PURE__ */ jsx(ScatterChartContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
61
+ return /* @__PURE__ */ jsx(ScatterChartContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
61
62
  ChartFigure,
62
63
  {
63
64
  title,
@@ -65,7 +66,12 @@ function ScatterChart({
65
66
  description,
66
67
  className,
67
68
  table,
68
- children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(RechartsScatterChart, { id: chartId, accessibilityLayer: true, margin: CHART_MARGIN, ...chartProps, children }) })
69
+ isEmpty: !isLoading && !!table && table.rows.length === 0,
70
+ empty,
71
+ children: [
72
+ /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(RechartsScatterChart, { id: chartId, accessibilityLayer: true, margin: CHART_MARGIN, ...chartProps, children }) }),
73
+ /* @__PURE__ */ jsx(LoadingIndicator, { isLoading })
74
+ ]
69
75
  }
70
76
  ) });
71
77
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/ScatterChart/ScatterChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n CartesianGrid,\n Scatter as RechartsScatter,\n ScatterChart as RechartsScatterChart,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n ZAxis as RechartsZAxis,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { GlowFilter, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CHART_MARGIN = { top: 12, right: 16, bottom: 0, left: 8 }\n\n/**\n * The mark each point is drawn as.\n *\n * Shape is doing what hue does elsewhere: with the monochrome ramp, two\n * overlapping clouds separate far better by circle-versus-cross than by two\n * steps of grey, and shape survives overprinting where a lightness step does\n * not.\n */\nexport type ScatterShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross' | 'ring'\n\n/** How a point is filled — solid, hollow, or the series gradient. */\nexport type ScatterVariant = 'solid' | 'outline' | 'gradient'\n\ninterface ScatterChartContextValue {\n config: ChartConfig\n isLoading: boolean\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ScatterChartContext = createContext<ScatterChartContextValue | null>(null)\n\nfunction useScatterChart(): ScatterChartContextValue {\n const context = useContext(ScatterChartContext)\n if (!context) throw new Error('A scatter chart part must be rendered inside <ScatterChart>')\n return context\n}\n\n/** The rows a scatter chart reads its hidden table view from. */\nexport interface ScatterTable {\n rows: Record<string, unknown>[]\n /** The field naming each row. */\n rowKey?: string\n columns: { key: string; label: ReactNode }[]\n}\n\nexport interface ScatterChartProps {\n /** Series keys → their label and paint. Declaration order is ramp order. */\n config: ChartConfig\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, legend, and the marks themselves. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsScatterChart>\n /** The series lit on first render. Selection dims every other series. */\n defaultSelectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * The rows behind the hidden table view, with the fields to print. Scatter\n * data lives on each `<Scatter>` rather than on the root, so unlike every\n * other chart here the table cannot be inferred — it is declared.\n */\n table?: ScatterTable | false\n}\n\n/**\n * Two measures against each other, one mark per observation — the shape for\n * \"is there a relationship here\".\n *\n * The only chart in the set whose x axis is a NUMBER rather than a category,\n * which is the whole point: a scatter answers correlation, clustering and\n * outliers, and none of those questions survive being bucketed into a bar.\n *\n * Past three series, shape stops separating them and the answer is small\n * multiples — one chart per series, same axes — rather than a fourth mark.\n *\n * @example\n * <ScatterChart title=\"Load time against bundle size\" config={config}>\n * <ScatterChart.Grid />\n * <ScatterChart.XAxis dataKey=\"kb\" name=\"Bundle\" unit=\" kB\" />\n * <ScatterChart.YAxis dataKey=\"ms\" name=\"Load\" unit=\" ms\" />\n * <ScatterChart.Tooltip />\n * <ScatterChart.Scatter dataKey=\"desktop\" data={desktop} />\n * </ScatterChart>\n */\nexport function ScatterChart({\n config,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n defaultSelectedDataKey = null,\n onSelectionChange,\n table,\n}: ScatterChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey)\n\n const selectDataKey = useCallback(\n (next: string | null) => {\n setSelectedDataKey(next)\n onSelectionChange?.(next)\n },\n [onSelectionChange],\n )\n\n const context = useMemo<ScatterChartContextValue>(\n () => ({ config, isLoading: false, selectedDataKey, selectDataKey }),\n [config, selectDataKey, selectedDataKey],\n )\n\n return (\n <ScatterChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={table}\n >\n <ChartContainer config={config}>\n <RechartsScatterChart id={chartId} accessibilityLayer margin={CHART_MARGIN} {...chartProps}>\n {children}\n </RechartsScatterChart>\n </ChartContainer>\n </ChartFigure>\n </ScatterChartContext.Provider>\n )\n}\n\n/** The SVG path for one mark, centred on the origin at radius `r`. */\nfunction shapePath(shape: ScatterShape, r: number): string {\n switch (shape) {\n case 'square':\n return `M${-r},${-r}h${r * 2}v${r * 2}h${-r * 2}z`\n case 'triangle':\n return `M0,${-r}L${r},${r}L${-r},${r}z`\n case 'diamond':\n return `M0,${-r}L${r},0L0,${r}L${-r},0z`\n case 'cross': {\n const a = r * 0.34\n return `M${-a},${-r}h${a * 2}v${r - a}h${r - a}v${a * 2}h${-(r - a)}v${r - a}h${-a * 2}v${-(r - a)}h${-(r - a)}v${-a * 2}h${r - a}z`\n }\n default:\n return `M0,${-r}A${r},${r} 0 1,1 0,${r}A${r},${r} 0 1,1 0,${-r}z`\n }\n}\n\nexport interface ScatterProps {\n /** The series key. Must exist on the config. */\n dataKey: string\n /** This series' observations. */\n data: Record<string, unknown>[]\n shape?: ScatterShape\n variant?: ScatterVariant\n /** Mark radius in pixels. Ignored when a `<ScatterChart.ZAxis>` sizes them. */\n size?: number\n /** A halo behind the cloud — for the one series that is the point. */\n glowing?: boolean\n /** Lets a click on this series select it, dimming the rest. */\n isClickable?: boolean\n scatterProps?: Omit<ComponentProps<typeof RechartsScatter>, 'data' | 'dataKey'>\n}\n\n/** One cloud of observations. */\nfunction Scatter({\n dataKey,\n data,\n shape = 'circle',\n variant = 'solid',\n size = 5,\n glowing = false,\n isClickable = false,\n scatterProps,\n}: ScatterProps) {\n const { config, selectedDataKey, selectDataKey } = useScatterChart()\n const id = useId().replace(/:/g, '')\n\n const isSelected = selectedDataKey === dataKey\n const dimmed = selectedDataKey !== null && !isSelected\n const paint = `url(#${id}-colors-${dataKey})`\n\n return (\n <>\n <RechartsScatter\n name={dataKey}\n dataKey={dataKey}\n data={data}\n isAnimationActive={false}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectDataKey(isSelected ? null : dataKey)\n }}\n shape={(props: { cx?: number; cy?: number; payload?: unknown; r?: number }) => {\n const { cx, cy } = props\n if (cx === undefined || cy === undefined) return <g />\n // `r` arrives only when a ZAxis is sizing the marks.\n const radius = typeof props.r === 'number' && props.r > 0 ? props.r : size\n const hollow = variant === 'outline' || shape === 'ring'\n\n return (\n <g\n transform={`translate(${cx}, ${cy})`}\n opacity={dimmed ? 0.2 : 1}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n {shape === 'ring' ? (\n <circle\n r={radius}\n fill=\"none\"\n stroke={paint}\n strokeWidth={Math.max(1.2, radius * 0.32)}\n />\n ) : (\n <path\n d={shapePath(shape, radius)}\n fill={hollow ? 'none' : paint}\n stroke={hollow ? paint : 'var(--chart-surface)'}\n // A surface-coloured ring on a solid mark is the 2px spacer\n // that keeps two overlapping observations countable.\n strokeWidth={hollow ? 1.4 : 1}\n />\n )}\n </g>\n )\n }}\n {...scatterProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={5} intensity={0.8} />}\n </defs>\n </>\n )\n}\n\n/** The horizontal measure. Numeric by default — a scatter has no categories. */\nfunction XAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsXAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The vertical measure. */\nfunction YAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsYAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/**\n * A third measure, mapped to each mark's AREA rather than its radius.\n *\n * Radius is the wrong encoding and the common mistake: doubling a radius\n * quadruples the ink, so a value twice as large reads as four times as large.\n * `range` is in area units, which is what Recharts scales — so the default\n * below is a ×4 area span, not a ×4 radius.\n */\nfunction ZAxis({\n range = [40, 400],\n ...rest\n}: ComponentProps<typeof RechartsZAxis>) {\n return <RechartsZAxis range={range} {...rest} />\n}\n\n/** Rules on both axes — a scatter is read in two directions. */\nfunction Grid({\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/**\n * The hover panel, with a crosshair on both axes.\n *\n * `cursor` is a pair of rules rather than a band: a scatter point is located by\n * two coordinates, and a single vertical cursor answers only half of that.\n */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { selectedDataKey } = useScatterChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3' } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useScatterChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nScatterChart.Scatter = Scatter\nScatterChart.XAxis = XAxis\nScatterChart.YAxis = YAxis\nScatterChart.ZAxis = ZAxis\nScatterChart.Grid = Grid\nScatterChart.Tooltip = Tooltip\nScatterChart.Legend = Legend\nScatterChart.ReferenceLine = ReferenceLine\nScatterChart.ReferenceBand = ReferenceBand\nScatterChart.Annotation = Annotation\nScatterChart.Background = Background\n\nexport default ScatterChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/ScatterChart/ScatterChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n CartesianGrid,\n Scatter as RechartsScatter,\n ScatterChart as RechartsScatterChart,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n ZAxis as RechartsZAxis,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { useChartSelection } from '../lib/selection'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { GlowFilter, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CHART_MARGIN = { top: 12, right: 16, bottom: 0, left: 8 }\n\n/**\n * The mark each point is drawn as.\n *\n * Shape is doing what hue does elsewhere: with the monochrome ramp, two\n * overlapping clouds separate far better by circle-versus-cross than by two\n * steps of grey, and shape survives overprinting where a lightness step does\n * not.\n */\nexport type ScatterShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross' | 'ring'\n\n/** How a point is filled — solid, hollow, or the series gradient. */\nexport type ScatterVariant = 'solid' | 'outline' | 'gradient'\n\ninterface ScatterChartContextValue {\n config: ChartConfig\n isLoading: boolean\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ScatterChartContext = createContext<ScatterChartContextValue | null>(null)\n\nfunction useScatterChart(): ScatterChartContextValue {\n const context = useContext(ScatterChartContext)\n if (!context) throw new Error('A scatter chart part must be rendered inside <ScatterChart>')\n return context\n}\n\n/** The rows a scatter chart reads its hidden table view from. */\nexport interface ScatterTable {\n rows: Record<string, unknown>[]\n /** The field naming each row. */\n rowKey?: string\n columns: { key: string; label: ReactNode }[]\n}\n\nexport interface ScatterChartProps {\n /** Series keys → their label and paint. Declaration order is ramp order. */\n config: ChartConfig\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, legend, and the marks themselves. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsScatterChart>\n /** The series lit on first render, when the chart keeps its own selection. */\n defaultSelectedDataKey?: string | null\n /**\n * The selected series, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedDataKey`.\n */\n selectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * The rows behind the hidden table view, with the fields to print. Scatter\n * data lives on each `<Scatter>` rather than on the root, so unlike every\n * other chart here the table cannot be inferred — it is declared.\n */\n table?: ScatterTable | false\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the empty\n * plot, for a chart whose emptiness is itself the reading.\n *\n * Read from the declared `table` rows, for the same reason the table is\n * declared: the observations live on each `<Scatter>`, and the root cannot\n * see them.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Two measures against each other, one mark per observation — the shape for\n * \"is there a relationship here\".\n *\n * The only chart in the set whose x axis is a NUMBER rather than a category,\n * which is the whole point: a scatter answers correlation, clustering and\n * outliers, and none of those questions survive being bucketed into a bar.\n *\n * Past three series, shape stops separating them and the answer is small\n * multiples — one chart per series, same axes — rather than a fourth mark.\n *\n * @example\n * <ScatterChart title=\"Load time against bundle size\" config={config}>\n * <ScatterChart.Grid />\n * <ScatterChart.XAxis dataKey=\"kb\" name=\"Bundle\" unit=\" kB\" />\n * <ScatterChart.YAxis dataKey=\"ms\" name=\"Load\" unit=\" ms\" />\n * <ScatterChart.Tooltip />\n * <ScatterChart.Scatter dataKey=\"desktop\" data={desktop} />\n * </ScatterChart>\n */\nexport function ScatterChart({\n config,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n defaultSelectedDataKey = null,\n selectedDataKey: controlledDataKey,\n onSelectionChange,\n table,\n isLoading = false,\n empty,\n}: ScatterChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedDataKey, selectDataKey] = useChartSelection(\n controlledDataKey,\n defaultSelectedDataKey,\n onSelectionChange,\n )\n\n const context = useMemo<ScatterChartContextValue>(\n () => ({ config, isLoading, selectedDataKey, selectDataKey }),\n [config, isLoading, selectDataKey, selectedDataKey],\n )\n\n return (\n <ScatterChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={table}\n // An UNDECLARED table is not an empty one. The observations live on\n // each `<Scatter>`, so a chart that never declared its rows is a chart\n // this cannot speak for — swapping its plot for \"no data\" would be a\n // worse lie than the one being fixed.\n isEmpty={!isLoading && !!table && table.rows.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsScatterChart id={chartId} accessibilityLayer margin={CHART_MARGIN} {...chartProps}>\n {children}\n </RechartsScatterChart>\n </ChartContainer>\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </ScatterChartContext.Provider>\n )\n}\n\n/** The SVG path for one mark, centred on the origin at radius `r`. */\nfunction shapePath(shape: ScatterShape, r: number): string {\n switch (shape) {\n case 'square':\n return `M${-r},${-r}h${r * 2}v${r * 2}h${-r * 2}z`\n case 'triangle':\n return `M0,${-r}L${r},${r}L${-r},${r}z`\n case 'diamond':\n return `M0,${-r}L${r},0L0,${r}L${-r},0z`\n case 'cross': {\n const a = r * 0.34\n return `M${-a},${-r}h${a * 2}v${r - a}h${r - a}v${a * 2}h${-(r - a)}v${r - a}h${-a * 2}v${-(r - a)}h${-(r - a)}v${-a * 2}h${r - a}z`\n }\n default:\n return `M0,${-r}A${r},${r} 0 1,1 0,${r}A${r},${r} 0 1,1 0,${-r}z`\n }\n}\n\nexport interface ScatterProps {\n /** The series key. Must exist on the config. */\n dataKey: string\n /** This series' observations. */\n data: Record<string, unknown>[]\n shape?: ScatterShape\n variant?: ScatterVariant\n /** Mark radius in pixels. Ignored when a `<ScatterChart.ZAxis>` sizes them. */\n size?: number\n /** A halo behind the cloud — for the one series that is the point. */\n glowing?: boolean\n /** Lets a click on this series select it, dimming the rest. */\n isClickable?: boolean\n scatterProps?: Omit<ComponentProps<typeof RechartsScatter>, 'data' | 'dataKey'>\n}\n\n/** One cloud of observations. */\nfunction Scatter({\n dataKey,\n data,\n shape = 'circle',\n variant = 'solid',\n size = 5,\n glowing = false,\n isClickable = false,\n scatterProps,\n}: ScatterProps) {\n const { config, selectedDataKey, selectDataKey } = useScatterChart()\n const id = useId().replace(/:/g, '')\n\n const isSelected = selectedDataKey === dataKey\n const dimmed = selectedDataKey !== null && !isSelected\n const paint = `url(#${id}-colors-${dataKey})`\n\n return (\n <>\n <RechartsScatter\n name={dataKey}\n dataKey={dataKey}\n data={data}\n isAnimationActive={false}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectDataKey(isSelected ? null : dataKey)\n }}\n shape={(props: { cx?: number; cy?: number; payload?: unknown; r?: number }) => {\n const { cx, cy } = props\n if (cx === undefined || cy === undefined) return <g />\n // `r` arrives only when a ZAxis is sizing the marks.\n const radius = typeof props.r === 'number' && props.r > 0 ? props.r : size\n const hollow = variant === 'outline' || shape === 'ring'\n\n return (\n <g\n transform={`translate(${cx}, ${cy})`}\n opacity={dimmed ? 0.2 : 1}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n {shape === 'ring' ? (\n <circle\n r={radius}\n fill=\"none\"\n stroke={paint}\n strokeWidth={Math.max(1.2, radius * 0.32)}\n />\n ) : (\n <path\n d={shapePath(shape, radius)}\n fill={hollow ? 'none' : paint}\n stroke={hollow ? paint : 'var(--chart-surface)'}\n // A surface-coloured ring on a solid mark is the 2px spacer\n // that keeps two overlapping observations countable.\n strokeWidth={hollow ? 1.4 : 1}\n />\n )}\n </g>\n )\n }}\n {...scatterProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={5} intensity={0.8} />}\n </defs>\n </>\n )\n}\n\n/** The horizontal measure. Numeric by default — a scatter has no categories. */\nfunction XAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsXAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The vertical measure. */\nfunction YAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsYAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/**\n * A third measure, mapped to each mark's AREA rather than its radius.\n *\n * Radius is the wrong encoding and the common mistake: doubling a radius\n * quadruples the ink, so a value twice as large reads as four times as large.\n * `range` is in area units, which is what Recharts scales — so the default\n * below is a ×4 area span, not a ×4 radius.\n */\nfunction ZAxis({\n range = [40, 400],\n ...rest\n}: ComponentProps<typeof RechartsZAxis>) {\n return <RechartsZAxis range={range} {...rest} />\n}\n\n/** Rules on both axes — a scatter is read in two directions. */\nfunction Grid({\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/**\n * The hover panel, with a crosshair on both axes.\n *\n * `cursor` is a pair of rules rather than a band: a scatter point is located by\n * two coordinates, and a single vertical cursor answers only half of that.\n */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { selectedDataKey } = useScatterChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3' } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useScatterChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nScatterChart.Scatter = Scatter\nScatterChart.XAxis = XAxis\nScatterChart.YAxis = YAxis\nScatterChart.ZAxis = ZAxis\nScatterChart.Grid = Grid\nScatterChart.Tooltip = Tooltip\nScatterChart.Legend = Legend\nScatterChart.ReferenceLine = ReferenceLine\nScatterChart.ReferenceBand = ReferenceBand\nScatterChart.Annotation = Annotation\nScatterChart.Background = Background\n\nexport default ScatterChart\n"],"mappings":"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","names":[]}
@@ -1,12 +1,14 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
+ import { fraction } from "../lib/scale.js";
3
4
  function points(data, min, max) {
4
- const span = max - min || 1;
5
5
  const step = data.length > 1 ? 100 / (data.length - 1) : 0;
6
6
  return data.map((value, index) => ({
7
7
  x: index * step,
8
- // SVG y grows downward; the data does not.
9
- y: 100 - (value - min) / span * 100
8
+ // SVG y grows downward; the data does not. `fraction` is what puts a run
9
+ // that never changed through the middle rather than along the floor — see
10
+ // `lib/scale.ts` for why the middle is the honest answer.
11
+ y: 100 - fraction(value, min, max) * 100
10
12
  }));
11
13
  }
12
14
  function Sparkline({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/Sparkline/Sparkline.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** The mark a sparkline is drawn with. */\nexport type SparklineVariant = 'line' | 'area' | 'bars'\n\nexport interface SparklineProps {\n /** The numbers, in order. Anything shorter than two points draws nothing. */\n data: number[]\n /**\n * What the run is, in a sentence. Required, and it is the whole accessible\n * name: a sparkline has no axes and no legend, so nothing else describes it.\n */\n label: string\n variant?: SparklineVariant\n /** Height in pixels. The width comes from the container. */\n height?: number\n /**\n * The domain, as `[min, max]`. Derived from the data when omitted.\n *\n * Pin it whenever a column of sparklines is meant to be compared: on\n * independent domains every row peaks and troughs identically, which is the\n * one way a table of sparklines can be actively misleading.\n */\n domain?: [number, number]\n /** Marks the last point, which is usually the one being asked about. */\n showLast?: boolean\n /**\n * The value announced alongside the label — the current reading, formatted.\n * Falls back to the last number.\n */\n value?: ReactNode\n className?: string\n}\n\n/** The polyline through a normalised run, in a 0–100 by 0–100 box. */\nfunction points(data: number[], min: number, max: number): { x: number; y: number }[] {\n const span = max - min || 1\n const step = data.length > 1 ? 100 / (data.length - 1) : 0\n return data.map((value, index) => ({\n x: index * step,\n // SVG y grows downward; the data does not.\n y: 100 - ((value - min) / span) * 100,\n }))\n}\n\n/**\n * A run of numbers at the size of a word — in a table cell, beside a figure, at\n * the end of a row.\n *\n * Deliberately axis-less, gridless and label-less: a sparkline answers \"what\n * shape has this been\", and every piece of chrome that would make it answer\n * \"what value exactly\" also makes it too big to sit inline, which was the only\n * reason to reach for it. When the exact value matters, print the number beside\n * it — `value` does — and when the trend needs reading precisely, it wants a\n * `<LineChart>` and its own space.\n *\n * No rendering engine: it is one `<path>` over a normalised viewBox, so it\n * costs nothing to put a hundred of them in a table.\n *\n * @example\n * <Sparkline label=\"Weekly signups\" data={[12, 18, 9, 24, 30, 22, 41]} value=\"41\" />\n */\nexport function Sparkline({\n data,\n label,\n variant = 'line',\n height = 28,\n domain,\n showLast = true,\n value,\n className,\n}: SparklineProps) {\n const clean = data.filter((entry) => Number.isFinite(entry))\n const [min, max] = domain ?? [Math.min(...clean), Math.max(...clean)]\n const reading = value ?? clean.at(-1)?.toLocaleString()\n\n if (clean.length < 2) {\n return (\n <span className={cn('mono-meta text-(--ink-3-aa)', className)}>\n {label}: not enough data\n </span>\n )\n }\n\n const plotted = points(clean, min, max)\n const line = plotted.map((point, index) => `${index === 0 ? 'M' : 'L'}${point.x},${point.y}`).join(' ')\n const last = plotted.at(-1)!\n\n return (\n <span className={cn('inline-flex w-full items-center gap-2', className)}>\n <svg\n role=\"img\"\n aria-label={reading === undefined ? label : `${label}: ${reading}`}\n viewBox=\"0 0 100 100\"\n // Not `xMidYMid`: the run has to fill the cell it is given, and a\n // sparkline's aspect ratio carries no meaning to preserve.\n preserveAspectRatio=\"none\"\n height={height}\n className=\"min-w-0 flex-1 overflow-visible text-(--series-1)\"\n >\n {variant === 'bars' ? (\n plotted.map((point, index) => {\n const width = 100 / (plotted.length * 1.6)\n return (\n <rect\n key={index}\n x={point.x - width / 2}\n y={point.y}\n width={width}\n height={Math.max(1, 100 - point.y)}\n fill=\"currentColor\"\n // The bars carry the reading on their own; a wash would only\n // add a second, weaker version of it.\n fillOpacity={0.85}\n />\n )\n })\n ) : (\n <>\n {variant === 'area' && (\n <path\n d={`${line} L100,100 L0,100 Z`}\n fill=\"currentColor\"\n fillOpacity=\"var(--chart-fill)\"\n />\n )}\n <path\n d={line}\n fill=\"none\"\n stroke=\"currentColor\"\n // The viewBox is stretched to the cell, so a stroke in user units\n // would be stretched with it. `non-scaling-stroke` is what keeps\n // the line the same weight in a narrow cell and a wide one.\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n />\n </>\n )}\n {showLast && variant !== 'bars' && (\n <circle cx={last.x} cy={last.y} r={2} fill=\"currentColor\" vectorEffect=\"non-scaling-stroke\" />\n )}\n </svg>\n {value !== undefined && (\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-(--ink)\">{value}</span>\n )}\n </span>\n )\n}\n\nexport default Sparkline\n"],"mappings":"AA+EM,SAwCI,UAdI,KA1BR;AA9EN,SAAS,UAAU;AAmCnB,SAAS,OAAO,MAAgB,KAAa,KAAyC;AACpF,QAAM,OAAO,MAAM,OAAO;AAC1B,QAAM,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,KAAK;AACzD,SAAO,KAAK,IAAI,CAAC,OAAO,WAAW;AAAA,IACjC,GAAG,QAAQ;AAAA;AAAA,IAEX,GAAG,OAAQ,QAAQ,OAAO,OAAQ;AAAA,EACpC,EAAE;AACJ;AAmBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,QAAQ,KAAK,OAAO,CAAC,UAAU,OAAO,SAAS,KAAK,CAAC;AAC3D,QAAM,CAAC,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,IAAI,GAAG,KAAK,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACpE,QAAM,UAAU,SAAS,MAAM,GAAG,EAAE,GAAG,eAAe;AAEtD,MAAI,MAAM,SAAS,GAAG;AACpB,WACE,qBAAC,UAAK,WAAW,GAAG,+BAA+B,SAAS,GACzD;AAAA;AAAA,MAAM;AAAA,OACT;AAAA,EAEJ;AAEA,QAAM,UAAU,OAAO,OAAO,KAAK,GAAG;AACtC,QAAM,OAAO,QAAQ,IAAI,CAAC,OAAO,UAAU,GAAG,UAAU,IAAI,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,EAAE,KAAK,GAAG;AACtG,QAAM,OAAO,QAAQ,GAAG,EAAE;AAE1B,SACE,qBAAC,UAAK,WAAW,GAAG,yCAAyC,SAAS,GACpE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY,YAAY,SAAY,QAAQ,GAAG,KAAK,KAAK,OAAO;AAAA,QAChE,SAAQ;AAAA,QAGR,qBAAoB;AAAA,QACpB;AAAA,QACA,WAAU;AAAA,QAET;AAAA,sBAAY,SACX,QAAQ,IAAI,CAAC,OAAO,UAAU;AAC5B,kBAAM,QAAQ,OAAO,QAAQ,SAAS;AACtC,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,GAAG,MAAM,IAAI,QAAQ;AAAA,gBACrB,GAAG,MAAM;AAAA,gBACT;AAAA,gBACA,QAAQ,KAAK,IAAI,GAAG,MAAM,MAAM,CAAC;AAAA,gBACjC,MAAK;AAAA,gBAGL,aAAa;AAAA;AAAA,cARR;AAAA,YASP;AAAA,UAEJ,CAAC,IAED,iCACG;AAAA,wBAAY,UACX;AAAA,cAAC;AAAA;AAAA,gBACC,GAAG,GAAG,IAAI;AAAA,gBACV,MAAK;AAAA,gBACL,aAAY;AAAA;AAAA,YACd;AAAA,YAEF;AAAA,cAAC;AAAA;AAAA,gBACC,GAAG;AAAA,gBACH,MAAK;AAAA,gBACL,QAAO;AAAA,gBAIP,aAAa;AAAA,gBACb,cAAa;AAAA,gBACb,gBAAe;AAAA,gBACf,eAAc;AAAA;AAAA,YAChB;AAAA,aACF;AAAA,UAED,YAAY,YAAY,UACvB,oBAAC,YAAO,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,GAAG,GAAG,MAAK,gBAAe,cAAa,sBAAqB;AAAA;AAAA;AAAA,IAEhG;AAAA,IACC,UAAU,UACT,oBAAC,UAAK,WAAU,wDAAwD,iBAAM;AAAA,KAElF;AAEJ;AAEA,IAAO,oBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/Sparkline/Sparkline.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { fraction } from '../lib/scale'\n\n/** The mark a sparkline is drawn with. */\nexport type SparklineVariant = 'line' | 'area' | 'bars'\n\nexport interface SparklineProps {\n /** The numbers, in order. Anything shorter than two points draws nothing. */\n data: number[]\n /**\n * What the run is, in a sentence. Required, and it is the whole accessible\n * name: a sparkline has no axes and no legend, so nothing else describes it.\n */\n label: string\n variant?: SparklineVariant\n /** Height in pixels. The width comes from the container. */\n height?: number\n /**\n * The domain, as `[min, max]`. Derived from the data when omitted.\n *\n * Pin it whenever a column of sparklines is meant to be compared: on\n * independent domains every row peaks and troughs identically, which is the\n * one way a table of sparklines can be actively misleading.\n */\n domain?: [number, number]\n /** Marks the last point, which is usually the one being asked about. */\n showLast?: boolean\n /**\n * The value announced alongside the label — the current reading, formatted.\n * Falls back to the last number.\n */\n value?: ReactNode\n className?: string\n}\n\n/** The polyline through a normalised run, in a 0–100 by 0–100 box. */\nfunction points(data: number[], min: number, max: number): { x: number; y: number }[] {\n const step = data.length > 1 ? 100 / (data.length - 1) : 0\n return data.map((value, index) => ({\n x: index * step,\n // SVG y grows downward; the data does not. `fraction` is what puts a run\n // that never changed through the middle rather than along the floor — see\n // `lib/scale.ts` for why the middle is the honest answer.\n y: 100 - fraction(value, min, max) * 100,\n }))\n}\n\n/**\n * A run of numbers at the size of a word — in a table cell, beside a figure, at\n * the end of a row.\n *\n * Deliberately axis-less, gridless and label-less: a sparkline answers \"what\n * shape has this been\", and every piece of chrome that would make it answer\n * \"what value exactly\" also makes it too big to sit inline, which was the only\n * reason to reach for it. When the exact value matters, print the number beside\n * it — `value` does — and when the trend needs reading precisely, it wants a\n * `<LineChart>` and its own space.\n *\n * No rendering engine: it is one `<path>` over a normalised viewBox, so it\n * costs nothing to put a hundred of them in a table.\n *\n * @example\n * <Sparkline label=\"Weekly signups\" data={[12, 18, 9, 24, 30, 22, 41]} value=\"41\" />\n */\nexport function Sparkline({\n data,\n label,\n variant = 'line',\n height = 28,\n domain,\n showLast = true,\n value,\n className,\n}: SparklineProps) {\n const clean = data.filter((entry) => Number.isFinite(entry))\n const [min, max] = domain ?? [Math.min(...clean), Math.max(...clean)]\n const reading = value ?? clean.at(-1)?.toLocaleString()\n\n if (clean.length < 2) {\n return (\n <span className={cn('mono-meta text-(--ink-3-aa)', className)}>\n {label}: not enough data\n </span>\n )\n }\n\n const plotted = points(clean, min, max)\n const line = plotted.map((point, index) => `${index === 0 ? 'M' : 'L'}${point.x},${point.y}`).join(' ')\n const last = plotted.at(-1)!\n\n return (\n <span className={cn('inline-flex w-full items-center gap-2', className)}>\n <svg\n role=\"img\"\n aria-label={reading === undefined ? label : `${label}: ${reading}`}\n viewBox=\"0 0 100 100\"\n // Not `xMidYMid`: the run has to fill the cell it is given, and a\n // sparkline's aspect ratio carries no meaning to preserve.\n preserveAspectRatio=\"none\"\n height={height}\n className=\"min-w-0 flex-1 overflow-visible text-(--series-1)\"\n >\n {variant === 'bars' ? (\n plotted.map((point, index) => {\n const width = 100 / (plotted.length * 1.6)\n return (\n <rect\n key={index}\n x={point.x - width / 2}\n y={point.y}\n width={width}\n height={Math.max(1, 100 - point.y)}\n fill=\"currentColor\"\n // The bars carry the reading on their own; a wash would only\n // add a second, weaker version of it.\n fillOpacity={0.85}\n />\n )\n })\n ) : (\n <>\n {variant === 'area' && (\n <path\n d={`${line} L100,100 L0,100 Z`}\n fill=\"currentColor\"\n fillOpacity=\"var(--chart-fill)\"\n />\n )}\n <path\n d={line}\n fill=\"none\"\n stroke=\"currentColor\"\n // The viewBox is stretched to the cell, so a stroke in user units\n // would be stretched with it. `non-scaling-stroke` is what keeps\n // the line the same weight in a narrow cell and a wide one.\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n />\n </>\n )}\n {showLast && variant !== 'bars' && (\n <circle cx={last.x} cy={last.y} r={2} fill=\"currentColor\" vectorEffect=\"non-scaling-stroke\" />\n )}\n </svg>\n {value !== undefined && (\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-(--ink)\">{value}</span>\n )}\n </span>\n )\n}\n\nexport default Sparkline\n"],"mappings":"AAiFM,SAwCI,UAdI,KA1BR;AAhFN,SAAS,UAAU;AACnB,SAAS,gBAAgB;AAmCzB,SAAS,OAAO,MAAgB,KAAa,KAAyC;AACpF,QAAM,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,KAAK;AACzD,SAAO,KAAK,IAAI,CAAC,OAAO,WAAW;AAAA,IACjC,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,IAIX,GAAG,MAAM,SAAS,OAAO,KAAK,GAAG,IAAI;AAAA,EACvC,EAAE;AACJ;AAmBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,QAAQ,KAAK,OAAO,CAAC,UAAU,OAAO,SAAS,KAAK,CAAC;AAC3D,QAAM,CAAC,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,IAAI,GAAG,KAAK,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACpE,QAAM,UAAU,SAAS,MAAM,GAAG,EAAE,GAAG,eAAe;AAEtD,MAAI,MAAM,SAAS,GAAG;AACpB,WACE,qBAAC,UAAK,WAAW,GAAG,+BAA+B,SAAS,GACzD;AAAA;AAAA,MAAM;AAAA,OACT;AAAA,EAEJ;AAEA,QAAM,UAAU,OAAO,OAAO,KAAK,GAAG;AACtC,QAAM,OAAO,QAAQ,IAAI,CAAC,OAAO,UAAU,GAAG,UAAU,IAAI,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,EAAE,KAAK,GAAG;AACtG,QAAM,OAAO,QAAQ,GAAG,EAAE;AAE1B,SACE,qBAAC,UAAK,WAAW,GAAG,yCAAyC,SAAS,GACpE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY,YAAY,SAAY,QAAQ,GAAG,KAAK,KAAK,OAAO;AAAA,QAChE,SAAQ;AAAA,QAGR,qBAAoB;AAAA,QACpB;AAAA,QACA,WAAU;AAAA,QAET;AAAA,sBAAY,SACX,QAAQ,IAAI,CAAC,OAAO,UAAU;AAC5B,kBAAM,QAAQ,OAAO,QAAQ,SAAS;AACtC,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,GAAG,MAAM,IAAI,QAAQ;AAAA,gBACrB,GAAG,MAAM;AAAA,gBACT;AAAA,gBACA,QAAQ,KAAK,IAAI,GAAG,MAAM,MAAM,CAAC;AAAA,gBACjC,MAAK;AAAA,gBAGL,aAAa;AAAA;AAAA,cARR;AAAA,YASP;AAAA,UAEJ,CAAC,IAED,iCACG;AAAA,wBAAY,UACX;AAAA,cAAC;AAAA;AAAA,gBACC,GAAG,GAAG,IAAI;AAAA,gBACV,MAAK;AAAA,gBACL,aAAY;AAAA;AAAA,YACd;AAAA,YAEF;AAAA,cAAC;AAAA;AAAA,gBACC,GAAG;AAAA,gBACH,MAAK;AAAA,gBACL,QAAO;AAAA,gBAIP,aAAa;AAAA,gBACb,cAAa;AAAA,gBACb,gBAAe;AAAA,gBACf,eAAc;AAAA;AAAA,YAChB;AAAA,aACF;AAAA,UAED,YAAY,YAAY,UACvB,oBAAC,YAAO,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,GAAG,GAAG,MAAK,gBAAe,cAAa,sBAAqB;AAAA;AAAA;AAAA,IAEhG;AAAA,IACC,UAAU,UACT,oBAAC,UAAK,WAAU,wDAAwD,iBAAM;AAAA,KAElF;AAEJ;AAEA,IAAO,oBAAQ;","names":[]}
@@ -2,8 +2,8 @@ import * as react from 'react';
2
2
  import { ReactNode, ComponentProps } from 'react';
3
3
  import { Treemap } from 'recharts';
4
4
  import { ChartConfig } from '../lib/chart.js';
5
+ import { ChartEmptyProps } from '../lib/empty.js';
5
6
  import { ChartTooltipSlotProps } from '../AreaChart/AreaChart.js';
6
- import '../lib/empty.js';
7
7
  import '../lib/values.js';
8
8
  import '../lib/sonify-control.js';
9
9
  import '../lib/sonify.js';
@@ -65,6 +65,11 @@ interface TreemapChartProps {
65
65
  chartProps?: TreemapExtras;
66
66
  /** Drops the hidden table view. Only correct when the page prints the data itself. */
67
67
  hideDataTable?: boolean;
68
+ /**
69
+ * What the chart shows when it has nothing to draw. `false` keeps the empty
70
+ * plot, for a chart whose emptiness is itself the reading.
71
+ */
72
+ empty?: ChartEmptyProps | false;
68
73
  }
69
74
  /**
70
75
  * Part of a whole, when the whole has too many parts for a pie — and the parts
@@ -85,7 +90,7 @@ interface TreemapChartProps {
85
90
  * <TreemapChart.Tooltip />
86
91
  * </TreemapChart>
87
92
  */
88
- declare function TreemapChart({ config, data, title, showTitle, description, className, variant, dataKey, aspectRatio, showLabels, children, chartProps, hideDataTable, }: TreemapChartProps): react.JSX.Element;
93
+ declare function TreemapChart({ config, data, title, showTitle, description, className, variant, dataKey, aspectRatio, showLabels, children, chartProps, hideDataTable, empty, }: TreemapChartProps): react.JSX.Element;
89
94
  declare namespace TreemapChart {
90
95
  var Tooltip: ({ variant, roundness }: ChartTooltipSlotProps) => react.JSX.Element;
91
96
  }