@krizaka/charts 0.0.0-stage → 0.1.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 (273) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +47 -2
  3. package/dist/bar.d.ts +17 -0
  4. package/dist/bar.js +16 -0
  5. package/dist/bar.js.map +1 -0
  6. package/dist/candlestick-IG626XL4.js +13 -0
  7. package/dist/candlestick-IG626XL4.js.map +1 -0
  8. package/dist/candlestick.d.ts +42 -0
  9. package/dist/candlestick.js +13 -0
  10. package/dist/candlestick.js.map +1 -0
  11. package/dist/canvas-4MSYDVFO.js +14 -0
  12. package/dist/canvas-4MSYDVFO.js.map +1 -0
  13. package/dist/cartesian-BRhNQ3ib.d.ts +34 -0
  14. package/dist/chart.d.ts +58 -0
  15. package/dist/chart.js +11 -0
  16. package/dist/chart.js.map +1 -0
  17. package/dist/chunk-3CJHBJXA.js +186 -0
  18. package/dist/chunk-3CJHBJXA.js.map +1 -0
  19. package/dist/chunk-6FINSUOS.js +49 -0
  20. package/dist/chunk-6FINSUOS.js.map +1 -0
  21. package/dist/chunk-7CSAGFBW.js +166 -0
  22. package/dist/chunk-7CSAGFBW.js.map +1 -0
  23. package/dist/chunk-7TIFCK5E.js +103 -0
  24. package/dist/chunk-7TIFCK5E.js.map +1 -0
  25. package/dist/chunk-AHQXJSAQ.js +38 -0
  26. package/dist/chunk-AHQXJSAQ.js.map +1 -0
  27. package/dist/chunk-ANUCSRUN.js +43 -0
  28. package/dist/chunk-ANUCSRUN.js.map +1 -0
  29. package/dist/chunk-BISL7DYW.js +66 -0
  30. package/dist/chunk-BISL7DYW.js.map +1 -0
  31. package/dist/chunk-BZ5CQFPH.js +19 -0
  32. package/dist/chunk-BZ5CQFPH.js.map +1 -0
  33. package/dist/chunk-DWJPPEMC.js +455 -0
  34. package/dist/chunk-DWJPPEMC.js.map +1 -0
  35. package/dist/chunk-DZB6HEVO.js +73 -0
  36. package/dist/chunk-DZB6HEVO.js.map +1 -0
  37. package/dist/chunk-EK3Z647J.js +133 -0
  38. package/dist/chunk-EK3Z647J.js.map +1 -0
  39. package/dist/chunk-I5QUZVU2.js +234 -0
  40. package/dist/chunk-I5QUZVU2.js.map +1 -0
  41. package/dist/chunk-OB5EFDDH.js +118 -0
  42. package/dist/chunk-OB5EFDDH.js.map +1 -0
  43. package/dist/chunk-OER6ETT4.js +155 -0
  44. package/dist/chunk-OER6ETT4.js.map +1 -0
  45. package/dist/chunk-PTSB3H5Q.js +205 -0
  46. package/dist/chunk-PTSB3H5Q.js.map +1 -0
  47. package/dist/chunk-TPJDOB7R.js +15 -0
  48. package/dist/chunk-TPJDOB7R.js.map +1 -0
  49. package/dist/chunk-UNK4MJVG.js +378 -0
  50. package/dist/chunk-UNK4MJVG.js.map +1 -0
  51. package/dist/chunk-VKO2OYYM.js +88 -0
  52. package/dist/chunk-VKO2OYYM.js.map +1 -0
  53. package/dist/chunk-VKTZD7UT.js +107 -0
  54. package/dist/chunk-VKTZD7UT.js.map +1 -0
  55. package/dist/chunk-W5FX4TU3.js +102 -0
  56. package/dist/chunk-W5FX4TU3.js.map +1 -0
  57. package/dist/chunk-WBH2QU77.js +178 -0
  58. package/dist/chunk-WBH2QU77.js.map +1 -0
  59. package/dist/chunk-WIRCIOLH.js +145 -0
  60. package/dist/chunk-WIRCIOLH.js.map +1 -0
  61. package/dist/chunk-XAJ3KCC4.js +116 -0
  62. package/dist/chunk-XAJ3KCC4.js.map +1 -0
  63. package/dist/chunk-XC6BQ3RN.js +92 -0
  64. package/dist/chunk-XC6BQ3RN.js.map +1 -0
  65. package/dist/chunk-XLLYNNC6.js +89 -0
  66. package/dist/chunk-XLLYNNC6.js.map +1 -0
  67. package/dist/chunk-XR4ZJDOV.js +15 -0
  68. package/dist/chunk-XR4ZJDOV.js.map +1 -0
  69. package/dist/context-BgYUymEO.d.ts +163 -0
  70. package/dist/frame-I7_xYGy5.d.ts +45 -0
  71. package/dist/full-S2MBPEEP.js +11 -0
  72. package/dist/full-S2MBPEEP.js.map +1 -0
  73. package/dist/funnel-QU5ZPC52.js +13 -0
  74. package/dist/funnel-QU5ZPC52.js.map +1 -0
  75. package/dist/funnel.d.ts +26 -0
  76. package/dist/funnel.js +13 -0
  77. package/dist/funnel.js.map +1 -0
  78. package/dist/gauge.d.ts +38 -0
  79. package/dist/gauge.js +12 -0
  80. package/dist/gauge.js.map +1 -0
  81. package/dist/geo.d.ts +37 -0
  82. package/dist/geo.js +13 -0
  83. package/dist/geo.js.map +1 -0
  84. package/dist/globe.d.ts +36 -0
  85. package/dist/globe.js +12 -0
  86. package/dist/globe.js.map +1 -0
  87. package/dist/heatmap-JWQOKUGM.js +13 -0
  88. package/dist/heatmap-JWQOKUGM.js.map +1 -0
  89. package/dist/heatmap.d.ts +47 -0
  90. package/dist/heatmap.js +15 -0
  91. package/dist/heatmap.js.map +1 -0
  92. package/dist/host-_F8RgiIz.d.ts +15 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.js +101 -0
  95. package/dist/index.js.map +1 -0
  96. package/dist/line.d.ts +25 -0
  97. package/dist/line.js +18 -0
  98. package/dist/line.js.map +1 -0
  99. package/dist/native.d.ts +300 -0
  100. package/dist/native.js +875 -0
  101. package/dist/native.js.map +1 -0
  102. package/dist/pie.d.ts +37 -0
  103. package/dist/pie.js +16 -0
  104. package/dist/pie.js.map +1 -0
  105. package/dist/provider.d.ts +73 -0
  106. package/dist/provider.js +24 -0
  107. package/dist/provider.js.map +1 -0
  108. package/dist/radar-75GRJ2IK.js +13 -0
  109. package/dist/radar-75GRJ2IK.js.map +1 -0
  110. package/dist/radar.d.ts +22 -0
  111. package/dist/radar.js +13 -0
  112. package/dist/radar.js.map +1 -0
  113. package/dist/sankey-XQTX2EG6.js +13 -0
  114. package/dist/sankey-XQTX2EG6.js.map +1 -0
  115. package/dist/sankey.d.ts +28 -0
  116. package/dist/sankey.js +13 -0
  117. package/dist/sankey.js.map +1 -0
  118. package/dist/scatter-XSNG4OKD.js +13 -0
  119. package/dist/scatter-XSNG4OKD.js.map +1 -0
  120. package/dist/scatter.d.ts +41 -0
  121. package/dist/scatter.js +15 -0
  122. package/dist/scatter.js.map +1 -0
  123. package/dist/sparkline.d.ts +26 -0
  124. package/dist/sparkline.js +15 -0
  125. package/dist/sparkline.js.map +1 -0
  126. package/dist/svg-BJUHYX3B.js +14 -0
  127. package/dist/svg-BJUHYX3B.js.map +1 -0
  128. package/dist/treemap-OMGGGB5U.js +13 -0
  129. package/dist/treemap-OMGGGB5U.js.map +1 -0
  130. package/dist/treemap.d.ts +24 -0
  131. package/dist/treemap.js +14 -0
  132. package/dist/treemap.js.map +1 -0
  133. package/dist/world-110m-DVFWXS42.js +18 -0
  134. package/dist/world-110m-DVFWXS42.js.map +1 -0
  135. package/package.json +518 -3
  136. package/registry/area-chart.json +784 -0
  137. package/registry/bar-chart.json +783 -0
  138. package/registry/calendar-heatmap.json +459 -0
  139. package/registry/candlestick.json +552 -0
  140. package/registry/chart.json +440 -0
  141. package/registry/charts-provider.json +190 -0
  142. package/registry/examples/area-chart/credits-usage.d.ts +4 -0
  143. package/registry/examples/area-chart/credits-usage.js +38 -0
  144. package/registry/examples/area-chart/credits-usage.tsx +32 -0
  145. package/registry/examples/area-chart/native/credits-usage.d.ts +4 -0
  146. package/registry/examples/area-chart/native/credits-usage.dark.png +0 -0
  147. package/registry/examples/area-chart/native/credits-usage.js +33 -0
  148. package/registry/examples/area-chart/native/credits-usage.light.png +0 -0
  149. package/registry/examples/area-chart/native/credits-usage.tsx +27 -0
  150. package/registry/examples/area-chart/storage.d.ts +4 -0
  151. package/registry/examples/area-chart/storage.js +27 -0
  152. package/registry/examples/area-chart/storage.tsx +21 -0
  153. package/registry/examples/bar-chart/jobs-by-status.d.ts +4 -0
  154. package/registry/examples/bar-chart/jobs-by-status.js +37 -0
  155. package/registry/examples/bar-chart/jobs-by-status.tsx +31 -0
  156. package/registry/examples/bar-chart/native/jobs-by-status.d.ts +4 -0
  157. package/registry/examples/bar-chart/native/jobs-by-status.dark.png +0 -0
  158. package/registry/examples/bar-chart/native/jobs-by-status.js +32 -0
  159. package/registry/examples/bar-chart/native/jobs-by-status.light.png +0 -0
  160. package/registry/examples/bar-chart/native/jobs-by-status.tsx +26 -0
  161. package/registry/examples/bar-chart/plans-by-quarter.d.ts +4 -0
  162. package/registry/examples/bar-chart/plans-by-quarter.js +31 -0
  163. package/registry/examples/bar-chart/plans-by-quarter.tsx +26 -0
  164. package/registry/examples/bar-chart/top-supporters.d.ts +4 -0
  165. package/registry/examples/bar-chart/top-supporters.js +32 -0
  166. package/registry/examples/bar-chart/top-supporters.tsx +27 -0
  167. package/registry/examples/calendar-heatmap/publishing.d.ts +4 -0
  168. package/registry/examples/calendar-heatmap/publishing.js +28 -0
  169. package/registry/examples/calendar-heatmap/publishing.tsx +23 -0
  170. package/registry/examples/candlestick/auction-bids.d.ts +4 -0
  171. package/registry/examples/candlestick/auction-bids.js +37 -0
  172. package/registry/examples/candlestick/auction-bids.tsx +32 -0
  173. package/registry/examples/chart/polar-uploads.d.ts +4 -0
  174. package/registry/examples/chart/polar-uploads.js +34 -0
  175. package/registry/examples/chart/polar-uploads.tsx +30 -0
  176. package/registry/examples/charts-provider/french-orazaka.d.ts +4 -0
  177. package/registry/examples/charts-provider/french-orazaka.js +26 -0
  178. package/registry/examples/charts-provider/french-orazaka.tsx +22 -0
  179. package/registry/examples/charts-provider/native/french-orazaka.d.ts +4 -0
  180. package/registry/examples/charts-provider/native/french-orazaka.dark.png +0 -0
  181. package/registry/examples/charts-provider/native/french-orazaka.js +17 -0
  182. package/registry/examples/charts-provider/native/french-orazaka.light.png +0 -0
  183. package/registry/examples/charts-provider/native/french-orazaka.tsx +17 -0
  184. package/registry/examples/funnel/signup.d.ts +4 -0
  185. package/registry/examples/funnel/signup.js +30 -0
  186. package/registry/examples/funnel/signup.tsx +24 -0
  187. package/registry/examples/gauge/challenge-goal.d.ts +4 -0
  188. package/registry/examples/gauge/challenge-goal.js +24 -0
  189. package/registry/examples/gauge/challenge-goal.tsx +18 -0
  190. package/registry/examples/gauge/credits-quota.d.ts +4 -0
  191. package/registry/examples/gauge/credits-quota.js +26 -0
  192. package/registry/examples/gauge/credits-quota.tsx +19 -0
  193. package/registry/examples/gauge/native/credits-quota.d.ts +4 -0
  194. package/registry/examples/gauge/native/credits-quota.dark.png +0 -0
  195. package/registry/examples/gauge/native/credits-quota.js +25 -0
  196. package/registry/examples/gauge/native/credits-quota.light.png +0 -0
  197. package/registry/examples/gauge/native/credits-quota.tsx +18 -0
  198. package/registry/examples/geo-map/viewers.d.ts +4 -0
  199. package/registry/examples/geo-map/viewers.js +43 -0
  200. package/registry/examples/geo-map/viewers.tsx +29 -0
  201. package/registry/examples/globe/deployment-regions.d.ts +4 -0
  202. package/registry/examples/globe/deployment-regions.js +36 -0
  203. package/registry/examples/globe/deployment-regions.tsx +30 -0
  204. package/registry/examples/heatmap/upload-activity.d.ts +4 -0
  205. package/registry/examples/heatmap/upload-activity.js +30 -0
  206. package/registry/examples/heatmap/upload-activity.tsx +25 -0
  207. package/registry/examples/line-chart/creator-earnings.d.ts +4 -0
  208. package/registry/examples/line-chart/creator-earnings.js +35 -0
  209. package/registry/examples/line-chart/creator-earnings.tsx +30 -0
  210. package/registry/examples/line-chart/forecast.d.ts +4 -0
  211. package/registry/examples/line-chart/forecast.js +36 -0
  212. package/registry/examples/line-chart/forecast.tsx +30 -0
  213. package/registry/examples/line-chart/live-jobs.d.ts +4 -0
  214. package/registry/examples/line-chart/live-jobs.js +21 -0
  215. package/registry/examples/line-chart/live-jobs.tsx +23 -0
  216. package/registry/examples/line-chart/native/creator-earnings.d.ts +4 -0
  217. package/registry/examples/line-chart/native/creator-earnings.dark.png +0 -0
  218. package/registry/examples/line-chart/native/creator-earnings.js +32 -0
  219. package/registry/examples/line-chart/native/creator-earnings.light.png +0 -0
  220. package/registry/examples/line-chart/native/creator-earnings.tsx +26 -0
  221. package/registry/examples/line-chart/states.d.ts +4 -0
  222. package/registry/examples/line-chart/states.js +16 -0
  223. package/registry/examples/line-chart/states.tsx +13 -0
  224. package/registry/examples/pie-chart/credit-spend.d.ts +4 -0
  225. package/registry/examples/pie-chart/credit-spend.js +30 -0
  226. package/registry/examples/pie-chart/credit-spend.tsx +24 -0
  227. package/registry/examples/pie-chart/native/credit-spend.d.ts +4 -0
  228. package/registry/examples/pie-chart/native/credit-spend.dark.png +0 -0
  229. package/registry/examples/pie-chart/native/credit-spend.js +17 -0
  230. package/registry/examples/pie-chart/native/credit-spend.light.png +0 -0
  231. package/registry/examples/pie-chart/native/credit-spend.tsx +12 -0
  232. package/registry/examples/pie-chart/storage-by-kind.d.ts +4 -0
  233. package/registry/examples/pie-chart/storage-by-kind.js +31 -0
  234. package/registry/examples/pie-chart/storage-by-kind.tsx +26 -0
  235. package/registry/examples/radar-chart/model-comparison.d.ts +4 -0
  236. package/registry/examples/radar-chart/model-comparison.js +33 -0
  237. package/registry/examples/radar-chart/model-comparison.tsx +28 -0
  238. package/registry/examples/sankey/credit-flow.d.ts +4 -0
  239. package/registry/examples/sankey/credit-flow.js +37 -0
  240. package/registry/examples/sankey/credit-flow.tsx +32 -0
  241. package/registry/examples/scatter-chart/watch-time.d.ts +4 -0
  242. package/registry/examples/scatter-chart/watch-time.js +36 -0
  243. package/registry/examples/scatter-chart/watch-time.tsx +31 -0
  244. package/registry/examples/sparkline/bars.d.ts +4 -0
  245. package/registry/examples/sparkline/bars.js +12 -0
  246. package/registry/examples/sparkline/bars.tsx +7 -0
  247. package/registry/examples/sparkline/native/stat-trend.d.ts +4 -0
  248. package/registry/examples/sparkline/native/stat-trend.dark.png +0 -0
  249. package/registry/examples/sparkline/native/stat-trend.js +12 -0
  250. package/registry/examples/sparkline/native/stat-trend.light.png +0 -0
  251. package/registry/examples/sparkline/native/stat-trend.tsx +7 -0
  252. package/registry/examples/sparkline/stat-trend.d.ts +4 -0
  253. package/registry/examples/sparkline/stat-trend.js +18 -0
  254. package/registry/examples/sparkline/stat-trend.tsx +15 -0
  255. package/registry/examples/sparkline/table-rows.d.ts +4 -0
  256. package/registry/examples/sparkline/table-rows.js +23 -0
  257. package/registry/examples/sparkline/table-rows.tsx +26 -0
  258. package/registry/examples/treemap/storage.d.ts +4 -0
  259. package/registry/examples/treemap/storage.js +17 -0
  260. package/registry/examples/treemap/storage.tsx +13 -0
  261. package/registry/funnel.json +486 -0
  262. package/registry/gauge.json +599 -0
  263. package/registry/geo-map.json +483 -0
  264. package/registry/globe.json +468 -0
  265. package/registry/heatmap.json +530 -0
  266. package/registry/index.json +420 -0
  267. package/registry/line-chart.json +792 -0
  268. package/registry/pie-chart.json +1287 -0
  269. package/registry/radar-chart.json +458 -0
  270. package/registry/sankey.json +502 -0
  271. package/registry/scatter-chart.json +558 -0
  272. package/registry/sparkline.json +562 -0
  273. package/registry/treemap.json +431 -0
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import {
3
+ EChartHost,
4
+ baseOption,
5
+ tooltipHtml
6
+ } from "./chunk-I5QUZVU2.js";
7
+ import {
8
+ describeParts
9
+ } from "./chunk-ANUCSRUN.js";
10
+ import {
11
+ shareFormatter,
12
+ valueFormatter
13
+ } from "./chunk-XLLYNNC6.js";
14
+ import {
15
+ useSettings
16
+ } from "./chunk-DWJPPEMC.js";
17
+ import {
18
+ contrast
19
+ } from "./chunk-3CJHBJXA.js";
20
+
21
+ // src/treemap/treemap.tsx
22
+ import { useCallback, useMemo } from "react";
23
+ import { jsx } from "react/jsx-runtime";
24
+ var engine = () => import("./treemap-OMGGGB5U.js");
25
+ var total = (node) => node.value ?? (node.children ?? []).reduce((sum, child) => sum + total(child), 0);
26
+ function leaves(nodes, path = []) {
27
+ return nodes.flatMap((n) => n.children?.length ? leaves(n.children, [...path, n.name]) : [{ path: [...path, n.name], value: total(n) }]);
28
+ }
29
+ function Treemap(props) {
30
+ const { data, format, pathLabel, valueLabel, title } = props;
31
+ const { locale, labels } = useSettings(props);
32
+ const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), share: shareFormatter(locale) }), [format, locale, labels.noValue]);
33
+ const parts = useMemo(() => data.map((n) => ({ label: n.name, value: total(n) })), [data]);
34
+ const all = parts.reduce((sum, p) => sum + p.value, 0);
35
+ const flat = useMemo(() => leaves(data), [data]);
36
+ const isEmpty = all <= 0;
37
+ const summary = isEmpty ? labels.empty : describeParts("treemap", parts, { labels, value: fmt.value, share: fmt.share });
38
+ const items = useMemo(() => {
39
+ const count = (n) => 1 + (n.children ?? []).reduce((sum, c) => sum + count(c), 0);
40
+ const starts = data.map((_, i) => 1 + data.slice(0, i).reduce((sum, n) => sum + count(n), 0));
41
+ return data.map((n, i) => ({ seriesIndex: 0, dataIndex: starts[i], text: `${n.name}: ${fmt.value(parts[i].value)} (${fmt.share(all ? parts[i].value / all : 0)})` }));
42
+ }, [data, parts, all, fmt]);
43
+ const build = useCallback(
44
+ (theme) => {
45
+ const base = baseOption(theme, true);
46
+ const ink = (fillColor) => contrast(fillColor, theme.text) >= contrast(fillColor, theme.surface) ? theme.text : theme.surface;
47
+ const toNode = (n, color) => ({
48
+ name: n.name,
49
+ value: total(n),
50
+ itemStyle: { color },
51
+ label: { color: ink(color) },
52
+ upperLabel: { color: ink(color) },
53
+ children: n.children?.map((c) => toNode(c, color))
54
+ });
55
+ return {
56
+ ...base,
57
+ tooltip: {
58
+ ...base.tooltip,
59
+ trigger: "item",
60
+ formatter: (p) => {
61
+ const path = p.treePathInfo.slice(1).map((s) => s.name);
62
+ return tooltipHtml(path.join(" \u203A "), [{ label: valueLabel ?? labels.value, value: fmt.value(p.value) }, { label: "%", value: fmt.share(all ? p.value / all : 0) }]);
63
+ }
64
+ },
65
+ series: [
66
+ {
67
+ type: "treemap",
68
+ name: title,
69
+ left: 0,
70
+ right: 0,
71
+ top: 0,
72
+ bottom: 28,
73
+ roam: false,
74
+ nodeClick: "zoomToNode",
75
+ data: data.map((n, i) => toNode(n, theme.palette[i % 8])),
76
+ label: { show: true, fontFamily: theme.font, fontSize: 12, formatter: (p) => `${p.name}
77
+ ${fmt.value(p.value)}` },
78
+ upperLabel: { show: true, height: 22, fontFamily: theme.font, fontSize: 11, fontWeight: 600 },
79
+ itemStyle: { borderColor: theme.surface, borderWidth: 2, gapWidth: 2, borderRadius: 4 },
80
+ levels: [{ itemStyle: { borderWidth: 0, gapWidth: 3 }, upperLabel: { show: false } }, { itemStyle: { borderColor: theme.surface, borderWidth: 2, gapWidth: 2 } }, { colorSaturation: [0.35, 0.6], itemStyle: { borderColor: theme.surface, borderWidth: 1, gapWidth: 1 } }],
81
+ breadcrumb: { show: true, bottom: 0, left: 0, height: 22, itemStyle: { color: theme.surface2, borderColor: theme.border, textStyle: { color: theme.text } }, emphasis: { itemStyle: { color: theme.surface3 } } }
82
+ }
83
+ ]
84
+ };
85
+ },
86
+ [data, fmt, all, valueLabel, title, labels.value]
87
+ );
88
+ return /* @__PURE__ */ jsx(
89
+ EChartHost,
90
+ {
91
+ kind: "treemap",
92
+ frame: props,
93
+ defaultHeight: 320,
94
+ table: { columns: [pathLabel ?? labels.category, valueLabel ?? labels.value, "%"], rows: flat.map((l) => [l.path.join(" \u203A "), fmt.value(l.value), fmt.share(all ? l.value / all : 0)]) },
95
+ summary,
96
+ isEmpty,
97
+ engine,
98
+ build,
99
+ items
100
+ }
101
+ );
102
+ }
103
+
104
+ export {
105
+ Treemap
106
+ };
107
+ //# sourceMappingURL=chunk-VKTZD7UT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/treemap/treemap.tsx"],"sourcesContent":["\"use client\";\n// Part of a whole, nested — storage by project then by kind, credits by team then by model. Each top-level part keeps\n// a palette slot and its children wear it; labels inside the rectangles take the text colour that reads best on the\n// fill (WCAG contrast computed, never assumed); a click zooms into a part, the path is written above it. ECharts,\n// loaded when the chart nears the screen.\nimport { useCallback, useMemo } from \"react\";\n\nimport { describeParts } from \"../core/describe\";\nimport { shareFormatter, valueFormatter } from \"../core/format\";\nimport { contrast } from \"../core/palette\";\nimport type { ValueFormat } from \"../core/types\";\nimport { EChartHost, type EngineProps, type WalkItem } from \"../echarts/host\";\nimport { baseOption, type ResolvedTheme, tooltipHtml } from \"../echarts/theme\";\nimport type { ChartFrameProps } from \"../web/frame\";\nimport { useSettings } from \"../web/frame\";\n\nconst engine = () => import(\"../echarts/engines/treemap\");\n\n/** A part: a name and either a value or children (whose values add up). */\nexport interface TreemapNode {\n readonly name: string;\n readonly value?: number;\n readonly children?: readonly TreemapNode[];\n}\n\nexport interface TreemapProps extends ChartFrameProps, EngineProps {\n /** The top-level parts. */\n data: readonly TreemapNode[];\n /** How the values are written. */\n format?: ValueFormat;\n /** The headers of the data table. */\n pathLabel?: string;\n/** The header of the values. */\n valueLabel?: string;\n}\n\nconst total = (node: TreemapNode): number => node.value ?? (node.children ?? []).reduce((sum, child) => sum + total(child), 0);\n\nfunction leaves(nodes: readonly TreemapNode[], path: string[] = []): { path: string[]; value: number }[] {\n return nodes.flatMap((n) => (n.children?.length ? leaves(n.children, [...path, n.name]) : [{ path: [...path, n.name], value: total(n) }]));\n}\n\nexport function Treemap(props: TreemapProps) {\n const { data, format, pathLabel, valueLabel, title } = props;\n const { locale, labels } = useSettings(props);\n const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), share: shareFormatter(locale) }), [format, locale, labels.noValue]);\n const parts = useMemo(() => data.map((n) => ({ label: n.name, value: total(n) })), [data]);\n const all = parts.reduce((sum, p) => sum + p.value, 0);\n const flat = useMemo(() => leaves(data), [data]);\n const isEmpty = all <= 0;\n const summary = isEmpty ? labels.empty : describeParts(\"treemap\", parts, { labels, value: fmt.value, share: fmt.share });\n // The keyboard walks the top-level parts (ECharts numbers the tree depth-first: the root is 0).\n const items: WalkItem[] = useMemo(() => {\n const count = (n: TreemapNode): number => 1 + (n.children ?? []).reduce((sum, c) => sum + count(c), 0);\n // Each part's index: 1 (after the root) plus the sizes of the parts before it.\n const starts = data.map((_, i) => 1 + data.slice(0, i).reduce((sum, n) => sum + count(n), 0));\n return data.map((n, i) => ({ seriesIndex: 0, dataIndex: starts[i]!, text: `${n.name}: ${fmt.value(parts[i]!.value)} (${fmt.share(all ? parts[i]!.value / all : 0)})` }));\n }, [data, parts, all, fmt]);\n\n const build = useCallback(\n (theme: ResolvedTheme) => {\n const base = baseOption(theme, true);\n const ink = (fillColor: string) => (contrast(fillColor, theme.text) >= contrast(fillColor, theme.surface) ? theme.text : theme.surface);\n const toNode = (n: TreemapNode, color: string): Record<string, unknown> => ({\n name: n.name,\n value: total(n),\n itemStyle: { color },\n label: { color: ink(color) },\n upperLabel: { color: ink(color) },\n children: n.children?.map((c) => toNode(c, color)),\n });\n return {\n ...base,\n tooltip: {\n ...(base.tooltip as object),\n trigger: \"item\",\n formatter: (p: { treePathInfo: { name: string }[]; value: number }) => {\n const path = p.treePathInfo.slice(1).map((s) => s.name);\n return tooltipHtml(path.join(\" › \"), [{ label: valueLabel ?? labels.value, value: fmt.value(p.value) }, { label: \"%\", value: fmt.share(all ? p.value / all : 0) }]);\n },\n },\n series: [\n {\n type: \"treemap\",\n name: title,\n left: 0,\n right: 0,\n top: 0,\n bottom: 28,\n roam: false,\n nodeClick: \"zoomToNode\",\n data: data.map((n, i) => toNode(n, theme.palette[i % 8]!)),\n label: { show: true, fontFamily: theme.font, fontSize: 12, formatter: (p: { name: string; value: number }) => `${p.name}\\n${fmt.value(p.value)}` },\n upperLabel: { show: true, height: 22, fontFamily: theme.font, fontSize: 11, fontWeight: 600 },\n itemStyle: { borderColor: theme.surface, borderWidth: 2, gapWidth: 2, borderRadius: 4 },\n levels: [{ itemStyle: { borderWidth: 0, gapWidth: 3 }, upperLabel: { show: false } }, { itemStyle: { borderColor: theme.surface, borderWidth: 2, gapWidth: 2 } }, { colorSaturation: [0.35, 0.6], itemStyle: { borderColor: theme.surface, borderWidth: 1, gapWidth: 1 } }],\n breadcrumb: { show: true, bottom: 0, left: 0, height: 22, itemStyle: { color: theme.surface2, borderColor: theme.border, textStyle: { color: theme.text } }, emphasis: { itemStyle: { color: theme.surface3 } } },\n },\n ],\n };\n },\n [data, fmt, all, valueLabel, title, labels.value],\n );\n\n return (\n <EChartHost\n kind=\"treemap\"\n frame={props}\n defaultHeight={320}\n table={{ columns: [pathLabel ?? labels.category, valueLabel ?? labels.value, \"%\"], rows: flat.map((l) => [l.path.join(\" › \"), fmt.value(l.value), fmt.share(all ? l.value / all : 0)]) }}\n summary={summary}\n isEmpty={isEmpty}\n engine={engine}\n build={build}\n items={items}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ // src/core/data.ts
4
+ function read(accessor, datum, index) {
5
+ return typeof accessor === "function" ? accessor(datum, index) : datum[accessor];
6
+ }
7
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8
+ function normalizeSeries(series, y, label) {
9
+ if (series?.length) return series;
10
+ if (y !== void 0) return [{ key: y, label: label ?? (typeof y === "string" ? y : "") }];
11
+ return [];
12
+ }
13
+ function tabulate(data, x, series) {
14
+ return {
15
+ categories: data.map((datum, i) => x === void 0 ? i + 1 : read(x, datum, i)),
16
+ series: series.map((spec, s) => ({
17
+ id: typeof spec.key === "string" ? spec.key : `series-${s}`,
18
+ label: spec.label,
19
+ values: data.map((datum, i) => toNumber(read(spec.key, datum, i))),
20
+ color: spec.color,
21
+ dashed: spec.dashed,
22
+ slot: s % 8 + 1
23
+ }))
24
+ };
25
+ }
26
+ function extent(series, stacked = false) {
27
+ let min = Number.POSITIVE_INFINITY;
28
+ let max = Number.NEGATIVE_INFINITY;
29
+ const count = series[0]?.values.length ?? 0;
30
+ if (stacked) {
31
+ for (let i = 0; i < count; i += 1) {
32
+ let pos = 0;
33
+ let neg = 0;
34
+ for (const s of series) {
35
+ const v = s.values[i] ?? 0;
36
+ if (v >= 0) pos += v;
37
+ else neg += v;
38
+ }
39
+ min = Math.min(min, neg);
40
+ max = Math.max(max, pos);
41
+ }
42
+ } else {
43
+ for (const s of series) {
44
+ for (const v of s.values) {
45
+ if (v == null) continue;
46
+ min = Math.min(min, v);
47
+ max = Math.max(max, v);
48
+ }
49
+ }
50
+ }
51
+ if (min === Number.POSITIVE_INFINITY) return [0, 0];
52
+ return [min, max];
53
+ }
54
+ function stack(series) {
55
+ const count = series[0]?.values.length ?? 0;
56
+ const out = series.map(() => []);
57
+ for (let i = 0; i < count; i += 1) {
58
+ let pos = 0;
59
+ let neg = 0;
60
+ series.forEach((s, k) => {
61
+ const v = s.values[i] ?? 0;
62
+ if (v >= 0) {
63
+ out[k].push({ base: pos, top: pos + v });
64
+ pos += v;
65
+ } else {
66
+ out[k].push({ base: neg, top: neg + v });
67
+ neg += v;
68
+ }
69
+ });
70
+ }
71
+ return out;
72
+ }
73
+ function summarize(series, categories, writeCategory) {
74
+ let min = null;
75
+ let max = null;
76
+ let first = null;
77
+ let last = null;
78
+ let total = 0;
79
+ let count = 0;
80
+ series.values.forEach((v, i) => {
81
+ if (v == null) return;
82
+ count += 1;
83
+ total += v;
84
+ if (first === null) first = v;
85
+ last = v;
86
+ if (!min || v < min.value) min = { value: v, category: writeCategory(categories[i]) };
87
+ if (!max || v > max.value) max = { value: v, category: writeCategory(categories[i]) };
88
+ });
89
+ const change = first !== null && last !== null && first !== 0 && count > 1 ? last / first - 1 : null;
90
+ const trend = count < 2 || change === null ? count < 2 ? "none" : last > first ? "up" : last < first ? "down" : "flat" : Math.abs(change) < 0.01 ? "flat" : change > 0 ? "up" : "down";
91
+ return { label: series.label, count, min, max, first, last, total, change, trend };
92
+ }
93
+
94
+ export {
95
+ read,
96
+ normalizeSeries,
97
+ tabulate,
98
+ extent,
99
+ stack,
100
+ summarize
101
+ };
102
+ //# sourceMappingURL=chunk-W5FX4TU3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/data.ts"],"sourcesContent":["// From the app's rows to what a chart draws: the categories (x) and, per series, one value per category. Missing\n// values stay `null` (a gap), never a zero.\nimport type { Accessor, Datum, SeriesColor, SeriesSpec, SeriesSummary } from \"./types\";\n\n/** Reads a field or calls the accessor. */\nexport function read<T, V>(accessor: Accessor<T, V>, datum: T, index: number): V {\n return typeof accessor === \"function\" ? accessor(datum, index) : (datum[accessor as keyof T] as V);\n}\n\nexport interface Series {\n readonly id: string;\n readonly label: string;\n readonly values: readonly (number | null)[];\n readonly color?: SeriesColor;\n readonly dashed?: boolean;\n /** Its palette slot (1–8), in the order the series were given: a series keeps its colour whatever is filtered. */\n readonly slot: number;\n}\n\nexport interface Table {\n readonly categories: readonly unknown[];\n readonly series: readonly Series[];\n}\n\nconst toNumber = (value: unknown): number | null => (typeof value === \"number\" && Number.isFinite(value) ? value : null);\n\n/** `series`, or a single series from `y` and `label`. */\nexport function normalizeSeries<T extends Datum>(series: readonly SeriesSpec<T>[] | undefined, y: Accessor<T, number | null | undefined> | undefined, label: string | undefined): readonly SeriesSpec<T>[] {\n if (series?.length) return series;\n if (y !== undefined) return [{ key: y, label: label ?? (typeof y === \"string\" ? y : \"\") }];\n return [];\n}\n\n/** The categories and the series' values, row by row. */\nexport function tabulate<T extends Datum>(data: readonly T[], x: Accessor<T, unknown> | undefined, series: readonly SeriesSpec<T>[]): Table {\n return {\n categories: data.map((datum, i) => (x === undefined ? i + 1 : read(x, datum, i))),\n series: series.map((spec, s) => ({\n id: typeof spec.key === \"string\" ? spec.key : `series-${s}`,\n label: spec.label,\n values: data.map((datum, i) => toNumber(read(spec.key, datum, i))),\n color: spec.color,\n dashed: spec.dashed,\n slot: (s % 8) + 1,\n })),\n };\n}\n\n/** The smallest and largest value of the series, `stacked` summing them per category (positives and negatives apart). */\nexport function extent(series: readonly Series[], stacked = false): [number, number] {\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n const count = series[0]?.values.length ?? 0;\n if (stacked) {\n for (let i = 0; i < count; i += 1) {\n let pos = 0;\n let neg = 0;\n for (const s of series) {\n const v = s.values[i] ?? 0;\n if (v >= 0) pos += v;\n else neg += v;\n }\n min = Math.min(min, neg);\n max = Math.max(max, pos);\n }\n } else {\n for (const s of series) {\n for (const v of s.values) {\n if (v == null) continue;\n min = Math.min(min, v);\n max = Math.max(max, v);\n }\n }\n }\n if (min === Number.POSITIVE_INFINITY) return [0, 0];\n return [min, max];\n}\n\n/** Per category and series, the base and top of a stacked mark (positives up, negatives down). */\nexport function stack(series: readonly Series[]): { base: number; top: number }[][] {\n const count = series[0]?.values.length ?? 0;\n const out = series.map(() => [] as { base: number; top: number }[]);\n for (let i = 0; i < count; i += 1) {\n let pos = 0;\n let neg = 0;\n series.forEach((s, k) => {\n const v = s.values[i] ?? 0;\n if (v >= 0) {\n out[k]!.push({ base: pos, top: pos + v });\n pos += v;\n } else {\n out[k]!.push({ base: neg, top: neg + v });\n neg += v;\n }\n });\n }\n return out;\n}\n\n/** What a series says: its extremes, its first and last values, its total and its trend (±1 % is flat). */\nexport function summarize(series: Series, categories: readonly unknown[], writeCategory: (c: unknown) => string): SeriesSummary {\n let min: SeriesSummary[\"min\"] = null;\n let max: SeriesSummary[\"max\"] = null;\n let first: number | null = null;\n let last: number | null = null;\n let total = 0;\n let count = 0;\n series.values.forEach((v, i) => {\n if (v == null) return;\n count += 1;\n total += v;\n if (first === null) first = v;\n last = v;\n if (!min || v < min.value) min = { value: v, category: writeCategory(categories[i]) };\n if (!max || v > max.value) max = { value: v, category: writeCategory(categories[i]) };\n });\n const change = first !== null && last !== null && first !== 0 && count > 1 ? (last as number) / first - 1 : null;\n const trend = count < 2 || change === null ? (count < 2 ? \"none\" : last! > first! ? \"up\" : last! < first! ? \"down\" : \"flat\") : Math.abs(change) < 0.01 ? \"flat\" : change > 0 ? \"up\" : \"down\";\n return { label: series.label, count, min, max, first, last, total, change, trend };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,178 @@
1
+ "use client";
2
+ import {
3
+ regionName,
4
+ useWorld
5
+ } from "./chunk-6FINSUOS.js";
6
+ import {
7
+ read
8
+ } from "./chunk-W5FX4TU3.js";
9
+ import {
10
+ shareFormatter,
11
+ valueFormatter
12
+ } from "./chunk-XLLYNNC6.js";
13
+ import {
14
+ ChartFrame,
15
+ RampLegend,
16
+ Tooltip,
17
+ stepColor,
18
+ useMounted,
19
+ useSettings
20
+ } from "./chunk-DWJPPEMC.js";
21
+ import {
22
+ capitalize,
23
+ fill
24
+ } from "./chunk-3CJHBJXA.js";
25
+
26
+ // src/geo/geo-map.tsx
27
+ import { geoEqualEarth, geoPath } from "d3-geo";
28
+ import { useMemo, useState } from "react";
29
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
30
+ function GeoMap(props) {
31
+ const { data, region, value, format, points = [], steps = 5, regionLabel, valueLabel, onSelect } = props;
32
+ const { locale, labels } = useSettings(props);
33
+ const mounted = useMounted();
34
+ const { world, failed } = useWorld(mounted);
35
+ const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), axis: valueFormatter(format, locale, labels.noValue, true), share: shareFormatter(locale) }), [format, locale, labels.noValue]);
36
+ const rows = useMemo(
37
+ () => data.map((datum, index) => ({ datum, index, code: String(read(region, datum, index)).toUpperCase(), value: Number(read(value, datum, index)) })).filter((r) => Number.isFinite(r.value)).sort((a, b) => b.value - a.value),
38
+ [data, region, value]
39
+ );
40
+ const byCode = useMemo(() => new Map(rows.map((r) => [r.code, r])), [rows]);
41
+ const max = rows[0]?.value ?? 0;
42
+ const min = rows[rows.length - 1]?.value ?? 0;
43
+ const stepOf = (v) => max === min ? steps - 1 : Math.min(steps - 1, Math.floor((v - min) / (max - min) * steps));
44
+ const name = (code) => regionName(code, code, locale);
45
+ const total = rows.reduce((sum, r) => sum + r.value, 0);
46
+ const isEmpty = rows.length === 0 && points.length === 0;
47
+ const summary = isEmpty ? labels.empty : [
48
+ fill(labels.summaryIntro, { kind: labels.kinds.map, count: rows.length || points.length }),
49
+ rows.slice(0, 5).map((r) => fill(labels.summaryShare, { label: name(r.code), value: fmt.value(r.value), share: fmt.share(total ? r.value / total : 0) })).join(", ") + (rows.length ? "." : ""),
50
+ rows.length ? `${capitalize(fill(labels.summaryTotal, { total: fmt.value(total) }))}.` : ""
51
+ ].filter(Boolean).join(" ");
52
+ const table = {
53
+ columns: [regionLabel ?? labels.category, valueLabel ?? labels.value, "%"],
54
+ rows: [
55
+ ...rows.map((r) => [name(r.code), fmt.value(r.value), fmt.share(total ? r.value / total : 0)]),
56
+ ...points.map((p) => [p.label, p.value == null ? labels.noValue : fmt.value(p.value), ""])
57
+ ]
58
+ };
59
+ const walk = [...rows.map((r) => ({ kind: "region", code: r.code })), ...points.map((_, i) => ({ kind: "point", i }))];
60
+ const [active, setActive] = useState(null);
61
+ const [hover, setHover] = useState(null);
62
+ const onKeyDown = (event) => {
63
+ const n = walk.length;
64
+ if (!n) return;
65
+ const moves = {
66
+ ArrowRight: () => ((active ?? -1) + 1) % n,
67
+ ArrowDown: () => ((active ?? -1) + 1) % n,
68
+ ArrowLeft: () => ((active ?? 0) - 1 + n) % n,
69
+ ArrowUp: () => ((active ?? 0) - 1 + n) % n,
70
+ Home: () => 0,
71
+ End: () => n - 1,
72
+ Escape: () => null
73
+ };
74
+ const current2 = active !== null ? walk[active] : null;
75
+ if (event.key === "Enter" && current2?.kind === "region" && onSelect) {
76
+ const row = byCode.get(current2.code);
77
+ onSelect(row.datum, row.index);
78
+ return;
79
+ }
80
+ const move = moves[event.key];
81
+ if (!move) return;
82
+ event.preventDefault();
83
+ setHover(null);
84
+ setActive(move());
85
+ };
86
+ const current = active !== null ? walk[active] : null;
87
+ const announce = !current ? "" : current.kind === "region" ? `${name(current.code)}: ${fmt.value(byCode.get(current.code).value)}` : `${points[current.i].label}${points[current.i].value != null ? `: ${fmt.value(points[current.i].value)}` : ""}`;
88
+ const legend = rows.length > 0 ? /* @__PURE__ */ jsx(RampLegend, { min, max, steps, write: fmt.axis }) : null;
89
+ return /* @__PURE__ */ jsx(
90
+ ChartFrame,
91
+ {
92
+ kind: "map",
93
+ frame: { ...props, error: props.error ?? (failed || void 0), loading: props.loading || mounted && !world && !failed },
94
+ pending: !world && !failed,
95
+ defaultHeight: 360,
96
+ table,
97
+ summary,
98
+ isEmpty,
99
+ legend,
100
+ announcement: announce,
101
+ active: active !== null,
102
+ handlers: {
103
+ onKeyDown,
104
+ onBlur: () => setActive(null),
105
+ onPointerLeave: () => setHover(null)
106
+ },
107
+ children: (width, height) => {
108
+ if (!world) return null;
109
+ const projection = geoEqualEarth().fitExtent(
110
+ [
111
+ [4, 4],
112
+ [width - 4, height - 4]
113
+ ],
114
+ { type: "Sphere" }
115
+ );
116
+ const path = geoPath(projection);
117
+ const maxPoint = Math.max(0, ...points.map((p) => p.value ?? 0));
118
+ const radiusOf = (p) => p.value && maxPoint ? 3 + Math.sqrt(p.value / maxPoint) * 15 : 4;
119
+ const focusCode = hover?.code ?? (current?.kind === "region" ? current.code : null);
120
+ let tip = null;
121
+ if (hover) {
122
+ const row = byCode.get(hover.code);
123
+ tip = { x: hover.x, y: hover.y, title: name(hover.code), value: row ? fmt.value(row.value) : labels.noValue };
124
+ } else if (current?.kind === "region") {
125
+ const feature = world.features.find((f) => f.id === current.code);
126
+ const [cx, cy] = feature ? path.centroid(feature) : [width / 2, height / 2];
127
+ tip = { x: cx, y: cy, title: name(current.code), value: fmt.value(byCode.get(current.code).value) };
128
+ } else if (current?.kind === "point") {
129
+ const p = points[current.i];
130
+ const [px, py] = projection([p.lon, p.lat]) ?? [0, 0];
131
+ tip = { x: px, y: py, title: p.label, value: p.value == null ? "" : fmt.value(p.value) };
132
+ }
133
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
134
+ /* @__PURE__ */ jsxs("svg", { className: "kzc-svg kzc-enter-fade", width, height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": true, children: [
135
+ /* @__PURE__ */ jsx("path", { d: path({ type: "Sphere" }) ?? "", style: { fill: "var(--kz-surface-2)", opacity: 0.5 } }),
136
+ world.features.map((f, i) => {
137
+ const row = f.id ? byCode.get(f.id) : void 0;
138
+ return /* @__PURE__ */ jsx(
139
+ "path",
140
+ {
141
+ d: path(f) ?? "",
142
+ className: "kzc-mark",
143
+ "data-dim": focusCode && focusCode !== f.id ? "" : void 0,
144
+ style: {
145
+ fill: row ? stepColor(stepOf(row.value), steps) : "var(--kz-surface-3)",
146
+ stroke: focusCode === f.id ? "var(--kzc-text)" : "var(--kzc-surface)",
147
+ strokeWidth: focusCode === f.id ? 1.5 : 0.5,
148
+ cursor: row && onSelect ? "pointer" : void 0
149
+ },
150
+ onPointerMove: (event) => {
151
+ if (!f.id) return;
152
+ const box = (event.currentTarget.ownerSVGElement ?? event.currentTarget).getBoundingClientRect();
153
+ setActive(null);
154
+ setHover({ code: f.id, x: event.clientX - box.left, y: event.clientY - box.top });
155
+ },
156
+ onClick: row && onSelect ? () => onSelect(row.datum, row.index) : void 0
157
+ },
158
+ `${f.id ?? "x"}-${i}`
159
+ );
160
+ }),
161
+ points.map((p, i) => {
162
+ const at = projection([p.lon, p.lat]);
163
+ if (!at) return null;
164
+ const on = current?.kind === "point" && current.i === i;
165
+ return /* @__PURE__ */ jsx("circle", { cx: at[0], cy: at[1], r: radiusOf(p), className: "kzc-dot", style: { fill: "var(--kzc-2)", fillOpacity: on ? 1 : 0.8, strokeWidth: on ? 2.5 : 1.5 } }, `${p.label}-${i}`);
166
+ })
167
+ ] }),
168
+ tip && /* @__PURE__ */ jsx(Tooltip, { x: tip.x, y: tip.y, width, title: tip.title, rows: tip.value ? [{ label: valueLabel ?? labels.value, value: tip.value }] : [] })
169
+ ] });
170
+ }
171
+ }
172
+ );
173
+ }
174
+
175
+ export {
176
+ GeoMap
177
+ };
178
+ //# sourceMappingURL=chunk-WBH2QU77.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/geo/geo-map.tsx"],"sourcesContent":["\"use client\";\n// Where: a choropleth of the world (one sequential hue — the brand's — in even steps from near the surface), and\n// optional bubbles on places (cities, data centres). Plain SVG through d3-geo's Equal Earth projection (areas stay\n// true: no Greenland the size of Africa); country names in the reader's language (Intl.DisplayNames). The keyboard\n// walks the regions that have data, largest first.\nimport { geoEqualEarth, geoPath } from \"d3-geo\";\nimport { type KeyboardEvent, useMemo, useState } from \"react\";\n\nimport { read } from \"../core/data\";\nimport { shareFormatter, valueFormatter } from \"../core/format\";\nimport { capitalize, fill } from \"../core/labels\";\nimport type { Accessor, Datum, ValueFormat } from \"../core/types\";\nimport { ChartFrame, type ChartFrameProps, useSettings } from \"../web/frame\";\nimport { useMounted } from \"../web/hooks\";\nimport { RampLegend, stepColor, Tooltip } from \"../web/parts\";\nimport { regionName, useWorld } from \"./world\";\n\n/** A place on the map or the globe. */\nexport interface GeoPoint {\n /** Longitude, then latitude, in degrees. */\n readonly lon: number;\n readonly lat: number;\n /** Its name (“Montréal”, “eu-west-3 · Paris”). */\n readonly label: string;\n /** An amount: the bubble's area on a map; written in the tooltip and the table. */\n readonly value?: number;\n}\n\nexport interface GeoMapProps<T extends Datum> extends ChartFrameProps {\n /** One row per region with a value. */\n data: readonly T[];\n /** The field (or function) of the region's ISO 3166-1 alpha-2 code (`\"FR\"`, `\"US\"`). */\n region: Accessor<T, string>;\n /** The field (or function) of its value. */\n value: Accessor<T, number>;\n /** How the values are written. */\n format?: ValueFormat;\n /** Bubbles on places, their area by `value`. */\n points?: readonly GeoPoint[];\n /** The number of colour steps of the scale. Default 5. */\n steps?: number;\n /** The header of the regions and of the values in the table (“Country”, “Viewers”). */\n regionLabel?: string;\n/** The header of the values (“Viewers”). */\n valueLabel?: string;\n /** Called with the row when a region is clicked (or Enter is pressed on it). */\n onSelect?: (datum: T, index: number) => void;\n}\n\nexport function GeoMap<T extends Datum>(props: GeoMapProps<T>) {\n const { data, region, value, format, points = [], steps = 5, regionLabel, valueLabel, onSelect } = props;\n const { locale, labels } = useSettings(props);\n const mounted = useMounted();\n const { world, failed } = useWorld(mounted);\n const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), axis: valueFormatter(format, locale, labels.noValue, true), share: shareFormatter(locale) }), [format, locale, labels.noValue]);\n\n const rows = useMemo(\n () =>\n data\n .map((datum, index) => ({ datum, index, code: String(read(region, datum, index)).toUpperCase(), value: Number(read(value, datum, index)) }))\n .filter((r) => Number.isFinite(r.value))\n .sort((a, b) => b.value - a.value),\n [data, region, value],\n );\n const byCode = useMemo(() => new Map(rows.map((r) => [r.code, r])), [rows]);\n const max = rows[0]?.value ?? 0;\n const min = rows[rows.length - 1]?.value ?? 0;\n const stepOf = (v: number) => (max === min ? steps - 1 : Math.min(steps - 1, Math.floor(((v - min) / (max - min)) * steps)));\n const name = (code: string) => regionName(code, code, locale);\n const total = rows.reduce((sum, r) => sum + r.value, 0);\n const isEmpty = rows.length === 0 && points.length === 0;\n\n const summary = isEmpty\n ? labels.empty\n : [\n fill(labels.summaryIntro, { kind: labels.kinds.map, count: rows.length || points.length }),\n rows\n .slice(0, 5)\n .map((r) => fill(labels.summaryShare, { label: name(r.code), value: fmt.value(r.value), share: fmt.share(total ? r.value / total : 0) }))\n .join(\", \") + (rows.length ? \".\" : \"\"),\n rows.length ? `${capitalize(fill(labels.summaryTotal, { total: fmt.value(total) }))}.` : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n const table = {\n columns: [regionLabel ?? labels.category, valueLabel ?? labels.value, \"%\"],\n rows: [\n ...rows.map((r) => [name(r.code), fmt.value(r.value), fmt.share(total ? r.value / total : 0)]),\n ...points.map((p) => [p.label, p.value == null ? labels.noValue : fmt.value(p.value), \"\"]),\n ],\n };\n\n const walk = [...rows.map((r) => ({ kind: \"region\" as const, code: r.code })), ...points.map((_, i) => ({ kind: \"point\" as const, i }))];\n const [active, setActive] = useState<number | null>(null);\n const [hover, setHover] = useState<{ code: string; x: number; y: number } | null>(null);\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const n = walk.length;\n if (!n) return;\n const moves: Record<string, () => number | null> = {\n ArrowRight: () => ((active ?? -1) + 1) % n,\n ArrowDown: () => ((active ?? -1) + 1) % n,\n ArrowLeft: () => ((active ?? 0) - 1 + n) % n,\n ArrowUp: () => ((active ?? 0) - 1 + n) % n,\n Home: () => 0,\n End: () => n - 1,\n Escape: () => null,\n };\n const current = active !== null ? walk[active] : null;\n if (event.key === \"Enter\" && current?.kind === \"region\" && onSelect) {\n const row = byCode.get(current.code)!;\n onSelect(row.datum, row.index);\n return;\n }\n const move = moves[event.key];\n if (!move) return;\n event.preventDefault();\n setHover(null);\n setActive(move());\n };\n const current = active !== null ? walk[active] : null;\n const announce = !current ? \"\" : current.kind === \"region\" ? `${name(current.code)}: ${fmt.value(byCode.get(current.code)!.value)}` : `${points[current.i]!.label}${points[current.i]!.value != null ? `: ${fmt.value(points[current.i]!.value)}` : \"\"}`;\n\n const legend = rows.length > 0 ? <RampLegend min={min} max={max} steps={steps} write={fmt.axis} /> : null;\n\n return (\n <ChartFrame\n kind=\"map\"\n frame={{ ...props, error: props.error ?? (failed || undefined), loading: props.loading || (mounted && !world && !failed) }}\n pending={!world && !failed}\n defaultHeight={360}\n table={table}\n summary={summary}\n isEmpty={isEmpty}\n legend={legend}\n announcement={announce}\n active={active !== null}\n handlers={{\n onKeyDown,\n onBlur: () => setActive(null),\n onPointerLeave: () => setHover(null),\n }}\n >\n {(width, height) => {\n if (!world) return null;\n const projection = geoEqualEarth().fitExtent(\n [\n [4, 4],\n [width - 4, height - 4],\n ],\n { type: \"Sphere\" },\n );\n const path = geoPath(projection);\n const maxPoint = Math.max(0, ...points.map((p) => p.value ?? 0));\n const radiusOf = (p: GeoPoint) => (p.value && maxPoint ? 3 + Math.sqrt(p.value / maxPoint) * 15 : 4);\n const focusCode = hover?.code ?? (current?.kind === \"region\" ? current.code : null);\n let tip: { x: number; y: number; title: string; value: string } | null = null;\n if (hover) {\n const row = byCode.get(hover.code);\n tip = { x: hover.x, y: hover.y, title: name(hover.code), value: row ? fmt.value(row.value) : labels.noValue };\n } else if (current?.kind === \"region\") {\n const feature = world.features.find((f) => f.id === current.code);\n const [cx, cy] = feature ? path.centroid(feature) : [width / 2, height / 2];\n tip = { x: cx, y: cy, title: name(current.code), value: fmt.value(byCode.get(current.code)!.value) };\n } else if (current?.kind === \"point\") {\n const p = points[current.i]!;\n const [px, py] = projection([p.lon, p.lat]) ?? [0, 0];\n tip = { x: px, y: py, title: p.label, value: p.value == null ? \"\" : fmt.value(p.value) };\n }\n return (\n <>\n <svg className=\"kzc-svg kzc-enter-fade\" width={width} height={height} viewBox={`0 0 ${width} ${height}`} aria-hidden>\n <path d={path({ type: \"Sphere\" }) ?? \"\"} style={{ fill: \"var(--kz-surface-2)\", opacity: 0.5 }} />\n {world.features.map((f, i) => {\n const row = f.id ? byCode.get(f.id) : undefined;\n return (\n <path\n key={`${f.id ?? \"x\"}-${i}`}\n d={path(f) ?? \"\"}\n className=\"kzc-mark\"\n data-dim={focusCode && focusCode !== f.id ? \"\" : undefined}\n style={{\n fill: row ? stepColor(stepOf(row.value), steps) : \"var(--kz-surface-3)\",\n stroke: focusCode === f.id ? \"var(--kzc-text)\" : \"var(--kzc-surface)\",\n strokeWidth: focusCode === f.id ? 1.5 : 0.5,\n cursor: row && onSelect ? \"pointer\" : undefined,\n }}\n onPointerMove={(event) => {\n if (!f.id) return;\n const box = (event.currentTarget.ownerSVGElement ?? event.currentTarget).getBoundingClientRect();\n setActive(null);\n setHover({ code: f.id, x: event.clientX - box.left, y: event.clientY - box.top });\n }}\n onClick={row && onSelect ? () => onSelect(row.datum, row.index) : undefined}\n />\n );\n })}\n {points.map((p, i) => {\n const at = projection([p.lon, p.lat]);\n if (!at) return null;\n const on = current?.kind === \"point\" && current.i === i;\n return <circle key={`${p.label}-${i}`} cx={at[0]} cy={at[1]} r={radiusOf(p)} className=\"kzc-dot\" style={{ fill: \"var(--kzc-2)\", fillOpacity: on ? 1 : 0.8, strokeWidth: on ? 2.5 : 1.5 }} />;\n })}\n </svg>\n {tip && <Tooltip x={tip.x} y={tip.y} width={width} title={tip.title} rows={tip.value ? [{ label: valueLabel ?? labels.value, value: tip.value }] : []} />}\n </>\n );\n }}\n </ChartFrame>\n );\n}\n"],"mappings":"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","names":["current"]}
@@ -0,0 +1,145 @@
1
+ "use client";
2
+ import {
3
+ describeParts
4
+ } from "./chunk-ANUCSRUN.js";
5
+ import {
6
+ arcPath,
7
+ polar
8
+ } from "./chunk-OB5EFDDH.js";
9
+ import {
10
+ read
11
+ } from "./chunk-W5FX4TU3.js";
12
+ import {
13
+ shareFormatter,
14
+ valueFormatter
15
+ } from "./chunk-XLLYNNC6.js";
16
+ import {
17
+ ChartFrame,
18
+ Tooltip,
19
+ colorOf,
20
+ useSettings
21
+ } from "./chunk-DWJPPEMC.js";
22
+
23
+ // src/pie/pie-chart.tsx
24
+ import { useMemo, useState } from "react";
25
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
+ function PieChart(props) {
27
+ const { data, label, value, color, format, donut = false, centerLabel, centerValue, maxSlices = 6, otherLabel = "Other", onSelect, labelHeader, valueHeader } = props;
28
+ const { locale, labels } = useSettings(props);
29
+ const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), share: shareFormatter(locale) }), [format, locale, labels.noValue]);
30
+ const slices = useMemo(() => {
31
+ const rows = data.map((datum, index) => ({ datum, index, label: read(label, datum, index), value: Math.max(0, Number(read(value, datum, index)) || 0) })).sort((a, b) => b.value - a.value);
32
+ const kept = rows.length > maxSlices ? rows.slice(0, maxSlices - 1) : rows;
33
+ const out = kept.map((row, i) => ({ ...row, color: colorOf(color ? read(color, row.datum, row.index) : void 0, i + 1) }));
34
+ if (rows.length > maxSlices) {
35
+ const rest = rows.slice(maxSlices - 1).reduce((sum, row) => sum + row.value, 0);
36
+ out.push({ label: otherLabel, value: rest, color: "var(--kz-text-muted)", index: -1 });
37
+ }
38
+ return out;
39
+ }, [data, label, value, color, maxSlices, otherLabel]);
40
+ const total = slices.reduce((sum, s) => sum + s.value, 0);
41
+ const isEmpty = total <= 0;
42
+ const kind = donut ? "donut" : "pie";
43
+ const summary = useMemo(() => isEmpty ? labels.empty : describeParts(kind, slices, { labels, value: fmt.value, share: fmt.share }), [isEmpty, kind, slices, labels, fmt]);
44
+ const table = useMemo(
45
+ () => ({
46
+ columns: [labelHeader ?? labels.category, valueHeader ?? labels.value, "%"],
47
+ rows: [...slices.map((s) => [s.label, fmt.value(s.value), fmt.share(total ? s.value / total : 0)]), [labels.total, fmt.value(total), fmt.share(1)]]
48
+ }),
49
+ [slices, fmt, total, labels, labelHeader, valueHeader]
50
+ );
51
+ const [active, setActive] = useState(null);
52
+ const onKeyDown = (event) => {
53
+ const n = slices.length;
54
+ const moves = {
55
+ ArrowRight: () => ((active ?? -1) + 1) % n,
56
+ ArrowDown: () => ((active ?? -1) + 1) % n,
57
+ ArrowLeft: () => ((active ?? 0) - 1 + n) % n,
58
+ ArrowUp: () => ((active ?? 0) - 1 + n) % n,
59
+ Home: () => 0,
60
+ End: () => n - 1,
61
+ Escape: () => null
62
+ };
63
+ if (event.key === "Enter" && active !== null && onSelect) {
64
+ const slice = slices[active];
65
+ if (slice.datum) onSelect(slice.datum, slice.index);
66
+ return;
67
+ }
68
+ const move = moves[event.key];
69
+ if (!move) return;
70
+ event.preventDefault();
71
+ setActive(move());
72
+ };
73
+ const say = (s) => `${s.label}: ${fmt.value(s.value)} (${fmt.share(total ? s.value / total : 0)})`;
74
+ const legend = /* @__PURE__ */ jsx("ul", { className: "kzc-legend", "aria-hidden": true, children: slices.map((s) => /* @__PURE__ */ jsxs("li", { children: [
75
+ /* @__PURE__ */ jsx("span", { className: "kzc-key", style: { background: s.color } }),
76
+ s.label,
77
+ " \xB7 ",
78
+ fmt.share(total ? s.value / total : 0)
79
+ ] }, s.label)) });
80
+ return /* @__PURE__ */ jsx(
81
+ ChartFrame,
82
+ {
83
+ kind,
84
+ frame: props,
85
+ defaultHeight: 240,
86
+ table,
87
+ summary,
88
+ isEmpty,
89
+ legend: isEmpty ? null : legend,
90
+ announcement: active === null ? "" : say(slices[active]),
91
+ active: active !== null,
92
+ handlers: { onKeyDown, onBlur: () => setActive(null), onPointerLeave: () => setActive(null) },
93
+ children: (width, height) => {
94
+ const radius = Math.max(10, Math.min(width, height) / 2 - 6);
95
+ const inner = donut ? radius * 0.64 : 0;
96
+ const cx = width / 2;
97
+ const cy = height / 2;
98
+ let angle = 0;
99
+ const arcs = slices.map((s, i) => {
100
+ const sweep = total ? s.value / total * Math.PI * 2 : 0;
101
+ const start = angle;
102
+ angle += sweep;
103
+ return { s, i, start, end: angle };
104
+ });
105
+ const current = active !== null ? arcs[active] : null;
106
+ const mid = current ? (current.start + current.end) / 2 : 0;
107
+ const [tx, ty] = current ? polar(cx, cy, (radius + inner) / 2 + (donut ? 0 : radius * 0.2), mid) : [0, 0];
108
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
109
+ /* @__PURE__ */ jsxs("svg", { className: "kzc-svg", width, height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": true, children: [
110
+ /* @__PURE__ */ jsx("g", { className: "kzc-enter-pop", children: arcs.map(({ s, i, start, end }) => {
111
+ const lift = active === i ? 4 : 0;
112
+ const [dx, dy] = lift ? polar(0, 0, lift, (start + end) / 2) : [0, 0];
113
+ return /* @__PURE__ */ jsx(
114
+ "path",
115
+ {
116
+ d: arcPath(cx + dx, cy + dy, inner, radius, start, end),
117
+ className: "kzc-mark",
118
+ "data-dim": active !== null && active !== i ? "" : void 0,
119
+ style: { fill: s.color, stroke: "var(--kzc-surface)", strokeWidth: slices.length > 1 ? 2 : 0, strokeLinejoin: "round", cursor: onSelect ? "pointer" : void 0 },
120
+ onPointerEnter: () => setActive(i),
121
+ onClick: onSelect && s.datum ? () => onSelect(s.datum, s.index) : void 0
122
+ },
123
+ s.label
124
+ );
125
+ }) }),
126
+ donut && /* @__PURE__ */ jsxs("g", { className: "kzc-enter-fade", children: [
127
+ /* @__PURE__ */ jsx("text", { x: cx, y: cy - (centerLabel ? 6 : 0), textAnchor: "middle", dominantBaseline: "central", className: "kzc-strong", style: { fontSize: Math.max(14, Math.min(26, inner / 3)) }, children: centerValue ?? fmt.value(total) }),
128
+ centerLabel && /* @__PURE__ */ jsx("text", { x: cx, y: cy + Math.max(12, Math.min(20, inner / 4)), textAnchor: "middle", dominantBaseline: "central", children: centerLabel })
129
+ ] })
130
+ ] }),
131
+ current && /* @__PURE__ */ jsx(Tooltip, { x: tx, y: ty, width, title: current.s.label, rows: [{ label: fmt.share(total ? current.s.value / total : 0), value: fmt.value(current.s.value), color: current.s.color }] })
132
+ ] });
133
+ }
134
+ }
135
+ );
136
+ }
137
+ function DonutChart(props) {
138
+ return /* @__PURE__ */ jsx(PieChart, { ...props, donut: true });
139
+ }
140
+
141
+ export {
142
+ PieChart,
143
+ DonutChart
144
+ };
145
+ //# sourceMappingURL=chunk-WIRCIOLH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pie/pie-chart.tsx"],"sourcesContent":["\"use client\";\n// Part of a whole: a pie, or a donut with the total in its hole. Plain SVG. The slices are sorted largest first and\n// folded into “Other” past `maxSlices` (a ninth hue is never invented); a 2 px surface gap separates them; the legend\n// carries each part's value and share, so the colour is never the only way to read it.\nimport { type KeyboardEvent, useMemo, useState } from \"react\";\n\nimport { read } from \"../core/data\";\nimport { describeParts } from \"../core/describe\";\nimport { shareFormatter, valueFormatter } from \"../core/format\";\nimport { arcPath, polar } from \"../core/shape\";\nimport type { Accessor, Datum, SeriesColor, ValueFormat } from \"../core/types\";\nimport { ChartFrame, type ChartFrameProps, useSettings } from \"../web/frame\";\nimport { colorOf, Tooltip } from \"../web/parts\";\n\nexport interface PieChartProps<T extends Datum> extends ChartFrameProps {\n /** One row per part. */\n data: readonly T[];\n /** The field (or function) naming the part. */\n label: Accessor<T, string>;\n /** The field (or function) of its amount (≥ 0). */\n value: Accessor<T, number>;\n /** A part's own colour (a slot, a tone or a CSS colour); default: the palette in order of size. */\n color?: Accessor<T, SeriesColor | undefined>;\n /** How the amounts are written. */\n format?: ValueFormat;\n /** A ring rather than a pie: the total (or `centerValue`) is written in its hole. */\n donut?: boolean;\n /** The words under the number in the hole of a donut (“Total credits”). */\n centerLabel?: string;\n /** The number in the hole, written already. Default: the total. */\n centerValue?: string;\n /** At most this many slices; the rest fold into one, named `otherLabel`. Default 6. */\n maxSlices?: number;\n /** The name of the folded slice. Default “Other”. */\n otherLabel?: string;\n /** Called with the row when a slice is clicked (or Enter is pressed on it). */\n onSelect?: (datum: T, index: number) => void;\n /** The header of the parts in the data table. */\n labelHeader?: string;\n /** The header of the amounts in the data table. */\n valueHeader?: string;\n}\n\ninterface Slice<T> {\n readonly label: string;\n readonly value: number;\n readonly color: string;\n readonly datum?: T;\n readonly index: number;\n}\n\nexport function PieChart<T extends Datum>(props: PieChartProps<T>) {\n const { data, label, value, color, format, donut = false, centerLabel, centerValue, maxSlices = 6, otherLabel = \"Other\", onSelect, labelHeader, valueHeader } = props;\n const { locale, labels } = useSettings(props);\n const fmt = useMemo(() => ({ value: valueFormatter(format, locale, labels.noValue), share: shareFormatter(locale) }), [format, locale, labels.noValue]);\n\n const slices = useMemo<Slice<T>[]>(() => {\n const rows = data\n .map((datum, index) => ({ datum, index, label: read(label, datum, index), value: Math.max(0, Number(read(value, datum, index)) || 0) }))\n .sort((a, b) => b.value - a.value);\n const kept = rows.length > maxSlices ? rows.slice(0, maxSlices - 1) : rows;\n const out: Slice<T>[] = kept.map((row, i) => ({ ...row, color: colorOf(color ? read(color, row.datum, row.index) : undefined, i + 1) }));\n if (rows.length > maxSlices) {\n const rest = rows.slice(maxSlices - 1).reduce((sum, row) => sum + row.value, 0);\n out.push({ label: otherLabel, value: rest, color: \"var(--kz-text-muted)\", index: -1 });\n }\n return out;\n }, [data, label, value, color, maxSlices, otherLabel]);\n\n const total = slices.reduce((sum, s) => sum + s.value, 0);\n const isEmpty = total <= 0;\n const kind = donut ? \"donut\" : \"pie\";\n const summary = useMemo(() => (isEmpty ? labels.empty : describeParts(kind, slices, { labels, value: fmt.value, share: fmt.share })), [isEmpty, kind, slices, labels, fmt]);\n const table = useMemo(\n () => ({\n columns: [labelHeader ?? labels.category, valueHeader ?? labels.value, \"%\"],\n rows: [...slices.map((s) => [s.label, fmt.value(s.value), fmt.share(total ? s.value / total : 0)]), [labels.total, fmt.value(total), fmt.share(1)]],\n }),\n [slices, fmt, total, labels, labelHeader, valueHeader],\n );\n\n const [active, setActive] = useState<number | null>(null);\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const n = slices.length;\n const moves: Record<string, () => number | null> = {\n ArrowRight: () => ((active ?? -1) + 1) % n,\n ArrowDown: () => ((active ?? -1) + 1) % n,\n ArrowLeft: () => ((active ?? 0) - 1 + n) % n,\n ArrowUp: () => ((active ?? 0) - 1 + n) % n,\n Home: () => 0,\n End: () => n - 1,\n Escape: () => null,\n };\n if (event.key === \"Enter\" && active !== null && onSelect) {\n const slice = slices[active]!;\n if (slice.datum) onSelect(slice.datum, slice.index);\n return;\n }\n const move = moves[event.key];\n if (!move) return;\n event.preventDefault();\n setActive(move());\n };\n const say = (s: Slice<T>) => `${s.label}: ${fmt.value(s.value)} (${fmt.share(total ? s.value / total : 0)})`;\n\n const legend = (\n <ul className=\"kzc-legend\" aria-hidden>\n {slices.map((s) => (\n <li key={s.label}>\n <span className=\"kzc-key\" style={{ background: s.color }} />\n {s.label} · {fmt.share(total ? s.value / total : 0)}\n </li>\n ))}\n </ul>\n );\n\n return (\n <ChartFrame\n kind={kind}\n frame={props}\n defaultHeight={240}\n table={table}\n summary={summary}\n isEmpty={isEmpty}\n legend={isEmpty ? null : legend}\n announcement={active === null ? \"\" : say(slices[active]!)}\n active={active !== null}\n handlers={{ onKeyDown, onBlur: () => setActive(null), onPointerLeave: () => setActive(null) }}\n >\n {(width, height) => {\n const radius = Math.max(10, Math.min(width, height) / 2 - 6);\n const inner = donut ? radius * 0.64 : 0;\n const cx = width / 2;\n const cy = height / 2;\n let angle = 0;\n const arcs = slices.map((s, i) => {\n const sweep = total ? (s.value / total) * Math.PI * 2 : 0;\n const start = angle;\n angle += sweep;\n return { s, i, start, end: angle };\n });\n const current = active !== null ? arcs[active] : null;\n const mid = current ? (current.start + current.end) / 2 : 0;\n const [tx, ty] = current ? polar(cx, cy, (radius + inner) / 2 + (donut ? 0 : radius * 0.2), mid) : [0, 0];\n return (\n <>\n <svg className=\"kzc-svg\" width={width} height={height} viewBox={`0 0 ${width} ${height}`} aria-hidden>\n <g className=\"kzc-enter-pop\">\n {arcs.map(({ s, i, start, end }) => {\n const lift = active === i ? 4 : 0;\n const [dx, dy] = lift ? polar(0, 0, lift, (start + end) / 2) : [0, 0];\n return (\n <path\n key={s.label}\n d={arcPath(cx + dx, cy + dy, inner, radius, start, end)}\n className=\"kzc-mark\"\n data-dim={active !== null && active !== i ? \"\" : undefined}\n style={{ fill: s.color, stroke: \"var(--kzc-surface)\", strokeWidth: slices.length > 1 ? 2 : 0, strokeLinejoin: \"round\", cursor: onSelect ? \"pointer\" : undefined }}\n onPointerEnter={() => setActive(i)}\n onClick={onSelect && s.datum ? () => onSelect(s.datum!, s.index) : undefined}\n />\n );\n })}\n </g>\n {donut && (\n <g className=\"kzc-enter-fade\">\n <text x={cx} y={cy - (centerLabel ? 6 : 0)} textAnchor=\"middle\" dominantBaseline=\"central\" className=\"kzc-strong\" style={{ fontSize: Math.max(14, Math.min(26, inner / 3)) }}>\n {centerValue ?? fmt.value(total)}\n </text>\n {centerLabel && (\n <text x={cx} y={cy + Math.max(12, Math.min(20, inner / 4))} textAnchor=\"middle\" dominantBaseline=\"central\">\n {centerLabel}\n </text>\n )}\n </g>\n )}\n </svg>\n {current && <Tooltip x={tx} y={ty} width={width} title={current.s.label} rows={[{ label: fmt.share(total ? current.s.value / total : 0), value: fmt.value(current.s.value), color: current.s.color }]} />}\n </>\n );\n }}\n </ChartFrame>\n );\n}\n\n/** A pie with a hole: the total (or `centerValue`) and `centerLabel` in it. */\nexport function DonutChart<T extends Datum>(props: Omit<PieChartProps<T>, \"donut\">) {\n return <PieChart {...props} donut />;\n}\n"],"mappings":"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","names":[]}