@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.
- package/LICENSE +202 -0
- package/README.md +47 -2
- package/dist/bar.d.ts +17 -0
- package/dist/bar.js +16 -0
- package/dist/bar.js.map +1 -0
- package/dist/candlestick-IG626XL4.js +13 -0
- package/dist/candlestick-IG626XL4.js.map +1 -0
- package/dist/candlestick.d.ts +42 -0
- package/dist/candlestick.js +13 -0
- package/dist/candlestick.js.map +1 -0
- package/dist/canvas-4MSYDVFO.js +14 -0
- package/dist/canvas-4MSYDVFO.js.map +1 -0
- package/dist/cartesian-BRhNQ3ib.d.ts +34 -0
- package/dist/chart.d.ts +58 -0
- package/dist/chart.js +11 -0
- package/dist/chart.js.map +1 -0
- package/dist/chunk-3CJHBJXA.js +186 -0
- package/dist/chunk-3CJHBJXA.js.map +1 -0
- package/dist/chunk-6FINSUOS.js +49 -0
- package/dist/chunk-6FINSUOS.js.map +1 -0
- package/dist/chunk-7CSAGFBW.js +166 -0
- package/dist/chunk-7CSAGFBW.js.map +1 -0
- package/dist/chunk-7TIFCK5E.js +103 -0
- package/dist/chunk-7TIFCK5E.js.map +1 -0
- package/dist/chunk-AHQXJSAQ.js +38 -0
- package/dist/chunk-AHQXJSAQ.js.map +1 -0
- package/dist/chunk-ANUCSRUN.js +43 -0
- package/dist/chunk-ANUCSRUN.js.map +1 -0
- package/dist/chunk-BISL7DYW.js +66 -0
- package/dist/chunk-BISL7DYW.js.map +1 -0
- package/dist/chunk-BZ5CQFPH.js +19 -0
- package/dist/chunk-BZ5CQFPH.js.map +1 -0
- package/dist/chunk-DWJPPEMC.js +455 -0
- package/dist/chunk-DWJPPEMC.js.map +1 -0
- package/dist/chunk-DZB6HEVO.js +73 -0
- package/dist/chunk-DZB6HEVO.js.map +1 -0
- package/dist/chunk-EK3Z647J.js +133 -0
- package/dist/chunk-EK3Z647J.js.map +1 -0
- package/dist/chunk-I5QUZVU2.js +234 -0
- package/dist/chunk-I5QUZVU2.js.map +1 -0
- package/dist/chunk-OB5EFDDH.js +118 -0
- package/dist/chunk-OB5EFDDH.js.map +1 -0
- package/dist/chunk-OER6ETT4.js +155 -0
- package/dist/chunk-OER6ETT4.js.map +1 -0
- package/dist/chunk-PTSB3H5Q.js +205 -0
- package/dist/chunk-PTSB3H5Q.js.map +1 -0
- package/dist/chunk-TPJDOB7R.js +15 -0
- package/dist/chunk-TPJDOB7R.js.map +1 -0
- package/dist/chunk-UNK4MJVG.js +378 -0
- package/dist/chunk-UNK4MJVG.js.map +1 -0
- package/dist/chunk-VKO2OYYM.js +88 -0
- package/dist/chunk-VKO2OYYM.js.map +1 -0
- package/dist/chunk-VKTZD7UT.js +107 -0
- package/dist/chunk-VKTZD7UT.js.map +1 -0
- package/dist/chunk-W5FX4TU3.js +102 -0
- package/dist/chunk-W5FX4TU3.js.map +1 -0
- package/dist/chunk-WBH2QU77.js +178 -0
- package/dist/chunk-WBH2QU77.js.map +1 -0
- package/dist/chunk-WIRCIOLH.js +145 -0
- package/dist/chunk-WIRCIOLH.js.map +1 -0
- package/dist/chunk-XAJ3KCC4.js +116 -0
- package/dist/chunk-XAJ3KCC4.js.map +1 -0
- package/dist/chunk-XC6BQ3RN.js +92 -0
- package/dist/chunk-XC6BQ3RN.js.map +1 -0
- package/dist/chunk-XLLYNNC6.js +89 -0
- package/dist/chunk-XLLYNNC6.js.map +1 -0
- package/dist/chunk-XR4ZJDOV.js +15 -0
- package/dist/chunk-XR4ZJDOV.js.map +1 -0
- package/dist/context-BgYUymEO.d.ts +163 -0
- package/dist/frame-I7_xYGy5.d.ts +45 -0
- package/dist/full-S2MBPEEP.js +11 -0
- package/dist/full-S2MBPEEP.js.map +1 -0
- package/dist/funnel-QU5ZPC52.js +13 -0
- package/dist/funnel-QU5ZPC52.js.map +1 -0
- package/dist/funnel.d.ts +26 -0
- package/dist/funnel.js +13 -0
- package/dist/funnel.js.map +1 -0
- package/dist/gauge.d.ts +38 -0
- package/dist/gauge.js +12 -0
- package/dist/gauge.js.map +1 -0
- package/dist/geo.d.ts +37 -0
- package/dist/geo.js +13 -0
- package/dist/geo.js.map +1 -0
- package/dist/globe.d.ts +36 -0
- package/dist/globe.js +12 -0
- package/dist/globe.js.map +1 -0
- package/dist/heatmap-JWQOKUGM.js +13 -0
- package/dist/heatmap-JWQOKUGM.js.map +1 -0
- package/dist/heatmap.d.ts +47 -0
- package/dist/heatmap.js +15 -0
- package/dist/heatmap.js.map +1 -0
- package/dist/host-_F8RgiIz.d.ts +15 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/line.d.ts +25 -0
- package/dist/line.js +18 -0
- package/dist/line.js.map +1 -0
- package/dist/native.d.ts +300 -0
- package/dist/native.js +875 -0
- package/dist/native.js.map +1 -0
- package/dist/pie.d.ts +37 -0
- package/dist/pie.js +16 -0
- package/dist/pie.js.map +1 -0
- package/dist/provider.d.ts +73 -0
- package/dist/provider.js +24 -0
- package/dist/provider.js.map +1 -0
- package/dist/radar-75GRJ2IK.js +13 -0
- package/dist/radar-75GRJ2IK.js.map +1 -0
- package/dist/radar.d.ts +22 -0
- package/dist/radar.js +13 -0
- package/dist/radar.js.map +1 -0
- package/dist/sankey-XQTX2EG6.js +13 -0
- package/dist/sankey-XQTX2EG6.js.map +1 -0
- package/dist/sankey.d.ts +28 -0
- package/dist/sankey.js +13 -0
- package/dist/sankey.js.map +1 -0
- package/dist/scatter-XSNG4OKD.js +13 -0
- package/dist/scatter-XSNG4OKD.js.map +1 -0
- package/dist/scatter.d.ts +41 -0
- package/dist/scatter.js +15 -0
- package/dist/scatter.js.map +1 -0
- package/dist/sparkline.d.ts +26 -0
- package/dist/sparkline.js +15 -0
- package/dist/sparkline.js.map +1 -0
- package/dist/svg-BJUHYX3B.js +14 -0
- package/dist/svg-BJUHYX3B.js.map +1 -0
- package/dist/treemap-OMGGGB5U.js +13 -0
- package/dist/treemap-OMGGGB5U.js.map +1 -0
- package/dist/treemap.d.ts +24 -0
- package/dist/treemap.js +14 -0
- package/dist/treemap.js.map +1 -0
- package/dist/world-110m-DVFWXS42.js +18 -0
- package/dist/world-110m-DVFWXS42.js.map +1 -0
- package/package.json +518 -3
- package/registry/area-chart.json +784 -0
- package/registry/bar-chart.json +783 -0
- package/registry/calendar-heatmap.json +459 -0
- package/registry/candlestick.json +552 -0
- package/registry/chart.json +440 -0
- package/registry/charts-provider.json +190 -0
- package/registry/examples/area-chart/credits-usage.d.ts +4 -0
- package/registry/examples/area-chart/credits-usage.js +38 -0
- package/registry/examples/area-chart/credits-usage.tsx +32 -0
- package/registry/examples/area-chart/native/credits-usage.d.ts +4 -0
- package/registry/examples/area-chart/native/credits-usage.dark.png +0 -0
- package/registry/examples/area-chart/native/credits-usage.js +33 -0
- package/registry/examples/area-chart/native/credits-usage.light.png +0 -0
- package/registry/examples/area-chart/native/credits-usage.tsx +27 -0
- package/registry/examples/area-chart/storage.d.ts +4 -0
- package/registry/examples/area-chart/storage.js +27 -0
- package/registry/examples/area-chart/storage.tsx +21 -0
- package/registry/examples/bar-chart/jobs-by-status.d.ts +4 -0
- package/registry/examples/bar-chart/jobs-by-status.js +37 -0
- package/registry/examples/bar-chart/jobs-by-status.tsx +31 -0
- package/registry/examples/bar-chart/native/jobs-by-status.d.ts +4 -0
- package/registry/examples/bar-chart/native/jobs-by-status.dark.png +0 -0
- package/registry/examples/bar-chart/native/jobs-by-status.js +32 -0
- package/registry/examples/bar-chart/native/jobs-by-status.light.png +0 -0
- package/registry/examples/bar-chart/native/jobs-by-status.tsx +26 -0
- package/registry/examples/bar-chart/plans-by-quarter.d.ts +4 -0
- package/registry/examples/bar-chart/plans-by-quarter.js +31 -0
- package/registry/examples/bar-chart/plans-by-quarter.tsx +26 -0
- package/registry/examples/bar-chart/top-supporters.d.ts +4 -0
- package/registry/examples/bar-chart/top-supporters.js +32 -0
- package/registry/examples/bar-chart/top-supporters.tsx +27 -0
- package/registry/examples/calendar-heatmap/publishing.d.ts +4 -0
- package/registry/examples/calendar-heatmap/publishing.js +28 -0
- package/registry/examples/calendar-heatmap/publishing.tsx +23 -0
- package/registry/examples/candlestick/auction-bids.d.ts +4 -0
- package/registry/examples/candlestick/auction-bids.js +37 -0
- package/registry/examples/candlestick/auction-bids.tsx +32 -0
- package/registry/examples/chart/polar-uploads.d.ts +4 -0
- package/registry/examples/chart/polar-uploads.js +34 -0
- package/registry/examples/chart/polar-uploads.tsx +30 -0
- package/registry/examples/charts-provider/french-orazaka.d.ts +4 -0
- package/registry/examples/charts-provider/french-orazaka.js +26 -0
- package/registry/examples/charts-provider/french-orazaka.tsx +22 -0
- package/registry/examples/charts-provider/native/french-orazaka.d.ts +4 -0
- package/registry/examples/charts-provider/native/french-orazaka.dark.png +0 -0
- package/registry/examples/charts-provider/native/french-orazaka.js +17 -0
- package/registry/examples/charts-provider/native/french-orazaka.light.png +0 -0
- package/registry/examples/charts-provider/native/french-orazaka.tsx +17 -0
- package/registry/examples/funnel/signup.d.ts +4 -0
- package/registry/examples/funnel/signup.js +30 -0
- package/registry/examples/funnel/signup.tsx +24 -0
- package/registry/examples/gauge/challenge-goal.d.ts +4 -0
- package/registry/examples/gauge/challenge-goal.js +24 -0
- package/registry/examples/gauge/challenge-goal.tsx +18 -0
- package/registry/examples/gauge/credits-quota.d.ts +4 -0
- package/registry/examples/gauge/credits-quota.js +26 -0
- package/registry/examples/gauge/credits-quota.tsx +19 -0
- package/registry/examples/gauge/native/credits-quota.d.ts +4 -0
- package/registry/examples/gauge/native/credits-quota.dark.png +0 -0
- package/registry/examples/gauge/native/credits-quota.js +25 -0
- package/registry/examples/gauge/native/credits-quota.light.png +0 -0
- package/registry/examples/gauge/native/credits-quota.tsx +18 -0
- package/registry/examples/geo-map/viewers.d.ts +4 -0
- package/registry/examples/geo-map/viewers.js +43 -0
- package/registry/examples/geo-map/viewers.tsx +29 -0
- package/registry/examples/globe/deployment-regions.d.ts +4 -0
- package/registry/examples/globe/deployment-regions.js +36 -0
- package/registry/examples/globe/deployment-regions.tsx +30 -0
- package/registry/examples/heatmap/upload-activity.d.ts +4 -0
- package/registry/examples/heatmap/upload-activity.js +30 -0
- package/registry/examples/heatmap/upload-activity.tsx +25 -0
- package/registry/examples/line-chart/creator-earnings.d.ts +4 -0
- package/registry/examples/line-chart/creator-earnings.js +35 -0
- package/registry/examples/line-chart/creator-earnings.tsx +30 -0
- package/registry/examples/line-chart/forecast.d.ts +4 -0
- package/registry/examples/line-chart/forecast.js +36 -0
- package/registry/examples/line-chart/forecast.tsx +30 -0
- package/registry/examples/line-chart/live-jobs.d.ts +4 -0
- package/registry/examples/line-chart/live-jobs.js +21 -0
- package/registry/examples/line-chart/live-jobs.tsx +23 -0
- package/registry/examples/line-chart/native/creator-earnings.d.ts +4 -0
- package/registry/examples/line-chart/native/creator-earnings.dark.png +0 -0
- package/registry/examples/line-chart/native/creator-earnings.js +32 -0
- package/registry/examples/line-chart/native/creator-earnings.light.png +0 -0
- package/registry/examples/line-chart/native/creator-earnings.tsx +26 -0
- package/registry/examples/line-chart/states.d.ts +4 -0
- package/registry/examples/line-chart/states.js +16 -0
- package/registry/examples/line-chart/states.tsx +13 -0
- package/registry/examples/pie-chart/credit-spend.d.ts +4 -0
- package/registry/examples/pie-chart/credit-spend.js +30 -0
- package/registry/examples/pie-chart/credit-spend.tsx +24 -0
- package/registry/examples/pie-chart/native/credit-spend.d.ts +4 -0
- package/registry/examples/pie-chart/native/credit-spend.dark.png +0 -0
- package/registry/examples/pie-chart/native/credit-spend.js +17 -0
- package/registry/examples/pie-chart/native/credit-spend.light.png +0 -0
- package/registry/examples/pie-chart/native/credit-spend.tsx +12 -0
- package/registry/examples/pie-chart/storage-by-kind.d.ts +4 -0
- package/registry/examples/pie-chart/storage-by-kind.js +31 -0
- package/registry/examples/pie-chart/storage-by-kind.tsx +26 -0
- package/registry/examples/radar-chart/model-comparison.d.ts +4 -0
- package/registry/examples/radar-chart/model-comparison.js +33 -0
- package/registry/examples/radar-chart/model-comparison.tsx +28 -0
- package/registry/examples/sankey/credit-flow.d.ts +4 -0
- package/registry/examples/sankey/credit-flow.js +37 -0
- package/registry/examples/sankey/credit-flow.tsx +32 -0
- package/registry/examples/scatter-chart/watch-time.d.ts +4 -0
- package/registry/examples/scatter-chart/watch-time.js +36 -0
- package/registry/examples/scatter-chart/watch-time.tsx +31 -0
- package/registry/examples/sparkline/bars.d.ts +4 -0
- package/registry/examples/sparkline/bars.js +12 -0
- package/registry/examples/sparkline/bars.tsx +7 -0
- package/registry/examples/sparkline/native/stat-trend.d.ts +4 -0
- package/registry/examples/sparkline/native/stat-trend.dark.png +0 -0
- package/registry/examples/sparkline/native/stat-trend.js +12 -0
- package/registry/examples/sparkline/native/stat-trend.light.png +0 -0
- package/registry/examples/sparkline/native/stat-trend.tsx +7 -0
- package/registry/examples/sparkline/stat-trend.d.ts +4 -0
- package/registry/examples/sparkline/stat-trend.js +18 -0
- package/registry/examples/sparkline/stat-trend.tsx +15 -0
- package/registry/examples/sparkline/table-rows.d.ts +4 -0
- package/registry/examples/sparkline/table-rows.js +23 -0
- package/registry/examples/sparkline/table-rows.tsx +26 -0
- package/registry/examples/treemap/storage.d.ts +4 -0
- package/registry/examples/treemap/storage.js +17 -0
- package/registry/examples/treemap/storage.tsx +13 -0
- package/registry/funnel.json +486 -0
- package/registry/gauge.json +599 -0
- package/registry/geo-map.json +483 -0
- package/registry/globe.json +468 -0
- package/registry/heatmap.json +530 -0
- package/registry/index.json +420 -0
- package/registry/line-chart.json +792 -0
- package/registry/pie-chart.json +1287 -0
- package/registry/radar-chart.json +458 -0
- package/registry/sankey.json +502 -0
- package/registry/scatter-chart.json +558 -0
- package/registry/sparkline.json +562 -0
- 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":[]}
|