@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
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/BoxPlot/BoxPlot.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 Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Value' } }\n\n/** Room for the first and last category label, and for a whisker cap. */\nconst CHART_MARGIN = { top: 12, right: 12, bottom: 0, left: 12 }\n\n/**\n * Tukey's multiplier. A point past `q3 + 1.5 × IQR` is drawn as itself rather\n * than swept into a whisker.\n *\n * 1.5 is a convention, not a test: on normal data it flags roughly 0.7% of\n * points, and on skewed data it flags a great many that are not anomalies at\n * all. It decides what the picture DRAWS, never what the reader concludes.\n */\nconst TUKEY_FENCE = 1.5\n\n/**\n * The notch's half-width, as a multiple of `IQR / √n`.\n *\n * 1.58 is McGill, Tukey and Larsen's: it makes two non-overlapping notches\n * roughly a 95% test of \"these medians differ\". Roughly, and only for\n * independent samples of similar shape — which is why the notch is opt-in.\n */\nconst NOTCH_FACTOR = 1.58\n\n/** How far the box pinches in at the median, as a share of its width. */\nconst NOTCH_INSET = 0.18\n\n/** How much room the value axis leaves above and below the data. */\nconst DOMAIN_PADDING = 0.06\n\n/** Which way the boxes run. */\nexport type BoxPlotOrientation = 'vertical' | 'horizontal'\n\n/** A category summarised into the five numbers a box is drawn from. */\nexport interface BoxPlotSummary {\n /** What the box describes. Doubles as its key, so it must be unique. */\n name: string\n /** The low whisker's end — the smallest observation inside the fence. */\n min: number\n q1: number\n median: number\n q3: number\n /** The high whisker's end — the largest observation inside the fence. */\n max: number\n /** Points outside the fences, drawn one dot each. */\n outliers?: number[]\n /**\n * How many observations the box stands for.\n *\n * Needed for a notch, and worth carrying anyway: a box drawn from six points\n * and a box drawn from six thousand look identical, and only one of them\n * means anything.\n */\n count?: number\n}\n\n/** A category handed over as raw observations, for the chart to summarise. */\nexport interface BoxPlotSample {\n /** What the box describes. Doubles as its key, so it must be unique. */\n name: string\n /** Every observation in this category, in any order. */\n values: number[]\n min?: never\n q1?: never\n median?: never\n q3?: never\n max?: never\n}\n\n/**\n * One category: either the raw observations, or the summary itself.\n *\n * Both forms exist because both are real. A chart fed from a warehouse gets\n * percentiles back from the query and never sees a row; a chart fed from a\n * browser has the array in hand and should not have to compute quartiles to\n * draw them.\n */\nexport type BoxPlotDatum = BoxPlotSample | (BoxPlotSummary & { values?: never })\n\n/** A summary with everything the marks need resolved. */\ninterface ResolvedBox extends BoxPlotSummary {\n outliers: number[]\n outlierCount: number\n}\n\n/**\n * The quantile at `p`, interpolated between the two neighbouring order\n * statistics.\n *\n * This is the R type 7 / NumPy default definition, and it is stated here\n * because there are nine of them and they disagree: on `[1, 2, 3, 4]` the\n * lower quartile is 1.75 under this rule and 1.5 under the \"median of the\n * lower half\" rule most people were taught. Two box plots of the same data\n * drawn under different rules are two different pictures, so the package\n * picks one and says which.\n */\nfunction quantile(sorted: number[], p: number): number {\n if (sorted.length === 0) return Number.NaN\n if (sorted.length === 1) return sorted[0]!\n\n const h = (sorted.length - 1) * p\n const lower = sorted[Math.floor(h)]!\n const upper = sorted[Math.ceil(h)]!\n return lower + (h - Math.floor(h)) * (upper - lower)\n}\n\n/** Raw observations, reduced to a five-number summary plus its outliers. */\nfunction summarise(name: string, values: number[]): ResolvedBox | null {\n const clean = values.filter(Number.isFinite).sort((a, b) => a - b)\n if (clean.length === 0) return null\n\n const q1 = quantile(clean, 0.25)\n const median = quantile(clean, 0.5)\n const q3 = quantile(clean, 0.75)\n const iqr = q3 - q1\n const low = q1 - TUKEY_FENCE * iqr\n const high = q3 + TUKEY_FENCE * iqr\n\n const inside = clean.filter((value) => value >= low && value <= high)\n const outliers = clean.filter((value) => value < low || value > high)\n\n return {\n name,\n // The whiskers stop at the most extreme observation still inside the\n // fence, not at the fence itself. A whisker drawn to `q1 - 1.5 × IQR`\n // claims a reading that may not exist in the data.\n min: inside.length > 0 ? inside[0]! : clean[0]!,\n q1,\n median,\n q3,\n max: inside.length > 0 ? inside[inside.length - 1]! : clean[clean.length - 1]!,\n outliers,\n outlierCount: outliers.length,\n count: clean.length,\n }\n}\n\n/** Every datum, in the one shape the marks read. */\nfunction resolve(data: BoxPlotDatum[]): ResolvedBox[] {\n return data.flatMap((datum) => {\n if (datum.values) {\n const summary = summarise(datum.name, datum.values)\n return summary ? [summary] : []\n }\n const outliers = datum.outliers ?? []\n return [{ ...datum, outliers, outlierCount: outliers.length }]\n })\n}\n\n/**\n * The value axis's span.\n *\n * Not anchored at zero, unlike a bar chart's: a box plot compares\n * DISTRIBUTIONS, and dragging the axis to zero to be honest about bar length\n * flattens every box into the same band of pixels. The honesty a box plot owes\n * is a labelled axis, which it has.\n */\nfunction valueDomain(boxes: ResolvedBox[]): [number, number] {\n const values = boxes.flatMap((box) => [box.min, box.max, ...box.outliers])\n if (values.length === 0) return [0, 1]\n\n const low = Math.min(...values)\n const high = Math.max(...values)\n const pad = (high - low || Math.abs(high) || 1) * DOMAIN_PADDING\n return [low - pad, high + pad]\n}\n\ninterface BoxPlotContextValue {\n config: ChartConfig\n seriesKey: string\n boxes: ResolvedBox[]\n isHorizontal: boolean\n domain: [number, number]\n formatValue: (value: number) => string\n}\n\nconst BoxPlotContext = createContext<BoxPlotContextValue | null>(null)\n\nfunction useBoxPlot(): BoxPlotContextValue {\n const context = useContext(BoxPlotContext)\n if (!context) throw new Error('A box plot part must be rendered inside <BoxPlot>')\n return context\n}\n\nexport interface BoxPlotProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a box plot has one measurement and as many categories as it has boxes.\n */\n config?: ChartConfig\n /** One entry per category, as raw observations or as a summary. */\n data: BoxPlotDatum[]\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, and `<BoxPlot.Boxes>`. */\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 RechartsBarChart>\n /**\n * Which way the boxes run. Reach for `horizontal` when the category names\n * are long enough to need rotating under a column.\n */\n orientation?: BoxPlotOrientation\n /** Formats every number the chart prints — ticks, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * The spread of a measurement, per category — median, middle half, reach, and\n * the points that sit outside it.\n *\n * The form to reach for when the question is \"how variable is this\", and the\n * one that answers it in a tenth of the ink a histogram per category would\n * take. Six response-time distributions fit across one screen as six boxes;\n * as six histograms they do not fit at all.\n *\n * **What a box plot hides is multimodality.** A box is five numbers, and five\n * numbers cannot tell a single hump from two. A bimodal distribution — a fast\n * cache path and a slow database path, two cohorts inside one average — draws\n * exactly the same box as a smooth one centred in the same place, and the\n * reader has no way to tell from the picture that the middle of the box is a\n * value almost nothing takes. It also hides sample size: a box over six points\n * and a box over six thousand are drawn identically, which is why `count` is\n * worth carrying and why a notch, which does read `count`, is worth turning on\n * when medians are being compared. When the SHAPE of one distribution is the\n * question, reach for `<Histogram>`; when there are few enough observations to\n * draw them all, reach for `<ScatterChart>` and plot the points.\n *\n * Recharts earns its place here: the boxes need a shared value axis with real\n * ticks, a category axis, a grid and a tooltip, which is most of a cartesian\n * chart. What it does not have is a box mark, so each box is drawn as a custom\n * shape over a range bar — the bar supplies the category band and the scale,\n * and the glyph inside it is ours.\n *\n * @example\n * <BoxPlot title=\"Response time by region\" data={regions}>\n * <BoxPlot.Grid />\n * <BoxPlot.XAxis />\n * <BoxPlot.YAxis label=\"ms\" />\n * <BoxPlot.Tooltip />\n * <BoxPlot.Boxes showOutliers />\n * </BoxPlot>\n *\n * @example\n * // Raw observations, summarised for you with Tukey's fences.\n * const data = [{ name: 'Sydney', values: [180, 194, 210, 205, 640] }]\n */\nexport function BoxPlot({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n orientation = 'vertical',\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: BoxPlotProps) {\n const chartId = useId().replace(/:/g, '')\n const isHorizontal = orientation === 'horizontal'\n const seriesKey = Object.keys(config)[0] ?? 'value'\n\n const boxes = useMemo(() => resolve(data), [data])\n const domain = useMemo(() => valueDomain(boxes), [boxes])\n\n const context = useMemo<BoxPlotContextValue>(\n () => ({ config, seriesKey, boxes, isHorizontal, domain, formatValue }),\n [config, seriesKey, boxes, isHorizontal, domain, formatValue],\n )\n\n const isEmpty = empty !== false && boxes.length === 0\n\n return (\n <BoxPlotContext.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: boxes as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'min', label: 'Minimum' },\n { key: 'q1', label: 'Lower quartile' },\n { key: 'median', label: 'Median' },\n { key: 'q3', label: 'Upper quartile' },\n { key: 'max', label: 'Maximum' },\n { key: 'outlierCount', label: 'Outliers' },\n ],\n }\n }\n >\n {isEmpty ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n // Recharts' `layout` names the axis the CATEGORIES run along,\n // which is the opposite of what a reader means by a horizontal\n // chart. The flip is done once, here.\n layout={isHorizontal ? 'vertical' : 'horizontal'}\n data={boxes}\n barCategoryGap=\"30%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n )}\n </ChartFigure>\n </BoxPlotContext.Provider>\n )\n}\n\nexport interface BoxPlotBoxesProps {\n /** Draws the points outside the fences as individual dots. */\n showOutliers?: boolean\n /**\n * Pinches the box in at the median by `1.58 × IQR / √n`.\n *\n * A confidence interval on the median drawn INSIDE the shape it belongs to,\n * which is the only place a reader will actually look at it. Needs `count`\n * on every box; a box without one is drawn square.\n */\n notch?: boolean\n /** Escape hatch onto the range bar the boxes are drawn over. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The boxes themselves.\n *\n * One range bar per category spanning `min` to `max`, replaced by a custom\n * shape: the bar is there for the category band and the scale, and nothing of\n * it is painted.\n */\nfunction Boxes({ showOutliers = true, notch = false, barProps }: BoxPlotBoxesProps) {\n const { config, seriesKey, isHorizontal } = useBoxPlot()\n const id = useId().replace(/:/g, '')\n\n return (\n <>\n <RechartsBar\n // The whisker span. Recharts reads a two-element value as a floating\n // bar, which is what puts the rect between two data values rather than\n // between a value and the baseline.\n dataKey={(row: unknown) => {\n const box = row as ResolvedBox\n return [box.min, box.max]\n }}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => (\n <BoxShape\n {...(props as BoxGeometry)}\n id={id}\n seriesKey={seriesKey}\n isHorizontal={isHorizontal}\n showOutliers={showOutliers}\n notch={notch}\n />\n )}\n activeBar={(props: unknown) => (\n <BoxShape\n {...(props as BoxGeometry)}\n id={id}\n seriesKey={seriesKey}\n isHorizontal={isHorizontal}\n showOutliers={showOutliers}\n notch={notch}\n />\n )}\n {...barProps}\n />\n <defs>\n <SeriesGradient\n id={id}\n dataKey={seriesKey}\n config={config}\n direction={isHorizontal ? 'horizontal' : 'vertical'}\n />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the row behind it. */\ninterface BoxGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n payload?: ResolvedBox\n}\n\ninterface BoxShapeProps extends BoxGeometry {\n id: string\n seriesKey: string\n isHorizontal: boolean\n showOutliers: boolean\n notch: boolean\n}\n\n/**\n * One drawn box: whisker, caps, box, median, and any outliers.\n *\n * The value scale is recovered from the bar's own rectangle — `min` sits at one\n * end of it and `max` at the other, and the axis between them is linear, so any\n * other value is one interpolation away, INCLUDING the outliers past both ends.\n * Reading the scale back out of the geometry rather than re-deriving it is what\n * keeps the glyph aligned with the ticks when a call site overrides the axis's\n * domain. A category whose whole distribution is one repeated value gives a\n * rectangle with no extent to read, and only there does it fall back to the\n * plot area and the chart's own domain.\n */\nfunction BoxShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n payload,\n id,\n seriesKey,\n isHorizontal,\n showOutliers,\n notch,\n}: BoxShapeProps) {\n const plot = usePlotArea()\n const { domain } = useBoxPlot()\n\n if (!payload) return null\n\n const box = payload\n const span = box.max - box.min\n const extent = isHorizontal ? width : height\n\n const scale = (value: number): number => {\n if (span !== 0 && extent > 0) {\n return isHorizontal\n ? x + ((value - box.min) / span) * width\n : y + ((box.max - value) / span) * height\n }\n if (!plot) return isHorizontal ? x : y\n const [low, high] = domain\n const range = high - low || 1\n return isHorizontal\n ? plot.x + ((value - low) / range) * plot.width\n : plot.y + ((high - value) / range) * plot.height\n }\n\n // The band the glyph is centred in: the bar's own thickness across the\n // category axis.\n const bandStart = isHorizontal ? y : x\n const bandSize = isHorizontal ? height : width\n const centre = bandStart + bandSize / 2\n const capHalf = bandSize / 4\n const dotRadius = Math.max(2, Math.min(3.5, bandSize / 8))\n\n const fill = `url(#${id}-colors-${seriesKey})`\n const stroke = 'var(--ink)'\n\n const whisker = (from: number, to: number, atCap: boolean) => {\n const a = scale(from)\n const b = scale(to)\n return (\n <>\n <line\n x1={isHorizontal ? a : centre}\n y1={isHorizontal ? centre : a}\n x2={isHorizontal ? b : centre}\n y2={isHorizontal ? centre : b}\n stroke={stroke}\n strokeWidth={1}\n />\n {atCap && (\n <line\n x1={isHorizontal ? b : centre - capHalf}\n y1={isHorizontal ? centre - capHalf : b}\n x2={isHorizontal ? b : centre + capHalf}\n y2={isHorizontal ? centre + capHalf : b}\n stroke={stroke}\n strokeWidth={1}\n />\n )}\n </>\n )\n }\n\n const q1px = scale(box.q1)\n const q3px = scale(box.q3)\n const medianPx = scale(box.median)\n const boxFrom = Math.min(q1px, q3px)\n const boxSize = Math.abs(q3px - q1px)\n\n const notchBounds = notch ? notchAt(box) : null\n const body = notchBounds ? (\n <polygon\n points={notchPoints({\n bandStart,\n bandSize,\n isHorizontal,\n q1: q1px,\n q3: q3px,\n median: medianPx,\n low: scale(notchBounds[0]),\n high: scale(notchBounds[1]),\n })}\n fill={fill}\n fillOpacity=\"var(--chart-fill)\"\n stroke={stroke}\n strokeWidth={1}\n strokeLinejoin=\"round\"\n />\n ) : (\n <rect\n x={isHorizontal ? boxFrom : bandStart}\n y={isHorizontal ? bandStart : boxFrom}\n width={isHorizontal ? Math.max(1, boxSize) : bandSize}\n height={isHorizontal ? bandSize : Math.max(1, boxSize)}\n fill={fill}\n // The box is a region, not a value, so it takes the translucent-mark\n // token rather than a literal — the one number in the system that is\n // different on each ground.\n fillOpacity=\"var(--chart-fill)\"\n stroke={stroke}\n strokeWidth={1}\n />\n )\n\n return (\n <g>\n {whisker(box.q1, box.min, true)}\n {whisker(box.q3, box.max, true)}\n {body}\n {/* The median is the one reading a box plot exists to give, so it is the\n heaviest stroke in the glyph — everything else around it is a\n hairline. */}\n <line\n x1={isHorizontal ? medianPx : bandStart}\n y1={isHorizontal ? bandStart : medianPx}\n x2={isHorizontal ? medianPx : bandStart + bandSize}\n y2={isHorizontal ? bandStart + bandSize : medianPx}\n stroke={stroke}\n strokeWidth={2}\n />\n {showOutliers &&\n box.outliers.map((value, index) => (\n <circle\n key={`${value}-${index}`}\n cx={isHorizontal ? scale(value) : centre}\n cy={isHorizontal ? centre : scale(value)}\n r={dotRadius}\n // Hollow, so a cluster of outliers at one value still reads as\n // several points rather than as one blot.\n fill=\"var(--chart-surface)\"\n stroke={stroke}\n strokeWidth={1}\n />\n ))}\n </g>\n )\n}\n\n/** The notch's bounds, or null when this box cannot carry one. */\nfunction notchAt(box: ResolvedBox): [number, number] | null {\n if (!box.count || box.count <= 0) return null\n\n const half = (NOTCH_FACTOR * (box.q3 - box.q1)) / Math.sqrt(box.count)\n // Clamped into the box: a notch wider than the middle half would be drawn\n // turning the box inside out, which is the standard \"the sample is too small\n // for this comparison\" artefact and reads as a rendering fault instead.\n return [Math.max(box.q1, box.median - half), Math.min(box.q3, box.median + half)]\n}\n\n/** The eight-sided box a notch turns the rectangle into. */\nfunction notchPoints({\n bandStart,\n bandSize,\n isHorizontal,\n q1,\n q3,\n median,\n low,\n high,\n}: {\n bandStart: number\n bandSize: number\n isHorizontal: boolean\n q1: number\n q3: number\n median: number\n low: number\n high: number\n}): string {\n const near = bandStart\n const far = bandStart + bandSize\n const inset = bandSize * NOTCH_INSET\n const value = [q1, low, median, high, q3]\n const across = [near, near, near + inset, near, near]\n const back = [far, far, far - inset, far, far]\n\n const points = [\n ...value.map((v, i) => [v, across[i]] as const),\n ...value.map((v, i) => [v, back[i]] as const).reverse(),\n ]\n\n return points\n .map(([v, a]) => (isHorizontal ? `${v},${a}` : `${a},${v}`))\n .join(' ')\n}\n\n/**\n * The category axis when the boxes stand up, the value axis when they lie down.\n * The type is resolved from the orientation so a consumer never has to know\n * which way round Recharts wants it.\n */\nfunction XAxis({\n dataKey,\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n type,\n label,\n tickFormatter,\n domain,\n ...rest\n}: ChartXAxisProps) {\n const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot()\n\n return (\n <RechartsXAxis\n dataKey={dataKey ?? (isHorizontal ? undefined : 'name')}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n type={type ?? (isHorizontal ? 'number' : 'category')}\n domain={domain ?? (isHorizontal ? valueSpan : undefined)}\n tickFormatter={tickFormatter ?? (isHorizontal ? formatValue : undefined)}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The other axis, resolved the same way. */\nfunction YAxis({\n dataKey,\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n width = 'auto',\n type,\n label,\n tickFormatter,\n domain,\n ...rest\n}: ChartYAxisProps) {\n const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot()\n\n return (\n <RechartsYAxis\n dataKey={dataKey ?? (isHorizontal ? 'name' : undefined)}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n width={width}\n type={type ?? (isHorizontal ? 'category' : 'number')}\n domain={domain ?? (isHorizontal ? undefined : valueSpan)}\n tickFormatter={tickFormatter ?? (isHorizontal ? undefined : formatValue)}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis — whichever axis that is for this orientation. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical,\n horizontal,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n const { isHorizontal } = useBoxPlot()\n return (\n <CartesianGrid\n strokeDasharray={strokeDasharray}\n vertical={vertical ?? isHorizontal}\n horizontal={horizontal ?? !isHorizontal}\n {...rest}\n />\n )\n}\n\n/**\n * The hover panel, printing all five numbers.\n *\n * The generic tooltip prints one value per series, and a box has five — so it\n * is the whole summary or nothing. Without it the reader can only read the\n * median off the axis, which is the one thing the shape already gives them.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useBoxPlot()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedBox }[]\n }\n const box = payload?.[0]?.payload\n if (!active || !box) return <span className=\"p-4\" />\n\n const rows: [string, number][] = [\n ['Maximum', box.max],\n ['Upper quartile', box.q3],\n ['Median', box.median],\n ['Lower quartile', box.q1],\n ['Minimum', box.min],\n ]\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{box.name}</div>\n {rows.map(([name, value]) => (\n <div key={name} className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">{name}</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(value)}\n </span>\n </div>\n ))}\n {/* Sample size sits with the summary rather than under the box: a\n median over six points and a median over six thousand are the\n same mark, and this is the only place the difference appears. */}\n {box.count !== undefined && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Observations</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {box.count.toLocaleString()}\n </span>\n </div>\n )}\n </div>\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\nBoxPlot.Boxes = Boxes\nBoxPlot.XAxis = XAxis\nBoxPlot.YAxis = YAxis\nBoxPlot.Grid = Grid\nBoxPlot.Tooltip = Tooltip\nBoxPlot.Background = Background\nBoxPlot.ReferenceLine = ReferenceLine\nBoxPlot.ReferenceBand = ReferenceBand\nBoxPlot.Annotation = Annotation\n\nexport default BoxPlot\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/BoxPlot/BoxPlot.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 Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Value' } }\n\n/** Room for the first and last category label, and for a whisker cap. */\nconst CHART_MARGIN = { top: 12, right: 12, bottom: 0, left: 12 }\n\n/**\n * Tukey's multiplier. A point past `q3 + 1.5 × IQR` is drawn as itself rather\n * than swept into a whisker.\n *\n * 1.5 is a convention, not a test: on normal data it flags roughly 0.7% of\n * points, and on skewed data it flags a great many that are not anomalies at\n * all. It decides what the picture DRAWS, never what the reader concludes.\n */\nconst TUKEY_FENCE = 1.5\n\n/**\n * The notch's half-width, as a multiple of `IQR / √n`.\n *\n * 1.58 is McGill, Tukey and Larsen's: it makes two non-overlapping notches\n * roughly a 95% test of \"these medians differ\". Roughly, and only for\n * independent samples of similar shape — which is why the notch is opt-in.\n */\nconst NOTCH_FACTOR = 1.58\n\n/** How far the box pinches in at the median, as a share of its width. */\nconst NOTCH_INSET = 0.18\n\n/** How much room the value axis leaves above and below the data. */\nconst DOMAIN_PADDING = 0.06\n\n/** Which way the boxes run. */\nexport type BoxPlotOrientation = 'vertical' | 'horizontal'\n\n/** A category summarised into the five numbers a box is drawn from. */\nexport interface BoxPlotSummary {\n /** What the box describes. Doubles as its key, so it must be unique. */\n name: string\n /** The low whisker's end — the smallest observation inside the fence. */\n min: number\n q1: number\n median: number\n q3: number\n /** The high whisker's end — the largest observation inside the fence. */\n max: number\n /** Points outside the fences, drawn one dot each. */\n outliers?: number[]\n /**\n * How many observations the box stands for.\n *\n * Needed for a notch, and worth carrying anyway: a box drawn from six points\n * and a box drawn from six thousand look identical, and only one of them\n * means anything.\n */\n count?: number\n}\n\n/** A category handed over as raw observations, for the chart to summarise. */\nexport interface BoxPlotSample {\n /** What the box describes. Doubles as its key, so it must be unique. */\n name: string\n /** Every observation in this category, in any order. */\n values: number[]\n min?: never\n q1?: never\n median?: never\n q3?: never\n max?: never\n}\n\n/**\n * One category: either the raw observations, or the summary itself.\n *\n * Both forms exist because both are real. A chart fed from a warehouse gets\n * percentiles back from the query and never sees a row; a chart fed from a\n * browser has the array in hand and should not have to compute quartiles to\n * draw them.\n */\nexport type BoxPlotDatum = BoxPlotSample | (BoxPlotSummary & { values?: never })\n\n/** A summary with everything the marks need resolved. */\ninterface ResolvedBox extends BoxPlotSummary {\n outliers: number[]\n outlierCount: number\n}\n\n/**\n * The quantile at `p`, interpolated between the two neighbouring order\n * statistics.\n *\n * This is the R type 7 / NumPy default definition, and it is stated here\n * because there are nine of them and they disagree: on `[1, 2, 3, 4]` the\n * lower quartile is 1.75 under this rule and 1.5 under the \"median of the\n * lower half\" rule most people were taught. Two box plots of the same data\n * drawn under different rules are two different pictures, so the package\n * picks one and says which.\n */\nfunction quantile(sorted: number[], p: number): number {\n if (sorted.length === 0) return Number.NaN\n if (sorted.length === 1) return sorted[0]!\n\n const h = (sorted.length - 1) * p\n const lower = sorted[Math.floor(h)]!\n const upper = sorted[Math.ceil(h)]!\n return lower + (h - Math.floor(h)) * (upper - lower)\n}\n\n/** Raw observations, reduced to a five-number summary plus its outliers. */\nfunction summarise(name: string, values: number[]): ResolvedBox | null {\n const clean = values.filter(Number.isFinite).sort((a, b) => a - b)\n if (clean.length === 0) return null\n\n const q1 = quantile(clean, 0.25)\n const median = quantile(clean, 0.5)\n const q3 = quantile(clean, 0.75)\n const iqr = q3 - q1\n const low = q1 - TUKEY_FENCE * iqr\n const high = q3 + TUKEY_FENCE * iqr\n\n const inside = clean.filter((value) => value >= low && value <= high)\n const outliers = clean.filter((value) => value < low || value > high)\n\n return {\n name,\n // The whiskers stop at the most extreme observation still inside the\n // fence, not at the fence itself. A whisker drawn to `q1 - 1.5 × IQR`\n // claims a reading that may not exist in the data.\n min: inside.length > 0 ? inside[0]! : clean[0]!,\n q1,\n median,\n q3,\n max: inside.length > 0 ? inside[inside.length - 1]! : clean[clean.length - 1]!,\n outliers,\n outlierCount: outliers.length,\n count: clean.length,\n }\n}\n\n/** Every datum, in the one shape the marks read. */\nfunction resolve(data: BoxPlotDatum[]): ResolvedBox[] {\n return data.flatMap((datum) => {\n if (datum.values) {\n const summary = summarise(datum.name, datum.values)\n return summary ? [summary] : []\n }\n const outliers = datum.outliers ?? []\n return [{ ...datum, outliers, outlierCount: outliers.length }]\n })\n}\n\n/**\n * The value axis's span.\n *\n * Not anchored at zero, unlike a bar chart's: a box plot compares\n * DISTRIBUTIONS, and dragging the axis to zero to be honest about bar length\n * flattens every box into the same band of pixels. The honesty a box plot owes\n * is a labelled axis, which it has.\n */\nfunction valueDomain(boxes: ResolvedBox[]): [number, number] {\n const values = boxes.flatMap((box) => [box.min, box.max, ...box.outliers])\n if (values.length === 0) return [0, 1]\n\n const low = Math.min(...values)\n const high = Math.max(...values)\n const pad = (high - low || Math.abs(high) || 1) * DOMAIN_PADDING\n return [low - pad, high + pad]\n}\n\ninterface BoxPlotContextValue {\n config: ChartConfig\n seriesKey: string\n boxes: ResolvedBox[]\n isHorizontal: boolean\n domain: [number, number]\n formatValue: (value: number) => string\n}\n\nconst BoxPlotContext = createContext<BoxPlotContextValue | null>(null)\n\nfunction useBoxPlot(): BoxPlotContextValue {\n const context = useContext(BoxPlotContext)\n if (!context) throw new Error('A box plot part must be rendered inside <BoxPlot>')\n return context\n}\n\nexport interface BoxPlotProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a box plot has one measurement and as many categories as it has boxes.\n */\n config?: ChartConfig\n /** One entry per category, as raw observations or as a summary. */\n data: BoxPlotDatum[]\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, and `<BoxPlot.Boxes>`. */\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 RechartsBarChart>\n /**\n * Which way the boxes run. Reach for `horizontal` when the category names\n * are long enough to need rotating under a column.\n */\n orientation?: BoxPlotOrientation\n /** Formats every number the chart prints — ticks, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * The spread of a measurement, per category — median, middle half, reach, and\n * the points that sit outside it.\n *\n * The form to reach for when the question is \"how variable is this\", and the\n * one that answers it in a tenth of the ink a histogram per category would\n * take. Six response-time distributions fit across one screen as six boxes;\n * as six histograms they do not fit at all.\n *\n * **What a box plot hides is multimodality.** A box is five numbers, and five\n * numbers cannot tell a single hump from two. A bimodal distribution — a fast\n * cache path and a slow database path, two cohorts inside one average — draws\n * exactly the same box as a smooth one centred in the same place, and the\n * reader has no way to tell from the picture that the middle of the box is a\n * value almost nothing takes. It also hides sample size: a box over six points\n * and a box over six thousand are drawn identically, which is why `count` is\n * worth carrying and why a notch, which does read `count`, is worth turning on\n * when medians are being compared. When the SHAPE of one distribution is the\n * question, reach for `<Histogram>`; when there are few enough observations to\n * draw them all, reach for `<ScatterChart>` and plot the points.\n *\n * Recharts earns its place here: the boxes need a shared value axis with real\n * ticks, a category axis, a grid and a tooltip, which is most of a cartesian\n * chart. What it does not have is a box mark, so each box is drawn as a custom\n * shape over a range bar — the bar supplies the category band and the scale,\n * and the glyph inside it is ours.\n *\n * @example\n * <BoxPlot title=\"Response time by region\" data={regions}>\n * <BoxPlot.Grid />\n * <BoxPlot.XAxis />\n * <BoxPlot.YAxis label=\"ms\" />\n * <BoxPlot.Tooltip />\n * <BoxPlot.Boxes showOutliers />\n * </BoxPlot>\n *\n * @example\n * // Raw observations, summarised for you with Tukey's fences.\n * const data = [{ name: 'Sydney', values: [180, 194, 210, 205, 640] }]\n */\nexport function BoxPlot({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n orientation = 'vertical',\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: BoxPlotProps) {\n const chartId = useId().replace(/:/g, '')\n const isHorizontal = orientation === 'horizontal'\n const seriesKey = Object.keys(config)[0] ?? 'value'\n\n const boxes = useMemo(() => resolve(data), [data])\n const domain = useMemo(() => valueDomain(boxes), [boxes])\n\n const context = useMemo<BoxPlotContextValue>(\n () => ({ config, seriesKey, boxes, isHorizontal, domain, formatValue }),\n [config, seriesKey, boxes, isHorizontal, domain, formatValue],\n )\n\n const isEmpty = empty !== false && boxes.length === 0\n\n return (\n <BoxPlotContext.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: boxes as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'min', label: 'Minimum' },\n { key: 'q1', label: 'Lower quartile' },\n { key: 'median', label: 'Median' },\n { key: 'q3', label: 'Upper quartile' },\n { key: 'max', label: 'Maximum' },\n { key: 'outlierCount', label: 'Outliers' },\n ],\n }\n }\n isEmpty={isEmpty}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n // Recharts' `layout` names the axis the CATEGORIES run along,\n // which is the opposite of what a reader means by a horizontal\n // chart. The flip is done once, here.\n layout={isHorizontal ? 'vertical' : 'horizontal'}\n data={boxes}\n barCategoryGap=\"30%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n </ChartFigure>\n </BoxPlotContext.Provider>\n )\n}\n\nexport interface BoxPlotBoxesProps {\n /** Draws the points outside the fences as individual dots. */\n showOutliers?: boolean\n /**\n * Pinches the box in at the median by `1.58 × IQR / √n`.\n *\n * A confidence interval on the median drawn INSIDE the shape it belongs to,\n * which is the only place a reader will actually look at it. Needs `count`\n * on every box; a box without one is drawn square.\n */\n notch?: boolean\n /** Escape hatch onto the range bar the boxes are drawn over. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The boxes themselves.\n *\n * One range bar per category spanning `min` to `max`, replaced by a custom\n * shape: the bar is there for the category band and the scale, and nothing of\n * it is painted.\n */\nfunction Boxes({ showOutliers = true, notch = false, barProps }: BoxPlotBoxesProps) {\n const { config, seriesKey, isHorizontal } = useBoxPlot()\n const id = useId().replace(/:/g, '')\n\n return (\n <>\n <RechartsBar\n // The whisker span. Recharts reads a two-element value as a floating\n // bar, which is what puts the rect between two data values rather than\n // between a value and the baseline.\n dataKey={(row: unknown) => {\n const box = row as ResolvedBox\n return [box.min, box.max]\n }}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => (\n <BoxShape\n {...(props as BoxGeometry)}\n id={id}\n seriesKey={seriesKey}\n isHorizontal={isHorizontal}\n showOutliers={showOutliers}\n notch={notch}\n />\n )}\n activeBar={(props: unknown) => (\n <BoxShape\n {...(props as BoxGeometry)}\n id={id}\n seriesKey={seriesKey}\n isHorizontal={isHorizontal}\n showOutliers={showOutliers}\n notch={notch}\n />\n )}\n {...barProps}\n />\n <defs>\n <SeriesGradient\n id={id}\n dataKey={seriesKey}\n config={config}\n direction={isHorizontal ? 'horizontal' : 'vertical'}\n />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the row behind it. */\ninterface BoxGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n payload?: ResolvedBox\n}\n\ninterface BoxShapeProps extends BoxGeometry {\n id: string\n seriesKey: string\n isHorizontal: boolean\n showOutliers: boolean\n notch: boolean\n}\n\n/**\n * One drawn box: whisker, caps, box, median, and any outliers.\n *\n * The value scale is recovered from the bar's own rectangle — `min` sits at one\n * end of it and `max` at the other, and the axis between them is linear, so any\n * other value is one interpolation away, INCLUDING the outliers past both ends.\n * Reading the scale back out of the geometry rather than re-deriving it is what\n * keeps the glyph aligned with the ticks when a call site overrides the axis's\n * domain. A category whose whole distribution is one repeated value gives a\n * rectangle with no extent to read, and only there does it fall back to the\n * plot area and the chart's own domain.\n */\nfunction BoxShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n payload,\n id,\n seriesKey,\n isHorizontal,\n showOutliers,\n notch,\n}: BoxShapeProps) {\n const plot = usePlotArea()\n const { domain } = useBoxPlot()\n\n if (!payload) return null\n\n const box = payload\n const span = box.max - box.min\n const extent = isHorizontal ? width : height\n\n const scale = (value: number): number => {\n if (span !== 0 && extent > 0) {\n return isHorizontal\n ? x + ((value - box.min) / span) * width\n : y + ((box.max - value) / span) * height\n }\n if (!plot) return isHorizontal ? x : y\n const [low, high] = domain\n const range = high - low || 1\n return isHorizontal\n ? plot.x + ((value - low) / range) * plot.width\n : plot.y + ((high - value) / range) * plot.height\n }\n\n // The band the glyph is centred in: the bar's own thickness across the\n // category axis.\n const bandStart = isHorizontal ? y : x\n const bandSize = isHorizontal ? height : width\n const centre = bandStart + bandSize / 2\n const capHalf = bandSize / 4\n const dotRadius = Math.max(2, Math.min(3.5, bandSize / 8))\n\n const fill = `url(#${id}-colors-${seriesKey})`\n const stroke = 'var(--ink)'\n\n const whisker = (from: number, to: number, atCap: boolean) => {\n const a = scale(from)\n const b = scale(to)\n return (\n <>\n <line\n x1={isHorizontal ? a : centre}\n y1={isHorizontal ? centre : a}\n x2={isHorizontal ? b : centre}\n y2={isHorizontal ? centre : b}\n stroke={stroke}\n strokeWidth={1}\n />\n {atCap && (\n <line\n x1={isHorizontal ? b : centre - capHalf}\n y1={isHorizontal ? centre - capHalf : b}\n x2={isHorizontal ? b : centre + capHalf}\n y2={isHorizontal ? centre + capHalf : b}\n stroke={stroke}\n strokeWidth={1}\n />\n )}\n </>\n )\n }\n\n const q1px = scale(box.q1)\n const q3px = scale(box.q3)\n const medianPx = scale(box.median)\n const boxFrom = Math.min(q1px, q3px)\n const boxSize = Math.abs(q3px - q1px)\n\n const notchBounds = notch ? notchAt(box) : null\n const body = notchBounds ? (\n <polygon\n points={notchPoints({\n bandStart,\n bandSize,\n isHorizontal,\n q1: q1px,\n q3: q3px,\n median: medianPx,\n low: scale(notchBounds[0]),\n high: scale(notchBounds[1]),\n })}\n fill={fill}\n fillOpacity=\"var(--chart-fill)\"\n stroke={stroke}\n strokeWidth={1}\n strokeLinejoin=\"round\"\n />\n ) : (\n <rect\n x={isHorizontal ? boxFrom : bandStart}\n y={isHorizontal ? bandStart : boxFrom}\n width={isHorizontal ? Math.max(1, boxSize) : bandSize}\n height={isHorizontal ? bandSize : Math.max(1, boxSize)}\n fill={fill}\n // The box is a region, not a value, so it takes the translucent-mark\n // token rather than a literal — the one number in the system that is\n // different on each ground.\n fillOpacity=\"var(--chart-fill)\"\n stroke={stroke}\n strokeWidth={1}\n />\n )\n\n return (\n <g>\n {whisker(box.q1, box.min, true)}\n {whisker(box.q3, box.max, true)}\n {body}\n {/* The median is the one reading a box plot exists to give, so it is the\n heaviest stroke in the glyph — everything else around it is a\n hairline. */}\n <line\n x1={isHorizontal ? medianPx : bandStart}\n y1={isHorizontal ? bandStart : medianPx}\n x2={isHorizontal ? medianPx : bandStart + bandSize}\n y2={isHorizontal ? bandStart + bandSize : medianPx}\n stroke={stroke}\n strokeWidth={2}\n />\n {showOutliers &&\n box.outliers.map((value, index) => (\n <circle\n key={`${value}-${index}`}\n cx={isHorizontal ? scale(value) : centre}\n cy={isHorizontal ? centre : scale(value)}\n r={dotRadius}\n // Hollow, so a cluster of outliers at one value still reads as\n // several points rather than as one blot.\n fill=\"var(--chart-surface)\"\n stroke={stroke}\n strokeWidth={1}\n />\n ))}\n </g>\n )\n}\n\n/** The notch's bounds, or null when this box cannot carry one. */\nfunction notchAt(box: ResolvedBox): [number, number] | null {\n if (!box.count || box.count <= 0) return null\n\n const half = (NOTCH_FACTOR * (box.q3 - box.q1)) / Math.sqrt(box.count)\n // Clamped into the box: a notch wider than the middle half would be drawn\n // turning the box inside out, which is the standard \"the sample is too small\n // for this comparison\" artefact and reads as a rendering fault instead.\n return [Math.max(box.q1, box.median - half), Math.min(box.q3, box.median + half)]\n}\n\n/** The eight-sided box a notch turns the rectangle into. */\nfunction notchPoints({\n bandStart,\n bandSize,\n isHorizontal,\n q1,\n q3,\n median,\n low,\n high,\n}: {\n bandStart: number\n bandSize: number\n isHorizontal: boolean\n q1: number\n q3: number\n median: number\n low: number\n high: number\n}): string {\n const near = bandStart\n const far = bandStart + bandSize\n const inset = bandSize * NOTCH_INSET\n const value = [q1, low, median, high, q3]\n const across = [near, near, near + inset, near, near]\n const back = [far, far, far - inset, far, far]\n\n const points = [\n ...value.map((v, i) => [v, across[i]] as const),\n ...value.map((v, i) => [v, back[i]] as const).reverse(),\n ]\n\n return points\n .map(([v, a]) => (isHorizontal ? `${v},${a}` : `${a},${v}`))\n .join(' ')\n}\n\n/**\n * The category axis when the boxes stand up, the value axis when they lie down.\n * The type is resolved from the orientation so a consumer never has to know\n * which way round Recharts wants it.\n */\nfunction XAxis({\n dataKey,\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n type,\n label,\n tickFormatter,\n domain,\n ...rest\n}: ChartXAxisProps) {\n const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot()\n\n return (\n <RechartsXAxis\n dataKey={dataKey ?? (isHorizontal ? undefined : 'name')}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n type={type ?? (isHorizontal ? 'number' : 'category')}\n domain={domain ?? (isHorizontal ? valueSpan : undefined)}\n tickFormatter={tickFormatter ?? (isHorizontal ? formatValue : undefined)}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The other axis, resolved the same way. */\nfunction YAxis({\n dataKey,\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n width = 'auto',\n type,\n label,\n tickFormatter,\n domain,\n ...rest\n}: ChartYAxisProps) {\n const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot()\n\n return (\n <RechartsYAxis\n dataKey={dataKey ?? (isHorizontal ? 'name' : undefined)}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n width={width}\n type={type ?? (isHorizontal ? 'category' : 'number')}\n domain={domain ?? (isHorizontal ? undefined : valueSpan)}\n tickFormatter={tickFormatter ?? (isHorizontal ? undefined : formatValue)}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis — whichever axis that is for this orientation. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical,\n horizontal,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n const { isHorizontal } = useBoxPlot()\n return (\n <CartesianGrid\n strokeDasharray={strokeDasharray}\n vertical={vertical ?? isHorizontal}\n horizontal={horizontal ?? !isHorizontal}\n {...rest}\n />\n )\n}\n\n/**\n * The hover panel, printing all five numbers.\n *\n * The generic tooltip prints one value per series, and a box has five — so it\n * is the whole summary or nothing. Without it the reader can only read the\n * median off the axis, which is the one thing the shape already gives them.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useBoxPlot()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedBox }[]\n }\n const box = payload?.[0]?.payload\n if (!active || !box) return <span className=\"p-4\" />\n\n const rows: [string, number][] = [\n ['Maximum', box.max],\n ['Upper quartile', box.q3],\n ['Median', box.median],\n ['Lower quartile', box.q1],\n ['Minimum', box.min],\n ]\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{box.name}</div>\n {rows.map(([name, value]) => (\n <div key={name} className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">{name}</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(value)}\n </span>\n </div>\n ))}\n {/* Sample size sits with the summary rather than under the box: a\n median over six points and a median over six thousand are the\n same mark, and this is the only place the difference appears. */}\n {box.count !== undefined && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Observations</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {box.count.toLocaleString()}\n </span>\n </div>\n )}\n </div>\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\nBoxPlot.Boxes = Boxes\nBoxPlot.XAxis = XAxis\nBoxPlot.YAxis = YAxis\nBoxPlot.Grid = Grid\nBoxPlot.Tooltip = Tooltip\nBoxPlot.Background = Background\nBoxPlot.ReferenceLine = ReferenceLine\nBoxPlot.ReferenceBand = ReferenceBand\nBoxPlot.Annotation = Annotation\n\nexport default BoxPlot\n"],"mappings":"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","names":[]}
@@ -1,19 +1,22 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { ChartFigure } from "../lib/figure.js";
3
- import { ChartEmpty } from "../lib/empty.js";
3
+ import {} from "../lib/empty.js";
4
4
  import { defaultTick } from "../lib/format.js";
5
+ import { clampedFraction } from "../lib/scale.js";
5
6
  const BAND_WEIGHT = { heaviest: 1, lightest: 0.3 };
6
7
  const BAR_SHARE = 0.34;
7
8
  function position(value, [low, high]) {
8
- const span = high - low;
9
- if (span === 0) return 0;
10
- return Math.min(1, Math.max(0, (value - low) / span));
9
+ return clampedFraction(value, low, high);
10
+ }
11
+ function overflows(value, [low, high]) {
12
+ return value !== void 0 && Number.isFinite(value) && (value > high || value < low);
11
13
  }
12
14
  function pct(share) {
13
15
  return `${Number((share * 100).toFixed(4))}%`;
14
16
  }
15
17
  function bands(ranges, [low, high], formatValue) {
16
- const bounds = [...new Set(ranges.filter(Number.isFinite))].sort((a, b) => a - b).filter((bound) => bound > low && bound < high);
18
+ const set = [...new Set(ranges.filter(Number.isFinite))].sort((a, b) => a - b);
19
+ const bounds = set.filter((bound) => bound > low && bound < high);
17
20
  const edges = [low, ...bounds, high];
18
21
  const count = edges.length - 1;
19
22
  const { heaviest, lightest } = BAND_WEIGHT;
@@ -24,7 +27,7 @@ function bands(ranges, [low, high], formatValue) {
24
27
  // bar, which is solid, stands out most where performance is best.
25
28
  weight: count === 1 ? lightest : heaviest - index / (count - 1) * (heaviest - lightest)
26
29
  }));
27
- return { list, label: bounds.map(formatValue).join(", ") };
30
+ return { list, label: set.map(formatValue).join(", ") };
28
31
  }
29
32
  function resolve(data, domain, ranges, formatValue) {
30
33
  return data.map((measure) => {
@@ -65,7 +68,9 @@ function BulletChart({
65
68
  { key: "rangeLabel", label: "Range bounds" }
66
69
  ]
67
70
  },
68
- children: measures.length === 0 ? /* @__PURE__ */ jsx(ChartEmpty, { ...empty || {} }) : /* @__PURE__ */ jsx("div", { className: "flex w-full flex-col gap-4", children: measures.map((measure) => /* @__PURE__ */ jsx(
71
+ isEmpty: measures.length === 0,
72
+ empty,
73
+ children: /* @__PURE__ */ jsx("div", { className: "flex w-full flex-col gap-4", children: measures.map((measure) => /* @__PURE__ */ jsx(
69
74
  BulletRow,
70
75
  {
71
76
  measure,
@@ -85,6 +90,7 @@ function BulletRow({
85
90
  const scale = measure.domain;
86
91
  const barShare = position(measure.value, scale);
87
92
  const targetShare = measure.target === void 0 ? null : position(measure.target, scale);
93
+ const runsOver = overflows(measure.value, scale) || overflows(measure.target, scale);
88
94
  return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1.5", children: [
89
95
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
90
96
  /* @__PURE__ */ jsxs("span", { className: "truncate text-sm text-(--ink)", children: [
@@ -144,6 +150,21 @@ function BulletRow({
144
150
  className: "absolute inset-y-1 block w-0.5 bg-(--ink)",
145
151
  style: { insetInlineStart: pct(targetShare), marginInlineStart: "-1px" }
146
152
  }
153
+ ),
154
+ runsOver && // Clamped and silent, a number past the end of the scale fills the
155
+ // track exactly as a number AT the end of it does — so the one thing
156
+ // the row cannot say is the one thing the reader most needs to know.
157
+ // The notch is what breaks that tie; the printed figure above still
158
+ // carries the real number.
159
+ /* @__PURE__ */ jsx(
160
+ "span",
161
+ {
162
+ "data-slot": "bullet-overflow",
163
+ className: "absolute inset-y-0 end-0 block w-1.5 bg-(--chart-surface)",
164
+ style: {
165
+ clipPath: "polygon(100% 0, 0 50%, 100% 100%)"
166
+ }
167
+ }
147
168
  )
148
169
  ]
149
170
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/BulletChart/BulletChart.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { defaultTick } from '../lib/format'\n\n/**\n * The heaviest and lightest a qualitative band may be, as multiples of\n * `--chart-fill`.\n *\n * Few's rule is no more than five bands in one hue, arranged so the low end of\n * the scale is the heaviest. The measure bar is solid ink on top of them, so\n * the ceiling has to stay well under it: bands that compete with the bar stop\n * being the ground it is read against.\n */\nconst BAND_WEIGHT = { heaviest: 1, lightest: 0.3 } as const\n\n/** How tall the measure bar is, as a share of the track. Few's is a third. */\nconst BAR_SHARE = 0.34\n\n/** One measure, its target, and the bands it is read against. */\nexport interface BulletMeasure {\n /** What is being measured. Doubles as the row's key, so it must be unique. */\n name: string\n /** The number the row is about. */\n value: number\n /**\n * The number the measure is being judged against — last year, the budget,\n * the SLO. Drawn as a rule across the bar, not as a second bar.\n */\n target?: number\n /**\n * The qualitative bands, as ascending UPPER bounds. `[60, 80]` on a 0–100\n * scale draws three bands: 0–60, 60–80, 80–100.\n *\n * Few's limit is five, and it is a real one: past that the shades stop being\n * separable and the bands become a gradient nobody can read a boundary off.\n */\n ranges?: number[]\n /** The scale, as `[min, max]`. Falls back to the chart's, then to the data. */\n domain?: [number, number]\n /** A second line under the name — the unit, the window, the owner. */\n detail?: ReactNode\n}\n\n/** A measure with its scale and bands resolved. */\ninterface ResolvedMeasure extends BulletMeasure {\n domain: [number, number]\n bands: { from: number; to: number; weight: number }[]\n /** Written out for the hidden table, which cannot print an array. */\n rangeLabel: string\n}\n\n/** Where a value sits on the scale, 0 to 1, clamped to it. */\nfunction position(value: number, [low, high]: [number, number]): number {\n const span = high - low\n if (span === 0) return 0\n return Math.min(1, Math.max(0, (value - low) / span))\n}\n\n/**\n * A share of the track, as a CSS percentage.\n *\n * Rounded, because binary floating point turns `0.8 - 0.5` into\n * `30.000000000000004%` — a number no layout engine cares about and every\n * reader of the DOM does. Four places is finer than a subpixel on any track\n * this chart will be given.\n */\nfunction pct(share: number): string {\n return `${Number((share * 100).toFixed(4))}%`\n}\n\n/** The bands, from the ascending upper bounds, each with its own weight. */\nfunction bands(\n ranges: number[],\n [low, high]: [number, number],\n formatValue: (value: number) => string,\n): { list: ResolvedMeasure['bands']; label: string } {\n const bounds = [...new Set(ranges.filter(Number.isFinite))]\n .sort((a, b) => a - b)\n .filter((bound) => bound > low && bound < high)\n\n const edges = [low, ...bounds, high]\n const count = edges.length - 1\n const { heaviest, lightest } = BAND_WEIGHT\n\n const list = Array.from({ length: count }, (_, index) => ({\n from: edges[index]!,\n to: edges[index + 1]!,\n // Heaviest at the low end, lightening as the scale rises — so the measure\n // bar, which is solid, stands out most where performance is best.\n weight: count === 1 ? lightest : heaviest - (index / (count - 1)) * (heaviest - lightest),\n }))\n\n // The label names the bounds the caller SET, not the band tops: the last\n // band's top is the end of the scale, which is not a threshold anybody chose.\n return { list, label: bounds.map(formatValue).join(', ') }\n}\n\n/** Every measure, with its scale and bands worked out. */\nfunction resolve(\n data: BulletMeasure[],\n domain: [number, number] | undefined,\n ranges: number[] | undefined,\n formatValue: (value: number) => string,\n): ResolvedMeasure[] {\n return data.map((measure) => {\n const own = measure.ranges ?? ranges ?? []\n const high = Math.max(measure.value, measure.target ?? Number.NEGATIVE_INFINITY, ...own)\n // Anchored at zero unless told otherwise: the measure is drawn as a BAR,\n // and a bar read off a scale that does not start at zero overstates every\n // difference on it.\n const scale: [number, number] = measure.domain ?? domain ?? [Math.min(0, measure.value), high || 1]\n const built = bands(own, scale, formatValue)\n\n return { ...measure, domain: scale, bands: built.list, rangeLabel: built.label }\n })\n}\n\nexport interface BulletChartProps {\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 rows 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 /** One entry per measure. Order is the reading order; it is not sorted. */\n data: BulletMeasure[]\n /**\n * The bands every measure falls back to, as ascending upper bounds.\n *\n * Shared bands only mean something when the measures share a scale. Where\n * they do not — a latency beside a conversion rate — put `ranges` and\n * `domain` on each measure instead.\n */\n ranges?: number[]\n /** The scale every measure falls back to, as `[min, max]`. */\n domain?: [number, number]\n /** Formats every number the chart prints. */\n formatValue?: (value: number) => string\n /** Prints the scale's two ends under each track. */\n showScale?: boolean\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /** What the chart shows when it has nothing to draw. */\n empty?: ChartEmptyProps\n}\n\n/**\n * A measure, its target, and the bands that say whether the number is any good\n * — one dense row per thing being tracked.\n *\n * Stephen Few designed this to replace the dashboard gauge, and the argument\n * has held: a speedometer spends a whole card saying one number badly, where a\n * bullet graph says the same number, its target, and the qualitative context\n * around it in the height of a line of text. Ten of them stack into a status\n * page a reader can scan in one pass.\n *\n * What it will not do is show change over time. A bullet graph is one instant,\n * and `target` is the only comparison it carries; when the question is\n * \"how did we get here\" it wants a `<LineChart>`, and when several measures\n * have to be compared against each OTHER rather than each against its own\n * target, a `<BarList>` ranks them and this does not.\n *\n * **The bands are the part to be careful with.** They are a judgement — someone\n * decided that 80 is \"good\" — drawn in the same ink as the measurement, and a\n * reader has no way to tell an agreed threshold from a number somebody typed.\n * They also compress: a value near the top of the scale sits in the same band\n * whether it cleared the boundary by a point or by thirty. The band answers\n * \"is this acceptable\", never \"by how much\".\n *\n * No rendering engine, deliberately — this is the one chart in the set with\n * nothing to compute. Each row is a single linear scale with no axis, no ticks\n * and no shared plot area, so it is laid out as HTML: the bands and the bar are\n * inline-axis offsets, which means the whole chart mirrors correctly in a\n * right-to-left document, where an SVG drawn in user space would not.\n *\n * @example\n * <BulletChart\n * title=\"Service levels against target\"\n * data={[{ name: 'Uptime', value: 99.4, target: 99.9, ranges: [98, 99.5] }]}\n * domain={[95, 100]}\n * />\n */\nexport function BulletChart({\n title,\n showTitle,\n description,\n data,\n ranges,\n domain,\n formatValue = defaultTick,\n showScale = false,\n className,\n hideDataTable = false,\n empty,\n}: BulletChartProps) {\n const measures = resolve(data, domain, ranges, formatValue)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: measures as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'value', label: 'Value' },\n { key: 'target', label: 'Target' },\n { key: 'rangeLabel', label: 'Range bounds' },\n ],\n }\n }\n >\n {measures.length === 0 ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <div className=\"flex w-full flex-col gap-4\">\n {measures.map((measure) => (\n <BulletRow\n key={measure.name}\n measure={measure}\n formatValue={formatValue}\n showScale={showScale}\n />\n ))}\n </div>\n )}\n </ChartFigure>\n )\n}\n\nfunction BulletRow({\n measure,\n formatValue,\n showScale,\n}: {\n measure: ResolvedMeasure\n formatValue: (value: number) => string\n showScale: boolean\n}) {\n const scale = measure.domain\n const barShare = position(measure.value, scale)\n const targetShare = measure.target === undefined ? null : position(measure.target, scale)\n\n return (\n <div className=\"flex w-full flex-col gap-1.5\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"truncate text-sm text-(--ink)\">\n {measure.name}\n {measure.detail && (\n <span className=\"ms-2 mono-meta text-(--ink-3-aa)\">{measure.detail}</span>\n )}\n </span>\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-(--ink)\">\n {formatValue(measure.value)}\n {measure.target !== undefined && (\n <span className=\"text-(--ink-3-aa)\"> / {formatValue(measure.target)}</span>\n )}\n </span>\n </div>\n\n {/* The graphic repeats what the line above and the hidden table already\n say, so it is out of the accessibility tree entirely rather than\n announced a third time as a stack of empty boxes. */}\n <div\n aria-hidden\n data-slot=\"bullet-track\"\n className=\"relative h-6 w-full overflow-hidden rounded-(--radius-sm)\"\n >\n {measure.bands.map((band) => (\n <span\n key={`${band.from}-${band.to}`}\n data-slot=\"bullet-band\"\n className=\"absolute inset-y-0 block\"\n style={{\n insetInlineStart: pct(position(band.from, scale)),\n inlineSize: pct(position(band.to, scale) - position(band.from, scale)),\n // The one translucent-mark pair in the token layer, because it is\n // the only one that holds a different number on each ground.\n backgroundColor: `color-mix(in srgb, var(--series-1) calc(var(--chart-fill) * ${band.weight} * 100%), transparent)`,\n }}\n />\n ))}\n\n {/* Solid, and a third of the track's height. The measure is the one\n thing here that is a measurement rather than a judgement, so it is\n the only mark drawn at full weight. */}\n <span\n data-slot=\"bullet-bar\"\n className=\"absolute block bg-(--series-1)\"\n style={{\n insetInlineStart: 0,\n insetBlockStart: pct((1 - BAR_SHARE) / 2),\n blockSize: pct(BAR_SHARE),\n inlineSize: pct(barShare),\n }}\n />\n\n {targetShare !== null && (\n // A rule ACROSS the bar rather than a second bar beside it: the two\n // are different kinds of number, and the reader has to be able to\n // tell at a glance which one was achieved and which was asked for.\n <span\n data-slot=\"bullet-target\"\n className=\"absolute inset-y-1 block w-0.5 bg-(--ink)\"\n style={{ insetInlineStart: pct(targetShare), marginInlineStart: '-1px' }}\n />\n )}\n </div>\n\n {showScale && (\n <div className=\"flex justify-between mono-meta text-(--chart-axis)\">\n <span>{formatValue(scale[0])}</span>\n <span>{formatValue(scale[1])}</span>\n </div>\n )}\n </div>\n )\n}\n\nexport default BulletChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/BulletChart/BulletChart.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { defaultTick } from '../lib/format'\nimport { clampedFraction } from '../lib/scale'\n\n/**\n * The heaviest and lightest a qualitative band may be, as multiples of\n * `--chart-fill`.\n *\n * Few's rule is no more than five bands in one hue, arranged so the low end of\n * the scale is the heaviest. The measure bar is solid ink on top of them, so\n * the ceiling has to stay well under it: bands that compete with the bar stop\n * being the ground it is read against.\n */\nconst BAND_WEIGHT = { heaviest: 1, lightest: 0.3 } as const\n\n/** How tall the measure bar is, as a share of the track. Few's is a third. */\nconst BAR_SHARE = 0.34\n\n/** One measure, its target, and the bands it is read against. */\nexport interface BulletMeasure {\n /** What is being measured. Doubles as the row's key, so it must be unique. */\n name: string\n /** The number the row is about. */\n value: number\n /**\n * The number the measure is being judged against — last year, the budget,\n * the SLO. Drawn as a rule across the bar, not as a second bar.\n */\n target?: number\n /**\n * The qualitative bands, as ascending UPPER bounds. `[60, 80]` on a 0–100\n * scale draws three bands: 0–60, 60–80, 80–100.\n *\n * Few's limit is five, and it is a real one: past that the shades stop being\n * separable and the bands become a gradient nobody can read a boundary off.\n */\n ranges?: number[]\n /** The scale, as `[min, max]`. Falls back to the chart's, then to the data. */\n domain?: [number, number]\n /** A second line under the name — the unit, the window, the owner. */\n detail?: ReactNode\n}\n\n/** A measure with its scale and bands resolved. */\ninterface ResolvedMeasure extends BulletMeasure {\n domain: [number, number]\n bands: { from: number; to: number; weight: number }[]\n /** Written out for the hidden table, which cannot print an array. */\n rangeLabel: string\n}\n\n/** Where a value sits on the scale, 0 to 1, clamped to it. */\nfunction position(value: number, [low, high]: [number, number]): number {\n return clampedFraction(value, low, high)\n}\n\n/** Whether a number is off the end of the scale it is being drawn against. */\nfunction overflows(value: number | undefined, [low, high]: [number, number]): boolean {\n return value !== undefined && Number.isFinite(value) && (value > high || value < low)\n}\n\n/**\n * A share of the track, as a CSS percentage.\n *\n * Rounded, because binary floating point turns `0.8 - 0.5` into\n * `30.000000000000004%` — a number no layout engine cares about and every\n * reader of the DOM does. Four places is finer than a subpixel on any track\n * this chart will be given.\n */\nfunction pct(share: number): string {\n return `${Number((share * 100).toFixed(4))}%`\n}\n\n/** The bands, from the ascending upper bounds, each with its own weight. */\nfunction bands(\n ranges: number[],\n [low, high]: [number, number],\n formatValue: (value: number) => string,\n): { list: ResolvedMeasure['bands']; label: string } {\n const set = [...new Set(ranges.filter(Number.isFinite))].sort((a, b) => a - b)\n // A bound off the end of the scale has no boundary to draw: it would be a\n // band with no width, or one that starts past the track. It is dropped from\n // the picture and kept in the LABEL, because a caller who pinned `[0, 50]`\n // and passed `[60, 80]` has made a mistake, and one flat band that says\n // nothing is how it stays invisible.\n const bounds = set.filter((bound) => bound > low && bound < high)\n\n const edges = [low, ...bounds, high]\n const count = edges.length - 1\n const { heaviest, lightest } = BAND_WEIGHT\n\n const list = Array.from({ length: count }, (_, index) => ({\n from: edges[index]!,\n to: edges[index + 1]!,\n // Heaviest at the low end, lightening as the scale rises — so the measure\n // bar, which is solid, stands out most where performance is best.\n weight: count === 1 ? lightest : heaviest - (index / (count - 1)) * (heaviest - lightest),\n }))\n\n // The label names the bounds the caller SET, not the band tops: the last\n // band's top is the end of the scale, which is not a threshold anybody chose\n // — and not the drawable subset either, or a bound the scale cannot hold\n // would go missing from the reading as well as from the picture.\n return { list, label: set.map(formatValue).join(', ') }\n}\n\n/** Every measure, with its scale and bands worked out. */\nfunction resolve(\n data: BulletMeasure[],\n domain: [number, number] | undefined,\n ranges: number[] | undefined,\n formatValue: (value: number) => string,\n): ResolvedMeasure[] {\n return data.map((measure) => {\n const own = measure.ranges ?? ranges ?? []\n const high = Math.max(measure.value, measure.target ?? Number.NEGATIVE_INFINITY, ...own)\n // Anchored at zero unless told otherwise: the measure is drawn as a BAR,\n // and a bar read off a scale that does not start at zero overstates every\n // difference on it.\n const scale: [number, number] = measure.domain ?? domain ?? [Math.min(0, measure.value), high || 1]\n const built = bands(own, scale, formatValue)\n\n return { ...measure, domain: scale, bands: built.list, rangeLabel: built.label }\n })\n}\n\nexport interface BulletChartProps {\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 rows 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 /** One entry per measure. Order is the reading order; it is not sorted. */\n data: BulletMeasure[]\n /**\n * The bands every measure falls back to, as ascending upper bounds.\n *\n * Shared bands only mean something when the measures share a scale. Where\n * they do not — a latency beside a conversion rate — put `ranges` and\n * `domain` on each measure instead.\n */\n ranges?: number[]\n /** The scale every measure falls back to, as `[min, max]`. */\n domain?: [number, number]\n /** Formats every number the chart prints. */\n formatValue?: (value: number) => string\n /** Prints the scale's two ends under each track. */\n showScale?: boolean\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /** What the chart shows when it has nothing to draw. */\n empty?: ChartEmptyProps\n}\n\n/**\n * A measure, its target, and the bands that say whether the number is any good\n * — one dense row per thing being tracked.\n *\n * Stephen Few designed this to replace the dashboard gauge, and the argument\n * has held: a speedometer spends a whole card saying one number badly, where a\n * bullet graph says the same number, its target, and the qualitative context\n * around it in the height of a line of text. Ten of them stack into a status\n * page a reader can scan in one pass.\n *\n * What it will not do is show change over time. A bullet graph is one instant,\n * and `target` is the only comparison it carries; when the question is\n * \"how did we get here\" it wants a `<LineChart>`, and when several measures\n * have to be compared against each OTHER rather than each against its own\n * target, a `<BarList>` ranks them and this does not.\n *\n * **The bands are the part to be careful with.** They are a judgement — someone\n * decided that 80 is \"good\" — drawn in the same ink as the measurement, and a\n * reader has no way to tell an agreed threshold from a number somebody typed.\n * They also compress: a value near the top of the scale sits in the same band\n * whether it cleared the boundary by a point or by thirty. The band answers\n * \"is this acceptable\", never \"by how much\".\n *\n * No rendering engine, deliberately — this is the one chart in the set with\n * nothing to compute. Each row is a single linear scale with no axis, no ticks\n * and no shared plot area, so it is laid out as HTML: the bands and the bar are\n * inline-axis offsets, which means the whole chart mirrors correctly in a\n * right-to-left document, where an SVG drawn in user space would not.\n *\n * @example\n * <BulletChart\n * title=\"Service levels against target\"\n * data={[{ name: 'Uptime', value: 99.4, target: 99.9, ranges: [98, 99.5] }]}\n * domain={[95, 100]}\n * />\n */\nexport function BulletChart({\n title,\n showTitle,\n description,\n data,\n ranges,\n domain,\n formatValue = defaultTick,\n showScale = false,\n className,\n hideDataTable = false,\n empty,\n}: BulletChartProps) {\n const measures = resolve(data, domain, ranges, formatValue)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: measures as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'value', label: 'Value' },\n { key: 'target', label: 'Target' },\n { key: 'rangeLabel', label: 'Range bounds' },\n ],\n }\n }\n isEmpty={measures.length === 0}\n empty={empty}\n >\n <div className=\"flex w-full flex-col gap-4\">\n {measures.map((measure) => (\n <BulletRow\n key={measure.name}\n measure={measure}\n formatValue={formatValue}\n showScale={showScale}\n />\n ))}\n </div>\n </ChartFigure>\n )\n}\n\nfunction BulletRow({\n measure,\n formatValue,\n showScale,\n}: {\n measure: ResolvedMeasure\n formatValue: (value: number) => string\n showScale: boolean\n}) {\n const scale = measure.domain\n const barShare = position(measure.value, scale)\n const targetShare = measure.target === undefined ? null : position(measure.target, scale)\n const runsOver = overflows(measure.value, scale) || overflows(measure.target, scale)\n\n return (\n <div className=\"flex w-full flex-col gap-1.5\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"truncate text-sm text-(--ink)\">\n {measure.name}\n {measure.detail && (\n <span className=\"ms-2 mono-meta text-(--ink-3-aa)\">{measure.detail}</span>\n )}\n </span>\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-(--ink)\">\n {formatValue(measure.value)}\n {measure.target !== undefined && (\n <span className=\"text-(--ink-3-aa)\"> / {formatValue(measure.target)}</span>\n )}\n </span>\n </div>\n\n {/* The graphic repeats what the line above and the hidden table already\n say, so it is out of the accessibility tree entirely rather than\n announced a third time as a stack of empty boxes. */}\n <div\n aria-hidden\n data-slot=\"bullet-track\"\n className=\"relative h-6 w-full overflow-hidden rounded-(--radius-sm)\"\n >\n {measure.bands.map((band) => (\n <span\n key={`${band.from}-${band.to}`}\n data-slot=\"bullet-band\"\n className=\"absolute inset-y-0 block\"\n style={{\n insetInlineStart: pct(position(band.from, scale)),\n inlineSize: pct(position(band.to, scale) - position(band.from, scale)),\n // The one translucent-mark pair in the token layer, because it is\n // the only one that holds a different number on each ground.\n backgroundColor: `color-mix(in srgb, var(--series-1) calc(var(--chart-fill) * ${band.weight} * 100%), transparent)`,\n }}\n />\n ))}\n\n {/* Solid, and a third of the track's height. The measure is the one\n thing here that is a measurement rather than a judgement, so it is\n the only mark drawn at full weight. */}\n <span\n data-slot=\"bullet-bar\"\n className=\"absolute block bg-(--series-1)\"\n style={{\n insetInlineStart: 0,\n insetBlockStart: pct((1 - BAR_SHARE) / 2),\n blockSize: pct(BAR_SHARE),\n inlineSize: pct(barShare),\n }}\n />\n\n {targetShare !== null && (\n // A rule ACROSS the bar rather than a second bar beside it: the two\n // are different kinds of number, and the reader has to be able to\n // tell at a glance which one was achieved and which was asked for.\n <span\n data-slot=\"bullet-target\"\n className=\"absolute inset-y-1 block w-0.5 bg-(--ink)\"\n style={{ insetInlineStart: pct(targetShare), marginInlineStart: '-1px' }}\n />\n )}\n\n {runsOver && (\n // Clamped and silent, a number past the end of the scale fills the\n // track exactly as a number AT the end of it does — so the one thing\n // the row cannot say is the one thing the reader most needs to know.\n // The notch is what breaks that tie; the printed figure above still\n // carries the real number.\n <span\n data-slot=\"bullet-overflow\"\n className=\"absolute inset-y-0 end-0 block w-1.5 bg-(--chart-surface)\"\n style={{\n clipPath: 'polygon(100% 0, 0 50%, 100% 100%)',\n }}\n />\n )}\n </div>\n\n {showScale && (\n <div className=\"flex justify-between mono-meta text-(--chart-axis)\">\n <span>{formatValue(scale[0])}</span>\n <span>{formatValue(scale[1])}</span>\n </div>\n )}\n </div>\n )\n}\n\nexport default BulletChart\n"],"mappings":"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","names":[]}
@@ -70,8 +70,15 @@ interface ComposedChartProps<TData extends Record<string, unknown>, TConfig exte
70
70
  animationType?: ChartRevealType;
71
71
  barGap?: number;
72
72
  barCategoryGap?: number;
73
- /** The series lit on first render. Selection dims every other series. */
73
+ /** The series lit on first render, when the chart keeps its own selection. */
74
74
  defaultSelectedDataKey?: string | null;
75
+ /**
76
+ * The selected series, driven from outside.
77
+ *
78
+ * Give this and the chart follows it; leave it undefined and the chart keeps
79
+ * its own, starting from `defaultSelectedDataKey`.
80
+ */
81
+ selectedDataKey?: string | null;
75
82
  /** Fires when the selection changes, and with null when it is cleared. */
76
83
  onSelectionChange?: (selectedDataKey: string | null) => void;
77
84
  /**
@@ -118,7 +125,7 @@ interface ComposedChartProps<TData extends Record<string, unknown>, TConfig exte
118
125
  * <ComposedChart.Line dataKey="profit" />
119
126
  * </ComposedChart>
120
127
  */
121
- declare function ComposedChart<TData extends Record<string, unknown>, TConfig extends Record<string, ChartConfig[string]>>({ config, data, title, showTitle, description, children, className, chartProps, curveType, animationType, barGap, barCategoryGap, defaultSelectedDataKey, onSelectionChange, isLoading, loadingBars, xDataKey, hideDataTable, empty, }: ComposedChartProps<TData, TConfig>): react.JSX.Element;
128
+ declare function ComposedChart<TData extends Record<string, unknown>, TConfig extends Record<string, ChartConfig[string]>>({ config, data, title, showTitle, description, children, className, chartProps, curveType, animationType, barGap, barCategoryGap, defaultSelectedDataKey, selectedDataKey: controlledDataKey, onSelectionChange, isLoading, loadingBars, xDataKey, hideDataTable, empty, }: ComposedChartProps<TData, TConfig>): react.JSX.Element;
122
129
  declare namespace ComposedChart {
123
130
  var Bar: ({ dataKey, variant, radius, glowing, animationType, isClickable, enableHoverHighlight, children, barProps, }: ComposedBarProps) => react.JSX.Element | null;
124
131
  var Line: ({ dataKey, strokeVariant, curveType, animationType, connectNulls, glowing, isClickable, children, lineProps, }: ComposedLineProps) => react.JSX.Element | null;
@@ -4,7 +4,6 @@ import {
4
4
  Children,
5
5
  createContext,
6
6
  isValidElement,
7
- useCallback,
8
7
  useContext,
9
8
  useId,
10
9
  useMemo,
@@ -21,7 +20,8 @@ import {
21
20
  import { motion, useReducedMotion } from "motion/react";
22
21
  import { ChartContainer } from "../lib/chart.js";
23
22
  import { ChartFigure } from "../lib/figure.js";
24
- import { ChartEmpty } from "../lib/empty.js";
23
+ import { useChartSelection } from "../lib/selection.js";
24
+ import {} from "../lib/empty.js";
25
25
  import { axisLabel } from "../lib/axis.js";
26
26
  import { defaultTick } from "../lib/format.js";
27
27
  import { Annotation, ReferenceBand, ReferenceLine } from "../lib/annotations.js";
@@ -70,6 +70,7 @@ function ComposedChart({
70
70
  barGap,
71
71
  barCategoryGap,
72
72
  defaultSelectedDataKey = null,
73
+ selectedDataKey: controlledDataKey,
73
74
  onSelectionChange,
74
75
  isLoading = false,
75
76
  loadingBars,
@@ -79,7 +80,6 @@ function ComposedChart({
79
80
  }) {
80
81
  const chartId = useId().replace(/:/g, "");
81
82
  const [introStartedAt] = useState(() => Date.now());
82
- const [selectedDataKey, setSelectedDataKey] = useState(defaultSelectedDataKey);
83
83
  const [hoveredIndex, setHoveredIndex] = useState(null);
84
84
  const { rows: loadingData, onShimmerExit } = useLoadingRows(isLoading, loadingBars ?? 12);
85
85
  const zoom = useChartZoom({ data, xDataKey });
@@ -103,12 +103,10 @@ function ComposedChart({
103
103
  const showToolbar = toolbarSlot !== null && !isLoading;
104
104
  const isEmpty = !isLoading && empty !== false && data.length === 0;
105
105
  const displayData = showBrush || showToolbar ? zoom.visibleData : data;
106
- const selectDataKey = useCallback(
107
- (next) => {
108
- setSelectedDataKey(next);
109
- onSelectionChange?.(next);
110
- },
111
- [onSelectionChange]
106
+ const [selectedDataKey, selectDataKey] = useChartSelection(
107
+ controlledDataKey,
108
+ defaultSelectedDataKey,
109
+ onSelectionChange
112
110
  );
113
111
  const context = useMemo(
114
112
  () => ({
@@ -143,8 +141,10 @@ function ComposedChart({
143
141
  description,
144
142
  className,
145
143
  table: hideDataTable ? false : { rows: data, rowKey: xDataKey, columns },
144
+ isEmpty,
145
+ empty,
146
146
  children: [
147
- isEmpty ? /* @__PURE__ */ jsx(ChartEmpty, { ...empty || {} }) : /* @__PURE__ */ jsx(
147
+ /* @__PURE__ */ jsx(
148
148
  ChartControls,
149
149
  {
150
150
  toolbar: showToolbar ? toolbarSlot : null,