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