@spatika/react 1.1.0 → 1.3.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 +72 -0
  16. package/dist/composites/EventCalendar.d.ts.map +1 -0
  17. package/dist/composites/EventCalendar.js +371 -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 +29 -0
  22. package/dist/composites/EventTimeline.d.ts.map +1 -0
  23. package/dist/composites/EventTimeline.js +186 -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 +44 -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 +68 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +58 -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 +156 -0
  84. package/dist/lib/scheduler.d.ts.map +1 -0
  85. package/dist/lib/scheduler.js +502 -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 +1030 -0
  219. package/src/composites/EventEditor.tsx +268 -0
  220. package/src/composites/EventTimeline.tsx +366 -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 +306 -0
  250. package/src/composites/scheduler.test.tsx +280 -0
  251. package/src/index.test.ts +26 -0
  252. package/src/index.ts +287 -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 +199 -0
  265. package/src/lib/scheduler.ts +693 -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,744 @@
1
+ /**
2
+ * Geometry and layout helpers for Spatika SVG charts.
3
+ * Theme colors stay as CSS variables so Mukta / Neelam / Usha / Sandhya restyle automatically.
4
+ */
5
+ export const CHART_COLOR_VARS = [
6
+ "var(--chart-1)",
7
+ "var(--chart-2)",
8
+ "var(--chart-3)",
9
+ "var(--chart-4)",
10
+ "var(--chart-5)",
11
+ ];
12
+ export const DEFAULT_ZOOM = { x: [0, 1] };
13
+ export const MIN_ZOOM_SPAN = 0.06;
14
+ export function plotBox(width, height, m) {
15
+ return {
16
+ left: m.left,
17
+ right: width - m.right,
18
+ top: m.top,
19
+ bottom: height - m.bottom,
20
+ iw: Math.max(1, width - m.left - m.right),
21
+ ih: Math.max(1, height - m.top - m.bottom),
22
+ };
23
+ }
24
+ export function clampZoom(zoom) {
25
+ const clampPair = (pair) => {
26
+ const span = clamp(pair[1] - pair[0], MIN_ZOOM_SPAN, 1);
27
+ let start = clamp(pair[0], 0, 1 - span);
28
+ return [start, start + span];
29
+ };
30
+ return { x: clampPair(zoom.x), y: zoom.y ? clampPair(zoom.y) : zoom.y };
31
+ }
32
+ export function applyZoom(px, origin, size, zoom) {
33
+ const t = (px - origin) / (size || 1);
34
+ return origin + ((t - zoom[0]) / (zoom[1] - zoom[0] || 1)) * size;
35
+ }
36
+ export function panZoom(zoom, dxVisible, dyVisible = 0) {
37
+ const xSpan = zoom.x[1] - zoom.x[0];
38
+ const ySpan = zoom.y ? zoom.y[1] - zoom.y[0] : 0;
39
+ return clampZoom({
40
+ x: [zoom.x[0] - dxVisible * xSpan, zoom.x[1] - dxVisible * xSpan],
41
+ y: zoom.y
42
+ ? [zoom.y[0] + dyVisible * ySpan, zoom.y[1] + dyVisible * ySpan]
43
+ : zoom.y,
44
+ });
45
+ }
46
+ export function wheelZoom(zoom, anchorVisible, factor) {
47
+ const span = zoom.x[1] - zoom.x[0];
48
+ const t = zoom.x[0] + anchorVisible * span;
49
+ const next = clamp(span * factor, MIN_ZOOM_SPAN, 1);
50
+ const leftFrac = span === 0 ? 0.5 : (t - zoom.x[0]) / span;
51
+ return clampZoom({ x: [t - next * leftFrac, t - next * leftFrac + next], y: zoom.y });
52
+ }
53
+ export function zoomFromBrush(startPx, endPx, origin, size, zoom) {
54
+ const a = clamp((Math.min(startPx, endPx) - origin) / (size || 1), 0, 1);
55
+ const b = clamp((Math.max(startPx, endPx) - origin) / (size || 1), 0, 1);
56
+ if (b - a < 0.02)
57
+ return zoom;
58
+ const span = zoom.x[1] - zoom.x[0];
59
+ return clampZoom({
60
+ x: [zoom.x[0] + a * span, zoom.x[0] + b * span],
61
+ y: zoom.y,
62
+ });
63
+ }
64
+ export function seriesDataFromDataset(dataset, dataKey) {
65
+ return dataset.map((row) => {
66
+ const value = row[dataKey];
67
+ if (value == null || value === "")
68
+ return null;
69
+ if (typeof value === "number")
70
+ return value;
71
+ if (value instanceof Date)
72
+ return value.getTime();
73
+ const n = Number(value);
74
+ return Number.isFinite(n) ? n : null;
75
+ });
76
+ }
77
+ export function axisDataFromDataset(dataset, dataKey) {
78
+ return dataset.map((row) => {
79
+ const value = row[dataKey];
80
+ if (value instanceof Date || typeof value === "number")
81
+ return value;
82
+ return String(value ?? "");
83
+ });
84
+ }
85
+ export function markOpacity(scope, highlighted, seriesId, dataIndex) {
86
+ if (!highlighted || scope === "none")
87
+ return 1;
88
+ if (scope === "series")
89
+ return highlighted.seriesId === seriesId ? 1 : 0.28;
90
+ return highlighted.seriesId === seriesId && highlighted.dataIndex === dataIndex ? 1 : 0.28;
91
+ }
92
+ export const DEFAULT_CHART_MARGIN = {
93
+ top: 16,
94
+ right: 16,
95
+ bottom: 36,
96
+ left: 44,
97
+ };
98
+ export function chartColor(index, colors) {
99
+ const palette = colors?.length ? colors : CHART_COLOR_VARS;
100
+ return palette[index % palette.length];
101
+ }
102
+ export function clamp(value, min, max) {
103
+ return Math.min(max, Math.max(min, value));
104
+ }
105
+ export function toNumber(value) {
106
+ if (value instanceof Date)
107
+ return value.getTime();
108
+ if (typeof value === "number")
109
+ return value;
110
+ if (typeof value === "string") {
111
+ const n = Number(value);
112
+ return Number.isFinite(n) ? n : 0;
113
+ }
114
+ return 0;
115
+ }
116
+ export function formatChartNumber(value, locale) {
117
+ if (!Number.isFinite(value))
118
+ return "—";
119
+ if (locale) {
120
+ return new Intl.NumberFormat(locale, {
121
+ notation: Math.abs(value) >= 1000 ? "compact" : "standard",
122
+ maximumFractionDigits: 2,
123
+ }).format(value);
124
+ }
125
+ const abs = Math.abs(value);
126
+ if (abs >= 1_000_000_000)
127
+ return `${trimFloat(value / 1_000_000_000)}B`;
128
+ if (abs >= 1_000_000)
129
+ return `${trimFloat(value / 1_000_000)}M`;
130
+ if (abs >= 1_000)
131
+ return `${trimFloat(value / 1_000)}k`;
132
+ if (Number.isInteger(value))
133
+ return String(value);
134
+ return trimFloat(value);
135
+ }
136
+ function trimFloat(value) {
137
+ return value.toFixed(2).replace(/\.?0+$/, "");
138
+ }
139
+ export function formatAxisValue(value) {
140
+ if (value instanceof Date) {
141
+ return value.toLocaleDateString(undefined, { month: "short", day: "numeric" });
142
+ }
143
+ if (typeof value === "number")
144
+ return formatChartNumber(value);
145
+ return String(value);
146
+ }
147
+ export function extent(values, pad = 0) {
148
+ const finite = values.filter((v) => Number.isFinite(v));
149
+ if (!finite.length)
150
+ return [0, 1];
151
+ let min = Math.min(...finite);
152
+ let max = Math.max(...finite);
153
+ if (min === max) {
154
+ const delta = Math.abs(min) || 1;
155
+ min -= delta;
156
+ max += delta;
157
+ }
158
+ if (pad > 0) {
159
+ const span = max - min;
160
+ min -= span * pad;
161
+ max += span * pad;
162
+ }
163
+ return [min, max];
164
+ }
165
+ export function niceTicks(min, max, count = 5) {
166
+ if (!Number.isFinite(min) || !Number.isFinite(max))
167
+ return [0, 1];
168
+ if (min === max) {
169
+ const pad = Math.abs(min) || 1;
170
+ min -= pad;
171
+ max += pad;
172
+ }
173
+ const span = max - min;
174
+ const step0 = span / Math.max(1, count - 1);
175
+ const mag = 10 ** Math.floor(Math.log10(Math.abs(step0) || 1));
176
+ const residual = step0 / mag;
177
+ const step = residual >= 7.5 ? 10 * mag : residual >= 3.5 ? 5 * mag : residual >= 1.5 ? 2 * mag : mag;
178
+ const start = Math.floor(min / step) * step;
179
+ const end = Math.ceil(max / step) * step;
180
+ const ticks = [];
181
+ for (let value = start; value <= end + step / 2; value += step) {
182
+ ticks.push(Number(value.toPrecision(12)));
183
+ }
184
+ return ticks;
185
+ }
186
+ export function linearScale(domain, range) {
187
+ const [d0, d1] = domain;
188
+ const [r0, r1] = range;
189
+ const span = d1 - d0 || 1;
190
+ const fn = (value) => r0 + ((value - d0) / span) * (r1 - r0);
191
+ fn.invert = (pixel) => d0 + ((pixel - r0) / (r1 - r0 || 1)) * span;
192
+ return fn;
193
+ }
194
+ export function bandScale(domain, range, paddingInner = 0.24) {
195
+ const labels = domain.length ? domain : [""];
196
+ const [r0, r1] = range;
197
+ const step = (r1 - r0) / labels.length;
198
+ const bandwidth = step * (1 - paddingInner);
199
+ const offset = (step - bandwidth) / 2;
200
+ const index = new Map(labels.map((label, i) => [label, i]));
201
+ const fn = (value) => r0 + (index.get(value) ?? 0) * step + offset;
202
+ return Object.assign(fn, { bandwidth, step });
203
+ }
204
+ export function pointScale(domain, range) {
205
+ const labels = domain.length ? domain : [""];
206
+ const [r0, r1] = range;
207
+ if (labels.length <= 1) {
208
+ const fn = () => (r0 + r1) / 2;
209
+ return Object.assign(fn, { step: 0 });
210
+ }
211
+ const step = (r1 - r0) / (labels.length - 1);
212
+ const index = new Map(labels.map((label, i) => [label, i]));
213
+ const fn = (value) => r0 + (index.get(value) ?? 0) * step;
214
+ return Object.assign(fn, { step });
215
+ }
216
+ export function inferScaleType(data) {
217
+ if (!data?.length)
218
+ return "linear";
219
+ if (data[0] instanceof Date)
220
+ return "time";
221
+ if (typeof data[0] === "number")
222
+ return "linear";
223
+ return "band";
224
+ }
225
+ /** Stack series by index, keeping positive and negative totals independent. */
226
+ export function stackSeries(seriesData) {
227
+ const count = seriesData[0]?.length ?? 0;
228
+ const result = seriesData.map(() => Array.from({ length: count }, () => ({ start: 0, end: 0 })));
229
+ for (let i = 0; i < count; i++) {
230
+ let positive = 0;
231
+ let negative = 0;
232
+ for (let s = 0; s < seriesData.length; s++) {
233
+ const value = seriesData[s]?.[i] ?? 0;
234
+ if (value >= 0) {
235
+ result[s][i] = { start: positive, end: positive + value };
236
+ positive += value;
237
+ }
238
+ else {
239
+ result[s][i] = { start: negative, end: negative + value };
240
+ negative += value;
241
+ }
242
+ }
243
+ }
244
+ return result;
245
+ }
246
+ export function polarPoint(cx, cy, radius, angleDeg) {
247
+ const rad = ((angleDeg - 90) * Math.PI) / 180;
248
+ return { x: cx + radius * Math.cos(rad), y: cy + radius * Math.sin(rad) };
249
+ }
250
+ export function arcPath(cx, cy, innerRadius, outerRadius, startAngle, endAngle) {
251
+ const sweep = endAngle - startAngle;
252
+ if (sweep <= 0 || outerRadius <= 0)
253
+ return "";
254
+ if (sweep >= 359.999) {
255
+ const mid = startAngle + 180;
256
+ return `${arcPath(cx, cy, innerRadius, outerRadius, startAngle, mid)} ${arcPath(cx, cy, innerRadius, outerRadius, mid, startAngle + 360)}`;
257
+ }
258
+ const large = sweep > 180 ? 1 : 0;
259
+ const outer0 = polarPoint(cx, cy, outerRadius, startAngle);
260
+ const outer1 = polarPoint(cx, cy, outerRadius, endAngle);
261
+ if (innerRadius <= 0) {
262
+ return `M${cx} ${cy} L${outer0.x} ${outer0.y} A${outerRadius} ${outerRadius} 0 ${large} 1 ${outer1.x} ${outer1.y} Z`;
263
+ }
264
+ const inner0 = polarPoint(cx, cy, innerRadius, startAngle);
265
+ const inner1 = polarPoint(cx, cy, innerRadius, endAngle);
266
+ return `M${outer0.x} ${outer0.y} A${outerRadius} ${outerRadius} 0 ${large} 1 ${outer1.x} ${outer1.y} L${inner1.x} ${inner1.y} A${innerRadius} ${innerRadius} 0 ${large} 0 ${inner0.x} ${inner0.y} Z`;
267
+ }
268
+ export function pieSlices(values, options) {
269
+ const { cx, cy, innerRadius, outerRadius, startAngle = 0, endAngle = 360, paddingAngle = 0, } = options;
270
+ const total = values.reduce((sum, value) => sum + Math.max(0, value), 0);
271
+ const span = endAngle - startAngle;
272
+ const pad = values.length > 1 ? paddingAngle : 0;
273
+ const usable = span - pad * values.length;
274
+ let cursor = startAngle;
275
+ return values.map((value, index) => {
276
+ const portion = total > 0 ? Math.max(0, value) / total : 1 / Math.max(values.length, 1);
277
+ const sliceSpan = usable * portion;
278
+ const a0 = cursor + pad / 2;
279
+ const a1 = a0 + sliceSpan;
280
+ cursor = a1 + pad / 2;
281
+ const mid = (a0 + a1) / 2;
282
+ const labelR = innerRadius + (outerRadius - innerRadius) * 0.62;
283
+ const label = polarPoint(cx, cy, labelR, mid);
284
+ return {
285
+ index,
286
+ value,
287
+ percent: portion,
288
+ startAngle: a0,
289
+ endAngle: a1,
290
+ midAngle: mid,
291
+ path: arcPath(cx, cy, innerRadius, outerRadius, a0, a1),
292
+ labelX: label.x,
293
+ labelY: label.y,
294
+ };
295
+ });
296
+ }
297
+ export function linePath(points, curve = "linear") {
298
+ const pts = points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
299
+ if (!pts.length)
300
+ return "";
301
+ if (pts.length === 1)
302
+ return `M${pts[0].x} ${pts[0].y}`;
303
+ if (curve === "step") {
304
+ const parts = [`M${pts[0].x} ${pts[0].y}`];
305
+ for (let i = 1; i < pts.length; i++) {
306
+ const mid = (pts[i - 1].x + pts[i].x) / 2;
307
+ parts.push(`L${mid} ${pts[i - 1].y} L${mid} ${pts[i].y} L${pts[i].x} ${pts[i].y}`);
308
+ }
309
+ return parts.join(" ");
310
+ }
311
+ if (curve === "monotone")
312
+ return monotonePath(pts);
313
+ return `M${pts.map((p) => `${p.x} ${p.y}`).join(" L")}`;
314
+ }
315
+ function monotonePath(points) {
316
+ const n = points.length;
317
+ const dx = [];
318
+ const m = [];
319
+ for (let i = 0; i < n - 1; i++) {
320
+ const deltaX = points[i + 1].x - points[i].x;
321
+ dx[i] = deltaX;
322
+ m[i] = deltaX === 0 ? 0 : (points[i + 1].y - points[i].y) / deltaX;
323
+ }
324
+ const t = [m[0] ?? 0];
325
+ for (let i = 0; i < n - 2; i++) {
326
+ const m0 = m[i] ?? 0;
327
+ const m1 = m[i + 1] ?? 0;
328
+ if (m0 * m1 <= 0) {
329
+ t[i + 1] = 0;
330
+ }
331
+ else {
332
+ const dx0 = dx[i] ?? 0;
333
+ const dx1 = dx[i + 1] ?? 0;
334
+ const next = (3 * (dx0 + dx1)) / ((2 * dx1 + dx0) / m0 + (dx1 + 2 * dx0) / m1);
335
+ t[i + 1] = Number.isFinite(next) ? next : 0;
336
+ }
337
+ }
338
+ t.push(m[m.length - 1] ?? 0);
339
+ const parts = [`M${points[0].x} ${points[0].y}`];
340
+ for (let i = 0; i < n - 1; i++) {
341
+ const p0 = points[i];
342
+ const p1 = points[i + 1];
343
+ const delta = dx[i] ?? 0;
344
+ const cpx1 = p0.x + delta / 3;
345
+ const cpy1 = p0.y + ((t[i] ?? 0) * delta) / 3;
346
+ const cpx2 = p1.x - delta / 3;
347
+ const cpy2 = p1.y - ((t[i + 1] ?? 0) * delta) / 3;
348
+ parts.push(`C${cpx1} ${cpy1} ${cpx2} ${cpy2} ${p1.x} ${p1.y}`);
349
+ }
350
+ return parts.join(" ");
351
+ }
352
+ export function areaPath(points, baselineY, curve = "linear") {
353
+ const pts = points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
354
+ if (!pts.length)
355
+ return "";
356
+ const line = linePath(pts, curve);
357
+ const first = pts[0];
358
+ const last = pts[pts.length - 1];
359
+ return `${line} L${last.x} ${baselineY} L${first.x} ${baselineY} Z`;
360
+ }
361
+ export function rangeAreaPath(high, low, curve = "linear") {
362
+ if (!high.length || high.length !== low.length)
363
+ return "";
364
+ const top = linePath(high, curve);
365
+ const reversed = [...low].reverse();
366
+ const bottom = linePath(reversed, curve).replace(/^M/, "L");
367
+ return `${top} ${bottom} Z`;
368
+ }
369
+ export function roundedRectPath(x, y, width, height, radius) {
370
+ const w = Math.abs(width);
371
+ const h = Math.abs(height);
372
+ const left = width < 0 ? x + width : x;
373
+ const top = height < 0 ? y + height : y;
374
+ const r = clamp(radius, 0, Math.min(w, h) / 2);
375
+ if (r <= 0)
376
+ return `M${left} ${top} h${w} v${h} h${-w} Z`;
377
+ return `M${left + r} ${top} h${w - 2 * r} a${r} ${r} 0 0 1 ${r} ${r} v${h - 2 * r} a${r} ${r} 0 0 1 ${-r} ${r} h${-(w - 2 * r)} a${r} ${r} 0 0 1 ${-r} ${-r} v${-(h - 2 * r)} a${r} ${r} 0 0 1 ${r} ${-r} Z`;
378
+ }
379
+ export function heatColor(t) {
380
+ const pct = Math.round(clamp(t, 0, 1) * 100);
381
+ return `color-mix(in srgb, var(--chart-1) ${pct}%, var(--muted) ${100 - pct}%)`;
382
+ }
383
+ export function boxPlotStats(values) {
384
+ const xs = values.filter((v) => Number.isFinite(v)).sort((a, b) => a - b);
385
+ if (!xs.length)
386
+ return { min: 0, q1: 0, median: 0, q3: 0, max: 0, outliers: [] };
387
+ const quantile = (p) => {
388
+ const i = (xs.length - 1) * p;
389
+ const lo = Math.floor(i);
390
+ const hi = Math.ceil(i);
391
+ return xs[lo] + (xs[hi] - xs[lo]) * (i - lo);
392
+ };
393
+ const q1 = quantile(0.25);
394
+ const median = quantile(0.5);
395
+ const q3 = quantile(0.75);
396
+ const iqr = q3 - q1;
397
+ const loFence = q1 - 1.5 * iqr;
398
+ const hiFence = q3 + 1.5 * iqr;
399
+ const inliers = xs.filter((v) => v >= loFence && v <= hiFence);
400
+ return {
401
+ min: inliers[0] ?? xs[0],
402
+ q1,
403
+ median,
404
+ q3,
405
+ max: inliers[inliers.length - 1] ?? xs[xs.length - 1],
406
+ outliers: xs.filter((v) => v < loFence || v > hiFence),
407
+ };
408
+ }
409
+ export function funnelLayout(items, width, height, gap = 6, maxValue) {
410
+ if (!items.length)
411
+ return [];
412
+ const max = Math.max(maxValue ?? 0, ...items.map((item) => item.value), 1);
413
+ const row = (height - gap * (items.length - 1)) / items.length;
414
+ return items.map((item, index) => {
415
+ const next = items[index + 1]?.value ?? item.value * 0.45;
416
+ const topW = (item.value / max) * width;
417
+ const botW = (next / max) * width;
418
+ const y = index * (row + gap);
419
+ return {
420
+ label: item.label,
421
+ value: item.value,
422
+ percent: item.value / max,
423
+ topLeft: (width - topW) / 2,
424
+ topRight: (width + topW) / 2,
425
+ bottomLeft: (width - botW) / 2,
426
+ bottomRight: (width + botW) / 2,
427
+ y,
428
+ height: row,
429
+ color: item.color,
430
+ };
431
+ });
432
+ }
433
+ export function pyramidLayout(items, width, height) {
434
+ if (!items.length)
435
+ return [];
436
+ const max = Math.max(...items.map((item) => item.value), 1);
437
+ const row = height / items.length;
438
+ return items.map((item, index) => {
439
+ const t = index / items.length;
440
+ const t2 = (index + 1) / items.length;
441
+ const topW = width * (1 - t) * (item.value / max) * 0.35 + width * (1 - t) * 0.65;
442
+ const botW = width * (1 - t2) * 0.85 + width * 0.08;
443
+ const y = index * row;
444
+ return {
445
+ label: item.label,
446
+ value: item.value,
447
+ percent: item.value / max,
448
+ topLeft: (width - topW) / 2,
449
+ topRight: (width + topW) / 2,
450
+ bottomLeft: (width - botW) / 2,
451
+ bottomRight: (width + botW) / 2,
452
+ y,
453
+ height: row,
454
+ color: item.color,
455
+ };
456
+ });
457
+ }
458
+ function nodeValue(node) {
459
+ if (node.children?.length)
460
+ return node.children.reduce((sum, child) => sum + nodeValue(child), 0);
461
+ return Math.max(0, node.value ?? 0);
462
+ }
463
+ export function squarify(nodes, x, y, width, height, depth = 0) {
464
+ const items = nodes
465
+ .map((node, index) => ({ node, value: nodeValue(node), index }))
466
+ .filter((item) => item.value > 0)
467
+ .sort((a, b) => b.value - a.value);
468
+ const rects = [];
469
+ layoutRow(items, x, y, width, height, depth, rects);
470
+ return rects;
471
+ }
472
+ function layoutRow(items, x, y, width, height, depth, out) {
473
+ if (!items.length || width <= 0 || height <= 0)
474
+ return;
475
+ const total = items.reduce((sum, item) => sum + item.value, 0) || 1;
476
+ const horizontal = width >= height;
477
+ const side = horizontal ? height : width;
478
+ let row = [];
479
+ let rowValue = 0;
480
+ let bestWorst = Infinity;
481
+ const worst = (candidate, value) => {
482
+ const rowArea = (value / total) * width * height;
483
+ const rowSide = side;
484
+ let maxRatio = 0;
485
+ for (const item of candidate) {
486
+ const itemSide = rowArea / rowSide;
487
+ const other = (item.value / value) * rowSide;
488
+ const ratio = Math.max(itemSide / other, other / itemSide);
489
+ maxRatio = Math.max(maxRatio, ratio);
490
+ }
491
+ return maxRatio;
492
+ };
493
+ for (const item of items) {
494
+ const next = [...row, item];
495
+ const nextValue = rowValue + item.value;
496
+ const nextWorst = worst(next, nextValue);
497
+ if (row.length && nextWorst > bestWorst)
498
+ break;
499
+ row = next;
500
+ rowValue = nextValue;
501
+ bestWorst = nextWorst;
502
+ }
503
+ const rowArea = (rowValue / total) * width * height;
504
+ const thickness = horizontal ? rowArea / height : rowArea / width;
505
+ let cursor = horizontal ? y : x;
506
+ for (const item of row) {
507
+ const frac = item.value / rowValue;
508
+ const span = (horizontal ? height : width) * frac;
509
+ const rx = horizontal ? x : cursor;
510
+ const ry = horizontal ? cursor : y;
511
+ const rw = horizontal ? thickness : span;
512
+ const rh = horizontal ? span : thickness;
513
+ if (item.node.children?.length) {
514
+ out.push(...squarify(item.node.children, rx, ry, rw, rh, depth + 1));
515
+ }
516
+ else {
517
+ out.push({
518
+ id: item.node.id ?? item.node.label,
519
+ label: item.node.label,
520
+ value: item.value,
521
+ x: rx,
522
+ y: ry,
523
+ w: rw,
524
+ h: rh,
525
+ color: item.node.color,
526
+ depth,
527
+ });
528
+ }
529
+ cursor += span;
530
+ }
531
+ const rest = items.slice(row.length);
532
+ if (!rest.length)
533
+ return;
534
+ if (horizontal) {
535
+ layoutRow(rest, x + thickness, y, width - thickness, height, depth, out);
536
+ }
537
+ else {
538
+ layoutRow(rest, x, y + thickness, width, height - thickness, depth, out);
539
+ }
540
+ }
541
+ export function sunburstLayout(nodes, cx, cy, innerRadius, outerRadius) {
542
+ const maxDepth = (list, depth = 1) => list.reduce((max, node) => Math.max(max, node.children?.length ? maxDepth(node.children, depth + 1) : depth), 1);
543
+ const depthCount = Math.max(1, maxDepth(nodes));
544
+ const ring = (outerRadius - innerRadius) / depthCount;
545
+ const slices = [];
546
+ const walk = (list, start, end, depth, parentId) => {
547
+ const total = list.reduce((sum, node) => sum + nodeValue(node), 0) || 1;
548
+ let cursor = start;
549
+ for (const node of list) {
550
+ const span = ((end - start) * nodeValue(node)) / total;
551
+ const a0 = cursor;
552
+ const a1 = cursor + span;
553
+ const r0 = innerRadius + (depth - 1) * ring;
554
+ const r1 = innerRadius + depth * ring;
555
+ const id = node.id ?? node.label;
556
+ slices.push({
557
+ id,
558
+ label: node.label,
559
+ value: nodeValue(node),
560
+ x: 0,
561
+ y: 0,
562
+ w: 0,
563
+ h: 0,
564
+ color: node.color,
565
+ depth,
566
+ startAngle: a0,
567
+ endAngle: a1,
568
+ innerRadius: r0,
569
+ outerRadius: r1,
570
+ path: arcPath(cx, cy, r0, r1, a0, a1),
571
+ parentId,
572
+ });
573
+ if (node.children?.length)
574
+ walk(node.children, a0, a1, depth + 1, id);
575
+ cursor = a1;
576
+ }
577
+ };
578
+ walk(nodes, 0, 360, 1);
579
+ return slices;
580
+ }
581
+ export function sankeyLayout(nodes, links, width, height, nodeWidth = 16) {
582
+ const ids = nodes.map((node) => node.id);
583
+ const incoming = new Map();
584
+ const outgoing = new Map();
585
+ for (const id of ids) {
586
+ incoming.set(id, 0);
587
+ outgoing.set(id, 0);
588
+ }
589
+ for (const link of links) {
590
+ outgoing.set(link.source, (outgoing.get(link.source) ?? 0) + link.value);
591
+ incoming.set(link.target, (incoming.get(link.target) ?? 0) + link.value);
592
+ }
593
+ const column = new Map();
594
+ const queue = ids.filter((id) => (incoming.get(id) ?? 0) === 0);
595
+ for (const id of queue)
596
+ column.set(id, 0);
597
+ const remaining = new Set(ids);
598
+ for (const id of queue)
599
+ remaining.delete(id);
600
+ while (queue.length) {
601
+ const id = queue.shift();
602
+ const col = column.get(id) ?? 0;
603
+ for (const link of links) {
604
+ if (link.source !== id)
605
+ continue;
606
+ const next = Math.max(column.get(link.target) ?? 0, col + 1);
607
+ column.set(link.target, next);
608
+ if (remaining.has(link.target)) {
609
+ remaining.delete(link.target);
610
+ queue.push(link.target);
611
+ }
612
+ }
613
+ }
614
+ for (const id of remaining)
615
+ column.set(id, Math.max(...column.values(), 0) + 1);
616
+ const columns = new Map();
617
+ for (const id of ids) {
618
+ const col = column.get(id) ?? 0;
619
+ const list = columns.get(col) ?? [];
620
+ list.push(id);
621
+ columns.set(col, list);
622
+ }
623
+ const colCount = Math.max(...columns.keys(), 0) + 1;
624
+ const xStep = colCount > 1 ? (width - nodeWidth) / (colCount - 1) : 0;
625
+ const nodeValueMap = new Map();
626
+ for (const id of ids) {
627
+ nodeValueMap.set(id, Math.max(incoming.get(id) ?? 0, outgoing.get(id) ?? 0, 1));
628
+ }
629
+ const placed = new Map();
630
+ for (const [col, list] of columns) {
631
+ const total = list.reduce((sum, id) => sum + (nodeValueMap.get(id) ?? 1), 0);
632
+ const gap = 10;
633
+ const usable = Math.max(height - gap * (list.length - 1), 1);
634
+ let y = 0;
635
+ for (const id of list) {
636
+ const value = nodeValueMap.get(id) ?? 1;
637
+ const h = Math.max(8, (value / total) * usable);
638
+ placed.set(id, { x: col * xStep, y, height: h, value });
639
+ y += h + gap;
640
+ }
641
+ }
642
+ const sourceOffset = new Map();
643
+ const targetOffset = new Map();
644
+ const laidLinks = links.map((link) => {
645
+ const source = placed.get(link.source);
646
+ const target = placed.get(link.target);
647
+ if (!source || !target) {
648
+ return { source: link.source, target: link.target, value: link.value, path: "", width: 0 };
649
+ }
650
+ const srcH = Math.max(2, (link.value / source.value) * source.height);
651
+ const tgtH = Math.max(2, (link.value / target.value) * target.height);
652
+ const sy = source.y + (sourceOffset.get(link.source) ?? 0) + srcH / 2;
653
+ const ty = target.y + (targetOffset.get(link.target) ?? 0) + tgtH / 2;
654
+ sourceOffset.set(link.source, (sourceOffset.get(link.source) ?? 0) + srcH);
655
+ targetOffset.set(link.target, (targetOffset.get(link.target) ?? 0) + tgtH);
656
+ const x0 = source.x + nodeWidth;
657
+ const x1 = target.x;
658
+ const mid = (x0 + x1) / 2;
659
+ const half = Math.max(srcH, tgtH) / 2;
660
+ const path = `M${x0} ${sy - half} C${mid} ${sy - half} ${mid} ${ty - half} ${x1} ${ty - half} L${x1} ${ty + half} C${mid} ${ty + half} ${mid} ${sy + half} ${x0} ${sy + half} Z`;
661
+ return { source: link.source, target: link.target, value: link.value, path, width: half * 2 };
662
+ });
663
+ return {
664
+ nodes: nodes.map((node) => {
665
+ const place = placed.get(node.id);
666
+ return {
667
+ id: node.id,
668
+ label: node.label ?? node.id,
669
+ color: node.color,
670
+ x: place?.x ?? 0,
671
+ y: place?.y ?? 0,
672
+ width: nodeWidth,
673
+ height: place?.height ?? 0,
674
+ value: place?.value ?? 0,
675
+ };
676
+ }),
677
+ links: laidLinks,
678
+ };
679
+ }
680
+ export function chordLayout(labels, matrix, cx, cy, innerRadius, outerRadius, padAngle = 4) {
681
+ const n = labels.length;
682
+ const totals = labels.map((_, i) => matrix[i]?.reduce((sum, value) => sum + value, 0) ?? 0);
683
+ const grand = totals.reduce((sum, value) => sum + value, 0) || 1;
684
+ const groups = [];
685
+ const starts = [];
686
+ let cursor = 0;
687
+ const usable = 360 - padAngle * n;
688
+ for (let i = 0; i < n; i++) {
689
+ const span = (totals[i] / grand) * usable;
690
+ const a0 = cursor + padAngle / 2;
691
+ const a1 = a0 + span;
692
+ starts[i] = a0;
693
+ groups.push({
694
+ index: i,
695
+ label: labels[i],
696
+ startAngle: a0,
697
+ endAngle: a1,
698
+ path: arcPath(cx, cy, innerRadius, outerRadius, a0, a1),
699
+ });
700
+ cursor = a1 + padAngle / 2;
701
+ }
702
+ const sourceCursor = [...starts];
703
+ const targetCursor = [...starts];
704
+ const ribbons = [];
705
+ for (let i = 0; i < n; i++) {
706
+ for (let j = 0; j < n; j++) {
707
+ const value = matrix[i]?.[j] ?? 0;
708
+ if (value <= 0)
709
+ continue;
710
+ const srcSpan = (value / grand) * usable;
711
+ const tgtSpan = (value / grand) * usable;
712
+ const a0 = sourceCursor[i];
713
+ const a1 = a0 + srcSpan;
714
+ const b0 = targetCursor[j];
715
+ const b1 = b0 + tgtSpan;
716
+ sourceCursor[i] = a1;
717
+ if (i !== j)
718
+ targetCursor[j] = b1;
719
+ const p0 = polarPoint(cx, cy, innerRadius, a0);
720
+ const p1 = polarPoint(cx, cy, innerRadius, a1);
721
+ const q0 = polarPoint(cx, cy, innerRadius, b0);
722
+ const q1 = polarPoint(cx, cy, innerRadius, b1);
723
+ ribbons.push({
724
+ source: i,
725
+ target: j,
726
+ value,
727
+ path: `M${p0.x} ${p0.y} Q${cx} ${cy} ${q1.x} ${q1.y} L${q0.x} ${q0.y} Q${cx} ${cy} ${p1.x} ${p1.y} Z`,
728
+ });
729
+ }
730
+ }
731
+ return { groups, ribbons };
732
+ }
733
+ export function waterfallBars(data) {
734
+ let running = 0;
735
+ return data.map((value, index) => {
736
+ if (value == null) {
737
+ return { index, start: 0, end: running, value: running, total: true };
738
+ }
739
+ const start = running;
740
+ const end = running + value;
741
+ running = end;
742
+ return { index, start, end, value, total: false };
743
+ });
744
+ }