@x-plat/design-system 0.5.35 → 0.5.36
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/dist/components/Chart/index.cjs +146 -200
- package/dist/components/Chart/index.css +12 -25
- package/dist/components/Chart/index.js +132 -186
- package/dist/components/index.cjs +193 -231
- package/dist/components/index.css +12 -25
- package/dist/components/index.js +173 -211
- package/dist/index.cjs +193 -231
- package/dist/index.css +12 -25
- package/dist/index.js +173 -211
- package/package.json +1 -1
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
// src/components/Chart/Chart.tsx
|
|
2
|
-
import React2 from "react";
|
|
3
|
-
|
|
4
|
-
// src/tokens/hooks/Portal.tsx
|
|
5
2
|
import React from "react";
|
|
6
|
-
import
|
|
7
|
-
import { jsx } from "react/jsx-runtime";
|
|
8
|
-
var PortalContainerContext = React.createContext(null);
|
|
9
|
-
var Portal = ({ children }) => {
|
|
10
|
-
const contextContainer = React.useContext(PortalContainerContext);
|
|
11
|
-
if (typeof document === "undefined") return null;
|
|
12
|
-
const container = contextContainer ?? document.body;
|
|
13
|
-
return ReactDOM.createPortal(children, container);
|
|
14
|
-
};
|
|
15
|
-
Portal.displayName = "Portal";
|
|
16
|
-
var Portal_default = Portal;
|
|
17
|
-
|
|
18
|
-
// src/components/Chart/Chart.tsx
|
|
19
|
-
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
20
4
|
var CATEGORICAL_COUNT = 8;
|
|
21
5
|
var LINE_BAR_PALETTES = Array.from({ length: CATEGORICAL_COUNT }, (_, i) => {
|
|
22
6
|
const n = i + 1;
|
|
@@ -62,11 +46,11 @@ var toSmoothPath = (points) => {
|
|
|
62
46
|
};
|
|
63
47
|
var RESIZE_SETTLE_MS = 150;
|
|
64
48
|
var useChartSize = (ref) => {
|
|
65
|
-
const [size, setSize] =
|
|
66
|
-
const settleTimer =
|
|
67
|
-
const committedSize =
|
|
68
|
-
const initialRef =
|
|
69
|
-
|
|
49
|
+
const [size, setSize] = React.useState({ width: 0, height: 0 });
|
|
50
|
+
const settleTimer = React.useRef(0);
|
|
51
|
+
const committedSize = React.useRef({ width: 0, height: 0 });
|
|
52
|
+
const initialRef = React.useRef(true);
|
|
53
|
+
React.useEffect(() => {
|
|
70
54
|
const el = ref.current;
|
|
71
55
|
if (!el) return;
|
|
72
56
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -108,10 +92,10 @@ var useChartSize = (ref) => {
|
|
|
108
92
|
};
|
|
109
93
|
var prefersReducedMotion = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
110
94
|
var useChartAnimation = (containerRef, dataKey) => {
|
|
111
|
-
const [animate, setAnimate] =
|
|
112
|
-
const prevDataKey =
|
|
113
|
-
const hasAnimated =
|
|
114
|
-
|
|
95
|
+
const [animate, setAnimate] = React.useState(false);
|
|
96
|
+
const prevDataKey = React.useRef(dataKey);
|
|
97
|
+
const hasAnimated = React.useRef(false);
|
|
98
|
+
React.useEffect(() => {
|
|
115
99
|
if (prefersReducedMotion()) return;
|
|
116
100
|
const el = containerRef.current;
|
|
117
101
|
if (!el) return;
|
|
@@ -127,7 +111,7 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
127
111
|
observer.observe(el);
|
|
128
112
|
return () => observer.disconnect();
|
|
129
113
|
}, [containerRef]);
|
|
130
|
-
|
|
114
|
+
React.useEffect(() => {
|
|
131
115
|
if (dataKey !== prevDataKey.current) {
|
|
132
116
|
prevDataKey.current = dataKey;
|
|
133
117
|
if (prefersReducedMotion()) return;
|
|
@@ -141,39 +125,47 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
141
125
|
};
|
|
142
126
|
var TOOLTIP_OFFSET = 12;
|
|
143
127
|
var useChartTooltip = (enabled) => {
|
|
144
|
-
const [tooltip, setTooltip] =
|
|
128
|
+
const [tooltip, setTooltip] = React.useState({
|
|
145
129
|
visible: false,
|
|
146
|
-
|
|
147
|
-
|
|
130
|
+
x: 0,
|
|
131
|
+
y: 0,
|
|
148
132
|
content: ""
|
|
149
133
|
});
|
|
150
|
-
const containerRef =
|
|
151
|
-
const rafRef =
|
|
152
|
-
const move =
|
|
134
|
+
const containerRef = React.useRef(null);
|
|
135
|
+
const rafRef = React.useRef(0);
|
|
136
|
+
const move = React.useCallback((e) => {
|
|
153
137
|
if (!enabled) return;
|
|
154
138
|
const cx = e.clientX;
|
|
155
139
|
const cy = e.clientY;
|
|
156
140
|
cancelAnimationFrame(rafRef.current);
|
|
157
141
|
rafRef.current = requestAnimationFrame(() => {
|
|
158
|
-
|
|
142
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
143
|
+
if (!rect) return;
|
|
144
|
+
setTooltip((prev) => ({ ...prev, x: cx - rect.left, y: cy - rect.top }));
|
|
159
145
|
});
|
|
160
146
|
}, [enabled]);
|
|
161
|
-
const show =
|
|
147
|
+
const show = React.useCallback((e, content) => {
|
|
162
148
|
if (!enabled) return;
|
|
163
|
-
|
|
149
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
150
|
+
if (!rect) return;
|
|
151
|
+
setTooltip({ visible: true, x: e.clientX - rect.left, y: e.clientY - rect.top, content });
|
|
164
152
|
}, [enabled]);
|
|
165
|
-
const
|
|
153
|
+
const showAt = React.useCallback((x, y, content) => {
|
|
154
|
+
if (!enabled) return;
|
|
155
|
+
setTooltip({ visible: true, x, y, content });
|
|
156
|
+
}, [enabled]);
|
|
157
|
+
const hide = React.useCallback(() => {
|
|
166
158
|
cancelAnimationFrame(rafRef.current);
|
|
167
159
|
setTooltip((prev) => ({ ...prev, visible: false }));
|
|
168
160
|
}, []);
|
|
169
|
-
return { tooltip, show, hide, move, containerRef };
|
|
161
|
+
return { tooltip, show, showAt, hide, move, containerRef };
|
|
170
162
|
};
|
|
171
|
-
var GridLines =
|
|
163
|
+
var GridLines = React.memo(({ width, height, chartH, maxVal }) => /* @__PURE__ */ jsx(Fragment, { children: [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
|
|
172
164
|
const y = PADDING.top + (1 - ratio) * chartH;
|
|
173
165
|
const val = Math.round(maxVal * ratio);
|
|
174
166
|
return /* @__PURE__ */ jsxs("g", { children: [
|
|
175
|
-
/* @__PURE__ */
|
|
176
|
-
/* @__PURE__ */
|
|
167
|
+
/* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
|
|
168
|
+
/* @__PURE__ */ jsx("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
|
|
177
169
|
] }, ratio);
|
|
178
170
|
}) }));
|
|
179
171
|
GridLines.displayName = "GridLines";
|
|
@@ -183,18 +175,18 @@ var getLabelStep = (count, chartW) => {
|
|
|
183
175
|
if (count <= maxLabels) return 1;
|
|
184
176
|
return Math.ceil(count / maxLabels);
|
|
185
177
|
};
|
|
186
|
-
var AxisLabels =
|
|
178
|
+
var AxisLabels = React.memo(({ labels, count, chartW, height }) => {
|
|
187
179
|
const step = getLabelStep(count, chartW);
|
|
188
|
-
return /* @__PURE__ */
|
|
180
|
+
return /* @__PURE__ */ jsx(Fragment, { children: labels.map((label, i) => {
|
|
189
181
|
if (i % step !== 0) return null;
|
|
190
182
|
const x = PADDING.left + i / (count - 1 || 1) * chartW;
|
|
191
|
-
return /* @__PURE__ */
|
|
183
|
+
return /* @__PURE__ */ jsx("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
|
|
192
184
|
}) });
|
|
193
185
|
});
|
|
194
186
|
AxisLabels.displayName = "AxisLabels";
|
|
195
187
|
var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
196
|
-
const [activeIndex, setActiveIndex] =
|
|
197
|
-
const handleMouseMove =
|
|
188
|
+
const [activeIndex, setActiveIndex] = React.useState(null);
|
|
189
|
+
const handleMouseMove = React.useCallback((e) => {
|
|
198
190
|
const svg = e.currentTarget;
|
|
199
191
|
const rect = svg.getBoundingClientRect();
|
|
200
192
|
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
@@ -213,17 +205,17 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
213
205
|
}
|
|
214
206
|
setActiveIndex(minDist <= threshold ? closest : null);
|
|
215
207
|
}, [seriesPoints]);
|
|
216
|
-
const handleMouseLeave =
|
|
208
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
217
209
|
setActiveIndex(null);
|
|
218
210
|
}, []);
|
|
219
|
-
const tooltipContent =
|
|
211
|
+
const tooltipContent = React.useMemo(() => {
|
|
220
212
|
if (activeIndex === null) return "";
|
|
221
213
|
return entries.map(([key], di) => {
|
|
222
214
|
const p = seriesPoints[di]?.[activeIndex];
|
|
223
215
|
return p ? `${key}: ${p.v}` : "";
|
|
224
216
|
}).filter(Boolean).join(" / ");
|
|
225
217
|
}, [activeIndex, entries, seriesPoints]);
|
|
226
|
-
const getTooltipAt =
|
|
218
|
+
const getTooltipAt = React.useCallback((idx) => {
|
|
227
219
|
return entries.map(([key], di) => {
|
|
228
220
|
const p = seriesPoints[di]?.[idx];
|
|
229
221
|
return p ? `${key}: ${p.v}` : "";
|
|
@@ -231,16 +223,16 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
231
223
|
}, [entries, seriesPoints]);
|
|
232
224
|
return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
|
|
233
225
|
};
|
|
234
|
-
var LineChart =
|
|
235
|
-
const entries =
|
|
236
|
-
const maxVal =
|
|
226
|
+
var LineChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
|
|
227
|
+
const entries = React.useMemo(() => Object.entries(data), [data]);
|
|
228
|
+
const maxVal = React.useMemo(() => {
|
|
237
229
|
const allValues = entries.flatMap(([, v]) => v);
|
|
238
230
|
return Math.max(...allValues) * 1.2 || 1;
|
|
239
231
|
}, [entries]);
|
|
240
232
|
const count = labels.length;
|
|
241
233
|
const chartW = width - PADDING.left - PADDING.right;
|
|
242
234
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
243
|
-
const seriesPoints =
|
|
235
|
+
const seriesPoints = React.useMemo(
|
|
244
236
|
() => entries.map(
|
|
245
237
|
([, values]) => values.map((v, i) => ({
|
|
246
238
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -250,9 +242,9 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
250
242
|
),
|
|
251
243
|
[entries, count, chartW, chartH, maxVal]
|
|
252
244
|
);
|
|
253
|
-
const clipRef =
|
|
245
|
+
const clipRef = React.useRef(null);
|
|
254
246
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
255
|
-
|
|
247
|
+
React.useEffect(() => {
|
|
256
248
|
if (!animate || !clipRef.current) return;
|
|
257
249
|
clipRef.current.setAttribute("width", "0");
|
|
258
250
|
requestAnimationFrame(() => {
|
|
@@ -271,23 +263,9 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
271
263
|
className: "chart-svg",
|
|
272
264
|
onMouseMove: (e) => {
|
|
273
265
|
handleMouseMove(e);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const points = seriesPoints[0];
|
|
278
|
-
if (!points || points.length === 0) return;
|
|
279
|
-
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
280
|
-
let closest = 0;
|
|
281
|
-
let minDist = Math.abs(points[0].x - mx);
|
|
282
|
-
for (let i = 1; i < points.length; i++) {
|
|
283
|
-
const dist = Math.abs(points[i].x - mx);
|
|
284
|
-
if (dist < minDist) {
|
|
285
|
-
minDist = dist;
|
|
286
|
-
closest = i;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (minDist <= step / 2) {
|
|
290
|
-
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
266
|
+
if (activeIndex !== null && seriesPoints[0]?.[activeIndex]) {
|
|
267
|
+
const p = seriesPoints[0][activeIndex];
|
|
268
|
+
onShowAt(p.x, p.y, `${labels[activeIndex]} \u2014 ${getTooltipAt(activeIndex)}`);
|
|
291
269
|
} else {
|
|
292
270
|
onLeave();
|
|
293
271
|
}
|
|
@@ -297,9 +275,9 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
297
275
|
onLeave();
|
|
298
276
|
},
|
|
299
277
|
children: [
|
|
300
|
-
animate && /* @__PURE__ */
|
|
301
|
-
/* @__PURE__ */
|
|
302
|
-
/* @__PURE__ */
|
|
278
|
+
animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
279
|
+
/* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
|
|
280
|
+
/* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
|
|
303
281
|
entries.map(([key], di) => {
|
|
304
282
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
305
283
|
const color = palette[2];
|
|
@@ -309,15 +287,15 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
309
287
|
const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
310
288
|
const areaD = `M ${points[0].x},${points[0].y} ${points.map((p) => `L ${p.x},${p.y}`).join(" ")} L ${points[points.length - 1].x},${PADDING.top + chartH} L ${points[0].x},${PADDING.top + chartH} Z`;
|
|
311
289
|
return /* @__PURE__ */ jsxs("g", { children: [
|
|
312
|
-
/* @__PURE__ */
|
|
313
|
-
/* @__PURE__ */
|
|
314
|
-
/* @__PURE__ */
|
|
290
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
291
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
|
|
292
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
|
|
315
293
|
] }) }),
|
|
316
294
|
/* @__PURE__ */ jsxs("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
|
|
317
|
-
/* @__PURE__ */
|
|
318
|
-
/* @__PURE__ */
|
|
295
|
+
/* @__PURE__ */ jsx("path", { d: areaD, fill: `url(#${gradientId})` }),
|
|
296
|
+
/* @__PURE__ */ jsx("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
|
|
319
297
|
] }),
|
|
320
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */
|
|
298
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
|
|
321
299
|
"circle",
|
|
322
300
|
{
|
|
323
301
|
cx: points[activeIndex].x,
|
|
@@ -329,7 +307,7 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
329
307
|
)
|
|
330
308
|
] }, di);
|
|
331
309
|
}),
|
|
332
|
-
activeX !== null && /* @__PURE__ */
|
|
310
|
+
activeX !== null && /* @__PURE__ */ jsx(
|
|
333
311
|
"line",
|
|
334
312
|
{
|
|
335
313
|
x1: activeX,
|
|
@@ -339,7 +317,7 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
339
317
|
className: "chart-crosshair"
|
|
340
318
|
}
|
|
341
319
|
),
|
|
342
|
-
/* @__PURE__ */
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
343
321
|
"rect",
|
|
344
322
|
{
|
|
345
323
|
x: PADDING.left,
|
|
@@ -355,16 +333,16 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
355
333
|
);
|
|
356
334
|
});
|
|
357
335
|
LineChart.displayName = "LineChart";
|
|
358
|
-
var CurveChart =
|
|
359
|
-
const entries =
|
|
360
|
-
const maxVal =
|
|
336
|
+
var CurveChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
|
|
337
|
+
const entries = React.useMemo(() => Object.entries(data), [data]);
|
|
338
|
+
const maxVal = React.useMemo(() => {
|
|
361
339
|
const allValues = entries.flatMap(([, v]) => v);
|
|
362
340
|
return Math.max(...allValues) * 1.2 || 1;
|
|
363
341
|
}, [entries]);
|
|
364
342
|
const count = labels.length;
|
|
365
343
|
const chartW = width - PADDING.left - PADDING.right;
|
|
366
344
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
367
|
-
const seriesPoints =
|
|
345
|
+
const seriesPoints = React.useMemo(
|
|
368
346
|
() => entries.map(
|
|
369
347
|
([, values]) => values.map((v, i) => ({
|
|
370
348
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -374,9 +352,9 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
374
352
|
),
|
|
375
353
|
[entries, count, chartW, chartH, maxVal]
|
|
376
354
|
);
|
|
377
|
-
const curveClipRef =
|
|
355
|
+
const curveClipRef = React.useRef(null);
|
|
378
356
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
379
|
-
|
|
357
|
+
React.useEffect(() => {
|
|
380
358
|
if (!animate || !curveClipRef.current) return;
|
|
381
359
|
curveClipRef.current.setAttribute("width", "0");
|
|
382
360
|
requestAnimationFrame(() => {
|
|
@@ -395,23 +373,9 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
395
373
|
className: "chart-svg",
|
|
396
374
|
onMouseMove: (e) => {
|
|
397
375
|
handleMouseMove(e);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const points = seriesPoints[0];
|
|
402
|
-
if (!points || points.length === 0) return;
|
|
403
|
-
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
404
|
-
let closest = 0;
|
|
405
|
-
let minDist = Math.abs(points[0].x - mx);
|
|
406
|
-
for (let i = 1; i < points.length; i++) {
|
|
407
|
-
const dist = Math.abs(points[i].x - mx);
|
|
408
|
-
if (dist < minDist) {
|
|
409
|
-
minDist = dist;
|
|
410
|
-
closest = i;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (minDist <= step / 2) {
|
|
414
|
-
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
376
|
+
if (activeIndex !== null && seriesPoints[0]?.[activeIndex]) {
|
|
377
|
+
const p = seriesPoints[0][activeIndex];
|
|
378
|
+
onShowAt(p.x, p.y, `${labels[activeIndex]} \u2014 ${getTooltipAt(activeIndex)}`);
|
|
415
379
|
} else {
|
|
416
380
|
onLeave();
|
|
417
381
|
}
|
|
@@ -421,9 +385,9 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
421
385
|
onLeave();
|
|
422
386
|
},
|
|
423
387
|
children: [
|
|
424
|
-
animate && /* @__PURE__ */
|
|
425
|
-
/* @__PURE__ */
|
|
426
|
-
/* @__PURE__ */
|
|
388
|
+
animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
389
|
+
/* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
|
|
390
|
+
/* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
|
|
427
391
|
entries.map(([key], di) => {
|
|
428
392
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
429
393
|
const color = palette[2];
|
|
@@ -433,15 +397,15 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
433
397
|
const linePath = toSmoothPath(points);
|
|
434
398
|
const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
|
|
435
399
|
return /* @__PURE__ */ jsxs("g", { children: [
|
|
436
|
-
/* @__PURE__ */
|
|
437
|
-
/* @__PURE__ */
|
|
438
|
-
/* @__PURE__ */
|
|
400
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
401
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
|
|
402
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
|
|
439
403
|
] }) }),
|
|
440
404
|
/* @__PURE__ */ jsxs("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
|
|
441
|
-
/* @__PURE__ */
|
|
442
|
-
/* @__PURE__ */
|
|
405
|
+
/* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradientId})` }),
|
|
406
|
+
/* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
|
|
443
407
|
] }),
|
|
444
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */
|
|
408
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
|
|
445
409
|
"circle",
|
|
446
410
|
{
|
|
447
411
|
cx: points[activeIndex].x,
|
|
@@ -453,7 +417,7 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
453
417
|
)
|
|
454
418
|
] }, di);
|
|
455
419
|
}),
|
|
456
|
-
activeX !== null && /* @__PURE__ */
|
|
420
|
+
activeX !== null && /* @__PURE__ */ jsx(
|
|
457
421
|
"line",
|
|
458
422
|
{
|
|
459
423
|
x1: activeX,
|
|
@@ -463,7 +427,7 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
463
427
|
className: "chart-crosshair"
|
|
464
428
|
}
|
|
465
429
|
),
|
|
466
|
-
/* @__PURE__ */
|
|
430
|
+
/* @__PURE__ */ jsx(
|
|
467
431
|
"rect",
|
|
468
432
|
{
|
|
469
433
|
x: PADDING.left,
|
|
@@ -479,9 +443,9 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
479
443
|
);
|
|
480
444
|
});
|
|
481
445
|
CurveChart.displayName = "CurveChart";
|
|
482
|
-
var BarChart =
|
|
483
|
-
const entries =
|
|
484
|
-
const maxVal =
|
|
446
|
+
var BarChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
|
|
447
|
+
const entries = React.useMemo(() => Object.entries(data), [data]);
|
|
448
|
+
const maxVal = React.useMemo(() => {
|
|
485
449
|
const allValues = entries.flatMap(([, v]) => v);
|
|
486
450
|
return Math.max(...allValues) * 1.2 || 1;
|
|
487
451
|
}, [entries]);
|
|
@@ -493,7 +457,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
|
|
|
493
457
|
const barGap = groupCount > 1 ? 2 : 0;
|
|
494
458
|
const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
|
|
495
459
|
const baseline = PADDING.top + chartH;
|
|
496
|
-
const bars =
|
|
460
|
+
const bars = React.useMemo(
|
|
497
461
|
() => entries.map(
|
|
498
462
|
([, values], di) => values.map((v, i) => {
|
|
499
463
|
const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
|
|
@@ -507,10 +471,10 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
|
|
|
507
471
|
);
|
|
508
472
|
const barLabelStep = getLabelStep(count, chartW);
|
|
509
473
|
return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
|
|
510
|
-
/* @__PURE__ */
|
|
474
|
+
/* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
|
|
511
475
|
labels.map((label, i) => {
|
|
512
476
|
if (i % barLabelStep !== 0) return null;
|
|
513
|
-
return /* @__PURE__ */
|
|
477
|
+
return /* @__PURE__ */ jsx("text", { x: PADDING.left + groupW * i + groupW / 2, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
|
|
514
478
|
}),
|
|
515
479
|
entries.map(([key], di) => {
|
|
516
480
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
@@ -519,7 +483,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
|
|
|
519
483
|
const r = Math.min(4, b.w / 2);
|
|
520
484
|
const d = b.h <= r ? `M ${b.x} ${b.y + b.h} V ${b.y} H ${b.x + b.w} V ${b.y + b.h} Z` : `M ${b.x} ${b.y + b.h} V ${b.y + r} Q ${b.x} ${b.y} ${b.x + r} ${b.y} H ${b.x + b.w - r} Q ${b.x + b.w} ${b.y} ${b.x + b.w} ${b.y + r} V ${b.y + b.h} Z`;
|
|
521
485
|
const delay = 100 + i * 80;
|
|
522
|
-
return /* @__PURE__ */
|
|
486
|
+
return /* @__PURE__ */ jsx(
|
|
523
487
|
"path",
|
|
524
488
|
{
|
|
525
489
|
d,
|
|
@@ -529,8 +493,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
|
|
|
529
493
|
transformOrigin: `${b.x + b.w / 2}px ${baseline}px`,
|
|
530
494
|
animationDelay: `${delay}ms`
|
|
531
495
|
} : void 0,
|
|
532
|
-
onMouseEnter: (
|
|
533
|
-
onMouseMove: onMove,
|
|
496
|
+
onMouseEnter: () => onShowAt(b.x + b.w / 2, b.y, `${key}: ${labels[i]} \u2014 ${b.v}`),
|
|
534
497
|
onMouseLeave: onLeave
|
|
535
498
|
},
|
|
536
499
|
`${di}-${i}`
|
|
@@ -540,11 +503,11 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
|
|
|
540
503
|
] });
|
|
541
504
|
});
|
|
542
505
|
BarChart.displayName = "BarChart";
|
|
543
|
-
var PieDonutChart =
|
|
506
|
+
var PieDonutChart = React.memo(
|
|
544
507
|
({ data, labels, width, height, animate, isDoughnut, onHover, onMove, onLeave }) => {
|
|
545
|
-
const entries =
|
|
546
|
-
const values =
|
|
547
|
-
const total =
|
|
508
|
+
const entries = React.useMemo(() => Object.entries(data), [data]);
|
|
509
|
+
const values = React.useMemo(() => entries.flatMap(([, v]) => v), [entries]);
|
|
510
|
+
const total = React.useMemo(() => values.reduce((a, b) => a + b, 0) || 1, [values]);
|
|
548
511
|
const size = Math.min(width, height);
|
|
549
512
|
const cx = size / 2;
|
|
550
513
|
const cy = size / 2;
|
|
@@ -552,10 +515,10 @@ var PieDonutChart = React2.memo(
|
|
|
552
515
|
const innerR = isDoughnut ? r * 0.5 : 0;
|
|
553
516
|
const firstKey = entries[0]?.[0] ?? "";
|
|
554
517
|
const colorOffset = hashString(firstKey);
|
|
555
|
-
const maskRef =
|
|
518
|
+
const maskRef = React.useRef(null);
|
|
556
519
|
const maskR = r + 10;
|
|
557
520
|
const maskCircumference = 2 * Math.PI * maskR;
|
|
558
|
-
|
|
521
|
+
React.useEffect(() => {
|
|
559
522
|
if (!animate || !maskRef.current) return;
|
|
560
523
|
const el = maskRef.current;
|
|
561
524
|
el.style.strokeDasharray = `${maskCircumference}`;
|
|
@@ -565,7 +528,7 @@ var PieDonutChart = React2.memo(
|
|
|
565
528
|
el.style.strokeDashoffset = "0";
|
|
566
529
|
});
|
|
567
530
|
}, [animate, maskCircumference]);
|
|
568
|
-
const sliceData =
|
|
531
|
+
const sliceData = React.useMemo(() => {
|
|
569
532
|
let angle0 = -Math.PI / 2;
|
|
570
533
|
let cumulativeAngle = 0;
|
|
571
534
|
return values.map((v, i) => {
|
|
@@ -600,7 +563,7 @@ var PieDonutChart = React2.memo(
|
|
|
600
563
|
}, [values, total, cx, cy, r, innerR, labels]);
|
|
601
564
|
const maskId = `pie-mask-${isDoughnut ? "d" : "p"}`;
|
|
602
565
|
return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${size} ${size}`, className: "chart-svg chart-pie", children: [
|
|
603
|
-
animate && /* @__PURE__ */
|
|
566
|
+
animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("mask", { id: maskId, children: /* @__PURE__ */ jsx(
|
|
604
567
|
"circle",
|
|
605
568
|
{
|
|
606
569
|
ref: maskRef,
|
|
@@ -613,56 +576,39 @@ var PieDonutChart = React2.memo(
|
|
|
613
576
|
transform: `rotate(-90 ${cx} ${cy})`
|
|
614
577
|
}
|
|
615
578
|
) }) }),
|
|
616
|
-
/* @__PURE__ */
|
|
579
|
+
/* @__PURE__ */ jsx("g", { mask: animate ? `url(#${maskId})` : void 0, children: sliceData.map((s, i) => /* @__PURE__ */ jsx("g", { children: /* @__PURE__ */ jsx(
|
|
617
580
|
"path",
|
|
618
581
|
{
|
|
619
582
|
d: s.d,
|
|
620
583
|
fill: PIE_COLORS[(i + colorOffset) % PIE_COLORS.length],
|
|
621
|
-
className: "chart-slice"
|
|
622
|
-
onMouseEnter: (e) => onHover(e, `${s.label}: ${s.v} (${s.pct}%)`),
|
|
623
|
-
onMouseMove: onMove,
|
|
624
|
-
onMouseLeave: onLeave
|
|
584
|
+
className: "chart-slice"
|
|
625
585
|
}
|
|
626
|
-
) }, i)) })
|
|
627
|
-
sliceData.map((s, i) => s.angle > 0.2 && /* @__PURE__ */ jsx2(
|
|
628
|
-
"text",
|
|
629
|
-
{
|
|
630
|
-
x: s.lx,
|
|
631
|
-
y: s.ly,
|
|
632
|
-
className: `chart-pie-label ${animate ? "chart-pie-label-animate" : ""}`,
|
|
633
|
-
style: animate ? { animationDelay: `${s.labelDelay}ms` } : void 0,
|
|
634
|
-
textAnchor: "middle",
|
|
635
|
-
dominantBaseline: "central",
|
|
636
|
-
children: s.v
|
|
637
|
-
},
|
|
638
|
-
`label-${i}`
|
|
639
|
-
))
|
|
586
|
+
) }, i)) })
|
|
640
587
|
] });
|
|
641
588
|
}
|
|
642
589
|
);
|
|
643
590
|
PieDonutChart.displayName = "PieDonutChart";
|
|
644
|
-
var
|
|
645
|
-
const ref =
|
|
646
|
-
const [pos, setPos] =
|
|
647
|
-
|
|
591
|
+
var ChartTooltip = ({ x, y, containerWidth, containerHeight, children }) => {
|
|
592
|
+
const ref = React.useRef(null);
|
|
593
|
+
const [pos, setPos] = React.useState({ left: 0, top: 0 });
|
|
594
|
+
React.useLayoutEffect(() => {
|
|
648
595
|
const el = ref.current;
|
|
649
596
|
if (!el) return;
|
|
650
597
|
const w = el.offsetWidth;
|
|
651
598
|
const h = el.offsetHeight;
|
|
652
|
-
|
|
653
|
-
let
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
if (
|
|
657
|
-
if (left < 8) left = 8;
|
|
599
|
+
let left = x + TOOLTIP_OFFSET;
|
|
600
|
+
let top = y - h - TOOLTIP_OFFSET;
|
|
601
|
+
if (left + w > containerWidth) left = x - w - TOOLTIP_OFFSET;
|
|
602
|
+
if (top < 0) top = y + TOOLTIP_OFFSET;
|
|
603
|
+
if (left < 0) left = 0;
|
|
658
604
|
setPos({ left, top });
|
|
659
|
-
}, [
|
|
660
|
-
return /* @__PURE__ */
|
|
605
|
+
}, [x, y, containerWidth, containerHeight]);
|
|
606
|
+
return /* @__PURE__ */ jsx(
|
|
661
607
|
"div",
|
|
662
608
|
{
|
|
663
609
|
ref,
|
|
664
|
-
className:
|
|
665
|
-
style: {
|
|
610
|
+
className: "chart-tooltip chart-tooltip-show",
|
|
611
|
+
style: { left: pos.left, top: pos.top },
|
|
666
612
|
children
|
|
667
613
|
}
|
|
668
614
|
);
|
|
@@ -674,13 +620,13 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
674
620
|
const total = values.reduce((a, b) => a + b, 0) || 1;
|
|
675
621
|
const firstKey = entries[0]?.[0] ?? "";
|
|
676
622
|
const colorOffset = hashString(firstKey);
|
|
677
|
-
return /* @__PURE__ */
|
|
623
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: values.map((v, i) => {
|
|
678
624
|
const pct = Math.round(v / total * 100);
|
|
679
625
|
const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
|
|
680
626
|
return /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
|
|
681
|
-
/* @__PURE__ */
|
|
627
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
682
628
|
/* @__PURE__ */ jsxs("div", { className: "chart-legend-text", children: [
|
|
683
|
-
/* @__PURE__ */
|
|
629
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
|
|
684
630
|
/* @__PURE__ */ jsxs("span", { className: "chart-legend-value", children: [
|
|
685
631
|
v.toLocaleString(),
|
|
686
632
|
"(",
|
|
@@ -691,37 +637,37 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
691
637
|
] }, i);
|
|
692
638
|
}) });
|
|
693
639
|
}
|
|
694
|
-
return /* @__PURE__ */
|
|
640
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: entries.map(([key], di) => {
|
|
695
641
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
696
642
|
const color = palette[2];
|
|
697
643
|
const values = entries[di][1];
|
|
698
644
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
699
645
|
return /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
|
|
700
|
-
/* @__PURE__ */
|
|
646
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
701
647
|
/* @__PURE__ */ jsxs("div", { className: "chart-legend-text", children: [
|
|
702
|
-
/* @__PURE__ */
|
|
703
|
-
/* @__PURE__ */
|
|
648
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: key }),
|
|
649
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-value", children: sum.toLocaleString() })
|
|
704
650
|
] })
|
|
705
651
|
] }, di);
|
|
706
652
|
}) });
|
|
707
653
|
};
|
|
708
|
-
var Chart =
|
|
654
|
+
var Chart = React.memo((props) => {
|
|
709
655
|
const { type, data, labels, tooltip: showTooltip = true } = props;
|
|
710
|
-
const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
656
|
+
const { tooltip, show, showAt, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
711
657
|
const { width, height } = useChartSize(containerRef);
|
|
712
|
-
const stableData =
|
|
713
|
-
const stableLabels =
|
|
714
|
-
const dataKey =
|
|
658
|
+
const stableData = React.useMemo(() => data, [JSON.stringify(data)]);
|
|
659
|
+
const stableLabels = React.useMemo(() => labels, [JSON.stringify(labels)]);
|
|
660
|
+
const dataKey = React.useMemo(() => JSON.stringify(labels), [labels]);
|
|
715
661
|
const animate = useChartAnimation(containerRef, dataKey);
|
|
716
662
|
const ready = width > 0 && height > 0;
|
|
717
663
|
return /* @__PURE__ */ jsxs("div", { className: "lib-xplat-chart", ref: containerRef, children: [
|
|
718
|
-
ready && type === "line" && /* @__PURE__ */
|
|
719
|
-
ready && type === "curve" && /* @__PURE__ */
|
|
720
|
-
ready && type === "bar" && /* @__PURE__ */
|
|
721
|
-
ready && type === "pie" && /* @__PURE__ */
|
|
722
|
-
ready && type === "doughnut" && /* @__PURE__ */
|
|
723
|
-
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */
|
|
724
|
-
tooltip.content && /* @__PURE__ */
|
|
664
|
+
ready && type === "line" && /* @__PURE__ */ jsx(LineChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
|
|
665
|
+
ready && type === "curve" && /* @__PURE__ */ jsx(CurveChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
|
|
666
|
+
ready && type === "bar" && /* @__PURE__ */ jsx(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
|
|
667
|
+
ready && type === "pie" && /* @__PURE__ */ jsx(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
|
|
668
|
+
ready && type === "doughnut" && /* @__PURE__ */ jsx(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
|
|
669
|
+
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ jsx(ChartLegend, { data: stableData, labels: stableLabels, type }),
|
|
670
|
+
tooltip.visible && tooltip.content && /* @__PURE__ */ jsx(ChartTooltip, { x: tooltip.x, y: tooltip.y, containerWidth: width, containerHeight: height, children: tooltip.content })
|
|
725
671
|
] });
|
|
726
672
|
});
|
|
727
673
|
Chart.displayName = "Chart";
|