@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,147 @@
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
+ Funnel as RechartsFunnel,
11
+ FunnelChart as RechartsFunnelChart,
12
+ LabelList
13
+ } from "recharts";
14
+ import { ChartContainer, colorStops, cssName } from "../lib/chart.js";
15
+ import { ChartFigure } from "../lib/figure.js";
16
+ import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
17
+ import { ColorStops } from "../lib/paint.js";
18
+ const FunnelChartContext = createContext(null);
19
+ function useFunnelChart() {
20
+ const context = useContext(FunnelChartContext);
21
+ if (!context) throw new Error("A funnel chart part must be rendered inside <FunnelChart>");
22
+ return context;
23
+ }
24
+ function FunnelChart({
25
+ config,
26
+ data,
27
+ dataKey,
28
+ nameKey,
29
+ title,
30
+ showTitle,
31
+ description,
32
+ children,
33
+ className,
34
+ chartProps,
35
+ hideDataTable = false
36
+ }) {
37
+ const chartId = useId().replace(/:/g, "");
38
+ const context = useMemo(
39
+ () => ({ config, nameKey, dataKey, data }),
40
+ [config, data, dataKey, nameKey]
41
+ );
42
+ return /* @__PURE__ */ jsx(FunnelChartContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
43
+ ChartFigure,
44
+ {
45
+ title,
46
+ showTitle,
47
+ description,
48
+ className,
49
+ table: hideDataTable ? false : { rows: data, rowKey: nameKey, columns: [{ key: dataKey, label: "Value" }] },
50
+ children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsxs(RechartsFunnelChart, { id: chartId, accessibilityLayer: true, ...chartProps, children: [
51
+ children,
52
+ /* @__PURE__ */ jsx("defs", { children: Object.entries(config).map(([key, series]) => /* @__PURE__ */ jsx(
53
+ "linearGradient",
54
+ {
55
+ id: `${chartId}-stage-${cssName(key)}`,
56
+ x1: "0",
57
+ y1: "0",
58
+ x2: "1",
59
+ y2: "0",
60
+ children: /* @__PURE__ */ jsx(ColorStops, { dataKey: cssName(key), stops: colorStops(series) })
61
+ },
62
+ key
63
+ )) })
64
+ ] }) })
65
+ }
66
+ ) });
67
+ }
68
+ function Funnel({
69
+ variant = "stepped",
70
+ orientation = "vertical",
71
+ gap = 2,
72
+ children,
73
+ funnelProps
74
+ }) {
75
+ const { config, data, dataKey, nameKey } = useFunnelChart();
76
+ const chartId = useId().replace(/:/g, "");
77
+ const painted = data.map((row, index) => {
78
+ const name = String(row[nameKey]);
79
+ const slot = variant === "ramp" ? `var(--series-${index % 8 + 1})` : void 0;
80
+ return {
81
+ ...row,
82
+ fill: slot ?? (name in config ? `url(#${chartId}-stage-${cssName(name)})` : "var(--series-1)")
83
+ };
84
+ });
85
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ /* @__PURE__ */ jsx(
87
+ RechartsFunnel,
88
+ {
89
+ dataKey,
90
+ nameKey,
91
+ data: painted,
92
+ isAnimationActive: false,
93
+ stroke: "var(--chart-surface)",
94
+ strokeWidth: gap,
95
+ orientation,
96
+ ...funnelProps,
97
+ children
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx("defs", { children: Object.entries(config).map(([key, series]) => /* @__PURE__ */ jsx(
101
+ "linearGradient",
102
+ {
103
+ id: `${chartId}-stage-${cssName(key)}`,
104
+ x1: "0",
105
+ y1: "0",
106
+ x2: "1",
107
+ y2: "0",
108
+ children: /* @__PURE__ */ jsx(ColorStops, { dataKey: cssName(key), stops: colorStops(series) })
109
+ },
110
+ key
111
+ )) })
112
+ ] });
113
+ }
114
+ function Label({ dataKey, position = "right", labelListProps }) {
115
+ const { nameKey } = useFunnelChart();
116
+ return /* @__PURE__ */ jsx(
117
+ LabelList,
118
+ {
119
+ dataKey: dataKey ?? nameKey,
120
+ position,
121
+ stroke: "none",
122
+ fontSize: 12,
123
+ className: "fill-(--ink)",
124
+ ...labelListProps
125
+ }
126
+ );
127
+ }
128
+ function Tooltip({ variant, roundness, defaultIndex }) {
129
+ const { nameKey } = useFunnelChart();
130
+ return /* @__PURE__ */ jsx(
131
+ ChartTooltip,
132
+ {
133
+ defaultIndex,
134
+ cursor: false,
135
+ content: /* @__PURE__ */ jsx(ChartTooltipContent, { nameKey, hideLabel: true, roundness, variant })
136
+ }
137
+ );
138
+ }
139
+ FunnelChart.Funnel = Funnel;
140
+ FunnelChart.Label = Label;
141
+ FunnelChart.Tooltip = Tooltip;
142
+ var FunnelChart_default = FunnelChart;
143
+ export {
144
+ FunnelChart,
145
+ FunnelChart_default as default
146
+ };
147
+ //# sourceMappingURL=FunnelChart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/charts/FunnelChart/FunnelChart.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 Funnel as RechartsFunnel,\n FunnelChart as RechartsFunnelChart,\n LabelList,\n} from 'recharts'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How each stage is painted.\n *\n * `stepped` is the honest default: every stage keeps the same fill and the\n * TAPER carries the drop. A ramp that also darkens each stage encodes the same\n * fact twice and invites the reader to compare two things that are one.\n */\nexport type FunnelVariant = 'stepped' | 'ramp'\n\ninterface FunnelChartContextValue {\n config: ChartConfig\n nameKey: string\n dataKey: string\n data: Record<string, unknown>[]\n}\n\nconst FunnelChartContext = createContext<FunnelChartContextValue | null>(null)\n\nfunction useFunnelChart(): FunnelChartContextValue {\n const context = useContext(FunnelChartContext)\n if (!context) throw new Error('A funnel chart part must be rendered inside <FunnelChart>')\n return context\n}\n\nexport interface FunnelChartProps<TData extends Record<string, unknown>> {\n /** Stage names → their label and paint. Keys must match the `nameKey` values. */\n config: ChartConfig\n /** The stages, widest first. Order is the funnel; it is not sorted for you. */\n data: TData[]\n /** The row field holding each stage's number. */\n dataKey: keyof TData & string\n /** The row field naming each stage. */\n nameKey: keyof TData & string\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 — `<Funnel>`, `<Tooltip>`. */\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 RechartsFunnelChart>\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n}\n\n/**\n * Stages that only ever narrow — a signup flow, a hiring pipeline, a checkout.\n *\n * The caveat is the same one every funnel has: the taper encodes a RATIO\n * between neighbouring stages, and the eye reads the enclosed area, so a funnel\n * exaggerates a shallow drop and flattens a steep one. Where the exact fall-off\n * is the point, put the percentages on the stages — `<Funnel.Label>` does — or\n * use a `<BarChart>`, which encodes each stage on one honest scale.\n *\n * Reach for `<SankeyChart>` instead when the flow can SPLIT rather than only\n * shrink: a funnel has one path through it by construction.\n *\n * @example\n * <FunnelChart title=\"Signup funnel\" config={config} data={stages} dataKey=\"people\" nameKey=\"stage\">\n * <FunnelChart.Funnel>\n * <FunnelChart.Label />\n * </FunnelChart.Funnel>\n * <FunnelChart.Tooltip />\n * </FunnelChart>\n */\nexport function FunnelChart<TData extends Record<string, unknown>>({\n config,\n data,\n dataKey,\n nameKey,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n hideDataTable = false,\n}: FunnelChartProps<TData>) {\n const chartId = useId().replace(/:/g, '')\n\n const context = useMemo<FunnelChartContextValue>(\n () => ({ config, nameKey, dataKey, data }),\n [config, data, dataKey, nameKey],\n )\n\n return (\n <FunnelChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : { rows: data, rowKey: nameKey, columns: [{ key: dataKey, label: 'Value' }] }\n }\n >\n <ChartContainer config={config}>\n <RechartsFunnelChart id={chartId} accessibilityLayer {...chartProps}>\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-stage-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"0\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsFunnelChart>\n </ChartContainer>\n </ChartFigure>\n </FunnelChartContext.Provider>\n )\n}\n\nexport interface FunnelProps {\n variant?: FunnelVariant\n /** Whether the funnel narrows downward or along the inline axis. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap between stages, in pixels. */\n gap?: number\n /** `<FunnelChart.Label>`. */\n children?: ReactNode\n funnelProps?: Omit<ComponentProps<typeof RechartsFunnel>, 'data' | 'dataKey'>\n}\n\n/** The stages themselves. */\nfunction Funnel({\n variant = 'stepped',\n orientation = 'vertical',\n gap = 2,\n children,\n funnelProps,\n}: FunnelProps) {\n const { config, data, dataKey, nameKey } = useFunnelChart()\n const chartId = useId().replace(/:/g, '')\n\n // `ramp` walks the series ramp stage by stage; `stepped` holds slot one for\n // all of them and lets the shape do the talking.\n const painted = data.map((row, index) => {\n const name = String(row[nameKey])\n const slot = variant === 'ramp' ? `var(--series-${(index % 8) + 1})` : undefined\n return {\n ...row,\n fill: slot ?? (name in config ? `url(#${chartId}-stage-${cssName(name)})` : 'var(--series-1)'),\n }\n })\n\n return (\n <>\n <RechartsFunnel\n dataKey={dataKey}\n nameKey={nameKey}\n data={painted}\n isAnimationActive={false}\n // A surface-coloured stroke rather than a transparent gap: the stages\n // touch by construction, and a hairline of the page between them is\n // what makes two similar stages countable.\n stroke=\"var(--chart-surface)\"\n strokeWidth={gap}\n orientation={orientation}\n {...funnelProps}\n >\n {children}\n </RechartsFunnel>\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-stage-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"0\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </>\n )\n}\n\nexport interface FunnelLabelProps {\n /** The field printed on each stage. Defaults to the stage's name. */\n dataKey?: string\n /** Where the label sits relative to its stage. */\n position?: ComponentProps<typeof LabelList>['position']\n labelListProps?: Omit<ComponentProps<typeof LabelList>, 'dataKey'>\n}\n\n/**\n * Names printed on the stages.\n *\n * Worth reaching for: a funnel's taper is a ratio the eye reads badly, and a\n * printed number is the relief for that.\n */\nfunction Label({ dataKey, position = 'right', labelListProps }: FunnelLabelProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <LabelList\n dataKey={dataKey ?? nameKey}\n position={position}\n stroke=\"none\"\n fontSize={12}\n className=\"fill-(--ink)\"\n {...labelListProps}\n />\n )\n}\n\n/** The hover panel. No heading: the stage's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={false}\n content={\n <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\nFunnelChart.Funnel = Funnel\nFunnelChart.Label = Label\nFunnelChart.Tooltip = Tooltip\n\nexport default FunnelChart\n"],"mappings":"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","names":[]}
@@ -0,0 +1,78 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * How a cell's value is turned into ink.
6
+ *
7
+ * `sequential` is one ramp light to dark, and is the right answer for a
8
+ * magnitude that only goes up: counts, durations, volume. `diverging` puts the
9
+ * page's own surface at the midpoint and darkens outward in both directions,
10
+ * for a value with a meaningful zero — a change, a delta, a residual.
11
+ *
12
+ * There is no third option, and that is the point. A rainbow scale reads as
13
+ * ordered to nobody, and is the single most common defect in a heatmap.
14
+ */
15
+ type HeatmapScale = 'sequential' | 'diverging';
16
+ interface HeatmapCell {
17
+ /** The column this cell sits in. Must match one of `columns`. */
18
+ column: string;
19
+ /** The row this cell sits in. Must match one of `rows`. */
20
+ row: string;
21
+ /** The number. `null` is drawn as an explicit gap, not as a zero. */
22
+ value: number | null;
23
+ }
24
+ interface HeatmapProps {
25
+ /** What the grid shows. Required, and it names the table. */
26
+ title: string;
27
+ /** Prints the title above the grid instead of hiding it from sight. */
28
+ showTitle?: boolean;
29
+ /** A line under the title — the unit, the window, the caveat. */
30
+ description?: ReactNode;
31
+ /** Column headers, in the order they should appear. */
32
+ columns: string[];
33
+ /** Row headers, in the order they should appear. */
34
+ rows: string[];
35
+ cells: HeatmapCell[];
36
+ scale?: HeatmapScale;
37
+ /**
38
+ * The domain, as `[min, max]`. Derived from the data when omitted.
39
+ *
40
+ * Pin it whenever two grids are meant to be compared: two heatmaps on
41
+ * independent domains look alike and mean different things, which is the one
42
+ * failure a shared legend cannot fix.
43
+ */
44
+ domain?: [number, number];
45
+ /** Formats a value for the cell's announcement and its printed label. */
46
+ formatValue?: (value: number) => string;
47
+ /** Prints the number inside each cell. Only legible on a coarse grid. */
48
+ showValues?: boolean;
49
+ className?: string;
50
+ }
51
+ /**
52
+ * A grid of values read by weight — a calendar of activity, a confusion matrix,
53
+ * an hour-by-weekday load.
54
+ *
55
+ * The one form in the set that a monochrome system renders BETTER than a
56
+ * chromatic one. A heatmap needs a scale whose order the eye can read without
57
+ * consulting a legend, and lightness is the only channel that is unambiguously
58
+ * ordered — which is why "one hue, light to dark" is the standing advice
59
+ * everywhere else, and why the rainbow heatmap is a perennial mistake. Here
60
+ * there is no hue left to get wrong.
61
+ *
62
+ * It is built as a real `<table>` rather than as an SVG, so the structure a
63
+ * screen reader walks is the structure the eye reads: a header row, a header
64
+ * column, and a cell that announces its own value. Nothing else in the package
65
+ * can say that, and nothing else needs to — which is also why this one takes no
66
+ * rendering engine at all.
67
+ *
68
+ * @example
69
+ * <Heatmap
70
+ * title="Commits by weekday and hour"
71
+ * columns={hours}
72
+ * rows={weekdays}
73
+ * cells={cells}
74
+ * />
75
+ */
76
+ declare function Heatmap({ title, showTitle, description, columns, rows, cells, scale, domain, formatValue, showValues, className, }: HeatmapProps): react.JSX.Element;
77
+
78
+ export { Heatmap, type HeatmapCell, type HeatmapProps, type HeatmapScale, Heatmap as default };
@@ -0,0 +1,108 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ const LABELLED_CEILING = 0.35;
4
+ function position(value, min, max) {
5
+ if (max === min) return 0.5;
6
+ return Math.min(1, Math.max(0, (value - min) / (max - min)));
7
+ }
8
+ function Heatmap({
9
+ title,
10
+ showTitle = false,
11
+ description,
12
+ columns,
13
+ rows,
14
+ cells,
15
+ scale = "sequential",
16
+ domain,
17
+ formatValue = (value) => value.toLocaleString(),
18
+ showValues = false,
19
+ className
20
+ }) {
21
+ const values = cells.map((cell) => cell.value).filter((value) => value !== null);
22
+ const [min, max] = domain ?? [Math.min(0, ...values), Math.max(0, ...values)];
23
+ const byKey = new Map(cells.map((cell) => [`${cell.row}\0${cell.column}`, cell.value]));
24
+ return /* @__PURE__ */ jsxs("figure", { className: cn("flex w-full flex-col", className), children: [
25
+ /* @__PURE__ */ jsxs("figcaption", { className: cn(!showTitle && "sr-only", showTitle && "mb-3 flex flex-col gap-1"), children: [
26
+ /* @__PURE__ */ jsx("span", { className: cn(showTitle && "font-heading text-[length:var(--fs-item)] text-(--ink)"), children: title }),
27
+ description && /* @__PURE__ */ jsx("span", { className: cn(showTitle && "mono-meta text-(--ink-3-aa)"), children: description })
28
+ ] }),
29
+ /* @__PURE__ */ jsxs("table", { className: "w-full border-separate border-spacing-[2px] text-xs", children: [
30
+ /* @__PURE__ */ jsx("caption", { className: "sr-only", children: title }),
31
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
32
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "sr-only", children: "Row" }),
33
+ columns.map((column) => /* @__PURE__ */ jsx("th", { scope: "col", className: "pb-1 mono-meta font-normal text-(--chart-axis)", children: column }, column))
34
+ ] }) }),
35
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
36
+ /* @__PURE__ */ jsx(
37
+ "th",
38
+ {
39
+ scope: "row",
40
+ className: "pe-2 text-end mono-meta font-normal whitespace-nowrap text-(--chart-axis)",
41
+ children: row
42
+ }
43
+ ),
44
+ columns.map((column) => /* @__PURE__ */ jsx(
45
+ HeatmapTile,
46
+ {
47
+ value: byKey.get(`${row}\0${column}`) ?? null,
48
+ min,
49
+ max,
50
+ scale,
51
+ label: `${row}, ${column}`,
52
+ formatValue,
53
+ showValue: showValues
54
+ },
55
+ column
56
+ ))
57
+ ] }, row)) })
58
+ ] })
59
+ ] });
60
+ }
61
+ function HeatmapTile({
62
+ value,
63
+ min,
64
+ max,
65
+ scale,
66
+ label,
67
+ formatValue,
68
+ showValue
69
+ }) {
70
+ if (value === null) {
71
+ return (
72
+ // A missing reading is not a zero, and drawing it as the palest cell
73
+ // would say that it was. The hairline says "no data" instead.
74
+ /* @__PURE__ */ jsx("td", { className: "h-7 rounded-[2px] border border-dashed border-(--rule) bg-transparent", children: /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
75
+ label,
76
+ ": no data"
77
+ ] }) })
78
+ );
79
+ }
80
+ const t = position(value, min, max);
81
+ const ramp = scale === "diverging" ? Math.abs(t - 0.5) * 2 : t;
82
+ const weight = showValue ? ramp * LABELLED_CEILING : ramp;
83
+ return /* @__PURE__ */ jsxs(
84
+ "td",
85
+ {
86
+ className: "h-7 rounded-[2px] text-center align-middle tabular-nums",
87
+ style: {
88
+ // `color-mix` rather than eight discrete steps: a heatmap's whole job is
89
+ // a continuous read, and banding it into a ramp invents boundaries the
90
+ // data does not have.
91
+ backgroundColor: `color-mix(in srgb, var(--series-1) ${(weight * 100).toFixed(1)}%, var(--chart-surface))`,
92
+ // One text colour at every weight, which is only safe because the ramp
93
+ // above is capped when a number is printed on it.
94
+ color: "var(--ink)"
95
+ },
96
+ children: [
97
+ /* @__PURE__ */ jsx("span", { className: cn(!showValue && "sr-only forced-colors:not-sr-only"), children: formatValue(value) }),
98
+ !showValue && /* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
99
+ ]
100
+ }
101
+ );
102
+ }
103
+ var Heatmap_default = Heatmap;
104
+ export {
105
+ Heatmap,
106
+ Heatmap_default as default
107
+ };
108
+ //# sourceMappingURL=Heatmap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/charts/Heatmap/Heatmap.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * How a cell's value is turned into ink.\n *\n * `sequential` is one ramp light to dark, and is the right answer for a\n * magnitude that only goes up: counts, durations, volume. `diverging` puts the\n * page's own surface at the midpoint and darkens outward in both directions,\n * for a value with a meaningful zero — a change, a delta, a residual.\n *\n * There is no third option, and that is the point. A rainbow scale reads as\n * ordered to nobody, and is the single most common defect in a heatmap.\n */\nexport type HeatmapScale = 'sequential' | 'diverging'\n\nexport interface HeatmapCell {\n /** The column this cell sits in. Must match one of `columns`. */\n column: string\n /** The row this cell sits in. Must match one of `rows`. */\n row: string\n /** The number. `null` is drawn as an explicit gap, not as a zero. */\n value: number | null\n}\n\nexport interface HeatmapProps {\n /** What the grid shows. Required, and it names the table. */\n title: string\n /** Prints the title above the grid 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 /** Column headers, in the order they should appear. */\n columns: string[]\n /** Row headers, in the order they should appear. */\n rows: string[]\n cells: HeatmapCell[]\n scale?: HeatmapScale\n /**\n * The domain, as `[min, max]`. Derived from the data when omitted.\n *\n * Pin it whenever two grids are meant to be compared: two heatmaps on\n * independent domains look alike and mean different things, which is the one\n * failure a shared legend cannot fix.\n */\n domain?: [number, number]\n /** Formats a value for the cell's announcement and its printed label. */\n formatValue?: (value: number) => string\n /** Prints the number inside each cell. Only legible on a coarse grid. */\n showValues?: boolean\n className?: string\n}\n\n/**\n * How far up the ramp a cell may go.\n *\n * A printed number has to clear 4.5:1 against whatever it is printed on, and a\n * mid-grey clears it against neither ink nor paper — which is the reason a\n * heatmap with values on it usually ends up with unreadable cells somewhere in\n * the middle of its own scale. Capping the wash is the trade: the ORDER of the\n * cells survives untouched, the contrast range narrows, and the numbers — which\n * are the precise read anyway — become the thing carrying the detail.\n *\n * With no number printed the span is the full ramp, because the only text in\n * the cell is then visually hidden and has nothing to contrast against.\n */\nconst LABELLED_CEILING = 0.35\n\n/** Where a value sits in the domain, 0 to 1. */\nfunction position(value: number, min: number, max: number): number {\n if (max === min) return 0.5\n return Math.min(1, Math.max(0, (value - min) / (max - min)))\n}\n\n/**\n * A grid of values read by weight — a calendar of activity, a confusion matrix,\n * an hour-by-weekday load.\n *\n * The one form in the set that a monochrome system renders BETTER than a\n * chromatic one. A heatmap needs a scale whose order the eye can read without\n * consulting a legend, and lightness is the only channel that is unambiguously\n * ordered — which is why \"one hue, light to dark\" is the standing advice\n * everywhere else, and why the rainbow heatmap is a perennial mistake. Here\n * there is no hue left to get wrong.\n *\n * It is built as a real `<table>` rather than as an SVG, so the structure a\n * screen reader walks is the structure the eye reads: a header row, a header\n * column, and a cell that announces its own value. Nothing else in the package\n * can say that, and nothing else needs to — which is also why this one takes no\n * rendering engine at all.\n *\n * @example\n * <Heatmap\n * title=\"Commits by weekday and hour\"\n * columns={hours}\n * rows={weekdays}\n * cells={cells}\n * />\n */\nexport function Heatmap({\n title,\n showTitle = false,\n description,\n columns,\n rows,\n cells,\n scale = 'sequential',\n domain,\n formatValue = (value) => value.toLocaleString(),\n showValues = false,\n className,\n}: HeatmapProps) {\n const values = cells.map((cell) => cell.value).filter((value): value is number => value !== null)\n const [min, max] = domain ?? [Math.min(0, ...values), Math.max(0, ...values)]\n\n const byKey = new Map(cells.map((cell) => [`${cell.row}\u0000${cell.column}`, cell.value]))\n\n return (\n <figure className={cn('flex w-full flex-col', className)}>\n <figcaption className={cn(!showTitle && 'sr-only', showTitle && 'mb-3 flex flex-col gap-1')}>\n <span className={cn(showTitle && 'font-heading text-[length:var(--fs-item)] text-(--ink)')}>\n {title}\n </span>\n {description && (\n <span className={cn(showTitle && 'mono-meta text-(--ink-3-aa)')}>{description}</span>\n )}\n </figcaption>\n\n {/* `border-separate` with a spacing rather than a gap on a grid: the two\n pixels of page between cells are what make two neighbouring weights\n countable, and a table is the only layout that keeps a header tied to\n its own line. */}\n <table className=\"w-full border-separate border-spacing-[2px] text-xs\">\n <caption className=\"sr-only\">{title}</caption>\n <thead>\n <tr>\n <th scope=\"col\" className=\"sr-only\">\n Row\n </th>\n {columns.map((column) => (\n <th key={column} scope=\"col\" className=\"pb-1 mono-meta font-normal text-(--chart-axis)\">\n {column}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row}>\n <th\n scope=\"row\"\n className=\"pe-2 text-end mono-meta font-normal whitespace-nowrap text-(--chart-axis)\"\n >\n {row}\n </th>\n {columns.map((column) => (\n <HeatmapTile\n key={column}\n value={byKey.get(`${row}\u0000${column}`) ?? null}\n min={min}\n max={max}\n scale={scale}\n label={`${row}, ${column}`}\n formatValue={formatValue}\n showValue={showValues}\n />\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n )\n}\n\nfunction HeatmapTile({\n value,\n min,\n max,\n scale,\n label,\n formatValue,\n showValue,\n}: {\n value: number | null\n min: number\n max: number\n scale: HeatmapScale\n label: string\n formatValue: (value: number) => string\n showValue: boolean\n}) {\n if (value === null) {\n return (\n // A missing reading is not a zero, and drawing it as the palest cell\n // would say that it was. The hairline says \"no data\" instead.\n <td className=\"h-7 rounded-[2px] border border-dashed border-(--rule) bg-transparent\">\n <span className=\"sr-only\">{label}: no data</span>\n </td>\n )\n }\n\n const t = position(value, min, max)\n // Diverging reads DISTANCE from the midpoint, so both directions darken and\n // the middle of the scale is the page itself.\n const ramp = scale === 'diverging' ? Math.abs(t - 0.5) * 2 : t\n const weight = showValue ? ramp * LABELLED_CEILING : ramp\n\n return (\n <td\n className=\"h-7 rounded-[2px] text-center align-middle tabular-nums\"\n style={{\n // `color-mix` rather than eight discrete steps: a heatmap's whole job is\n // a continuous read, and banding it into a ramp invents boundaries the\n // data does not have.\n backgroundColor: `color-mix(in srgb, var(--series-1) ${(weight * 100).toFixed(1)}%, var(--chart-surface))`,\n // One text colour at every weight, which is only safe because the ramp\n // above is capped when a number is printed on it.\n color: 'var(--ink)',\n }}\n >\n {/* Revealed under forced colours, where the cell's own background is\n remapped away and the wash that WAS the encoding no longer exists.\n The grid degrades to the table it always was underneath, rather than\n to a page of identical empty boxes. */}\n <span className={cn(!showValue && 'sr-only forced-colors:not-sr-only')}>\n {formatValue(value)}\n </span>\n {!showValue && <span className=\"sr-only\">{label}</span>}\n </td>\n )\n}\n\nexport default Heatmap\n"],"mappings":"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","names":[]}
@@ -0,0 +1,165 @@
1
+ import { ChartTooltipSlotProps } from '../AreaChart/AreaChart.js';
2
+ import { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart.js';
3
+ import * as react from 'react';
4
+ import { ReactNode, ComponentProps } from 'react';
5
+ import { BarChart, Bar, CartesianGrid } from 'recharts';
6
+ import { ChartConfig } from '../lib/chart.js';
7
+ import { ChartEmptyProps } from '../lib/empty.js';
8
+ import { ChartBackgroundVariant } from '../lib/background.js';
9
+ import { ReferenceLine, ReferenceBand, Annotation } from '../lib/annotations.js';
10
+ import '../lib/values.js';
11
+ import '../lib/sonify-control.js';
12
+ import '../lib/sonify.js';
13
+ import '../lib/brush.js';
14
+ import '../lib/toolbar.js';
15
+ import '../lib/zoom.js';
16
+ import '../lib/figure.js';
17
+ import '../lib/paint.js';
18
+ import '../lib/dot.js';
19
+ import '../lib/legend.js';
20
+ import '../lib/tooltip.js';
21
+ import 'recharts/types/component/DefaultTooltipContent';
22
+
23
+ /** What the bar heights mean. */
24
+ type HistogramMode = 'frequency' | 'density';
25
+ /** One bucket, as the chart draws it. */
26
+ interface HistogramBin {
27
+ /** The bucket's lower edge, inclusive. */
28
+ from: number;
29
+ /** The bucket's upper edge, exclusive — except the last, which includes it. */
30
+ to: number;
31
+ /** How many observations fell in it. */
32
+ count: number;
33
+ }
34
+ interface HistogramProps {
35
+ /**
36
+ * The single series — its label and its paint. Only the FIRST entry is read;
37
+ * a histogram has one distribution and as many bars as it has buckets.
38
+ */
39
+ config?: ChartConfig;
40
+ /**
41
+ * The raw observations, in any order. Binned for you by `bins`.
42
+ *
43
+ * Give this OR `data`, not both — `data` wins if both arrive.
44
+ */
45
+ values?: number[];
46
+ /**
47
+ * Buckets that were counted somewhere else — by a database, by a sketch, by
48
+ * a metrics backend that only ever ships histograms.
49
+ *
50
+ * Uneven bucket widths are drawn at their real widths, which is the whole
51
+ * reason this takes edges rather than labels. Set `mode="density"` when they
52
+ * are uneven, or the wide buckets will read as tall ones.
53
+ */
54
+ data?: HistogramBin[];
55
+ /**
56
+ * How to cut `values` up: a number of equal-width bins, or the explicit
57
+ * edges. Defaults to Freedman–Diaconis (`2 × IQR × n^(-1/3)`), falling back
58
+ * to Sturges when the interquartile range is zero.
59
+ */
60
+ bins?: number | number[];
61
+ /**
62
+ * What the bar heights mean.
63
+ *
64
+ * `frequency` is the count in each bucket and is what a reader assumes.
65
+ * `density` is `count / (n × bin width)`, so the total area is 1 — which is
66
+ * what makes two histograms of different sample sizes comparable, and what
67
+ * makes uneven buckets honest.
68
+ */
69
+ mode?: HistogramMode;
70
+ /**
71
+ * What the chart shows, in a sentence a reader could act on. Required, and
72
+ * announced to a screen reader even when it is not printed.
73
+ */
74
+ title: string;
75
+ /** Prints the title above the plot instead of hiding it from sight. */
76
+ showTitle?: boolean;
77
+ /** A line under the title — the unit, the window, the caveat. */
78
+ description?: ReactNode;
79
+ /** The composed parts — axes, grid, tooltip, and `<Histogram.Bars>`. */
80
+ children: ReactNode;
81
+ /** Merged onto the figure, last, so a call site can size or space it. */
82
+ className?: string;
83
+ /** Escape hatch onto the raw Recharts chart element. */
84
+ chartProps?: ComponentProps<typeof BarChart>;
85
+ /** Formats the measured values — bin edges, ticks, the tooltip's heading. */
86
+ formatValue?: (value: number) => string;
87
+ /** Drops the hidden table view. Only correct when the page prints the data itself. */
88
+ hideDataTable?: boolean;
89
+ /**
90
+ * What the chart shows when it has nothing to draw. `false` keeps the axes,
91
+ * for a chart whose emptiness is itself the reading.
92
+ */
93
+ empty?: ChartEmptyProps | false;
94
+ }
95
+ /**
96
+ * The shape of one distribution — where the mass sits, how it leans, whether
97
+ * there is more than one hump in it.
98
+ *
99
+ * The form that answers what a `<BoxPlot>` structurally cannot: two clusters,
100
+ * a hard floor, a pile-up at a timeout value. Reach for the box plot when
101
+ * several distributions have to be compared side by side, and for this one when
102
+ * a single distribution has to be understood.
103
+ *
104
+ * **A histogram's shape is a property of its bin width, not only of its data.**
105
+ * The same numbers cut into eight buckets and into eighty are two different
106
+ * pictures, and a gap between two humps can be created or erased by moving a
107
+ * bin edge. That is not a defect to be fixed, it is what binning IS, and the
108
+ * defence is to say which rule drew the picture — the default here is
109
+ * Freedman–Diaconis — and to look at more than one width before believing a
110
+ * feature. Uneven buckets add a second trap: under `frequency` a bucket twice
111
+ * as wide stands twice as tall at the same underlying rate, which is what
112
+ * `mode="density"` exists to correct.
113
+ *
114
+ * Recharts earns its place here for the axes, the grid and the tooltip, but not
115
+ * for the bars: a bar chart's bars are positioned by CATEGORY and a histogram's
116
+ * are positioned and SIZED by a continuous measurement. So the x axis is
117
+ * numeric and each bar is drawn from its own two edges — which is what lets an
118
+ * uneven bucket be as wide as it really is instead of being flattened into an
119
+ * equal slot beside its neighbours.
120
+ *
121
+ * @example
122
+ * <Histogram title="Response times" values={samples} bins={20}>
123
+ * <Histogram.Grid />
124
+ * <Histogram.XAxis label="ms" />
125
+ * <Histogram.YAxis />
126
+ * <Histogram.Tooltip />
127
+ * <Histogram.Bars />
128
+ * </Histogram>
129
+ *
130
+ * @example
131
+ * // Buckets that arrived already counted, with uneven widths.
132
+ * <Histogram title="Payload size" data={buckets} mode="density">
133
+ * <Histogram.XAxis />
134
+ * <Histogram.YAxis />
135
+ * <Histogram.Bars />
136
+ * </Histogram>
137
+ */
138
+ declare function Histogram({ config, values, data, bins, mode, title, showTitle, description, children, className, chartProps, formatValue, hideDataTable, empty, }: HistogramProps): react.JSX.Element;
139
+ declare namespace Histogram {
140
+ var Bars: ({ radius, hatched, barProps }: HistogramBarsProps) => react.JSX.Element;
141
+ var XAxis: ({ dataKey, type, tickLine, axisLine, tickMargin, minTickGap, label, domain, tickFormatter, ...rest }: ChartXAxisProps) => react.JSX.Element;
142
+ var YAxis: ({ tickLine, axisLine, tickMargin, width, label, tickFormatter, ...rest }: ChartYAxisProps) => react.JSX.Element;
143
+ var Grid: ({ strokeDasharray, vertical, ...rest }: ComponentProps<typeof CartesianGrid>) => react.JSX.Element;
144
+ var Tooltip: ({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) => react.JSX.Element;
145
+ var Background: ({ variant }: {
146
+ variant: ChartBackgroundVariant;
147
+ }) => react.JSX.Element;
148
+ var ReferenceLine: typeof ReferenceLine;
149
+ var ReferenceBand: typeof ReferenceBand;
150
+ var Annotation: typeof Annotation;
151
+ }
152
+ interface HistogramBarsProps {
153
+ /**
154
+ * The bars' corner. Zero by default, unlike `<BarChart>`: a histogram's bars
155
+ * are one continuous surface cut into pieces, and a rounded corner draws a
156
+ * gap between two buckets that are touching.
157
+ */
158
+ radius?: number;
159
+ /** Adds a 45° texture, for a histogram printed in greyscale or overlaid. */
160
+ hatched?: boolean;
161
+ /** Escape hatch onto the underlying Recharts bar. */
162
+ barProps?: ComponentProps<typeof Bar>;
163
+ }
164
+
165
+ export { Histogram, type HistogramBarsProps, type HistogramBin, type HistogramMode, type HistogramProps, Histogram as default };