@sckyzo/cosmos-charts 0.2.0 → 0.3.1
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/CHANGELOG.md +98 -0
- package/README.md +107 -22
- package/THIRD_PARTY_NOTICES.md +81 -0
- package/dist/BarChart.cjs +137 -0
- package/dist/BarChart.cjs.map +1 -0
- package/dist/BarChart.js +135 -0
- package/dist/BarChart.js.map +1 -0
- package/dist/BoxPlotChart.cjs +214 -0
- package/dist/BoxPlotChart.cjs.map +1 -0
- package/dist/BoxPlotChart.js +212 -0
- package/dist/BoxPlotChart.js.map +1 -0
- package/dist/BumpChart.cjs +328 -0
- package/dist/BumpChart.cjs.map +1 -0
- package/dist/BumpChart.js +326 -0
- package/dist/BumpChart.js.map +1 -0
- package/dist/CalendarChart.cjs +218 -0
- package/dist/CalendarChart.cjs.map +1 -0
- package/dist/CalendarChart.js +216 -0
- package/dist/CalendarChart.js.map +1 -0
- package/dist/DonutChart.cjs +122 -0
- package/dist/DonutChart.cjs.map +1 -0
- package/dist/DonutChart.js +120 -0
- package/dist/DonutChart.js.map +1 -0
- package/dist/FunnelChart.cjs +424 -0
- package/dist/FunnelChart.cjs.map +1 -0
- package/dist/FunnelChart.js +422 -0
- package/dist/FunnelChart.js.map +1 -0
- package/dist/GradientRadial.cjs +51 -0
- package/dist/GradientRadial.cjs.map +1 -0
- package/dist/GradientRadial.js +49 -0
- package/dist/GradientRadial.js.map +1 -0
- package/dist/HeatmapChart.cjs +148 -0
- package/dist/HeatmapChart.cjs.map +1 -0
- package/dist/HeatmapChart.js +146 -0
- package/dist/HeatmapChart.js.map +1 -0
- package/dist/LineAreaChart.cjs +98 -0
- package/dist/LineAreaChart.cjs.map +1 -0
- package/dist/LineAreaChart.js +96 -0
- package/dist/LineAreaChart.js.map +1 -0
- package/dist/MixedChart.cjs +195 -0
- package/dist/MixedChart.cjs.map +1 -0
- package/dist/MixedChart.js +193 -0
- package/dist/MixedChart.js.map +1 -0
- package/dist/RadarChart.cjs +109 -0
- package/dist/RadarChart.cjs.map +1 -0
- package/dist/RadarChart.js +107 -0
- package/dist/RadarChart.js.map +1 -0
- package/dist/RadialBarChart.cjs +99 -0
- package/dist/RadialBarChart.cjs.map +1 -0
- package/dist/RadialBarChart.js +97 -0
- package/dist/RadialBarChart.js.map +1 -0
- package/dist/RangeBarChart.cjs +218 -0
- package/dist/RangeBarChart.cjs.map +1 -0
- package/dist/RangeBarChart.js +216 -0
- package/dist/RangeBarChart.js.map +1 -0
- package/dist/RealtimeChart.cjs +173 -0
- package/dist/RealtimeChart.cjs.map +1 -0
- package/dist/RealtimeChart.js +171 -0
- package/dist/RealtimeChart.js.map +1 -0
- package/dist/ScatterChart.cjs +134 -0
- package/dist/ScatterChart.cjs.map +1 -0
- package/dist/ScatterChart.js +131 -0
- package/dist/ScatterChart.js.map +1 -0
- package/dist/SemiCircleGauge.cjs +142 -0
- package/dist/SemiCircleGauge.cjs.map +1 -0
- package/dist/SemiCircleGauge.js +140 -0
- package/dist/SemiCircleGauge.js.map +1 -0
- package/dist/Sparkline.cjs +117 -0
- package/dist/Sparkline.cjs.map +1 -0
- package/dist/Sparkline.js +115 -0
- package/dist/Sparkline.js.map +1 -0
- package/dist/StrokedGauge.cjs +94 -0
- package/dist/StrokedGauge.cjs.map +1 -0
- package/dist/StrokedGauge.js +92 -0
- package/dist/StrokedGauge.js.map +1 -0
- package/dist/TreemapChart.cjs +263 -0
- package/dist/TreemapChart.cjs.map +1 -0
- package/dist/TreemapChart.js +261 -0
- package/dist/TreemapChart.js.map +1 -0
- package/dist/WaterfallChart.cjs +235 -0
- package/dist/WaterfallChart.cjs.map +1 -0
- package/dist/WaterfallChart.js +233 -0
- package/dist/WaterfallChart.js.map +1 -0
- package/dist/WorldMap.cjs +2088 -0
- package/dist/WorldMap.cjs.map +1 -0
- package/dist/WorldMap.js +603 -0
- package/dist/WorldMap.js.map +1 -0
- package/dist/chunk-4AKHZNWP.js +127 -0
- package/dist/chunk-4AKHZNWP.js.map +1 -0
- package/dist/chunk-56WD3OHA.js +62 -0
- package/dist/chunk-56WD3OHA.js.map +1 -0
- package/dist/chunk-5C2RLJYZ.js +11 -0
- package/dist/chunk-5C2RLJYZ.js.map +1 -0
- package/dist/chunk-6FGJZBTC.js +61 -0
- package/dist/chunk-6FGJZBTC.js.map +1 -0
- package/dist/chunk-AAYHG7YK.cjs +30 -0
- package/dist/chunk-AAYHG7YK.cjs.map +1 -0
- package/dist/chunk-AOSO5QHL.js +19 -0
- package/dist/chunk-AOSO5QHL.js.map +1 -0
- package/dist/chunk-BFEDLXVW.js +12 -0
- package/dist/chunk-BFEDLXVW.js.map +1 -0
- package/dist/chunk-CMBNNLIV.cjs +63 -0
- package/dist/chunk-CMBNNLIV.cjs.map +1 -0
- package/dist/chunk-DBZQI4HM.cjs +14 -0
- package/dist/chunk-DBZQI4HM.cjs.map +1 -0
- package/dist/chunk-DI2FBGKM.js +44 -0
- package/dist/chunk-DI2FBGKM.js.map +1 -0
- package/dist/chunk-ESVGCUJN.js +58 -0
- package/dist/chunk-ESVGCUJN.js.map +1 -0
- package/dist/chunk-F2PMFYJ5.cjs +40 -0
- package/dist/chunk-F2PMFYJ5.cjs.map +1 -0
- package/dist/chunk-FUZW3H5X.js +38 -0
- package/dist/chunk-FUZW3H5X.js.map +1 -0
- package/dist/chunk-G4WOPX4D.cjs +51 -0
- package/dist/chunk-G4WOPX4D.cjs.map +1 -0
- package/dist/chunk-GDZLYYL2.js +27 -0
- package/dist/chunk-GDZLYYL2.js.map +1 -0
- package/dist/chunk-GVTMZYOM.cjs +30 -0
- package/dist/chunk-GVTMZYOM.cjs.map +1 -0
- package/dist/chunk-IW6EWB26.cjs +21 -0
- package/dist/chunk-IW6EWB26.cjs.map +1 -0
- package/dist/chunk-K3R7JGOW.cjs +18 -0
- package/dist/chunk-K3R7JGOW.cjs.map +1 -0
- package/dist/chunk-LT3UC65O.cjs +68 -0
- package/dist/chunk-LT3UC65O.cjs.map +1 -0
- package/dist/chunk-OCD6JIIX.js +23 -0
- package/dist/chunk-OCD6JIIX.js.map +1 -0
- package/dist/chunk-RCDTBZC2.cjs +131 -0
- package/dist/chunk-RCDTBZC2.cjs.map +1 -0
- package/dist/chunk-SR2ESX3R.cjs +66 -0
- package/dist/chunk-SR2ESX3R.cjs.map +1 -0
- package/dist/chunk-T3K6Q34U.cjs +82 -0
- package/dist/chunk-T3K6Q34U.cjs.map +1 -0
- package/dist/chunk-TYQWZR3S.cjs +13 -0
- package/dist/chunk-TYQWZR3S.cjs.map +1 -0
- package/dist/chunk-UATB2IRY.js +51 -0
- package/dist/chunk-UATB2IRY.js.map +1 -0
- package/dist/chunk-UJIV5PVI.js +13 -0
- package/dist/chunk-UJIV5PVI.js.map +1 -0
- package/dist/chunk-UL3SNXQR.js +280 -0
- package/dist/chunk-UL3SNXQR.js.map +1 -0
- package/dist/chunk-URNXLVSK.js +44 -0
- package/dist/chunk-URNXLVSK.js.map +1 -0
- package/dist/chunk-VIOSQN4Z.js +79 -0
- package/dist/chunk-VIOSQN4Z.js.map +1 -0
- package/dist/chunk-X3YZP5WF.cjs +46 -0
- package/dist/chunk-X3YZP5WF.cjs.map +1 -0
- package/dist/chunk-XMFBVMW7.cjs +30 -0
- package/dist/chunk-XMFBVMW7.cjs.map +1 -0
- package/dist/chunk-XUGQ6VXO.cjs +60 -0
- package/dist/chunk-XUGQ6VXO.cjs.map +1 -0
- package/dist/chunk-YMRNTIOK.js +27 -0
- package/dist/chunk-YMRNTIOK.js.map +1 -0
- package/dist/chunk-ZWHCUYIN.cjs +282 -0
- package/dist/chunk-ZWHCUYIN.cjs.map +1 -0
- package/dist/index.cjs +154 -1166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +470 -80
- package/dist/index.d.ts +470 -80
- package/dist/index.js +23 -1144
- package/dist/index.js.map +1 -1
- package/dist/theme.cjs +88 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.js +84 -0
- package/dist/theme.js.map +1 -0
- package/package.json +23 -12
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { HeatGrid } from './chunk-UL3SNXQR.js';
|
|
3
|
+
import './chunk-BFEDLXVW.js';
|
|
4
|
+
import { TEXT_SIZE } from './chunk-DI2FBGKM.js';
|
|
5
|
+
import './chunk-VIOSQN4Z.js';
|
|
6
|
+
import { ChartEmpty } from './chunk-56WD3OHA.js';
|
|
7
|
+
import { MUTED, CALENDAR_DARK_CLASS, TEXT, CALENDAR_STEPS } from './chunk-UATB2IRY.js';
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
// src/calendarGrid.ts
|
|
11
|
+
var DAY_MS = 864e5;
|
|
12
|
+
var mod = (n, by) => (n % by + by) % by;
|
|
13
|
+
var parseDay = (iso) => {
|
|
14
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
15
|
+
if (!match) return void 0;
|
|
16
|
+
const [year, month, date] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
17
|
+
const time = Date.UTC(year, month - 1, date);
|
|
18
|
+
const check = new Date(time);
|
|
19
|
+
return check.getUTCFullYear() === year && check.getUTCMonth() === month - 1 && check.getUTCDate() === date ? time / DAY_MS : void 0;
|
|
20
|
+
};
|
|
21
|
+
var dayOf = (year, month, date) => Date.UTC(year, month, date) / DAY_MS;
|
|
22
|
+
var weekdayOf = (day) => mod(day + 4, 7);
|
|
23
|
+
var partsOf = (day) => {
|
|
24
|
+
const date = new Date(day * DAY_MS);
|
|
25
|
+
return { year: date.getUTCFullYear(), month: date.getUTCMonth(), date: date.getUTCDate() };
|
|
26
|
+
};
|
|
27
|
+
var rowOf = (day, weekStart) => mod(weekdayOf(day) - weekStart, 7);
|
|
28
|
+
var weekGrid = (from, to, weekStart) => {
|
|
29
|
+
if (to < from) return { start: from, weeks: 0, days: [] };
|
|
30
|
+
const start = from - rowOf(from, weekStart);
|
|
31
|
+
const days = Array.from({ length: to - from + 1 }, (_, i) => {
|
|
32
|
+
const day = from + i;
|
|
33
|
+
return { day, row: rowOf(day, weekStart), column: Math.floor((day - start) / 7) };
|
|
34
|
+
});
|
|
35
|
+
return { start, weeks: Math.floor((to - start) / 7) + 1, days };
|
|
36
|
+
};
|
|
37
|
+
var yearSpans = (from, to) => {
|
|
38
|
+
const spans = [];
|
|
39
|
+
if (to < from) return spans;
|
|
40
|
+
for (let year = partsOf(from).year; year <= partsOf(to).year; year++) {
|
|
41
|
+
spans.push({
|
|
42
|
+
year,
|
|
43
|
+
from: Math.max(from, dayOf(year, 0, 1)),
|
|
44
|
+
to: Math.min(to, dayOf(year, 11, 31))
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return spans;
|
|
48
|
+
};
|
|
49
|
+
var MIN_MARK_GAP = 3;
|
|
50
|
+
var monthMarks = (grid) => {
|
|
51
|
+
if (grid.weeks === 0) return [];
|
|
52
|
+
const marks = [];
|
|
53
|
+
for (let column = 0; column < grid.weeks; column++) {
|
|
54
|
+
const { month, date } = partsOf(grid.start + column * 7);
|
|
55
|
+
if (date <= 7) marks.push({ column, month });
|
|
56
|
+
}
|
|
57
|
+
const first = grid.days[0];
|
|
58
|
+
const leading = partsOf(first.day).month;
|
|
59
|
+
const next = marks[0];
|
|
60
|
+
if (!next || next.month !== leading && next.column >= MIN_MARK_GAP) {
|
|
61
|
+
marks.unshift({ column: 0, month: leading });
|
|
62
|
+
}
|
|
63
|
+
return marks;
|
|
64
|
+
};
|
|
65
|
+
var stepOf = (value, max, steps = 4) => !(value > 0) || !(max > 0) ? 0 : Math.min(steps, Math.max(1, Math.ceil(value / max * steps)));
|
|
66
|
+
var cellMetrics = (weeks) => weeks <= 26 ? { size: 18, gap: 4, radius: 4 } : { size: 12, gap: 3, radius: 3 };
|
|
67
|
+
var collectDays = (data) => {
|
|
68
|
+
const values = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const { day, value } of data) {
|
|
70
|
+
const n = parseDay(day);
|
|
71
|
+
if (n === void 0 || !Number.isFinite(value)) continue;
|
|
72
|
+
values.set(n, (values.get(n) ?? 0) + value);
|
|
73
|
+
}
|
|
74
|
+
if (values.size === 0) return void 0;
|
|
75
|
+
const days = [...values.keys()];
|
|
76
|
+
return { values, first: Math.min(...days), last: Math.max(...days) };
|
|
77
|
+
};
|
|
78
|
+
var EMPTY_HEIGHT = 120;
|
|
79
|
+
var NAMED_WEEKDAYS = [1, 3, 5];
|
|
80
|
+
var CalendarChart = ({
|
|
81
|
+
data,
|
|
82
|
+
from,
|
|
83
|
+
to,
|
|
84
|
+
layout = "calendar",
|
|
85
|
+
weekStart = 1,
|
|
86
|
+
formatValue = String,
|
|
87
|
+
locale = "en-US",
|
|
88
|
+
lessLabel = "Less",
|
|
89
|
+
moreLabel = "More",
|
|
90
|
+
emptyMessage = "No data",
|
|
91
|
+
ariaLabel,
|
|
92
|
+
width
|
|
93
|
+
}) => {
|
|
94
|
+
const name = ariaLabel ?? "Calendar chart";
|
|
95
|
+
const days = collectDays(data);
|
|
96
|
+
if (!days) {
|
|
97
|
+
return /* @__PURE__ */ jsx(ChartEmpty, { message: emptyMessage, ariaLabel: name, height: EMPTY_HEIGHT, width });
|
|
98
|
+
}
|
|
99
|
+
const given = {
|
|
100
|
+
from: from === void 0 ? void 0 : parseDay(from),
|
|
101
|
+
to: to === void 0 ? void 0 : parseDay(to)
|
|
102
|
+
};
|
|
103
|
+
const calendar = layout === "calendar";
|
|
104
|
+
const start = given.from ?? (calendar ? dayOf(partsOf(days.first).year, 0, 1) : days.first);
|
|
105
|
+
const end = given.to ?? (calendar ? dayOf(partsOf(days.last).year, 11, 31) : days.last);
|
|
106
|
+
const blocks = calendar ? yearSpans(start, end) : end >= start ? [{ year: void 0, from: start, to: end }] : [];
|
|
107
|
+
if (blocks.length === 0) {
|
|
108
|
+
return /* @__PURE__ */ jsx(ChartEmpty, { message: emptyMessage, ariaLabel: name, height: EMPTY_HEIGHT, width });
|
|
109
|
+
}
|
|
110
|
+
const month = new Intl.DateTimeFormat(locale, { month: "short", timeZone: "UTC" });
|
|
111
|
+
const date = new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" });
|
|
112
|
+
const weekday = new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" });
|
|
113
|
+
const monthName = (m) => month.format(Date.UTC(2024, m, 1));
|
|
114
|
+
const weekdayName = (d) => weekday.format(Date.UTC(2024, 0, 7 + d));
|
|
115
|
+
const rows = Array.from({ length: 7 }, (_, row) => {
|
|
116
|
+
const d = (row + weekStart) % 7;
|
|
117
|
+
return NAMED_WEEKDAYS.includes(d) ? weekdayName(d) : "";
|
|
118
|
+
});
|
|
119
|
+
const max = Math.max(0, ...days.values.values());
|
|
120
|
+
const color = (value) => CALENDAR_STEPS[stepOf(value, max)];
|
|
121
|
+
const grids = blocks.map((block) => {
|
|
122
|
+
const grid = weekGrid(block.from, block.to, weekStart);
|
|
123
|
+
const metrics2 = cellMetrics(grid.weeks);
|
|
124
|
+
const columns = Array.from({ length: grid.weeks }, () => "");
|
|
125
|
+
for (const mark of monthMarks(grid)) columns[mark.column] = monthName(mark.month);
|
|
126
|
+
const cells = grid.days.map((d) => {
|
|
127
|
+
const value = days.values.get(d.day) ?? 0;
|
|
128
|
+
return {
|
|
129
|
+
row: d.row,
|
|
130
|
+
column: d.column,
|
|
131
|
+
color: color(value),
|
|
132
|
+
title: date.format(d.day * DAY_MS),
|
|
133
|
+
label: "",
|
|
134
|
+
value: formatValue(value)
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
const label = block.year !== void 0 && blocks.length > 1 ? `${name}, ${block.year}` : name;
|
|
138
|
+
return { block, grid, metrics: metrics2, columns, cells, label };
|
|
139
|
+
});
|
|
140
|
+
const metrics = grids[0].metrics;
|
|
141
|
+
return /* @__PURE__ */ jsxs(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
className: CALENDAR_DARK_CLASS,
|
|
145
|
+
style: {
|
|
146
|
+
// Time keeps its order under `dir="rtl"`, as a chart's axis does.
|
|
147
|
+
direction: "ltr",
|
|
148
|
+
display: "flex",
|
|
149
|
+
flexDirection: "column",
|
|
150
|
+
gap: "0.75rem",
|
|
151
|
+
// As wide as the grid, so the scale lines up with its right edge.
|
|
152
|
+
width: width ?? "fit-content",
|
|
153
|
+
maxWidth: "100%",
|
|
154
|
+
minWidth: 0,
|
|
155
|
+
fontSize: TEXT_SIZE,
|
|
156
|
+
color: MUTED
|
|
157
|
+
},
|
|
158
|
+
children: [
|
|
159
|
+
grids.map(({ block, metrics: m, columns, cells, label }) => /* @__PURE__ */ jsxs("div", { "data-calendar-block": "", style: { minWidth: 0 }, children: [
|
|
160
|
+
blocks.length > 1 && /* @__PURE__ */ jsx("p", { style: { margin: "0 0 0.25rem", fontWeight: 600, color: TEXT }, children: block.year }),
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
HeatGrid,
|
|
163
|
+
{
|
|
164
|
+
rows,
|
|
165
|
+
columns,
|
|
166
|
+
cells,
|
|
167
|
+
cellHeight: m.size,
|
|
168
|
+
columnWidth: m.size,
|
|
169
|
+
gap: m.gap,
|
|
170
|
+
cellRadius: m.radius,
|
|
171
|
+
labelsAbove: true,
|
|
172
|
+
thinLabels: false,
|
|
173
|
+
ariaLabel: label
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }, block.year ?? "range")),
|
|
177
|
+
/* @__PURE__ */ jsxs(
|
|
178
|
+
"div",
|
|
179
|
+
{
|
|
180
|
+
"data-calendar-legend": "",
|
|
181
|
+
"aria-hidden": true,
|
|
182
|
+
style: {
|
|
183
|
+
display: "flex",
|
|
184
|
+
alignItems: "center",
|
|
185
|
+
justifyContent: "flex-end",
|
|
186
|
+
// The grid sits in a box padded by 3px, room for its focus rings.
|
|
187
|
+
paddingRight: 3,
|
|
188
|
+
gap: metrics.gap
|
|
189
|
+
},
|
|
190
|
+
children: [
|
|
191
|
+
/* @__PURE__ */ jsx("span", { style: { marginRight: 4 }, children: lessLabel }),
|
|
192
|
+
CALENDAR_STEPS.map((step, i) => /* @__PURE__ */ jsx(
|
|
193
|
+
"span",
|
|
194
|
+
{
|
|
195
|
+
"data-calendar-step": "",
|
|
196
|
+
style: {
|
|
197
|
+
width: metrics.size,
|
|
198
|
+
height: metrics.size,
|
|
199
|
+
borderRadius: metrics.radius,
|
|
200
|
+
background: step
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
i
|
|
204
|
+
)),
|
|
205
|
+
/* @__PURE__ */ jsx("span", { style: { marginLeft: 4 }, children: moreLabel })
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export { CalendarChart };
|
|
215
|
+
//# sourceMappingURL=CalendarChart.js.map
|
|
216
|
+
//# sourceMappingURL=CalendarChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/calendarGrid.ts","../src/CalendarChart.tsx"],"names":["metrics"],"mappings":"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","file":"CalendarChart.js","sourcesContent":["// The date maths of `CalendarChart`. A day is a whole number: the days since\n// 1970-01-01 in UTC, so nothing here reads the time zone of the machine or the\n// date of today.\n\nexport const DAY_MS = 86_400_000;\n\n/** The weekday a week starts on: 0 is Sunday, 1 is Monday. */\nexport type WeekStart = 0 | 1;\n\nconst mod = (n: number, by: number) => ((n % by) + by) % by;\n\n/** The day number of an ISO date (`2026-03-14`), or `undefined` when it is not a real date. */\nexport const parseDay = (iso: string): number | undefined => {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (!match) return undefined;\n const [year, month, date] = [Number(match[1]), Number(match[2]), Number(match[3])];\n const time = Date.UTC(year, month - 1, date);\n const check = new Date(time);\n // `Date.UTC` rolls 02-30 over to 03-02: a date that moved is not a date.\n return check.getUTCFullYear() === year &&\n check.getUTCMonth() === month - 1 &&\n check.getUTCDate() === date\n ? time / DAY_MS\n : undefined;\n};\n\n/** The day number of a date: `month` is 0 to 11. */\nexport const dayOf = (year: number, month: number, date: number): number =>\n Date.UTC(year, month, date) / DAY_MS;\n\n/** The ISO date of a day number. */\nexport const formatDay = (day: number): string => new Date(day * DAY_MS).toISOString().slice(0, 10);\n\n/** The weekday of a day number, 0 (Sunday) to 6. 1970-01-01 was a Thursday. */\nexport const weekdayOf = (day: number): number => mod(day + 4, 7);\n\n/** The year, month (0 to 11) and day of the month of a day number. */\nexport const partsOf = (day: number) => {\n const date = new Date(day * DAY_MS);\n return { year: date.getUTCFullYear(), month: date.getUTCMonth(), date: date.getUTCDate() };\n};\n\n/** The row of a day in a week that starts on `weekStart`: 0 for the first day of the week. */\nexport const rowOf = (day: number, weekStart: WeekStart): number =>\n mod(weekdayOf(day) - weekStart, 7);\n\nexport type GridDay = { day: number; row: number; column: number };\n\nexport type WeekGrid = {\n /** The first day of the first column: on or before `from`. */\n start: number;\n /** The number of columns. 0 when the range is empty. */\n weeks: number;\n /** Every day of the range, in order. The slots before `from` and after `to` have no day. */\n days: GridDay[];\n};\n\n/** The days from `from` to `to` (inclusive) laid out as weeks in columns and weekdays in rows. */\nexport const weekGrid = (from: number, to: number, weekStart: WeekStart): WeekGrid => {\n if (to < from) return { start: from, weeks: 0, days: [] };\n const start = from - rowOf(from, weekStart);\n const days = Array.from({ length: to - from + 1 }, (_, i): GridDay => {\n const day = from + i;\n return { day, row: rowOf(day, weekStart), column: Math.floor((day - start) / 7) };\n });\n return { start, weeks: Math.floor((to - start) / 7) + 1, days };\n};\n\nexport type YearSpan = { year: number; from: number; to: number };\n\n/** `from` to `to` cut at the turn of each year: one block of the `calendar` layout per span. */\nexport const yearSpans = (from: number, to: number): YearSpan[] => {\n const spans: YearSpan[] = [];\n if (to < from) return spans;\n for (let year = partsOf(from).year; year <= partsOf(to).year; year++) {\n spans.push({\n year,\n from: Math.max(from, dayOf(year, 0, 1)),\n to: Math.min(to, dayOf(year, 11, 31)),\n });\n }\n return spans;\n};\n\nexport type MonthMark = { column: number; month: number };\n\n// Room a month name needs, in columns: two months closer than this would\n// print over each other.\nconst MIN_MARK_GAP = 3;\n\n/**\n * Where the month names go: above the first full week of each month, that is\n * the first column whose first day falls in the first seven days of the\n * month. A range that starts in the middle of a month also names that month\n * above its first column, unless the next name is too close to leave room.\n */\nexport const monthMarks = (grid: WeekGrid): MonthMark[] => {\n if (grid.weeks === 0) return [];\n const marks: MonthMark[] = [];\n for (let column = 0; column < grid.weeks; column++) {\n const { month, date } = partsOf(grid.start + column * 7);\n if (date <= 7) marks.push({ column, month });\n }\n const first = grid.days[0];\n const leading = partsOf(first.day).month;\n const next = marks[0];\n if (!next || (next.month !== leading && next.column >= MIN_MARK_GAP)) {\n marks.unshift({ column: 0, month: leading });\n }\n return marks;\n};\n\n/** The 0-based step a value takes on the scale: 0 is empty, 1 to `steps` split `(0, max]` evenly. */\nexport const stepOf = (value: number, max: number, steps = 4): number =>\n !(value > 0) || !(max > 0) ? 0 : Math.min(steps, Math.max(1, Math.ceil((value / max) * steps)));\n\n/** How big the cells are: a short range gets larger ones, a long one fits more weeks on screen. */\nexport const cellMetrics = (weeks: number) =>\n weeks <= 26 ? { size: 18, gap: 4, radius: 4 } : { size: 12, gap: 3, radius: 3 };\n\nexport type DayValues = {\n /** The summed value of each valid day. */\n values: Map<number, number>;\n first: number;\n last: number;\n};\n\n/** The valid days of `data` by day number, a day listed twice summed. `undefined` when none is valid. */\nexport const collectDays = (\n data: readonly { day: string; value: number }[]\n): DayValues | undefined => {\n const values = new Map<number, number>();\n for (const { day, value } of data) {\n const n = parseDay(day);\n if (n === undefined || !Number.isFinite(value)) continue;\n values.set(n, (values.get(n) ?? 0) + value);\n }\n if (values.size === 0) return undefined;\n const days = [...values.keys()];\n return { values, first: Math.min(...days), last: Math.max(...days) };\n};\n","import { TEXT_SIZE } from './core/axes';\nimport { CALENDAR_DARK_CLASS, CALENDAR_STEPS, MUTED, TEXT } from './core/colors';\nimport { ChartEmpty } from './core/frame';\nimport { HeatGrid, type HeatCell } from './core/HeatGrid';\nimport {\n cellMetrics,\n collectDays,\n DAY_MS,\n dayOf,\n monthMarks,\n parseDay,\n partsOf,\n stepOf,\n weekGrid,\n yearSpans,\n type WeekStart,\n} from './calendarGrid';\nimport type { ChartEmptyProps, ChartProps } from './types';\n\nexport type CalendarDay = {\n /** The date, `YYYY-MM-DD`. A day listed twice adds up; anything else is ignored. */\n day: string;\n value: number;\n};\n\nexport type CalendarChartProps = Omit<ChartProps, 'children' | 'height'> &\n ChartEmptyProps & {\n data: CalendarDay[];\n /**\n * First day shown, `YYYY-MM-DD`. Default: the first day of the earliest\n * year of `data` in the `calendar` layout, the earliest day in `range`.\n */\n from?: string;\n /** Last day shown, `YYYY-MM-DD`. Default: the end of the latest year, or the latest day in `range`. */\n to?: string;\n /**\n * `calendar` draws one block per year, with the months named above their\n * first full week. `range` draws one continuous grid from `from` to `to`,\n * with larger cells when the range is short. Default `calendar`.\n */\n layout?: 'calendar' | 'range';\n /** The weekday a week starts on: 1 for Monday (default), 0 for Sunday. */\n weekStart?: WeekStart;\n /** Format the value in the tooltip and in the name of each day. */\n formatValue?: (value: number) => string;\n /** Text at the low end of the scale under the grid. Default `Less`. */\n lessLabel?: string;\n /** Text at the high end of the scale under the grid. Default `More`. */\n moreLabel?: string;\n /** The language of the month, weekday and date names (BCP 47). Default `en-US`. */\n locale?: string;\n };\n\n// Height of the empty state, which has no grid to take one from.\nconst EMPTY_HEIGHT = 120;\n\n// Tuesday, Thursday and the weekend carry no name: a name every second row\n// leaves each one room.\nconst NAMED_WEEKDAYS = [1, 3, 5];\n\ntype Block = { year: number | undefined; from: number; to: number };\n\nexport const CalendarChart = ({\n data,\n from,\n to,\n layout = 'calendar',\n weekStart = 1,\n formatValue = String,\n locale = 'en-US',\n lessLabel = 'Less',\n moreLabel = 'More',\n emptyMessage = 'No data',\n ariaLabel,\n width,\n}: CalendarChartProps) => {\n const name = ariaLabel ?? 'Calendar chart';\n const days = collectDays(data);\n if (!days) {\n return (\n <ChartEmpty message={emptyMessage} ariaLabel={name} height={EMPTY_HEIGHT} width={width} />\n );\n }\n const given = {\n from: from === undefined ? undefined : parseDay(from),\n to: to === undefined ? undefined : parseDay(to),\n };\n const calendar = layout === 'calendar';\n const start = given.from ?? (calendar ? dayOf(partsOf(days.first).year, 0, 1) : days.first);\n const end = given.to ?? (calendar ? dayOf(partsOf(days.last).year, 11, 31) : days.last);\n const blocks: Block[] = calendar\n ? yearSpans(start, end)\n : end >= start\n ? [{ year: undefined, from: start, to: end }]\n : [];\n if (blocks.length === 0) {\n return (\n <ChartEmpty message={emptyMessage} ariaLabel={name} height={EMPTY_HEIGHT} width={width} />\n );\n }\n\n const month = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });\n const date = new Intl.DateTimeFormat(locale, { dateStyle: 'full', timeZone: 'UTC' });\n const weekday = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });\n const monthName = (m: number) => month.format(Date.UTC(2024, m, 1));\n // 2024-01-07 was a Sunday.\n const weekdayName = (d: number) => weekday.format(Date.UTC(2024, 0, 7 + d));\n const rows = Array.from({ length: 7 }, (_, row) => {\n const d = (row + weekStart) % 7;\n return NAMED_WEEKDAYS.includes(d) ? weekdayName(d) : '';\n });\n\n // One scale for every block, so two years compare.\n const max = Math.max(0, ...days.values.values());\n const color = (value: number) => CALENDAR_STEPS[stepOf(value, max)];\n\n const grids = blocks.map((block) => {\n const grid = weekGrid(block.from, block.to, weekStart);\n const metrics = cellMetrics(grid.weeks);\n const columns = Array.from({ length: grid.weeks }, () => '');\n for (const mark of monthMarks(grid)) columns[mark.column] = monthName(mark.month);\n const cells = grid.days.map((d): HeatCell => {\n const value = days.values.get(d.day) ?? 0;\n return {\n row: d.row,\n column: d.column,\n color: color(value),\n title: date.format(d.day * DAY_MS),\n label: '',\n value: formatValue(value),\n };\n });\n const label = block.year !== undefined && blocks.length > 1 ? `${name}, ${block.year}` : name;\n return { block, grid, metrics, columns, cells, label };\n });\n\n const metrics = grids[0].metrics;\n return (\n <div\n className={CALENDAR_DARK_CLASS}\n style={{\n // Time keeps its order under `dir=\"rtl\"`, as a chart's axis does.\n direction: 'ltr',\n display: 'flex',\n flexDirection: 'column',\n gap: '0.75rem',\n // As wide as the grid, so the scale lines up with its right edge.\n width: width ?? 'fit-content',\n maxWidth: '100%',\n minWidth: 0,\n fontSize: TEXT_SIZE,\n color: MUTED,\n }}\n >\n {grids.map(({ block, metrics: m, columns, cells, label }) => (\n <div key={block.year ?? 'range'} data-calendar-block=\"\" style={{ minWidth: 0 }}>\n {blocks.length > 1 && (\n <p style={{ margin: '0 0 0.25rem', fontWeight: 600, color: TEXT }}>{block.year}</p>\n )}\n <HeatGrid\n rows={rows}\n columns={columns}\n cells={cells}\n cellHeight={m.size}\n columnWidth={m.size}\n gap={m.gap}\n cellRadius={m.radius}\n labelsAbove\n thinLabels={false}\n ariaLabel={label}\n />\n </div>\n ))}\n <div\n data-calendar-legend=\"\"\n aria-hidden\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n // The grid sits in a box padded by 3px, room for its focus rings.\n paddingRight: 3,\n gap: metrics.gap,\n }}\n >\n <span style={{ marginRight: 4 }}>{lessLabel}</span>\n {CALENDAR_STEPS.map((step, i) => (\n <span\n key={i}\n data-calendar-step=\"\"\n style={{\n width: metrics.size,\n height: metrics.size,\n borderRadius: metrics.radius,\n background: step,\n }}\n />\n ))}\n <span style={{ marginLeft: 4 }}>{moreLabel}</span>\n </div>\n </div>\n );\n};\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkRCDTBZC2_cjs = require('./chunk-RCDTBZC2.cjs');
|
|
5
|
+
var chunkX3YZP5WF_cjs = require('./chunk-X3YZP5WF.cjs');
|
|
6
|
+
var chunkTYQWZR3S_cjs = require('./chunk-TYQWZR3S.cjs');
|
|
7
|
+
require('./chunk-G4WOPX4D.cjs');
|
|
8
|
+
var chunkT3K6Q34U_cjs = require('./chunk-T3K6Q34U.cjs');
|
|
9
|
+
var chunkK3R7JGOW_cjs = require('./chunk-K3R7JGOW.cjs');
|
|
10
|
+
var chunkSR2ESX3R_cjs = require('./chunk-SR2ESX3R.cjs');
|
|
11
|
+
var chunkLT3UC65O_cjs = require('./chunk-LT3UC65O.cjs');
|
|
12
|
+
var react = require('react');
|
|
13
|
+
var recharts = require('recharts');
|
|
14
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
15
|
+
|
|
16
|
+
// src/core/shares.ts
|
|
17
|
+
var shares = (values) => {
|
|
18
|
+
const parts = values.map((v) => Math.max(0, v));
|
|
19
|
+
const total = parts.reduce((sum, v) => sum + v, 0);
|
|
20
|
+
if (total === 0) return parts.map(() => 0);
|
|
21
|
+
const exact = parts.map((v) => v / total * 100);
|
|
22
|
+
const whole = exact.map(Math.floor);
|
|
23
|
+
const byRemainder = exact.map((v, i) => ({ i, rest: v - whole[i] })).sort((a, b) => b.rest - a.rest || a.i - b.i);
|
|
24
|
+
const missing = 100 - whole.reduce((sum, v) => sum + v, 0);
|
|
25
|
+
for (let k = 0; k < missing; k++) whole[byRemainder[k].i]++;
|
|
26
|
+
return whole;
|
|
27
|
+
};
|
|
28
|
+
var PieLegend = ({ payload, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
29
|
+
chunkRCDTBZC2_cjs.ChartLegend,
|
|
30
|
+
{
|
|
31
|
+
...props,
|
|
32
|
+
payload: payload?.map((entry, i) => ({ ...entry, dataKey: chunkK3R7JGOW_cjs.seriesKey(i) }))
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
var DonutChart = ({
|
|
36
|
+
series,
|
|
37
|
+
labels,
|
|
38
|
+
colors,
|
|
39
|
+
formatValue = String,
|
|
40
|
+
variant = "donut",
|
|
41
|
+
centerLabel,
|
|
42
|
+
centerValue,
|
|
43
|
+
legendValues = false,
|
|
44
|
+
legendPosition = "bottom",
|
|
45
|
+
emptyMessage = "No data",
|
|
46
|
+
ariaLabel,
|
|
47
|
+
children,
|
|
48
|
+
height = 280,
|
|
49
|
+
width
|
|
50
|
+
}) => {
|
|
51
|
+
const hidden = chunkRCDTBZC2_cjs.useHiddenSeries();
|
|
52
|
+
const [active, setActive] = react.useState();
|
|
53
|
+
const pie = variant === "pie";
|
|
54
|
+
const name = ariaLabel ?? `${pie ? "Pie" : "Donut"} chart: ${labels.join(", ")}`;
|
|
55
|
+
if (series.length === 0) {
|
|
56
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkSR2ESX3R_cjs.ChartEmpty, { message: emptyMessage, ariaLabel: name, height, width });
|
|
57
|
+
}
|
|
58
|
+
const data = labels.map((label, i) => ({
|
|
59
|
+
name: label,
|
|
60
|
+
value: hidden.hidden.has(chunkK3R7JGOW_cjs.seriesKey(i)) ? 0 : series[i],
|
|
61
|
+
fill: chunkLT3UC65O_cjs.seriesColor(i, colors)
|
|
62
|
+
}));
|
|
63
|
+
const total = data.reduce((sum, slice2) => sum + slice2.value, 0);
|
|
64
|
+
const slice = active === void 0 ? void 0 : data[active];
|
|
65
|
+
const legend = chunkRCDTBZC2_cjs.legendProps(legendPosition, hidden);
|
|
66
|
+
const percents = shares(series);
|
|
67
|
+
const detail = legendValues ? (index) => ({ value: formatValue(series[index]), share: `${percents[index]}%` }) : void 0;
|
|
68
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
69
|
+
recharts.PieChart,
|
|
70
|
+
{
|
|
71
|
+
...chunkSR2ESX3R_cjs.frameProps({ height, width, ariaLabel: name }),
|
|
72
|
+
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
75
|
+
recharts.Pie,
|
|
76
|
+
{
|
|
77
|
+
data,
|
|
78
|
+
dataKey: "value",
|
|
79
|
+
nameKey: "name",
|
|
80
|
+
innerRadius: pie ? 0 : "70%",
|
|
81
|
+
outerRadius: "100%",
|
|
82
|
+
paddingAngle: pie ? 0 : 3,
|
|
83
|
+
cornerRadius: pie ? 0 : 6,
|
|
84
|
+
stroke: pie ? chunkLT3UC65O_cjs.PANEL : "none",
|
|
85
|
+
strokeWidth: pie ? 2 : void 0,
|
|
86
|
+
onMouseEnter: (_, index) => setActive(index),
|
|
87
|
+
onMouseLeave: () => setActive(void 0),
|
|
88
|
+
...chunkTYQWZR3S_cjs.ANIMATION
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
!pie && /* @__PURE__ */ jsxRuntime.jsx(
|
|
92
|
+
chunkX3YZP5WF_cjs.CenterText,
|
|
93
|
+
{
|
|
94
|
+
value: slice ? formatValue(slice.value) : centerValue ?? formatValue(total),
|
|
95
|
+
label: slice ? slice.name : centerLabel ?? "Total"
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(chunkT3K6Q34U_cjs.ChartTooltip, { format: formatValue, hideTitle: true }), animationDuration: 150 }),
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100
|
+
recharts.Legend,
|
|
101
|
+
{
|
|
102
|
+
...legend,
|
|
103
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(
|
|
104
|
+
PieLegend,
|
|
105
|
+
{
|
|
106
|
+
hidden: hidden.hidden,
|
|
107
|
+
onToggle: hidden.toggle,
|
|
108
|
+
column: legendPosition === "left" || legendPosition === "right",
|
|
109
|
+
detail
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
children
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
exports.DonutChart = DonutChart;
|
|
121
|
+
//# sourceMappingURL=DonutChart.cjs.map
|
|
122
|
+
//# sourceMappingURL=DonutChart.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/shares.ts","../src/DonutChart.tsx"],"names":["jsx","ChartLegend","seriesKey","useHiddenSeries","useState","ChartEmpty","seriesColor","slice","legendProps","jsxs","PieChart","frameProps","Pie","PANEL","ANIMATION","CenterText","Tooltip","ChartTooltip","Legend"],"mappings":"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","file":"DonutChart.cjs","sourcesContent":["/**\n * The share of each value in their sum, as whole percents that add up to 100\n * (the largest remainders take the rounding). All zero when the sum is zero.\n * A negative value counts as zero.\n */\nexport const shares = (values: readonly number[]): number[] => {\n const parts = values.map((v) => Math.max(0, v));\n const total = parts.reduce((sum, v) => sum + v, 0);\n if (total === 0) return parts.map(() => 0);\n const exact = parts.map((v) => (v / total) * 100);\n const whole = exact.map(Math.floor);\n const byRemainder = exact\n .map((v, i) => ({ i, rest: v - whole[i] }))\n .sort((a, b) => b.rest - a.rest || a.i - b.i);\n const missing = 100 - whole.reduce((sum, v) => sum + v, 0);\n for (let k = 0; k < missing; k++) whole[byRemainder[k].i]++;\n return whole;\n};\n","import { useState, type ComponentProps } from 'react';\nimport { Legend, Pie, PieChart, Tooltip } from 'recharts';\nimport { ANIMATION } from './core/animation';\nimport { ChartLegend, legendProps, useHiddenSeries } from './core/ChartLegend';\nimport { ChartTooltip } from './core/ChartTooltip';\nimport { PANEL, seriesColor } from './core/colors';\nimport { seriesKey } from './core/data';\nimport { ChartEmpty, frameProps } from './core/frame';\nimport { CenterText } from './core/radial';\nimport { shares } from './core/shares';\nimport type { ChartEmptyProps, ChartLegendProps, ChartProps } from './types';\n\nexport type DonutChartVariant = 'donut' | 'pie';\n\nexport type DonutChartProps = ChartProps &\n ChartEmptyProps &\n ChartLegendProps & {\n series: number[];\n labels: string[];\n /** Override the default Cosmos palette. */\n colors?: string[];\n /** Format the slice values in the center and the tooltip, and the total. */\n formatValue?: (value: number) => string;\n /** `donut` (default) leaves a hole with the total in it; `pie` fills the disc, with no center text. */\n variant?: DonutChartVariant;\n /** The small text under the center value, in place of \"Total\". An empty string draws nothing. Ignored by `pie`. Drawn as plain text. */\n centerLabel?: string;\n /** The center value, in place of the total (which `formatValue` formats). Ignored by `pie`. Drawn as plain text. */\n centerValue?: string;\n /**\n * Write each slice's value (through `formatValue`) and its share of the total in the\n * legend, in whole percents that add up to 100. With the legend on the left or the right\n * the figures sit in columns at the right edge of the legend.\n */\n legendValues?: boolean;\n };\n\n// A pie's legend entries all carry the pie's data key: key them by position.\n// A component of its own, so the entries are not remounted at every render.\nconst PieLegend = ({ payload, ...props }: ComponentProps<typeof ChartLegend>) => (\n <ChartLegend\n {...props}\n payload={payload?.map((entry, i) => ({ ...entry, dataKey: seriesKey(i) }))}\n />\n);\n\nexport const DonutChart = ({\n series,\n labels,\n colors,\n formatValue = String,\n variant = 'donut',\n centerLabel,\n centerValue,\n legendValues = false,\n legendPosition = 'bottom',\n emptyMessage = 'No data',\n ariaLabel,\n children,\n height = 280,\n width,\n}: DonutChartProps) => {\n const hidden = useHiddenSeries();\n // The slice the pointer is on, shown in the center in place of the total.\n const [active, setActive] = useState<number>();\n const pie = variant === 'pie';\n const name = ariaLabel ?? `${pie ? 'Pie' : 'Donut'} chart: ${labels.join(', ')}`;\n if (series.length === 0) {\n return <ChartEmpty message={emptyMessage} ariaLabel={name} height={height} width={width} />;\n }\n // A hidden slice keeps its place (and its legend entry) at zero.\n const data = labels.map((label, i) => ({\n name: label,\n value: hidden.hidden.has(seriesKey(i)) ? 0 : series[i],\n fill: seriesColor(i, colors),\n }));\n const total = data.reduce((sum, slice) => sum + slice.value, 0);\n const slice = active === undefined ? undefined : data[active];\n const legend = legendProps(legendPosition, hidden);\n // The legend figures keep the whole series: hiding a slice does not move the others' share.\n const percents = shares(series);\n const detail = legendValues\n ? (index: number) => ({ value: formatValue(series[index]), share: `${percents[index]}%` })\n : undefined;\n return (\n <PieChart\n {...frameProps({ height, width, ariaLabel: name })}\n margin={{ top: 0, right: 0, bottom: 0, left: 0 }}\n >\n <Pie\n data={data}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={pie ? 0 : '70%'}\n outerRadius=\"100%\"\n paddingAngle={pie ? 0 : 3}\n cornerRadius={pie ? 0 : 6}\n // A pie has no gap between slices: a line in the color of the card tells them apart.\n stroke={pie ? PANEL : 'none'}\n strokeWidth={pie ? 2 : undefined}\n onMouseEnter={(_, index) => setActive(index)}\n onMouseLeave={() => setActive(undefined)}\n {...ANIMATION}\n />\n {!pie && (\n <CenterText\n value={slice ? formatValue(slice.value) : (centerValue ?? formatValue(total))}\n label={slice ? slice.name : (centerLabel ?? 'Total')}\n />\n )}\n <Tooltip content={<ChartTooltip format={formatValue} hideTitle />} animationDuration={150} />\n <Legend\n {...legend}\n content={\n <PieLegend\n hidden={hidden.hidden}\n onToggle={hidden.toggle}\n column={legendPosition === 'left' || legendPosition === 'right'}\n detail={detail}\n />\n }\n />\n {children}\n </PieChart>\n );\n};\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useHiddenSeries, legendProps, ChartLegend } from './chunk-4AKHZNWP.js';
|
|
3
|
+
import { CenterText } from './chunk-URNXLVSK.js';
|
|
4
|
+
import { ANIMATION } from './chunk-5C2RLJYZ.js';
|
|
5
|
+
import './chunk-DI2FBGKM.js';
|
|
6
|
+
import { ChartTooltip } from './chunk-VIOSQN4Z.js';
|
|
7
|
+
import { seriesKey } from './chunk-UJIV5PVI.js';
|
|
8
|
+
import { ChartEmpty, frameProps } from './chunk-56WD3OHA.js';
|
|
9
|
+
import { seriesColor, PANEL } from './chunk-UATB2IRY.js';
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { PieChart, Pie, Tooltip, Legend } from 'recharts';
|
|
12
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
13
|
+
|
|
14
|
+
// src/core/shares.ts
|
|
15
|
+
var shares = (values) => {
|
|
16
|
+
const parts = values.map((v) => Math.max(0, v));
|
|
17
|
+
const total = parts.reduce((sum, v) => sum + v, 0);
|
|
18
|
+
if (total === 0) return parts.map(() => 0);
|
|
19
|
+
const exact = parts.map((v) => v / total * 100);
|
|
20
|
+
const whole = exact.map(Math.floor);
|
|
21
|
+
const byRemainder = exact.map((v, i) => ({ i, rest: v - whole[i] })).sort((a, b) => b.rest - a.rest || a.i - b.i);
|
|
22
|
+
const missing = 100 - whole.reduce((sum, v) => sum + v, 0);
|
|
23
|
+
for (let k = 0; k < missing; k++) whole[byRemainder[k].i]++;
|
|
24
|
+
return whole;
|
|
25
|
+
};
|
|
26
|
+
var PieLegend = ({ payload, ...props }) => /* @__PURE__ */ jsx(
|
|
27
|
+
ChartLegend,
|
|
28
|
+
{
|
|
29
|
+
...props,
|
|
30
|
+
payload: payload?.map((entry, i) => ({ ...entry, dataKey: seriesKey(i) }))
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
var DonutChart = ({
|
|
34
|
+
series,
|
|
35
|
+
labels,
|
|
36
|
+
colors,
|
|
37
|
+
formatValue = String,
|
|
38
|
+
variant = "donut",
|
|
39
|
+
centerLabel,
|
|
40
|
+
centerValue,
|
|
41
|
+
legendValues = false,
|
|
42
|
+
legendPosition = "bottom",
|
|
43
|
+
emptyMessage = "No data",
|
|
44
|
+
ariaLabel,
|
|
45
|
+
children,
|
|
46
|
+
height = 280,
|
|
47
|
+
width
|
|
48
|
+
}) => {
|
|
49
|
+
const hidden = useHiddenSeries();
|
|
50
|
+
const [active, setActive] = useState();
|
|
51
|
+
const pie = variant === "pie";
|
|
52
|
+
const name = ariaLabel ?? `${pie ? "Pie" : "Donut"} chart: ${labels.join(", ")}`;
|
|
53
|
+
if (series.length === 0) {
|
|
54
|
+
return /* @__PURE__ */ jsx(ChartEmpty, { message: emptyMessage, ariaLabel: name, height, width });
|
|
55
|
+
}
|
|
56
|
+
const data = labels.map((label, i) => ({
|
|
57
|
+
name: label,
|
|
58
|
+
value: hidden.hidden.has(seriesKey(i)) ? 0 : series[i],
|
|
59
|
+
fill: seriesColor(i, colors)
|
|
60
|
+
}));
|
|
61
|
+
const total = data.reduce((sum, slice2) => sum + slice2.value, 0);
|
|
62
|
+
const slice = active === void 0 ? void 0 : data[active];
|
|
63
|
+
const legend = legendProps(legendPosition, hidden);
|
|
64
|
+
const percents = shares(series);
|
|
65
|
+
const detail = legendValues ? (index) => ({ value: formatValue(series[index]), share: `${percents[index]}%` }) : void 0;
|
|
66
|
+
return /* @__PURE__ */ jsxs(
|
|
67
|
+
PieChart,
|
|
68
|
+
{
|
|
69
|
+
...frameProps({ height, width, ariaLabel: name }),
|
|
70
|
+
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
Pie,
|
|
74
|
+
{
|
|
75
|
+
data,
|
|
76
|
+
dataKey: "value",
|
|
77
|
+
nameKey: "name",
|
|
78
|
+
innerRadius: pie ? 0 : "70%",
|
|
79
|
+
outerRadius: "100%",
|
|
80
|
+
paddingAngle: pie ? 0 : 3,
|
|
81
|
+
cornerRadius: pie ? 0 : 6,
|
|
82
|
+
stroke: pie ? PANEL : "none",
|
|
83
|
+
strokeWidth: pie ? 2 : void 0,
|
|
84
|
+
onMouseEnter: (_, index) => setActive(index),
|
|
85
|
+
onMouseLeave: () => setActive(void 0),
|
|
86
|
+
...ANIMATION
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
!pie && /* @__PURE__ */ jsx(
|
|
90
|
+
CenterText,
|
|
91
|
+
{
|
|
92
|
+
value: slice ? formatValue(slice.value) : centerValue ?? formatValue(total),
|
|
93
|
+
label: slice ? slice.name : centerLabel ?? "Total"
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsx(ChartTooltip, { format: formatValue, hideTitle: true }), animationDuration: 150 }),
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
Legend,
|
|
99
|
+
{
|
|
100
|
+
...legend,
|
|
101
|
+
content: /* @__PURE__ */ jsx(
|
|
102
|
+
PieLegend,
|
|
103
|
+
{
|
|
104
|
+
hidden: hidden.hidden,
|
|
105
|
+
onToggle: hidden.toggle,
|
|
106
|
+
column: legendPosition === "left" || legendPosition === "right",
|
|
107
|
+
detail
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
children
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { DonutChart };
|
|
119
|
+
//# sourceMappingURL=DonutChart.js.map
|
|
120
|
+
//# sourceMappingURL=DonutChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/shares.ts","../src/DonutChart.tsx"],"names":["slice"],"mappings":"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","file":"DonutChart.js","sourcesContent":["/**\n * The share of each value in their sum, as whole percents that add up to 100\n * (the largest remainders take the rounding). All zero when the sum is zero.\n * A negative value counts as zero.\n */\nexport const shares = (values: readonly number[]): number[] => {\n const parts = values.map((v) => Math.max(0, v));\n const total = parts.reduce((sum, v) => sum + v, 0);\n if (total === 0) return parts.map(() => 0);\n const exact = parts.map((v) => (v / total) * 100);\n const whole = exact.map(Math.floor);\n const byRemainder = exact\n .map((v, i) => ({ i, rest: v - whole[i] }))\n .sort((a, b) => b.rest - a.rest || a.i - b.i);\n const missing = 100 - whole.reduce((sum, v) => sum + v, 0);\n for (let k = 0; k < missing; k++) whole[byRemainder[k].i]++;\n return whole;\n};\n","import { useState, type ComponentProps } from 'react';\nimport { Legend, Pie, PieChart, Tooltip } from 'recharts';\nimport { ANIMATION } from './core/animation';\nimport { ChartLegend, legendProps, useHiddenSeries } from './core/ChartLegend';\nimport { ChartTooltip } from './core/ChartTooltip';\nimport { PANEL, seriesColor } from './core/colors';\nimport { seriesKey } from './core/data';\nimport { ChartEmpty, frameProps } from './core/frame';\nimport { CenterText } from './core/radial';\nimport { shares } from './core/shares';\nimport type { ChartEmptyProps, ChartLegendProps, ChartProps } from './types';\n\nexport type DonutChartVariant = 'donut' | 'pie';\n\nexport type DonutChartProps = ChartProps &\n ChartEmptyProps &\n ChartLegendProps & {\n series: number[];\n labels: string[];\n /** Override the default Cosmos palette. */\n colors?: string[];\n /** Format the slice values in the center and the tooltip, and the total. */\n formatValue?: (value: number) => string;\n /** `donut` (default) leaves a hole with the total in it; `pie` fills the disc, with no center text. */\n variant?: DonutChartVariant;\n /** The small text under the center value, in place of \"Total\". An empty string draws nothing. Ignored by `pie`. Drawn as plain text. */\n centerLabel?: string;\n /** The center value, in place of the total (which `formatValue` formats). Ignored by `pie`. Drawn as plain text. */\n centerValue?: string;\n /**\n * Write each slice's value (through `formatValue`) and its share of the total in the\n * legend, in whole percents that add up to 100. With the legend on the left or the right\n * the figures sit in columns at the right edge of the legend.\n */\n legendValues?: boolean;\n };\n\n// A pie's legend entries all carry the pie's data key: key them by position.\n// A component of its own, so the entries are not remounted at every render.\nconst PieLegend = ({ payload, ...props }: ComponentProps<typeof ChartLegend>) => (\n <ChartLegend\n {...props}\n payload={payload?.map((entry, i) => ({ ...entry, dataKey: seriesKey(i) }))}\n />\n);\n\nexport const DonutChart = ({\n series,\n labels,\n colors,\n formatValue = String,\n variant = 'donut',\n centerLabel,\n centerValue,\n legendValues = false,\n legendPosition = 'bottom',\n emptyMessage = 'No data',\n ariaLabel,\n children,\n height = 280,\n width,\n}: DonutChartProps) => {\n const hidden = useHiddenSeries();\n // The slice the pointer is on, shown in the center in place of the total.\n const [active, setActive] = useState<number>();\n const pie = variant === 'pie';\n const name = ariaLabel ?? `${pie ? 'Pie' : 'Donut'} chart: ${labels.join(', ')}`;\n if (series.length === 0) {\n return <ChartEmpty message={emptyMessage} ariaLabel={name} height={height} width={width} />;\n }\n // A hidden slice keeps its place (and its legend entry) at zero.\n const data = labels.map((label, i) => ({\n name: label,\n value: hidden.hidden.has(seriesKey(i)) ? 0 : series[i],\n fill: seriesColor(i, colors),\n }));\n const total = data.reduce((sum, slice) => sum + slice.value, 0);\n const slice = active === undefined ? undefined : data[active];\n const legend = legendProps(legendPosition, hidden);\n // The legend figures keep the whole series: hiding a slice does not move the others' share.\n const percents = shares(series);\n const detail = legendValues\n ? (index: number) => ({ value: formatValue(series[index]), share: `${percents[index]}%` })\n : undefined;\n return (\n <PieChart\n {...frameProps({ height, width, ariaLabel: name })}\n margin={{ top: 0, right: 0, bottom: 0, left: 0 }}\n >\n <Pie\n data={data}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={pie ? 0 : '70%'}\n outerRadius=\"100%\"\n paddingAngle={pie ? 0 : 3}\n cornerRadius={pie ? 0 : 6}\n // A pie has no gap between slices: a line in the color of the card tells them apart.\n stroke={pie ? PANEL : 'none'}\n strokeWidth={pie ? 2 : undefined}\n onMouseEnter={(_, index) => setActive(index)}\n onMouseLeave={() => setActive(undefined)}\n {...ANIMATION}\n />\n {!pie && (\n <CenterText\n value={slice ? formatValue(slice.value) : (centerValue ?? formatValue(total))}\n label={slice ? slice.name : (centerLabel ?? 'Total')}\n />\n )}\n <Tooltip content={<ChartTooltip format={formatValue} hideTitle />} animationDuration={150} />\n <Legend\n {...legend}\n content={\n <PieLegend\n hidden={hidden.hidden}\n onToggle={hidden.toggle}\n column={legendPosition === 'left' || legendPosition === 'right'}\n detail={detail}\n />\n }\n />\n {children}\n </PieChart>\n );\n};\n"]}
|