@misoto22/design 0.3.1 → 0.6.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 (266) hide show
  1. package/README.md +124 -0
  2. package/bin/misoto22-design.mjs +205 -0
  3. package/dist/agent/Accordion.md +41 -0
  4. package/dist/agent/Alert.md +42 -0
  5. package/dist/agent/AppShell.md +40 -0
  6. package/dist/agent/AreaChart.md +68 -0
  7. package/dist/agent/Article.md +41 -0
  8. package/dist/agent/Avatar.md +32 -0
  9. package/dist/agent/Badge.md +40 -0
  10. package/dist/agent/BarChart.md +65 -0
  11. package/dist/agent/BarList.md +40 -0
  12. package/dist/agent/BigNumber.md +45 -0
  13. package/dist/agent/BoxPlot.md +58 -0
  14. package/dist/agent/Breadcrumb.md +32 -0
  15. package/dist/agent/BulletChart.md +48 -0
  16. package/dist/agent/Button.md +56 -0
  17. package/dist/agent/Calendar.md +42 -0
  18. package/dist/agent/Card.md +49 -0
  19. package/dist/agent/Checkbox.md +35 -0
  20. package/dist/agent/Collapsible.md +46 -0
  21. package/dist/agent/Combobox.md +50 -0
  22. package/dist/agent/Command.md +93 -0
  23. package/dist/agent/ComposedChart.md +57 -0
  24. package/dist/agent/ContextMenu.md +59 -0
  25. package/dist/agent/DatePicker.md +75 -0
  26. package/dist/agent/Diagram.md +46 -0
  27. package/dist/agent/Dialog.md +52 -0
  28. package/dist/agent/DropdownMenu.md +70 -0
  29. package/dist/agent/EmptyState.md +37 -0
  30. package/dist/agent/ErrorState.md +41 -0
  31. package/dist/agent/Facet.md +82 -0
  32. package/dist/agent/Field.md +37 -0
  33. package/dist/agent/FigureBand.md +37 -0
  34. package/dist/agent/FloatingIconButton.md +38 -0
  35. package/dist/agent/FunnelChart.md +52 -0
  36. package/dist/agent/Heatmap.md +53 -0
  37. package/dist/agent/Histogram.md +63 -0
  38. package/dist/agent/Input.md +30 -0
  39. package/dist/agent/Kbd.md +29 -0
  40. package/dist/agent/LineChart.md +60 -0
  41. package/dist/agent/LinkArrow.md +29 -0
  42. package/dist/agent/NativeSelect.md +40 -0
  43. package/dist/agent/NavItem.md +35 -0
  44. package/dist/agent/Pagination.md +43 -0
  45. package/dist/agent/PieChart.md +55 -0
  46. package/dist/agent/Popover.md +57 -0
  47. package/dist/agent/Progress.md +33 -0
  48. package/dist/agent/RadarChart.md +54 -0
  49. package/dist/agent/RadialChart.md +57 -0
  50. package/dist/agent/RadioGroup.md +44 -0
  51. package/dist/agent/SankeyChart.md +61 -0
  52. package/dist/agent/ScatterChart.md +54 -0
  53. package/dist/agent/ScrollArea.md +40 -0
  54. package/dist/agent/SearchableMenu.md +46 -0
  55. package/dist/agent/Select.md +75 -0
  56. package/dist/agent/Separator.md +35 -0
  57. package/dist/agent/Sheet.md +57 -0
  58. package/dist/agent/Skeleton.md +73 -0
  59. package/dist/agent/Slider.md +39 -0
  60. package/dist/agent/Sparkline.md +45 -0
  61. package/dist/agent/Spinner.md +39 -0
  62. package/dist/agent/StatusDot.md +36 -0
  63. package/dist/agent/StatusPill.md +28 -0
  64. package/dist/agent/Steps.md +44 -0
  65. package/dist/agent/Switch.md +29 -0
  66. package/dist/agent/Table.md +85 -0
  67. package/dist/agent/Tabs.md +48 -0
  68. package/dist/agent/Tag.md +35 -0
  69. package/dist/agent/Textarea.md +26 -0
  70. package/dist/agent/Toast.md +26 -0
  71. package/dist/agent/ToggleGroup.md +44 -0
  72. package/dist/agent/Tooltip.md +51 -0
  73. package/dist/agent/TreemapChart.md +52 -0
  74. package/dist/agent/WaterfallChart.md +62 -0
  75. package/dist/agent/catalog.json +1791 -0
  76. package/dist/agent/index.md +131 -0
  77. package/dist/article.css +393 -0
  78. package/dist/charts/AreaChart/AreaChart.d.ts +191 -0
  79. package/dist/charts/AreaChart/AreaChart.js +480 -0
  80. package/dist/charts/AreaChart/AreaChart.js.map +1 -0
  81. package/dist/charts/BarChart/BarChart.d.ts +179 -0
  82. package/dist/charts/BarChart/BarChart.js +591 -0
  83. package/dist/charts/BarChart/BarChart.js.map +1 -0
  84. package/dist/charts/BarList/BarList.d.ts +57 -0
  85. package/dist/charts/BarList/BarList.js +71 -0
  86. package/dist/charts/BarList/BarList.js.map +1 -0
  87. package/dist/charts/BigNumber/BigNumber.d.ts +56 -0
  88. package/dist/charts/BigNumber/BigNumber.js +31 -0
  89. package/dist/charts/BigNumber/BigNumber.js.map +1 -0
  90. package/dist/charts/BoxPlot/BoxPlot.d.ts +177 -0
  91. package/dist/charts/BoxPlot/BoxPlot.js +495 -0
  92. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -0
  93. package/dist/charts/BulletChart/BulletChart.d.ts +100 -0
  94. package/dist/charts/BulletChart/BulletChart.js +162 -0
  95. package/dist/charts/BulletChart/BulletChart.js.map +1 -0
  96. package/dist/charts/ComposedChart/ComposedChart.d.ts +171 -0
  97. package/dist/charts/ComposedChart/ComposedChart.js +667 -0
  98. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -0
  99. package/dist/charts/Facet/Facet.d.ts +240 -0
  100. package/dist/charts/Facet/Facet.js +133 -0
  101. package/dist/charts/Facet/Facet.js.map +1 -0
  102. package/dist/charts/Facet/panels.d.ts +187 -0
  103. package/dist/charts/Facet/panels.js +203 -0
  104. package/dist/charts/Facet/panels.js.map +1 -0
  105. package/dist/charts/FunnelChart/FunnelChart.d.ts +101 -0
  106. package/dist/charts/FunnelChart/FunnelChart.js +147 -0
  107. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -0
  108. package/dist/charts/Heatmap/Heatmap.d.ts +78 -0
  109. package/dist/charts/Heatmap/Heatmap.js +108 -0
  110. package/dist/charts/Heatmap/Heatmap.js.map +1 -0
  111. package/dist/charts/Histogram/Histogram.d.ts +165 -0
  112. package/dist/charts/Histogram/Histogram.js +350 -0
  113. package/dist/charts/Histogram/Histogram.js.map +1 -0
  114. package/dist/charts/LineChart/LineChart.d.ts +167 -0
  115. package/dist/charts/LineChart/LineChart.js +486 -0
  116. package/dist/charts/LineChart/LineChart.js.map +1 -0
  117. package/dist/charts/PieChart/PieChart.d.ts +128 -0
  118. package/dist/charts/PieChart/PieChart.js +309 -0
  119. package/dist/charts/PieChart/PieChart.js.map +1 -0
  120. package/dist/charts/RadarChart/RadarChart.d.ts +123 -0
  121. package/dist/charts/RadarChart/RadarChart.js +299 -0
  122. package/dist/charts/RadarChart/RadarChart.js.map +1 -0
  123. package/dist/charts/RadialChart/RadialChart.d.ts +124 -0
  124. package/dist/charts/RadialChart/RadialChart.js +257 -0
  125. package/dist/charts/RadialChart/RadialChart.js.map +1 -0
  126. package/dist/charts/SankeyChart/SankeyChart.d.ts +137 -0
  127. package/dist/charts/SankeyChart/SankeyChart.js +501 -0
  128. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -0
  129. package/dist/charts/ScatterChart/ScatterChart.d.ts +128 -0
  130. package/dist/charts/ScatterChart/ScatterChart.js +269 -0
  131. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -0
  132. package/dist/charts/Sparkline/Sparkline.d.ts +53 -0
  133. package/dist/charts/Sparkline/Sparkline.js +93 -0
  134. package/dist/charts/Sparkline/Sparkline.js.map +1 -0
  135. package/dist/charts/TreemapChart/TreemapChart.d.ts +93 -0
  136. package/dist/charts/TreemapChart/TreemapChart.js +118 -0
  137. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -0
  138. package/dist/charts/WaterfallChart/WaterfallChart.d.ts +164 -0
  139. package/dist/charts/WaterfallChart/WaterfallChart.js +319 -0
  140. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -0
  141. package/dist/charts/index.d.ts +41 -0
  142. package/dist/charts/index.js +128 -0
  143. package/dist/charts/index.js.map +1 -0
  144. package/dist/charts/lib/annotations.d.ts +103 -0
  145. package/dist/charts/lib/annotations.js +99 -0
  146. package/dist/charts/lib/annotations.js.map +1 -0
  147. package/dist/charts/lib/axis.d.ts +18 -0
  148. package/dist/charts/lib/axis.js +21 -0
  149. package/dist/charts/lib/axis.js.map +1 -0
  150. package/dist/charts/lib/background.d.ts +33 -0
  151. package/dist/charts/lib/background.js +173 -0
  152. package/dist/charts/lib/background.js.map +1 -0
  153. package/dist/charts/lib/brush.d.ts +94 -0
  154. package/dist/charts/lib/brush.js +535 -0
  155. package/dist/charts/lib/brush.js.map +1 -0
  156. package/dist/charts/lib/chart.d.ts +94 -0
  157. package/dist/charts/lib/chart.js +134 -0
  158. package/dist/charts/lib/chart.js.map +1 -0
  159. package/dist/charts/lib/dot.d.ts +29 -0
  160. package/dist/charts/lib/dot.js +78 -0
  161. package/dist/charts/lib/dot.js.map +1 -0
  162. package/dist/charts/lib/empty.d.ts +31 -0
  163. package/dist/charts/lib/empty.js +13 -0
  164. package/dist/charts/lib/empty.js.map +1 -0
  165. package/dist/charts/lib/export.d.ts +103 -0
  166. package/dist/charts/lib/export.js +204 -0
  167. package/dist/charts/lib/export.js.map +1 -0
  168. package/dist/charts/lib/figure.d.ts +61 -0
  169. package/dist/charts/lib/figure.js +59 -0
  170. package/dist/charts/lib/figure.js.map +1 -0
  171. package/dist/charts/lib/format.d.ts +46 -0
  172. package/dist/charts/lib/format.js +56 -0
  173. package/dist/charts/lib/format.js.map +1 -0
  174. package/dist/charts/lib/legend.d.ts +45 -0
  175. package/dist/charts/lib/legend.js +117 -0
  176. package/dist/charts/lib/legend.js.map +1 -0
  177. package/dist/charts/lib/loading.d.ts +48 -0
  178. package/dist/charts/lib/loading.js +105 -0
  179. package/dist/charts/lib/loading.js.map +1 -0
  180. package/dist/charts/lib/paint.d.ts +154 -0
  181. package/dist/charts/lib/paint.js +551 -0
  182. package/dist/charts/lib/paint.js.map +1 -0
  183. package/dist/charts/lib/sonify-control.d.ts +163 -0
  184. package/dist/charts/lib/sonify-control.js +172 -0
  185. package/dist/charts/lib/sonify-control.js.map +1 -0
  186. package/dist/charts/lib/sonify.d.ts +248 -0
  187. package/dist/charts/lib/sonify.js +262 -0
  188. package/dist/charts/lib/sonify.js.map +1 -0
  189. package/dist/charts/lib/toolbar.d.ts +143 -0
  190. package/dist/charts/lib/toolbar.js +156 -0
  191. package/dist/charts/lib/toolbar.js.map +1 -0
  192. package/dist/charts/lib/tooltip.d.ts +59 -0
  193. package/dist/charts/lib/tooltip.js +135 -0
  194. package/dist/charts/lib/tooltip.js.map +1 -0
  195. package/dist/charts/lib/values.d.ts +56 -0
  196. package/dist/charts/lib/values.js +71 -0
  197. package/dist/charts/lib/values.js.map +1 -0
  198. package/dist/charts/lib/zoom.d.ts +139 -0
  199. package/dist/charts/lib/zoom.js +314 -0
  200. package/dist/charts/lib/zoom.js.map +1 -0
  201. package/dist/components/AppShell/AppShell.d.ts +15 -1
  202. package/dist/components/AppShell/AppShell.js +8 -4
  203. package/dist/components/AppShell/AppShell.js.map +1 -1
  204. package/dist/components/Article/Article.d.ts +51 -0
  205. package/dist/components/Article/Article.js +21 -0
  206. package/dist/components/Article/Article.js.map +1 -0
  207. package/dist/components/Button/Button.js +2 -2
  208. package/dist/components/Button/Button.js.map +1 -1
  209. package/dist/components/Calendar/Calendar.d.ts +1 -1
  210. package/dist/components/Calendar/Calendar.js +259 -24
  211. package/dist/components/Calendar/Calendar.js.map +1 -1
  212. package/dist/components/Checkbox/Checkbox.js +1 -1
  213. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  214. package/dist/components/Collapsible/Collapsible.d.ts +15 -1
  215. package/dist/components/Collapsible/Collapsible.js +18 -0
  216. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  217. package/dist/components/Combobox/Combobox.js +1 -1
  218. package/dist/components/Combobox/Combobox.js.map +1 -1
  219. package/dist/components/Command/Command.js +7 -7
  220. package/dist/components/Command/Command.js.map +1 -1
  221. package/dist/components/ContextMenu/ContextMenu.js +2 -2
  222. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  223. package/dist/components/Diagram/Diagram.d.ts +81 -0
  224. package/dist/components/Diagram/Diagram.js +139 -0
  225. package/dist/components/Diagram/Diagram.js.map +1 -0
  226. package/dist/components/Dialog/Dialog.js +1 -1
  227. package/dist/components/Dialog/Dialog.js.map +1 -1
  228. package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
  229. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  230. package/dist/components/FigureBand/FigureBand.js +41 -30
  231. package/dist/components/FigureBand/FigureBand.js.map +1 -1
  232. package/dist/components/Popover/Popover.js +1 -1
  233. package/dist/components/Popover/Popover.js.map +1 -1
  234. package/dist/components/Select/Select.js +2 -2
  235. package/dist/components/Select/Select.js.map +1 -1
  236. package/dist/components/Sheet/Sheet.js +1 -1
  237. package/dist/components/Sheet/Sheet.js.map +1 -1
  238. package/dist/components/Slider/Slider.js +1 -1
  239. package/dist/components/Slider/Slider.js.map +1 -1
  240. package/dist/components/Steps/Steps.d.ts +65 -0
  241. package/dist/components/Steps/Steps.js +55 -0
  242. package/dist/components/Steps/Steps.js.map +1 -0
  243. package/dist/components/Switch/Switch.js +1 -1
  244. package/dist/components/Switch/Switch.js.map +1 -1
  245. package/dist/components/Tooltip/Tooltip.js +1 -1
  246. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  247. package/dist/index.d.ts +4 -1
  248. package/dist/index.js +3 -0
  249. package/dist/index.js.map +1 -1
  250. package/dist/lib/useSelectionIndicator.js +14 -10
  251. package/dist/lib/useSelectionIndicator.js.map +1 -1
  252. package/dist/semantic.css +19 -0
  253. package/dist/styles.css +1 -1
  254. package/dist/themes.css +103 -7
  255. package/dist/tokens.css +193 -10
  256. package/dist/tokens.d.ts +25 -0
  257. package/dist/tokens.js +154 -8
  258. package/dist/tokens.json +154 -8
  259. package/package.json +45 -20
  260. package/skills/misoto22-design/SKILL.md +171 -0
  261. package/skills/misoto22-design/evals/claims.json +95 -0
  262. package/skills/misoto22-design/rules/a11y.md +115 -0
  263. package/skills/misoto22-design/rules/composition.md +108 -0
  264. package/skills/misoto22-design/rules/forms.md +128 -0
  265. package/skills/misoto22-design/rules/naming.md +155 -0
  266. package/skills/misoto22-design/rules/tokens.md +131 -0
@@ -0,0 +1,495 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useId,
7
+ useMemo
8
+ } from "react";
9
+ import {
10
+ Bar as RechartsBar,
11
+ BarChart as RechartsBarChart,
12
+ CartesianGrid,
13
+ XAxis as RechartsXAxis,
14
+ YAxis as RechartsYAxis,
15
+ usePlotArea
16
+ } from "recharts";
17
+ import { ChartContainer } from "../lib/chart.js";
18
+ import { ChartFigure } from "../lib/figure.js";
19
+ import { ChartEmpty } from "../lib/empty.js";
20
+ import { ChartBackground } from "../lib/background.js";
21
+ import { ChartTooltip } from "../lib/tooltip.js";
22
+ import { SeriesGradient } from "../lib/paint.js";
23
+ import { Annotation, ReferenceBand, ReferenceLine } from "../lib/annotations.js";
24
+ import { axisLabel } from "../lib/axis.js";
25
+ import { defaultTick } from "../lib/format.js";
26
+ import { cn } from "../../lib/cn.js";
27
+ const DEFAULT_CONFIG = { value: { label: "Value" } };
28
+ const CHART_MARGIN = { top: 12, right: 12, bottom: 0, left: 12 };
29
+ const TUKEY_FENCE = 1.5;
30
+ const NOTCH_FACTOR = 1.58;
31
+ const NOTCH_INSET = 0.18;
32
+ const DOMAIN_PADDING = 0.06;
33
+ function quantile(sorted, p) {
34
+ if (sorted.length === 0) return Number.NaN;
35
+ if (sorted.length === 1) return sorted[0];
36
+ const h = (sorted.length - 1) * p;
37
+ const lower = sorted[Math.floor(h)];
38
+ const upper = sorted[Math.ceil(h)];
39
+ return lower + (h - Math.floor(h)) * (upper - lower);
40
+ }
41
+ function summarise(name, values) {
42
+ const clean = values.filter(Number.isFinite).sort((a, b) => a - b);
43
+ if (clean.length === 0) return null;
44
+ const q1 = quantile(clean, 0.25);
45
+ const median = quantile(clean, 0.5);
46
+ const q3 = quantile(clean, 0.75);
47
+ const iqr = q3 - q1;
48
+ const low = q1 - TUKEY_FENCE * iqr;
49
+ const high = q3 + TUKEY_FENCE * iqr;
50
+ const inside = clean.filter((value) => value >= low && value <= high);
51
+ const outliers = clean.filter((value) => value < low || value > high);
52
+ return {
53
+ name,
54
+ // The whiskers stop at the most extreme observation still inside the
55
+ // fence, not at the fence itself. A whisker drawn to `q1 - 1.5 × IQR`
56
+ // claims a reading that may not exist in the data.
57
+ min: inside.length > 0 ? inside[0] : clean[0],
58
+ q1,
59
+ median,
60
+ q3,
61
+ max: inside.length > 0 ? inside[inside.length - 1] : clean[clean.length - 1],
62
+ outliers,
63
+ outlierCount: outliers.length,
64
+ count: clean.length
65
+ };
66
+ }
67
+ function resolve(data) {
68
+ return data.flatMap((datum) => {
69
+ if (datum.values) {
70
+ const summary = summarise(datum.name, datum.values);
71
+ return summary ? [summary] : [];
72
+ }
73
+ const outliers = datum.outliers ?? [];
74
+ return [{ ...datum, outliers, outlierCount: outliers.length }];
75
+ });
76
+ }
77
+ function valueDomain(boxes) {
78
+ const values = boxes.flatMap((box) => [box.min, box.max, ...box.outliers]);
79
+ if (values.length === 0) return [0, 1];
80
+ const low = Math.min(...values);
81
+ const high = Math.max(...values);
82
+ const pad = (high - low || Math.abs(high) || 1) * DOMAIN_PADDING;
83
+ return [low - pad, high + pad];
84
+ }
85
+ const BoxPlotContext = createContext(null);
86
+ function useBoxPlot() {
87
+ const context = useContext(BoxPlotContext);
88
+ if (!context) throw new Error("A box plot part must be rendered inside <BoxPlot>");
89
+ return context;
90
+ }
91
+ function BoxPlot({
92
+ config = DEFAULT_CONFIG,
93
+ data,
94
+ title,
95
+ showTitle,
96
+ description,
97
+ children,
98
+ className,
99
+ chartProps,
100
+ orientation = "vertical",
101
+ formatValue = defaultTick,
102
+ hideDataTable = false,
103
+ empty
104
+ }) {
105
+ const chartId = useId().replace(/:/g, "");
106
+ const isHorizontal = orientation === "horizontal";
107
+ const seriesKey = Object.keys(config)[0] ?? "value";
108
+ const boxes = useMemo(() => resolve(data), [data]);
109
+ const domain = useMemo(() => valueDomain(boxes), [boxes]);
110
+ const context = useMemo(
111
+ () => ({ config, seriesKey, boxes, isHorizontal, domain, formatValue }),
112
+ [config, seriesKey, boxes, isHorizontal, domain, formatValue]
113
+ );
114
+ const isEmpty = empty !== false && boxes.length === 0;
115
+ return /* @__PURE__ */ jsx(BoxPlotContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
116
+ ChartFigure,
117
+ {
118
+ title,
119
+ showTitle,
120
+ description,
121
+ className,
122
+ table: hideDataTable ? false : {
123
+ rows: boxes,
124
+ rowKey: "name",
125
+ columns: [
126
+ { key: "min", label: "Minimum" },
127
+ { key: "q1", label: "Lower quartile" },
128
+ { key: "median", label: "Median" },
129
+ { key: "q3", label: "Upper quartile" },
130
+ { key: "max", label: "Maximum" },
131
+ { key: "outlierCount", label: "Outliers" }
132
+ ]
133
+ },
134
+ children: isEmpty ? /* @__PURE__ */ jsx(ChartEmpty, { ...empty || {} }) : /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
135
+ RechartsBarChart,
136
+ {
137
+ id: chartId,
138
+ accessibilityLayer: true,
139
+ margin: CHART_MARGIN,
140
+ layout: isHorizontal ? "vertical" : "horizontal",
141
+ data: boxes,
142
+ barCategoryGap: "30%",
143
+ ...chartProps,
144
+ children
145
+ }
146
+ ) })
147
+ }
148
+ ) });
149
+ }
150
+ function Boxes({ showOutliers = true, notch = false, barProps }) {
151
+ const { config, seriesKey, isHorizontal } = useBoxPlot();
152
+ const id = useId().replace(/:/g, "");
153
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
154
+ /* @__PURE__ */ jsx(
155
+ RechartsBar,
156
+ {
157
+ dataKey: (row) => {
158
+ const box = row;
159
+ return [box.min, box.max];
160
+ },
161
+ name: seriesKey,
162
+ isAnimationActive: false,
163
+ fill: "transparent",
164
+ shape: (props) => /* @__PURE__ */ jsx(
165
+ BoxShape,
166
+ {
167
+ ...props,
168
+ id,
169
+ seriesKey,
170
+ isHorizontal,
171
+ showOutliers,
172
+ notch
173
+ }
174
+ ),
175
+ activeBar: (props) => /* @__PURE__ */ jsx(
176
+ BoxShape,
177
+ {
178
+ ...props,
179
+ id,
180
+ seriesKey,
181
+ isHorizontal,
182
+ showOutliers,
183
+ notch
184
+ }
185
+ ),
186
+ ...barProps
187
+ }
188
+ ),
189
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
190
+ SeriesGradient,
191
+ {
192
+ id,
193
+ dataKey: seriesKey,
194
+ config,
195
+ direction: isHorizontal ? "horizontal" : "vertical"
196
+ }
197
+ ) })
198
+ ] });
199
+ }
200
+ function BoxShape({
201
+ x = 0,
202
+ y = 0,
203
+ width = 0,
204
+ height = 0,
205
+ payload,
206
+ id,
207
+ seriesKey,
208
+ isHorizontal,
209
+ showOutliers,
210
+ notch
211
+ }) {
212
+ const plot = usePlotArea();
213
+ const { domain } = useBoxPlot();
214
+ if (!payload) return null;
215
+ const box = payload;
216
+ const span = box.max - box.min;
217
+ const extent = isHorizontal ? width : height;
218
+ const scale = (value) => {
219
+ if (span !== 0 && extent > 0) {
220
+ return isHorizontal ? x + (value - box.min) / span * width : y + (box.max - value) / span * height;
221
+ }
222
+ if (!plot) return isHorizontal ? x : y;
223
+ const [low, high] = domain;
224
+ const range = high - low || 1;
225
+ return isHorizontal ? plot.x + (value - low) / range * plot.width : plot.y + (high - value) / range * plot.height;
226
+ };
227
+ const bandStart = isHorizontal ? y : x;
228
+ const bandSize = isHorizontal ? height : width;
229
+ const centre = bandStart + bandSize / 2;
230
+ const capHalf = bandSize / 4;
231
+ const dotRadius = Math.max(2, Math.min(3.5, bandSize / 8));
232
+ const fill = `url(#${id}-colors-${seriesKey})`;
233
+ const stroke = "var(--ink)";
234
+ const whisker = (from, to, atCap) => {
235
+ const a = scale(from);
236
+ const b = scale(to);
237
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
238
+ /* @__PURE__ */ jsx(
239
+ "line",
240
+ {
241
+ x1: isHorizontal ? a : centre,
242
+ y1: isHorizontal ? centre : a,
243
+ x2: isHorizontal ? b : centre,
244
+ y2: isHorizontal ? centre : b,
245
+ stroke,
246
+ strokeWidth: 1
247
+ }
248
+ ),
249
+ atCap && /* @__PURE__ */ jsx(
250
+ "line",
251
+ {
252
+ x1: isHorizontal ? b : centre - capHalf,
253
+ y1: isHorizontal ? centre - capHalf : b,
254
+ x2: isHorizontal ? b : centre + capHalf,
255
+ y2: isHorizontal ? centre + capHalf : b,
256
+ stroke,
257
+ strokeWidth: 1
258
+ }
259
+ )
260
+ ] });
261
+ };
262
+ const q1px = scale(box.q1);
263
+ const q3px = scale(box.q3);
264
+ const medianPx = scale(box.median);
265
+ const boxFrom = Math.min(q1px, q3px);
266
+ const boxSize = Math.abs(q3px - q1px);
267
+ const notchBounds = notch ? notchAt(box) : null;
268
+ const body = notchBounds ? /* @__PURE__ */ jsx(
269
+ "polygon",
270
+ {
271
+ points: notchPoints({
272
+ bandStart,
273
+ bandSize,
274
+ isHorizontal,
275
+ q1: q1px,
276
+ q3: q3px,
277
+ median: medianPx,
278
+ low: scale(notchBounds[0]),
279
+ high: scale(notchBounds[1])
280
+ }),
281
+ fill,
282
+ fillOpacity: "var(--chart-fill)",
283
+ stroke,
284
+ strokeWidth: 1,
285
+ strokeLinejoin: "round"
286
+ }
287
+ ) : /* @__PURE__ */ jsx(
288
+ "rect",
289
+ {
290
+ x: isHorizontal ? boxFrom : bandStart,
291
+ y: isHorizontal ? bandStart : boxFrom,
292
+ width: isHorizontal ? Math.max(1, boxSize) : bandSize,
293
+ height: isHorizontal ? bandSize : Math.max(1, boxSize),
294
+ fill,
295
+ fillOpacity: "var(--chart-fill)",
296
+ stroke,
297
+ strokeWidth: 1
298
+ }
299
+ );
300
+ return /* @__PURE__ */ jsxs("g", { children: [
301
+ whisker(box.q1, box.min, true),
302
+ whisker(box.q3, box.max, true),
303
+ body,
304
+ /* @__PURE__ */ jsx(
305
+ "line",
306
+ {
307
+ x1: isHorizontal ? medianPx : bandStart,
308
+ y1: isHorizontal ? bandStart : medianPx,
309
+ x2: isHorizontal ? medianPx : bandStart + bandSize,
310
+ y2: isHorizontal ? bandStart + bandSize : medianPx,
311
+ stroke,
312
+ strokeWidth: 2
313
+ }
314
+ ),
315
+ showOutliers && box.outliers.map((value, index) => /* @__PURE__ */ jsx(
316
+ "circle",
317
+ {
318
+ cx: isHorizontal ? scale(value) : centre,
319
+ cy: isHorizontal ? centre : scale(value),
320
+ r: dotRadius,
321
+ fill: "var(--chart-surface)",
322
+ stroke,
323
+ strokeWidth: 1
324
+ },
325
+ `${value}-${index}`
326
+ ))
327
+ ] });
328
+ }
329
+ function notchAt(box) {
330
+ if (!box.count || box.count <= 0) return null;
331
+ const half = NOTCH_FACTOR * (box.q3 - box.q1) / Math.sqrt(box.count);
332
+ return [Math.max(box.q1, box.median - half), Math.min(box.q3, box.median + half)];
333
+ }
334
+ function notchPoints({
335
+ bandStart,
336
+ bandSize,
337
+ isHorizontal,
338
+ q1,
339
+ q3,
340
+ median,
341
+ low,
342
+ high
343
+ }) {
344
+ const near = bandStart;
345
+ const far = bandStart + bandSize;
346
+ const inset = bandSize * NOTCH_INSET;
347
+ const value = [q1, low, median, high, q3];
348
+ const across = [near, near, near + inset, near, near];
349
+ const back = [far, far, far - inset, far, far];
350
+ const points = [
351
+ ...value.map((v, i) => [v, across[i]]),
352
+ ...value.map((v, i) => [v, back[i]]).reverse()
353
+ ];
354
+ return points.map(([v, a]) => isHorizontal ? `${v},${a}` : `${a},${v}`).join(" ");
355
+ }
356
+ function XAxis({
357
+ dataKey,
358
+ tickLine = false,
359
+ axisLine = false,
360
+ tickMargin = 8,
361
+ minTickGap = 8,
362
+ type,
363
+ label,
364
+ tickFormatter,
365
+ domain,
366
+ ...rest
367
+ }) {
368
+ const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot();
369
+ return /* @__PURE__ */ jsx(
370
+ RechartsXAxis,
371
+ {
372
+ dataKey: dataKey ?? (isHorizontal ? void 0 : "name"),
373
+ tickLine,
374
+ axisLine,
375
+ tickMargin,
376
+ minTickGap,
377
+ type: type ?? (isHorizontal ? "number" : "category"),
378
+ domain: domain ?? (isHorizontal ? valueSpan : void 0),
379
+ tickFormatter: tickFormatter ?? (isHorizontal ? formatValue : void 0),
380
+ ...rest,
381
+ children: axisLabel(label, "x")
382
+ }
383
+ );
384
+ }
385
+ function YAxis({
386
+ dataKey,
387
+ tickLine = false,
388
+ axisLine = false,
389
+ tickMargin = 8,
390
+ minTickGap = 8,
391
+ width = "auto",
392
+ type,
393
+ label,
394
+ tickFormatter,
395
+ domain,
396
+ ...rest
397
+ }) {
398
+ const { isHorizontal, domain: valueSpan, formatValue } = useBoxPlot();
399
+ return /* @__PURE__ */ jsx(
400
+ RechartsYAxis,
401
+ {
402
+ dataKey: dataKey ?? (isHorizontal ? "name" : void 0),
403
+ tickLine,
404
+ axisLine,
405
+ tickMargin,
406
+ minTickGap,
407
+ width,
408
+ type: type ?? (isHorizontal ? "category" : "number"),
409
+ domain: domain ?? (isHorizontal ? void 0 : valueSpan),
410
+ tickFormatter: tickFormatter ?? (isHorizontal ? void 0 : formatValue),
411
+ ...rest,
412
+ children: axisLabel(label, "y")
413
+ }
414
+ );
415
+ }
416
+ function Grid({
417
+ strokeDasharray = "3 3",
418
+ vertical,
419
+ horizontal,
420
+ ...rest
421
+ }) {
422
+ const { isHorizontal } = useBoxPlot();
423
+ return /* @__PURE__ */ jsx(
424
+ CartesianGrid,
425
+ {
426
+ strokeDasharray,
427
+ vertical: vertical ?? isHorizontal,
428
+ horizontal: horizontal ?? !isHorizontal,
429
+ ...rest
430
+ }
431
+ );
432
+ }
433
+ function Tooltip({ variant = "solid", roundness = "lg", defaultIndex }) {
434
+ const { formatValue } = useBoxPlot();
435
+ return /* @__PURE__ */ jsx(
436
+ ChartTooltip,
437
+ {
438
+ cursor: false,
439
+ defaultIndex,
440
+ content: (props) => {
441
+ const { active, payload } = props;
442
+ const box = payload?.[0]?.payload;
443
+ if (!active || !box) return /* @__PURE__ */ jsx("span", { className: "p-4" });
444
+ const rows = [
445
+ ["Maximum", box.max],
446
+ ["Upper quartile", box.q3],
447
+ ["Median", box.median],
448
+ ["Lower quartile", box.q1],
449
+ ["Minimum", box.min]
450
+ ];
451
+ return /* @__PURE__ */ jsxs(
452
+ "div",
453
+ {
454
+ className: cn(
455
+ "grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs",
456
+ roundness === "sm" && "rounded-(--radius-sm)",
457
+ roundness === "md" && "rounded-(--radius)",
458
+ roundness === "lg" && "rounded-(--radius-lg)",
459
+ variant === "frosted" ? "bg-(--chart-surface)/75 backdrop-blur-sm" : "bg-(--chart-surface)"
460
+ ),
461
+ children: [
462
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-(--ink)", children: box.name }),
463
+ rows.map(([name, value]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-4 leading-none", children: [
464
+ /* @__PURE__ */ jsx("span", { className: "text-(--ink-3-aa)", children: name }),
465
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-medium tabular-nums text-(--ink)", children: formatValue(value) })
466
+ ] }, name)),
467
+ box.count !== void 0 && /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-4 leading-none", children: [
468
+ /* @__PURE__ */ jsx("span", { className: "text-(--ink-3-aa)", children: "Observations" }),
469
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-medium tabular-nums text-(--ink)", children: box.count.toLocaleString() })
470
+ ] })
471
+ ]
472
+ }
473
+ );
474
+ }
475
+ }
476
+ );
477
+ }
478
+ function Background({ variant }) {
479
+ return /* @__PURE__ */ jsx(ChartBackground, { variant });
480
+ }
481
+ BoxPlot.Boxes = Boxes;
482
+ BoxPlot.XAxis = XAxis;
483
+ BoxPlot.YAxis = YAxis;
484
+ BoxPlot.Grid = Grid;
485
+ BoxPlot.Tooltip = Tooltip;
486
+ BoxPlot.Background = Background;
487
+ BoxPlot.ReferenceLine = ReferenceLine;
488
+ BoxPlot.ReferenceBand = ReferenceBand;
489
+ BoxPlot.Annotation = Annotation;
490
+ var BoxPlot_default = BoxPlot;
491
+ export {
492
+ BoxPlot,
493
+ BoxPlot_default as default
494
+ };
495
+ //# sourceMappingURL=BoxPlot.js.map
@@ -0,0 +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":[]}
@@ -0,0 +1,100 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { ChartEmptyProps } from '../lib/empty.js';
4
+
5
+ /** One measure, its target, and the bands it is read against. */
6
+ interface BulletMeasure {
7
+ /** What is being measured. Doubles as the row's key, so it must be unique. */
8
+ name: string;
9
+ /** The number the row is about. */
10
+ value: number;
11
+ /**
12
+ * The number the measure is being judged against — last year, the budget,
13
+ * the SLO. Drawn as a rule across the bar, not as a second bar.
14
+ */
15
+ target?: number;
16
+ /**
17
+ * The qualitative bands, as ascending UPPER bounds. `[60, 80]` on a 0–100
18
+ * scale draws three bands: 0–60, 60–80, 80–100.
19
+ *
20
+ * Few's limit is five, and it is a real one: past that the shades stop being
21
+ * separable and the bands become a gradient nobody can read a boundary off.
22
+ */
23
+ ranges?: number[];
24
+ /** The scale, as `[min, max]`. Falls back to the chart's, then to the data. */
25
+ domain?: [number, number];
26
+ /** A second line under the name — the unit, the window, the owner. */
27
+ detail?: ReactNode;
28
+ }
29
+ interface BulletChartProps {
30
+ /**
31
+ * What the chart shows, in a sentence a reader could act on. Required, and
32
+ * announced to a screen reader even when it is not printed.
33
+ */
34
+ title: string;
35
+ /** Prints the title above the rows instead of hiding it from sight. */
36
+ showTitle?: boolean;
37
+ /** A line under the title — the unit, the window, the caveat. */
38
+ description?: ReactNode;
39
+ /** One entry per measure. Order is the reading order; it is not sorted. */
40
+ data: BulletMeasure[];
41
+ /**
42
+ * The bands every measure falls back to, as ascending upper bounds.
43
+ *
44
+ * Shared bands only mean something when the measures share a scale. Where
45
+ * they do not — a latency beside a conversion rate — put `ranges` and
46
+ * `domain` on each measure instead.
47
+ */
48
+ ranges?: number[];
49
+ /** The scale every measure falls back to, as `[min, max]`. */
50
+ domain?: [number, number];
51
+ /** Formats every number the chart prints. */
52
+ formatValue?: (value: number) => string;
53
+ /** Prints the scale's two ends under each track. */
54
+ showScale?: boolean;
55
+ /** Merged onto the figure, last, so a call site can size or space it. */
56
+ className?: string;
57
+ /** Drops the hidden table view. Only correct when the page prints the data itself. */
58
+ hideDataTable?: boolean;
59
+ /** What the chart shows when it has nothing to draw. */
60
+ empty?: ChartEmptyProps;
61
+ }
62
+ /**
63
+ * A measure, its target, and the bands that say whether the number is any good
64
+ * — one dense row per thing being tracked.
65
+ *
66
+ * Stephen Few designed this to replace the dashboard gauge, and the argument
67
+ * has held: a speedometer spends a whole card saying one number badly, where a
68
+ * bullet graph says the same number, its target, and the qualitative context
69
+ * around it in the height of a line of text. Ten of them stack into a status
70
+ * page a reader can scan in one pass.
71
+ *
72
+ * What it will not do is show change over time. A bullet graph is one instant,
73
+ * and `target` is the only comparison it carries; when the question is
74
+ * "how did we get here" it wants a `<LineChart>`, and when several measures
75
+ * have to be compared against each OTHER rather than each against its own
76
+ * target, a `<BarList>` ranks them and this does not.
77
+ *
78
+ * **The bands are the part to be careful with.** They are a judgement — someone
79
+ * decided that 80 is "good" — drawn in the same ink as the measurement, and a
80
+ * reader has no way to tell an agreed threshold from a number somebody typed.
81
+ * They also compress: a value near the top of the scale sits in the same band
82
+ * whether it cleared the boundary by a point or by thirty. The band answers
83
+ * "is this acceptable", never "by how much".
84
+ *
85
+ * No rendering engine, deliberately — this is the one chart in the set with
86
+ * nothing to compute. Each row is a single linear scale with no axis, no ticks
87
+ * and no shared plot area, so it is laid out as HTML: the bands and the bar are
88
+ * inline-axis offsets, which means the whole chart mirrors correctly in a
89
+ * right-to-left document, where an SVG drawn in user space would not.
90
+ *
91
+ * @example
92
+ * <BulletChart
93
+ * title="Service levels against target"
94
+ * data={[{ name: 'Uptime', value: 99.4, target: 99.9, ranges: [98, 99.5] }]}
95
+ * domain={[95, 100]}
96
+ * />
97
+ */
98
+ declare function BulletChart({ title, showTitle, description, data, ranges, domain, formatValue, showScale, className, hideDataTable, empty, }: BulletChartProps): react.JSX.Element;
99
+
100
+ export { BulletChart, type BulletChartProps, type BulletMeasure, BulletChart as default };