@spatika/react 1.1.0 → 1.2.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 (311) hide show
  1. package/AGENTS.md +71 -0
  2. package/README.md +4 -3
  3. package/dist/composites/Card.d.ts +7 -1
  4. package/dist/composites/Card.d.ts.map +1 -1
  5. package/dist/composites/Card.js +10 -0
  6. package/dist/composites/ChartDataGrid.d.ts +33 -0
  7. package/dist/composites/ChartDataGrid.d.ts.map +1 -0
  8. package/dist/composites/ChartDataGrid.js +77 -0
  9. package/dist/composites/CoverHero.d.ts +1 -1
  10. package/dist/composites/CoverHero.d.ts.map +1 -1
  11. package/dist/composites/CoverHero.js +2 -2
  12. package/dist/composites/EntityMediaCard.d.ts +1 -1
  13. package/dist/composites/EntityMediaCard.d.ts.map +1 -1
  14. package/dist/composites/EntityMediaCard.js +12 -4
  15. package/dist/composites/EventCalendar.d.ts +35 -0
  16. package/dist/composites/EventCalendar.d.ts.map +1 -0
  17. package/dist/composites/EventCalendar.js +339 -0
  18. package/dist/composites/EventEditor.d.ts +22 -0
  19. package/dist/composites/EventEditor.d.ts.map +1 -0
  20. package/dist/composites/EventEditor.js +86 -0
  21. package/dist/composites/EventTimeline.d.ts +24 -0
  22. package/dist/composites/EventTimeline.d.ts.map +1 -0
  23. package/dist/composites/EventTimeline.js +184 -0
  24. package/dist/composites/MapChart.d.ts +24 -0
  25. package/dist/composites/MapChart.d.ts.map +1 -0
  26. package/dist/composites/MapChart.js +58 -0
  27. package/dist/composites/ProfileHero.d.ts +6 -4
  28. package/dist/composites/ProfileHero.d.ts.map +1 -1
  29. package/dist/composites/ProfileHero.js +8 -8
  30. package/dist/composites/ScatterWebGL.d.ts +22 -0
  31. package/dist/composites/ScatterWebGL.d.ts.map +1 -0
  32. package/dist/composites/ScatterWebGL.js +45 -0
  33. package/dist/composites/SiteFooter.d.ts +1 -1
  34. package/dist/composites/SiteFooter.js +1 -1
  35. package/dist/composites/SiteNav.d.ts +7 -2
  36. package/dist/composites/SiteNav.d.ts.map +1 -1
  37. package/dist/composites/SiteNav.js +9 -5
  38. package/dist/composites/StatCard.d.ts +2 -1
  39. package/dist/composites/StatCard.d.ts.map +1 -1
  40. package/dist/composites/StatCard.js +3 -3
  41. package/dist/composites/StatusDot.d.ts +1 -1
  42. package/dist/composites/Wordmark.d.ts +2 -2
  43. package/dist/composites/Wordmark.d.ts.map +1 -1
  44. package/dist/composites/cartesian-charts.d.ts +138 -0
  45. package/dist/composites/cartesian-charts.d.ts.map +1 -0
  46. package/dist/composites/cartesian-charts.js +401 -0
  47. package/dist/composites/chart-composition.d.ts +139 -0
  48. package/dist/composites/chart-composition.d.ts.map +1 -0
  49. package/dist/composites/chart-composition.js +486 -0
  50. package/dist/composites/chart-ui.d.ts +83 -0
  51. package/dist/composites/chart-ui.d.ts.map +1 -0
  52. package/dist/composites/chart-ui.js +108 -0
  53. package/dist/composites/charts-3d.d.ts +37 -0
  54. package/dist/composites/charts-3d.d.ts.map +1 -0
  55. package/dist/composites/charts-3d.js +121 -0
  56. package/dist/composites/flow-charts.d.ts +89 -0
  57. package/dist/composites/flow-charts.d.ts.map +1 -0
  58. package/dist/composites/flow-charts.js +186 -0
  59. package/dist/composites/radial-charts.d.ts +109 -0
  60. package/dist/composites/radial-charts.d.ts.map +1 -0
  61. package/dist/composites/radial-charts.js +176 -0
  62. package/dist/composites/scheduler-ui.d.ts +43 -0
  63. package/dist/composites/scheduler-ui.d.ts.map +1 -0
  64. package/dist/composites/scheduler-ui.js +71 -0
  65. package/dist/index.d.ts +67 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +57 -7
  68. package/dist/lib/chart-export.d.ts +10 -0
  69. package/dist/lib/chart-export.d.ts.map +1 -0
  70. package/dist/lib/chart-export.js +102 -0
  71. package/dist/lib/charts.d.ts +235 -0
  72. package/dist/lib/charts.d.ts.map +1 -0
  73. package/dist/lib/charts.js +744 -0
  74. package/dist/lib/geo.d.ts +39 -0
  75. package/dist/lib/geo.d.ts.map +1 -0
  76. package/dist/lib/geo.js +101 -0
  77. package/dist/lib/iso.d.ts +21 -0
  78. package/dist/lib/iso.d.ts.map +1 -0
  79. package/dist/lib/iso.js +56 -0
  80. package/dist/lib/rrule.d.ts +20 -0
  81. package/dist/lib/rrule.d.ts.map +1 -0
  82. package/dist/lib/rrule.js +236 -0
  83. package/dist/lib/scheduler.d.ts +152 -0
  84. package/dist/lib/scheduler.d.ts.map +1 -0
  85. package/dist/lib/scheduler.js +501 -0
  86. package/dist/lib/webgl-scatter.d.ts +44 -0
  87. package/dist/lib/webgl-scatter.d.ts.map +1 -0
  88. package/dist/lib/webgl-scatter.js +185 -0
  89. package/dist/primitives/Accordion.d.ts +44 -20
  90. package/dist/primitives/Accordion.d.ts.map +1 -1
  91. package/dist/primitives/Accordion.js +82 -22
  92. package/dist/primitives/AppBar.d.ts +13 -0
  93. package/dist/primitives/AppBar.d.ts.map +1 -0
  94. package/dist/primitives/AppBar.js +27 -0
  95. package/dist/primitives/Autocomplete.d.ts +28 -0
  96. package/dist/primitives/Autocomplete.d.ts.map +1 -0
  97. package/dist/primitives/Autocomplete.js +118 -0
  98. package/dist/primitives/Avatar.d.ts +13 -1
  99. package/dist/primitives/Avatar.d.ts.map +1 -1
  100. package/dist/primitives/Avatar.js +17 -2
  101. package/dist/primitives/Backdrop.d.ts +17 -0
  102. package/dist/primitives/Backdrop.d.ts.map +1 -0
  103. package/dist/primitives/Backdrop.js +17 -0
  104. package/dist/primitives/BottomNavigation.d.ts +23 -0
  105. package/dist/primitives/BottomNavigation.d.ts.map +1 -0
  106. package/dist/primitives/BottomNavigation.js +30 -0
  107. package/dist/primitives/ButtonBase.d.ts +14 -0
  108. package/dist/primitives/ButtonBase.d.ts.map +1 -0
  109. package/dist/primitives/ButtonBase.js +13 -0
  110. package/dist/primitives/ButtonGroup.d.ts +25 -0
  111. package/dist/primitives/ButtonGroup.d.ts.map +1 -0
  112. package/dist/primitives/ButtonGroup.js +44 -0
  113. package/dist/primitives/CircularProgress.d.ts +16 -0
  114. package/dist/primitives/CircularProgress.d.ts.map +1 -0
  115. package/dist/primitives/CircularProgress.js +17 -0
  116. package/dist/primitives/ClickAwayListener.d.ts +16 -0
  117. package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
  118. package/dist/primitives/ClickAwayListener.js +33 -0
  119. package/dist/primitives/Collapse.d.ts +19 -0
  120. package/dist/primitives/Collapse.d.ts.map +1 -0
  121. package/dist/primitives/Collapse.js +14 -0
  122. package/dist/primitives/Container.d.ts +13 -0
  123. package/dist/primitives/Container.d.ts.map +1 -0
  124. package/dist/primitives/Container.js +26 -0
  125. package/dist/primitives/Fab.d.ts +22 -0
  126. package/dist/primitives/Fab.d.ts.map +1 -0
  127. package/dist/primitives/Fab.js +42 -0
  128. package/dist/primitives/FormControl.d.ts +27 -0
  129. package/dist/primitives/FormControl.d.ts.map +1 -0
  130. package/dist/primitives/FormControl.js +24 -0
  131. package/dist/primitives/FormControlLabel.d.ts +18 -0
  132. package/dist/primitives/FormControlLabel.d.ts.map +1 -0
  133. package/dist/primitives/FormControlLabel.js +19 -0
  134. package/dist/primitives/Grid.d.ts +32 -0
  135. package/dist/primitives/Grid.d.ts.map +1 -0
  136. package/dist/primitives/Grid.js +50 -0
  137. package/dist/primitives/IconButton.d.ts +22 -0
  138. package/dist/primitives/IconButton.d.ts.map +1 -0
  139. package/dist/primitives/IconButton.js +39 -0
  140. package/dist/primitives/ImageList.d.ts +31 -0
  141. package/dist/primitives/ImageList.d.ts.map +1 -0
  142. package/dist/primitives/ImageList.js +33 -0
  143. package/dist/primitives/InputAdornment.d.ts +28 -0
  144. package/dist/primitives/InputAdornment.d.ts.map +1 -0
  145. package/dist/primitives/InputAdornment.js +13 -0
  146. package/dist/primitives/Link.d.ts +17 -0
  147. package/dist/primitives/Link.d.ts.map +1 -0
  148. package/dist/primitives/Link.js +29 -0
  149. package/dist/primitives/List.d.ts +41 -0
  150. package/dist/primitives/List.d.ts.map +1 -0
  151. package/dist/primitives/List.js +22 -0
  152. package/dist/primitives/Masonry.d.ts +14 -0
  153. package/dist/primitives/Masonry.d.ts.map +1 -0
  154. package/dist/primitives/Masonry.js +15 -0
  155. package/dist/primitives/Menu.d.ts +28 -0
  156. package/dist/primitives/Menu.d.ts.map +1 -0
  157. package/dist/primitives/Menu.js +54 -0
  158. package/dist/primitives/MobileStepper.d.ts +15 -0
  159. package/dist/primitives/MobileStepper.d.ts.map +1 -0
  160. package/dist/primitives/MobileStepper.js +8 -0
  161. package/dist/primitives/Modal.d.ts +18 -0
  162. package/dist/primitives/Modal.d.ts.map +1 -0
  163. package/dist/primitives/Modal.js +29 -0
  164. package/dist/primitives/NoSsr.d.ts +15 -0
  165. package/dist/primitives/NoSsr.d.ts.map +1 -0
  166. package/dist/primitives/NoSsr.js +19 -0
  167. package/dist/primitives/Paper.d.ts +22 -0
  168. package/dist/primitives/Paper.d.ts.map +1 -0
  169. package/dist/primitives/Paper.js +33 -0
  170. package/dist/primitives/Progress.d.ts +6 -2
  171. package/dist/primitives/Progress.d.ts.map +1 -1
  172. package/dist/primitives/Progress.js +8 -3
  173. package/dist/primitives/Snackbar.d.ts +23 -0
  174. package/dist/primitives/Snackbar.d.ts.map +1 -0
  175. package/dist/primitives/Snackbar.js +33 -0
  176. package/dist/primitives/SpeedDial.d.ts +26 -0
  177. package/dist/primitives/SpeedDial.d.ts.map +1 -0
  178. package/dist/primitives/SpeedDial.js +26 -0
  179. package/dist/primitives/Stack.d.ts +15 -0
  180. package/dist/primitives/Stack.d.ts.map +1 -0
  181. package/dist/primitives/Stack.js +45 -0
  182. package/dist/primitives/StarRating.d.ts +19 -1
  183. package/dist/primitives/StarRating.d.ts.map +1 -1
  184. package/dist/primitives/StarRating.js +36 -6
  185. package/dist/primitives/Stepper.d.ts +36 -0
  186. package/dist/primitives/Stepper.d.ts.map +1 -0
  187. package/dist/primitives/Stepper.js +53 -0
  188. package/dist/primitives/TablePagination.d.ts +16 -0
  189. package/dist/primitives/TablePagination.d.ts.map +1 -0
  190. package/dist/primitives/TablePagination.js +12 -0
  191. package/dist/primitives/TextField.d.ts +24 -0
  192. package/dist/primitives/TextField.d.ts.map +1 -0
  193. package/dist/primitives/TextField.js +19 -0
  194. package/dist/primitives/TextareaAutosize.d.ts +11 -0
  195. package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
  196. package/dist/primitives/TextareaAutosize.js +26 -0
  197. package/dist/primitives/ToggleButton.d.ts +28 -0
  198. package/dist/primitives/ToggleButton.d.ts.map +1 -0
  199. package/dist/primitives/ToggleButton.js +56 -0
  200. package/dist/primitives/TransferList.d.ts +21 -0
  201. package/dist/primitives/TransferList.d.ts.map +1 -0
  202. package/dist/primitives/TransferList.js +40 -0
  203. package/dist/primitives/Typography.d.ts +23 -0
  204. package/dist/primitives/Typography.d.ts.map +1 -0
  205. package/dist/primitives/Typography.js +69 -0
  206. package/dist/primitives/transitions.d.ts +29 -0
  207. package/dist/primitives/transitions.d.ts.map +1 -0
  208. package/dist/primitives/transitions.js +52 -0
  209. package/package.json +28 -4
  210. package/skills/spatika-ui/SKILL.md +96 -0
  211. package/src/composites/Card.tsx +51 -1
  212. package/src/composites/ChartDataGrid.test.tsx +57 -0
  213. package/src/composites/ChartDataGrid.tsx +196 -0
  214. package/src/composites/CoverHero.test.tsx +38 -0
  215. package/src/composites/CoverHero.tsx +29 -22
  216. package/src/composites/EntityMediaCard.test.tsx +58 -0
  217. package/src/composites/EntityMediaCard.tsx +31 -13
  218. package/src/composites/EventCalendar.tsx +928 -0
  219. package/src/composites/EventEditor.tsx +268 -0
  220. package/src/composites/EventTimeline.tsx +357 -0
  221. package/src/composites/MapChart.test.tsx +39 -0
  222. package/src/composites/MapChart.tsx +130 -0
  223. package/src/composites/ProfileHero.test.tsx +33 -0
  224. package/src/composites/ProfileHero.tsx +96 -79
  225. package/src/composites/ScatterWebGL.test.tsx +25 -0
  226. package/src/composites/ScatterWebGL.tsx +79 -0
  227. package/src/composites/SiteFooter.test.tsx +37 -0
  228. package/src/composites/SiteFooter.tsx +1 -1
  229. package/src/composites/SiteNav.test.tsx +46 -0
  230. package/src/composites/SiteNav.tsx +14 -4
  231. package/src/composites/StatCard.test.tsx +30 -0
  232. package/src/composites/StatCard.tsx +4 -0
  233. package/src/composites/Wordmark.test.tsx +23 -0
  234. package/src/composites/Wordmark.tsx +2 -2
  235. package/src/composites/cartesian-charts.test.tsx +52 -0
  236. package/src/composites/cartesian-charts.tsx +1287 -0
  237. package/src/composites/chart-composition.test.tsx +53 -0
  238. package/src/composites/chart-composition.tsx +898 -0
  239. package/src/composites/chart-ui.tsx +328 -0
  240. package/src/composites/charts-3d.test.tsx +25 -0
  241. package/src/composites/charts-3d.tsx +318 -0
  242. package/src/composites/charts.test.tsx +254 -0
  243. package/src/composites/chrome.test.tsx +29 -0
  244. package/src/composites/flow-charts.test.tsx +79 -0
  245. package/src/composites/flow-charts.tsx +607 -0
  246. package/src/composites/marketing.test.tsx +38 -34
  247. package/src/composites/radial-charts.test.tsx +74 -0
  248. package/src/composites/radial-charts.tsx +617 -0
  249. package/src/composites/scheduler-ui.tsx +304 -0
  250. package/src/composites/scheduler.test.tsx +194 -0
  251. package/src/index.test.ts +22 -0
  252. package/src/index.ts +277 -5
  253. package/src/lib/chart-export.test.ts +20 -0
  254. package/src/lib/chart-export.ts +99 -0
  255. package/src/lib/charts-platform.test.ts +93 -0
  256. package/src/lib/charts.test.ts +132 -0
  257. package/src/lib/charts.ts +1021 -0
  258. package/src/lib/geo.test.ts +42 -0
  259. package/src/lib/geo.ts +138 -0
  260. package/src/lib/iso.test.ts +22 -0
  261. package/src/lib/iso.ts +102 -0
  262. package/src/lib/rrule.test.ts +53 -0
  263. package/src/lib/rrule.ts +281 -0
  264. package/src/lib/scheduler.test.ts +186 -0
  265. package/src/lib/scheduler.ts +688 -0
  266. package/src/lib/tokens.test.ts +2 -2
  267. package/src/lib/webgl-scatter.test.ts +36 -0
  268. package/src/lib/webgl-scatter.ts +230 -0
  269. package/src/primitives/Accordion.tsx +218 -63
  270. package/src/primitives/AppBar.tsx +39 -0
  271. package/src/primitives/Autocomplete.tsx +236 -0
  272. package/src/primitives/Avatar.tsx +52 -1
  273. package/src/primitives/Backdrop.tsx +48 -0
  274. package/src/primitives/BottomNavigation.tsx +102 -0
  275. package/src/primitives/ButtonBase.tsx +34 -0
  276. package/src/primitives/ButtonGroup.tsx +90 -0
  277. package/src/primitives/CircularProgress.tsx +104 -0
  278. package/src/primitives/ClickAwayListener.tsx +46 -0
  279. package/src/primitives/Collapse.tsx +39 -0
  280. package/src/primitives/Container.tsx +40 -0
  281. package/src/primitives/Fab.tsx +62 -0
  282. package/src/primitives/FormControl.tsx +90 -0
  283. package/src/primitives/FormControlLabel.tsx +51 -0
  284. package/src/primitives/Grid.tsx +101 -0
  285. package/src/primitives/IconButton.tsx +59 -0
  286. package/src/primitives/ImageList.test.tsx +48 -0
  287. package/src/primitives/ImageList.tsx +112 -0
  288. package/src/primitives/InputAdornment.tsx +86 -0
  289. package/src/primitives/Link.tsx +48 -0
  290. package/src/primitives/List.tsx +121 -0
  291. package/src/primitives/Masonry.tsx +32 -0
  292. package/src/primitives/Menu.tsx +112 -0
  293. package/src/primitives/MobileStepper.tsx +64 -0
  294. package/src/primitives/Modal.tsx +70 -0
  295. package/src/primitives/NoSsr.tsx +28 -0
  296. package/src/primitives/Paper.tsx +55 -0
  297. package/src/primitives/Progress.tsx +25 -7
  298. package/src/primitives/Snackbar.tsx +83 -0
  299. package/src/primitives/SpeedDial.tsx +110 -0
  300. package/src/primitives/Stack.tsx +88 -0
  301. package/src/primitives/StarRating.tsx +87 -28
  302. package/src/primitives/Stepper.tsx +184 -0
  303. package/src/primitives/TablePagination.tsx +58 -0
  304. package/src/primitives/TextField.tsx +105 -0
  305. package/src/primitives/TextareaAutosize.tsx +49 -0
  306. package/src/primitives/ToggleButton.tsx +148 -0
  307. package/src/primitives/TransferList.tsx +110 -0
  308. package/src/primitives/Typography.tsx +90 -0
  309. package/src/primitives/mui-more.test.tsx +76 -0
  310. package/src/primitives/mui.test.tsx +134 -0
  311. package/src/primitives/transitions.tsx +120 -0
@@ -0,0 +1,617 @@
1
+ import {
2
+ arcPath,
3
+ formatChartNumber,
4
+ linePath,
5
+ pieSlices,
6
+ polarPoint,
7
+ type ChartMargin,
8
+ } from "../lib/charts";
9
+ import {
10
+ ChartFrame,
11
+ formatSeriesValue,
12
+ seriesMeta,
13
+ useChartHover,
14
+ useHiddenSeries,
15
+ } from "./chart-ui";
16
+
17
+ export type PieDatum = {
18
+ id?: string | number;
19
+ value: number;
20
+ label?: string;
21
+ color?: string;
22
+ };
23
+
24
+ export type PieSeries = {
25
+ data: PieDatum[];
26
+ innerRadius?: number;
27
+ outerRadius?: number;
28
+ paddingAngle?: number;
29
+ startAngle?: number;
30
+ endAngle?: number;
31
+ cx?: number;
32
+ cy?: number;
33
+ faded?: boolean;
34
+ };
35
+
36
+ export type PieChartProps = {
37
+ series: PieSeries[];
38
+ height?: number;
39
+ width?: number;
40
+ colors?: string[];
41
+ hideLegend?: boolean;
42
+ margin?: ChartMargin;
43
+ className?: string;
44
+ slot?: string;
45
+ };
46
+
47
+ export function PieChart({
48
+ series,
49
+ height = 280,
50
+ width,
51
+ colors,
52
+ hideLegend,
53
+ margin,
54
+ className,
55
+ slot = "pie-chart",
56
+ }: PieChartProps) {
57
+ const first = series[0] ?? { data: [] };
58
+ const meta = seriesMeta(
59
+ first.data.map((item, i) => ({
60
+ id: String(item.id ?? item.label ?? i),
61
+ label: item.label ?? `Slice ${i + 1}`,
62
+ color: item.color,
63
+ })),
64
+ colors,
65
+ );
66
+ const ids = meta.map((item) => item.id);
67
+ const { hidden, toggle } = useHiddenSeries(ids);
68
+ const { hover, setHover, clear } = useChartHover();
69
+
70
+ return (
71
+ <ChartFrame
72
+ slot={slot}
73
+ className={className}
74
+ width={width}
75
+ height={height}
76
+ margin={margin ?? { top: 12, right: 12, bottom: 12, left: 12 }}
77
+ legend={hideLegend ? undefined : meta}
78
+ hiddenIds={hidden}
79
+ onToggleSeries={toggle}
80
+ hover={hover}
81
+ aria-label="Pie chart"
82
+ >
83
+ {({ width: w, height: h, m }) => {
84
+ const cx = first.cx ?? w / 2;
85
+ const cy = first.cy ?? (h - 8) / 2;
86
+ const maxR = Math.min(w - m.left - m.right, h - m.top - m.bottom) / 2;
87
+ const outer = first.outerRadius ?? maxR;
88
+ const inner = first.innerRadius ?? 0;
89
+ const visible = first.data.map((item, i) => (hidden.has(meta[i]!.id) ? 0 : item.value));
90
+ const slices = pieSlices(visible, {
91
+ cx,
92
+ cy,
93
+ innerRadius: inner,
94
+ outerRadius: outer,
95
+ startAngle: first.startAngle,
96
+ endAngle: first.endAngle,
97
+ paddingAngle: first.paddingAngle,
98
+ });
99
+ return (
100
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
101
+ {slices.map((slice, i) => {
102
+ const info = meta[i]!;
103
+ if (hidden.has(info.id) || !slice.path) return null;
104
+ const item = first.data[i]!;
105
+ return (
106
+ <g key={info.id}>
107
+ <path
108
+ d={slice.path}
109
+ fill={info.color}
110
+ className="spk-chart-mark"
111
+ onMouseEnter={(event) =>
112
+ setHover({
113
+ x: event.nativeEvent.offsetX,
114
+ y: event.nativeEvent.offsetY,
115
+ title: info.label,
116
+ items: [
117
+ {
118
+ color: info.color,
119
+ label: info.label,
120
+ value: `${formatChartNumber(item.value)} (${Math.round(slice.percent * 100)}%)`,
121
+ },
122
+ ],
123
+ })
124
+ }
125
+ onMouseLeave={clear}
126
+ />
127
+ {slice.percent > 0.08 ? (
128
+ <text
129
+ className="spk-chart-label"
130
+ fill="var(--primary-foreground)"
131
+ x={slice.labelX}
132
+ y={slice.labelY}
133
+ textAnchor="middle"
134
+ dominantBaseline="middle"
135
+ >
136
+ {Math.round(slice.percent * 100)}%
137
+ </text>
138
+ ) : null}
139
+ </g>
140
+ );
141
+ })}
142
+ </svg>
143
+ );
144
+ }}
145
+ </ChartFrame>
146
+ );
147
+ }
148
+
149
+ export type GaugeSection = {
150
+ value: number;
151
+ color?: string;
152
+ label?: string;
153
+ };
154
+
155
+ export type GaugeProps = {
156
+ value?: number;
157
+ valueMin?: number;
158
+ valueMax?: number;
159
+ startAngle?: number;
160
+ endAngle?: number;
161
+ innerRadius?: number;
162
+ outerRadius?: number;
163
+ height?: number;
164
+ width?: number;
165
+ color?: string;
166
+ colors?: string[];
167
+ text?: string | null | ((value: number) => string);
168
+ sections?: GaugeSection[];
169
+ hideLegend?: boolean;
170
+ paddingAngle?: number;
171
+ className?: string;
172
+ };
173
+
174
+ export function Gauge({
175
+ value = 0,
176
+ valueMin = 0,
177
+ valueMax = 100,
178
+ startAngle = -110,
179
+ endAngle = 110,
180
+ innerRadius,
181
+ outerRadius,
182
+ height = 200,
183
+ width,
184
+ color = "var(--chart-1)",
185
+ colors,
186
+ text,
187
+ sections,
188
+ hideLegend,
189
+ paddingAngle = 2,
190
+ className,
191
+ }: GaugeProps) {
192
+ const pct = (value - valueMin) / (valueMax - valueMin || 1);
193
+ const segmented = Boolean(sections?.length);
194
+ const resolvedText =
195
+ text === null
196
+ ? null
197
+ : typeof text === "function"
198
+ ? text(value)
199
+ : (text ?? (segmented ? null : `${Math.round(pct * 100)}%`));
200
+ const meta = seriesMeta(
201
+ (sections ?? []).map((item, i) => ({
202
+ id: item.label ?? `section-${i}`,
203
+ label: item.label ?? `Section ${i + 1}`,
204
+ color: item.color,
205
+ })),
206
+ colors,
207
+ );
208
+
209
+ return (
210
+ <ChartFrame
211
+ slot="gauge"
212
+ className={className}
213
+ width={width}
214
+ height={height}
215
+ margin={{ top: 8, right: 8, bottom: 8, left: 8 }}
216
+ legend={segmented && !hideLegend ? meta : undefined}
217
+ aria-label={segmented ? "Gauge" : `Gauge ${value}`}
218
+ >
219
+ {({ width: w, height: h }) => {
220
+ const cx = w / 2;
221
+ const cy = segmented ? h * 0.72 : h * 0.62;
222
+ const maxR = Math.min(w, h) * (segmented ? 0.5 : 0.42);
223
+ const outer = outerRadius ?? maxR;
224
+ const inner = innerRadius ?? outer * 0.72;
225
+ const span = endAngle - startAngle;
226
+ const valueAngle = startAngle + span * Math.min(1, Math.max(0, pct));
227
+ const slices = segmented
228
+ ? pieSlices(
229
+ (sections ?? []).map((item) => item.value),
230
+ {
231
+ cx,
232
+ cy,
233
+ innerRadius: inner,
234
+ outerRadius: outer,
235
+ startAngle,
236
+ endAngle,
237
+ paddingAngle,
238
+ },
239
+ )
240
+ : [];
241
+ return (
242
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
243
+ {segmented ? (
244
+ slices.map((slice, i) => {
245
+ const info = meta[i]!;
246
+ if (!slice.path) return null;
247
+ return (
248
+ <path
249
+ key={info.id}
250
+ d={slice.path}
251
+ fill={info.color}
252
+ className="spk-chart-mark"
253
+ />
254
+ );
255
+ })
256
+ ) : (
257
+ <>
258
+ <path
259
+ d={arcPath(cx, cy, inner, outer, startAngle, endAngle)}
260
+ fill="color-mix(in srgb, var(--muted) 80%, transparent)"
261
+ />
262
+ <path d={arcPath(cx, cy, inner, outer, startAngle, valueAngle)} fill={color} />
263
+ </>
264
+ )}
265
+ {resolvedText != null ? (
266
+ <text
267
+ className="spk-chart-label"
268
+ x={cx}
269
+ y={cy}
270
+ textAnchor="middle"
271
+ dominantBaseline="middle"
272
+ fontSize="1.35rem"
273
+ >
274
+ {resolvedText}
275
+ </text>
276
+ ) : null}
277
+ </svg>
278
+ );
279
+ }}
280
+ </ChartFrame>
281
+ );
282
+ }
283
+
284
+ export type LinearGaugeProps = {
285
+ value: number;
286
+ valueMin?: number;
287
+ valueMax?: number;
288
+ height?: number;
289
+ width?: number;
290
+ color?: string;
291
+ className?: string;
292
+ };
293
+
294
+ export function LinearGauge({
295
+ value,
296
+ valueMin = 0,
297
+ valueMax = 100,
298
+ height = 48,
299
+ width,
300
+ color = "var(--chart-1)",
301
+ className,
302
+ }: LinearGaugeProps) {
303
+ const pct = Math.min(1, Math.max(0, (value - valueMin) / (valueMax - valueMin || 1)));
304
+ return (
305
+ <ChartFrame
306
+ slot="linear-gauge"
307
+ className={className}
308
+ width={width}
309
+ height={height}
310
+ margin={{ top: 14, right: 12, bottom: 14, left: 12 }}
311
+ aria-label={`Linear gauge ${value}`}
312
+ >
313
+ {({ width: w, height: h, m }) => {
314
+ const y = h / 2 - 7;
315
+ const trackW = w - m.left - m.right;
316
+ return (
317
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
318
+ <rect
319
+ x={m.left}
320
+ y={y}
321
+ width={trackW}
322
+ height={14}
323
+ rx={7}
324
+ fill="color-mix(in srgb, var(--muted) 80%, transparent)"
325
+ />
326
+ <rect x={m.left} y={y} width={Math.max(14, trackW * pct)} height={14} rx={7} fill={color} />
327
+ <text
328
+ className="spk-chart-label"
329
+ x={m.left + trackW * pct}
330
+ y={y - 4}
331
+ textAnchor={pct > 0.85 ? "end" : "middle"}
332
+ >
333
+ {formatChartNumber(value)}
334
+ </text>
335
+ </svg>
336
+ );
337
+ }}
338
+ </ChartFrame>
339
+ );
340
+ }
341
+
342
+ export type RadarSeries = {
343
+ id?: string;
344
+ label?: string;
345
+ color?: string;
346
+ data: number[];
347
+ };
348
+
349
+ export type RadarChartProps = {
350
+ series: RadarSeries[];
351
+ radar: { metrics: string[]; max?: number };
352
+ height?: number;
353
+ width?: number;
354
+ colors?: string[];
355
+ hideLegend?: boolean;
356
+ className?: string;
357
+ fill?: boolean;
358
+ slot?: string;
359
+ };
360
+
361
+ export function RadarChart({
362
+ series,
363
+ radar,
364
+ height = 300,
365
+ width,
366
+ colors,
367
+ hideLegend,
368
+ className,
369
+ fill = true,
370
+ slot = "radar-chart",
371
+ }: RadarChartProps) {
372
+ const meta = seriesMeta(series, colors);
373
+ const ids = meta.map((item) => item.id);
374
+ const { hidden, toggle } = useHiddenSeries(ids);
375
+ const { hover, setHover, clear } = useChartHover();
376
+ const max = radar.max ?? Math.max(...series.flatMap((item) => item.data), 1);
377
+
378
+ return (
379
+ <ChartFrame
380
+ slot={slot}
381
+ className={className}
382
+ width={width}
383
+ height={height}
384
+ margin={{ top: 28, right: 28, bottom: 28, left: 28 }}
385
+ legend={hideLegend ? undefined : meta}
386
+ hiddenIds={hidden}
387
+ onToggleSeries={toggle}
388
+ hover={hover}
389
+ aria-label="Radar chart"
390
+ >
391
+ {({ width: w, height: h }) => {
392
+ const cx = w / 2;
393
+ const cy = h / 2;
394
+ const radius = Math.min(w, h) * 0.36;
395
+ const n = radar.metrics.length || 1;
396
+ const angle = 360 / n;
397
+ const rings = [0.25, 0.5, 0.75, 1];
398
+ return (
399
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
400
+ {rings.map((ring) => (
401
+ <polygon
402
+ key={ring}
403
+ fill="none"
404
+ className="spk-chart-grid"
405
+ points={radar.metrics
406
+ .map((_, i) => {
407
+ const p = polarPoint(cx, cy, radius * ring, i * angle);
408
+ return `${p.x},${p.y}`;
409
+ })
410
+ .join(" ")}
411
+ />
412
+ ))}
413
+ {radar.metrics.map((metric, i) => {
414
+ const p = polarPoint(cx, cy, radius, i * angle);
415
+ const label = polarPoint(cx, cy, radius + 18, i * angle);
416
+ return (
417
+ <g key={metric}>
418
+ <line className="spk-chart-grid" x1={cx} y1={cy} x2={p.x} y2={p.y} />
419
+ <text
420
+ className="spk-chart-tick"
421
+ x={label.x}
422
+ y={label.y}
423
+ textAnchor="middle"
424
+ dominantBaseline="middle"
425
+ >
426
+ {metric}
427
+ </text>
428
+ </g>
429
+ );
430
+ })}
431
+ {series.map((item, s) => {
432
+ const info = meta[s]!;
433
+ if (hidden.has(info.id)) return null;
434
+ const pts = item.data.map((value, i) =>
435
+ polarPoint(cx, cy, radius * (value / max), i * angle),
436
+ );
437
+ const closed = [...pts, pts[0]!];
438
+ return (
439
+ <g key={info.id}>
440
+ {fill ? (
441
+ <polygon
442
+ points={pts.map((p) => `${p.x},${p.y}`).join(" ")}
443
+ fill={info.color}
444
+ fillOpacity={0.18}
445
+ stroke={info.color}
446
+ strokeWidth={2}
447
+ className="spk-chart-mark"
448
+ />
449
+ ) : (
450
+ <path
451
+ d={linePath(closed, "linear")}
452
+ fill="none"
453
+ stroke={info.color}
454
+ strokeWidth={2}
455
+ className="spk-chart-mark"
456
+ />
457
+ )}
458
+ {pts.map((p, i) => (
459
+ <circle
460
+ key={i}
461
+ cx={p.x}
462
+ cy={p.y}
463
+ r={3.2}
464
+ fill={info.color}
465
+ onMouseEnter={() =>
466
+ setHover({
467
+ x: p.x,
468
+ y: p.y,
469
+ title: radar.metrics[i],
470
+ items: [
471
+ {
472
+ color: info.color,
473
+ label: info.label,
474
+ value: formatSeriesValue(item.data[i]),
475
+ },
476
+ ],
477
+ })
478
+ }
479
+ onMouseLeave={clear}
480
+ />
481
+ ))}
482
+ </g>
483
+ );
484
+ })}
485
+ </svg>
486
+ );
487
+ }}
488
+ </ChartFrame>
489
+ );
490
+ }
491
+
492
+ export function PolarLineChart(props: RadarChartProps) {
493
+ return <RadarChart {...props} fill={false} slot="polar-line-chart" />;
494
+ }
495
+
496
+ export type RadialBarChartProps = {
497
+ series: Array<{ data: PieDatum[] }>;
498
+ height?: number;
499
+ width?: number;
500
+ colors?: string[];
501
+ hideLegend?: boolean;
502
+ className?: string;
503
+ startAngle?: number;
504
+ endAngle?: number;
505
+ };
506
+
507
+ export function RadialBarChart({
508
+ series,
509
+ height = 280,
510
+ width,
511
+ colors,
512
+ hideLegend,
513
+ className,
514
+ startAngle = -90,
515
+ endAngle = 180,
516
+ }: RadialBarChartProps) {
517
+ const data = series[0]?.data ?? [];
518
+ const meta = seriesMeta(
519
+ data.map((item, i) => ({ id: String(item.id ?? item.label ?? i), label: item.label ?? `Item ${i + 1}`, color: item.color })),
520
+ colors,
521
+ );
522
+ const ids = meta.map((item) => item.id);
523
+ const { hidden, toggle } = useHiddenSeries(ids);
524
+ const { hover, setHover, clear } = useChartHover();
525
+ const max = Math.max(...data.map((item) => item.value), 1);
526
+
527
+ return (
528
+ <ChartFrame
529
+ slot="radial-bar-chart"
530
+ className={className}
531
+ width={width}
532
+ height={height}
533
+ margin={{ top: 12, right: 12, bottom: 12, left: 12 }}
534
+ legend={hideLegend ? undefined : meta}
535
+ hiddenIds={hidden}
536
+ onToggleSeries={toggle}
537
+ hover={hover}
538
+ aria-label="Radial bar chart"
539
+ >
540
+ {({ width: w, height: h }) => {
541
+ const cx = w / 2;
542
+ const cy = h / 2;
543
+ const outer = Math.min(w, h) * 0.42;
544
+ const track = Math.max(8, (outer * 0.62) / Math.max(data.length, 1));
545
+ return (
546
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
547
+ {data.map((item, i) => {
548
+ const info = meta[i]!;
549
+ if (hidden.has(info.id)) return null;
550
+ const r1 = outer - i * track;
551
+ const r0 = r1 - track * 0.72;
552
+ const span = endAngle - startAngle;
553
+ const a1 = startAngle + span * (item.value / max);
554
+ return (
555
+ <g key={info.id}>
556
+ <path
557
+ d={arcPath(cx, cy, r0, r1, startAngle, endAngle)}
558
+ fill="color-mix(in srgb, var(--muted) 70%, transparent)"
559
+ />
560
+ <path
561
+ d={arcPath(cx, cy, r0, r1, startAngle, a1)}
562
+ fill={info.color}
563
+ className="spk-chart-mark"
564
+ onMouseEnter={(event) =>
565
+ setHover({
566
+ x: event.nativeEvent.offsetX,
567
+ y: event.nativeEvent.offsetY,
568
+ title: info.label,
569
+ items: [{ color: info.color, label: info.label, value: formatChartNumber(item.value) }],
570
+ })
571
+ }
572
+ onMouseLeave={clear}
573
+ />
574
+ </g>
575
+ );
576
+ })}
577
+ </svg>
578
+ );
579
+ }}
580
+ </ChartFrame>
581
+ );
582
+ }
583
+
584
+ export type RadialLineChartProps = {
585
+ series: RadarSeries[];
586
+ metrics?: string[];
587
+ height?: number;
588
+ width?: number;
589
+ colors?: string[];
590
+ hideLegend?: boolean;
591
+ className?: string;
592
+ };
593
+
594
+ export function RadialLineChart({
595
+ series,
596
+ metrics,
597
+ height = 280,
598
+ width,
599
+ colors,
600
+ hideLegend,
601
+ className,
602
+ }: RadialLineChartProps) {
603
+ const labels = metrics ?? series[0]?.data.map((_, i) => String(i + 1)) ?? [];
604
+ return (
605
+ <RadarChart
606
+ series={series}
607
+ radar={{ metrics: labels }}
608
+ height={height}
609
+ width={width}
610
+ colors={colors}
611
+ hideLegend={hideLegend}
612
+ className={className}
613
+ fill={false}
614
+ slot="radial-line-chart"
615
+ />
616
+ );
617
+ }