@x-plat/design-system 0.5.34 → 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 +131 -115
- package/dist/components/Chart/index.css +4 -1
- package/dist/components/Chart/index.js +117 -101
- package/dist/components/index.cjs +162 -162
- package/dist/components/index.css +4 -1
- package/dist/components/index.js +142 -142
- package/dist/index.cjs +162 -162
- package/dist/index.css +4 -1
- package/dist/index.js +142 -142
- package/package.json +1 -1
|
@@ -35,8 +35,24 @@ __export(Chart_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(Chart_exports);
|
|
36
36
|
|
|
37
37
|
// src/components/Chart/Chart.tsx
|
|
38
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
39
|
+
|
|
40
|
+
// src/tokens/hooks/Portal.tsx
|
|
38
41
|
var import_react = __toESM(require("react"), 1);
|
|
42
|
+
var import_react_dom = __toESM(require("react-dom"), 1);
|
|
39
43
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
var PortalContainerContext = import_react.default.createContext(null);
|
|
45
|
+
var Portal = ({ children }) => {
|
|
46
|
+
const contextContainer = import_react.default.useContext(PortalContainerContext);
|
|
47
|
+
if (typeof document === "undefined") return null;
|
|
48
|
+
const container = contextContainer ?? document.body;
|
|
49
|
+
return import_react_dom.default.createPortal(children, container);
|
|
50
|
+
};
|
|
51
|
+
Portal.displayName = "Portal";
|
|
52
|
+
var Portal_default = Portal;
|
|
53
|
+
|
|
54
|
+
// src/components/Chart/Chart.tsx
|
|
55
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
40
56
|
var CATEGORICAL_COUNT = 8;
|
|
41
57
|
var LINE_BAR_PALETTES = Array.from({ length: CATEGORICAL_COUNT }, (_, i) => {
|
|
42
58
|
const n = i + 1;
|
|
@@ -82,11 +98,11 @@ var toSmoothPath = (points) => {
|
|
|
82
98
|
};
|
|
83
99
|
var RESIZE_SETTLE_MS = 150;
|
|
84
100
|
var useChartSize = (ref) => {
|
|
85
|
-
const [size, setSize] =
|
|
86
|
-
const settleTimer =
|
|
87
|
-
const committedSize =
|
|
88
|
-
const initialRef =
|
|
89
|
-
|
|
101
|
+
const [size, setSize] = import_react2.default.useState({ width: 0, height: 0 });
|
|
102
|
+
const settleTimer = import_react2.default.useRef(0);
|
|
103
|
+
const committedSize = import_react2.default.useRef({ width: 0, height: 0 });
|
|
104
|
+
const initialRef = import_react2.default.useRef(true);
|
|
105
|
+
import_react2.default.useEffect(() => {
|
|
90
106
|
const el = ref.current;
|
|
91
107
|
if (!el) return;
|
|
92
108
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -128,10 +144,10 @@ var useChartSize = (ref) => {
|
|
|
128
144
|
};
|
|
129
145
|
var prefersReducedMotion = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
130
146
|
var useChartAnimation = (containerRef, dataKey) => {
|
|
131
|
-
const [animate, setAnimate] =
|
|
132
|
-
const prevDataKey =
|
|
133
|
-
const hasAnimated =
|
|
134
|
-
|
|
147
|
+
const [animate, setAnimate] = import_react2.default.useState(false);
|
|
148
|
+
const prevDataKey = import_react2.default.useRef(dataKey);
|
|
149
|
+
const hasAnimated = import_react2.default.useRef(false);
|
|
150
|
+
import_react2.default.useEffect(() => {
|
|
135
151
|
if (prefersReducedMotion()) return;
|
|
136
152
|
const el = containerRef.current;
|
|
137
153
|
if (!el) return;
|
|
@@ -147,7 +163,7 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
147
163
|
observer.observe(el);
|
|
148
164
|
return () => observer.disconnect();
|
|
149
165
|
}, [containerRef]);
|
|
150
|
-
|
|
166
|
+
import_react2.default.useEffect(() => {
|
|
151
167
|
if (dataKey !== prevDataKey.current) {
|
|
152
168
|
prevDataKey.current = dataKey;
|
|
153
169
|
if (prefersReducedMotion()) return;
|
|
@@ -161,15 +177,15 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
161
177
|
};
|
|
162
178
|
var TOOLTIP_OFFSET = 12;
|
|
163
179
|
var useChartTooltip = (enabled) => {
|
|
164
|
-
const [tooltip, setTooltip] =
|
|
180
|
+
const [tooltip, setTooltip] = import_react2.default.useState({
|
|
165
181
|
visible: false,
|
|
166
182
|
clientX: 0,
|
|
167
183
|
clientY: 0,
|
|
168
184
|
content: ""
|
|
169
185
|
});
|
|
170
|
-
const containerRef =
|
|
171
|
-
const rafRef =
|
|
172
|
-
const move =
|
|
186
|
+
const containerRef = import_react2.default.useRef(null);
|
|
187
|
+
const rafRef = import_react2.default.useRef(0);
|
|
188
|
+
const move = import_react2.default.useCallback((e) => {
|
|
173
189
|
if (!enabled) return;
|
|
174
190
|
const cx = e.clientX;
|
|
175
191
|
const cy = e.clientY;
|
|
@@ -178,22 +194,22 @@ var useChartTooltip = (enabled) => {
|
|
|
178
194
|
setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
|
|
179
195
|
});
|
|
180
196
|
}, [enabled]);
|
|
181
|
-
const show =
|
|
197
|
+
const show = import_react2.default.useCallback((e, content) => {
|
|
182
198
|
if (!enabled) return;
|
|
183
199
|
setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
|
|
184
200
|
}, [enabled]);
|
|
185
|
-
const hide =
|
|
201
|
+
const hide = import_react2.default.useCallback(() => {
|
|
186
202
|
cancelAnimationFrame(rafRef.current);
|
|
187
203
|
setTooltip((prev) => ({ ...prev, visible: false }));
|
|
188
204
|
}, []);
|
|
189
205
|
return { tooltip, show, hide, move, containerRef };
|
|
190
206
|
};
|
|
191
|
-
var GridLines =
|
|
207
|
+
var GridLines = import_react2.default.memo(({ width, height, chartH, maxVal }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
|
|
192
208
|
const y = PADDING.top + (1 - ratio) * chartH;
|
|
193
209
|
const val = Math.round(maxVal * ratio);
|
|
194
|
-
return /* @__PURE__ */ (0,
|
|
195
|
-
/* @__PURE__ */ (0,
|
|
196
|
-
/* @__PURE__ */ (0,
|
|
210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
|
|
197
213
|
] }, ratio);
|
|
198
214
|
}) }));
|
|
199
215
|
GridLines.displayName = "GridLines";
|
|
@@ -203,18 +219,18 @@ var getLabelStep = (count, chartW) => {
|
|
|
203
219
|
if (count <= maxLabels) return 1;
|
|
204
220
|
return Math.ceil(count / maxLabels);
|
|
205
221
|
};
|
|
206
|
-
var AxisLabels =
|
|
222
|
+
var AxisLabels = import_react2.default.memo(({ labels, count, chartW, height }) => {
|
|
207
223
|
const step = getLabelStep(count, chartW);
|
|
208
|
-
return /* @__PURE__ */ (0,
|
|
224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: labels.map((label, i) => {
|
|
209
225
|
if (i % step !== 0) return null;
|
|
210
226
|
const x = PADDING.left + i / (count - 1 || 1) * chartW;
|
|
211
|
-
return /* @__PURE__ */ (0,
|
|
227
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
|
|
212
228
|
}) });
|
|
213
229
|
});
|
|
214
230
|
AxisLabels.displayName = "AxisLabels";
|
|
215
231
|
var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
216
|
-
const [activeIndex, setActiveIndex] =
|
|
217
|
-
const handleMouseMove =
|
|
232
|
+
const [activeIndex, setActiveIndex] = import_react2.default.useState(null);
|
|
233
|
+
const handleMouseMove = import_react2.default.useCallback((e) => {
|
|
218
234
|
const svg = e.currentTarget;
|
|
219
235
|
const rect = svg.getBoundingClientRect();
|
|
220
236
|
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
@@ -233,17 +249,17 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
233
249
|
}
|
|
234
250
|
setActiveIndex(minDist <= threshold ? closest : null);
|
|
235
251
|
}, [seriesPoints]);
|
|
236
|
-
const handleMouseLeave =
|
|
252
|
+
const handleMouseLeave = import_react2.default.useCallback(() => {
|
|
237
253
|
setActiveIndex(null);
|
|
238
254
|
}, []);
|
|
239
|
-
const tooltipContent =
|
|
255
|
+
const tooltipContent = import_react2.default.useMemo(() => {
|
|
240
256
|
if (activeIndex === null) return "";
|
|
241
257
|
return entries.map(([key], di) => {
|
|
242
258
|
const p = seriesPoints[di]?.[activeIndex];
|
|
243
259
|
return p ? `${key}: ${p.v}` : "";
|
|
244
260
|
}).filter(Boolean).join(" / ");
|
|
245
261
|
}, [activeIndex, entries, seriesPoints]);
|
|
246
|
-
const getTooltipAt =
|
|
262
|
+
const getTooltipAt = import_react2.default.useCallback((idx) => {
|
|
247
263
|
return entries.map(([key], di) => {
|
|
248
264
|
const p = seriesPoints[di]?.[idx];
|
|
249
265
|
return p ? `${key}: ${p.v}` : "";
|
|
@@ -251,16 +267,16 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
251
267
|
}, [entries, seriesPoints]);
|
|
252
268
|
return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
|
|
253
269
|
};
|
|
254
|
-
var LineChart =
|
|
255
|
-
const entries =
|
|
256
|
-
const maxVal =
|
|
270
|
+
var LineChart = import_react2.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
271
|
+
const entries = import_react2.default.useMemo(() => Object.entries(data), [data]);
|
|
272
|
+
const maxVal = import_react2.default.useMemo(() => {
|
|
257
273
|
const allValues = entries.flatMap(([, v]) => v);
|
|
258
274
|
return Math.max(...allValues) * 1.2 || 1;
|
|
259
275
|
}, [entries]);
|
|
260
276
|
const count = labels.length;
|
|
261
277
|
const chartW = width - PADDING.left - PADDING.right;
|
|
262
278
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
263
|
-
const seriesPoints =
|
|
279
|
+
const seriesPoints = import_react2.default.useMemo(
|
|
264
280
|
() => entries.map(
|
|
265
281
|
([, values]) => values.map((v, i) => ({
|
|
266
282
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -270,9 +286,9 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
270
286
|
),
|
|
271
287
|
[entries, count, chartW, chartH, maxVal]
|
|
272
288
|
);
|
|
273
|
-
const clipRef =
|
|
289
|
+
const clipRef = import_react2.default.useRef(null);
|
|
274
290
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
275
|
-
|
|
291
|
+
import_react2.default.useEffect(() => {
|
|
276
292
|
if (!animate || !clipRef.current) return;
|
|
277
293
|
clipRef.current.setAttribute("width", "0");
|
|
278
294
|
requestAnimationFrame(() => {
|
|
@@ -284,7 +300,7 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
284
300
|
}, [animate, width]);
|
|
285
301
|
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
286
302
|
const lineClipId = "line-area-clip";
|
|
287
|
-
return /* @__PURE__ */ (0,
|
|
303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
288
304
|
"svg",
|
|
289
305
|
{
|
|
290
306
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -317,9 +333,9 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
317
333
|
onLeave();
|
|
318
334
|
},
|
|
319
335
|
children: [
|
|
320
|
-
animate && /* @__PURE__ */ (0,
|
|
321
|
-
/* @__PURE__ */ (0,
|
|
322
|
-
/* @__PURE__ */ (0,
|
|
336
|
+
animate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("clipPath", { id: lineClipId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GridLines, { width, height, chartH, maxVal }),
|
|
338
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AxisLabels, { labels, count, chartW, height }),
|
|
323
339
|
entries.map(([key], di) => {
|
|
324
340
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
325
341
|
const color = palette[2];
|
|
@@ -328,16 +344,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
328
344
|
const gradientId = `line-gradient-${di}`;
|
|
329
345
|
const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
330
346
|
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`;
|
|
331
|
-
return /* @__PURE__ */ (0,
|
|
332
|
-
/* @__PURE__ */ (0,
|
|
333
|
-
/* @__PURE__ */ (0,
|
|
334
|
-
/* @__PURE__ */ (0,
|
|
347
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
|
|
335
351
|
] }) }),
|
|
336
|
-
/* @__PURE__ */ (0,
|
|
337
|
-
/* @__PURE__ */ (0,
|
|
338
|
-
/* @__PURE__ */ (0,
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: areaD, fill: `url(#${gradientId})` }),
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
|
|
339
355
|
] }),
|
|
340
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0,
|
|
356
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
341
357
|
"circle",
|
|
342
358
|
{
|
|
343
359
|
cx: points[activeIndex].x,
|
|
@@ -349,7 +365,7 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
349
365
|
)
|
|
350
366
|
] }, di);
|
|
351
367
|
}),
|
|
352
|
-
activeX !== null && /* @__PURE__ */ (0,
|
|
368
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
353
369
|
"line",
|
|
354
370
|
{
|
|
355
371
|
x1: activeX,
|
|
@@ -359,7 +375,7 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
359
375
|
className: "chart-crosshair"
|
|
360
376
|
}
|
|
361
377
|
),
|
|
362
|
-
/* @__PURE__ */ (0,
|
|
378
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
363
379
|
"rect",
|
|
364
380
|
{
|
|
365
381
|
x: PADDING.left,
|
|
@@ -375,16 +391,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
375
391
|
);
|
|
376
392
|
});
|
|
377
393
|
LineChart.displayName = "LineChart";
|
|
378
|
-
var CurveChart =
|
|
379
|
-
const entries =
|
|
380
|
-
const maxVal =
|
|
394
|
+
var CurveChart = import_react2.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
395
|
+
const entries = import_react2.default.useMemo(() => Object.entries(data), [data]);
|
|
396
|
+
const maxVal = import_react2.default.useMemo(() => {
|
|
381
397
|
const allValues = entries.flatMap(([, v]) => v);
|
|
382
398
|
return Math.max(...allValues) * 1.2 || 1;
|
|
383
399
|
}, [entries]);
|
|
384
400
|
const count = labels.length;
|
|
385
401
|
const chartW = width - PADDING.left - PADDING.right;
|
|
386
402
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
387
|
-
const seriesPoints =
|
|
403
|
+
const seriesPoints = import_react2.default.useMemo(
|
|
388
404
|
() => entries.map(
|
|
389
405
|
([, values]) => values.map((v, i) => ({
|
|
390
406
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -394,9 +410,9 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
394
410
|
),
|
|
395
411
|
[entries, count, chartW, chartH, maxVal]
|
|
396
412
|
);
|
|
397
|
-
const curveClipRef =
|
|
413
|
+
const curveClipRef = import_react2.default.useRef(null);
|
|
398
414
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
399
|
-
|
|
415
|
+
import_react2.default.useEffect(() => {
|
|
400
416
|
if (!animate || !curveClipRef.current) return;
|
|
401
417
|
curveClipRef.current.setAttribute("width", "0");
|
|
402
418
|
requestAnimationFrame(() => {
|
|
@@ -408,7 +424,7 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
408
424
|
}, [animate, width]);
|
|
409
425
|
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
410
426
|
const curveClipId = "curve-area-clip";
|
|
411
|
-
return /* @__PURE__ */ (0,
|
|
427
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
412
428
|
"svg",
|
|
413
429
|
{
|
|
414
430
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -441,9 +457,9 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
441
457
|
onLeave();
|
|
442
458
|
},
|
|
443
459
|
children: [
|
|
444
|
-
animate && /* @__PURE__ */ (0,
|
|
445
|
-
/* @__PURE__ */ (0,
|
|
446
|
-
/* @__PURE__ */ (0,
|
|
460
|
+
animate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("clipPath", { id: curveClipId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GridLines, { width, height, chartH, maxVal }),
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AxisLabels, { labels, count, chartW, height }),
|
|
447
463
|
entries.map(([key], di) => {
|
|
448
464
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
449
465
|
const color = palette[2];
|
|
@@ -452,16 +468,16 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
452
468
|
const gradientId = `curve-gradient-${di}`;
|
|
453
469
|
const linePath = toSmoothPath(points);
|
|
454
470
|
const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
|
|
455
|
-
return /* @__PURE__ */ (0,
|
|
456
|
-
/* @__PURE__ */ (0,
|
|
457
|
-
/* @__PURE__ */ (0,
|
|
458
|
-
/* @__PURE__ */ (0,
|
|
471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
472
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
|
|
474
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
|
|
459
475
|
] }) }),
|
|
460
|
-
/* @__PURE__ */ (0,
|
|
461
|
-
/* @__PURE__ */ (0,
|
|
462
|
-
/* @__PURE__ */ (0,
|
|
476
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
|
|
477
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: areaPath, fill: `url(#${gradientId})` }),
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
|
|
463
479
|
] }),
|
|
464
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0,
|
|
480
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
465
481
|
"circle",
|
|
466
482
|
{
|
|
467
483
|
cx: points[activeIndex].x,
|
|
@@ -473,7 +489,7 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
473
489
|
)
|
|
474
490
|
] }, di);
|
|
475
491
|
}),
|
|
476
|
-
activeX !== null && /* @__PURE__ */ (0,
|
|
492
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
477
493
|
"line",
|
|
478
494
|
{
|
|
479
495
|
x1: activeX,
|
|
@@ -483,7 +499,7 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
483
499
|
className: "chart-crosshair"
|
|
484
500
|
}
|
|
485
501
|
),
|
|
486
|
-
/* @__PURE__ */ (0,
|
|
502
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
487
503
|
"rect",
|
|
488
504
|
{
|
|
489
505
|
x: PADDING.left,
|
|
@@ -499,9 +515,9 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
499
515
|
);
|
|
500
516
|
});
|
|
501
517
|
CurveChart.displayName = "CurveChart";
|
|
502
|
-
var BarChart =
|
|
503
|
-
const entries =
|
|
504
|
-
const maxVal =
|
|
518
|
+
var BarChart = import_react2.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
519
|
+
const entries = import_react2.default.useMemo(() => Object.entries(data), [data]);
|
|
520
|
+
const maxVal = import_react2.default.useMemo(() => {
|
|
505
521
|
const allValues = entries.flatMap(([, v]) => v);
|
|
506
522
|
return Math.max(...allValues) * 1.2 || 1;
|
|
507
523
|
}, [entries]);
|
|
@@ -513,7 +529,7 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
513
529
|
const barGap = groupCount > 1 ? 2 : 0;
|
|
514
530
|
const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
|
|
515
531
|
const baseline = PADDING.top + chartH;
|
|
516
|
-
const bars =
|
|
532
|
+
const bars = import_react2.default.useMemo(
|
|
517
533
|
() => entries.map(
|
|
518
534
|
([, values], di) => values.map((v, i) => {
|
|
519
535
|
const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
|
|
@@ -526,11 +542,11 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
526
542
|
[entries, maxVal, chartH, groupW, barW, barGap, groupCount]
|
|
527
543
|
);
|
|
528
544
|
const barLabelStep = getLabelStep(count, chartW);
|
|
529
|
-
return /* @__PURE__ */ (0,
|
|
530
|
-
/* @__PURE__ */ (0,
|
|
545
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
|
|
546
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GridLines, { width, height, chartH, maxVal }),
|
|
531
547
|
labels.map((label, i) => {
|
|
532
548
|
if (i % barLabelStep !== 0) return null;
|
|
533
|
-
return /* @__PURE__ */ (0,
|
|
549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { x: PADDING.left + groupW * i + groupW / 2, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
|
|
534
550
|
}),
|
|
535
551
|
entries.map(([key], di) => {
|
|
536
552
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
@@ -539,7 +555,7 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
539
555
|
const r = Math.min(4, b.w / 2);
|
|
540
556
|
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`;
|
|
541
557
|
const delay = 100 + i * 80;
|
|
542
|
-
return /* @__PURE__ */ (0,
|
|
558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
543
559
|
"path",
|
|
544
560
|
{
|
|
545
561
|
d,
|
|
@@ -560,11 +576,11 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
560
576
|
] });
|
|
561
577
|
});
|
|
562
578
|
BarChart.displayName = "BarChart";
|
|
563
|
-
var PieDonutChart =
|
|
579
|
+
var PieDonutChart = import_react2.default.memo(
|
|
564
580
|
({ data, labels, width, height, animate, isDoughnut, onHover, onMove, onLeave }) => {
|
|
565
|
-
const entries =
|
|
566
|
-
const values =
|
|
567
|
-
const total =
|
|
581
|
+
const entries = import_react2.default.useMemo(() => Object.entries(data), [data]);
|
|
582
|
+
const values = import_react2.default.useMemo(() => entries.flatMap(([, v]) => v), [entries]);
|
|
583
|
+
const total = import_react2.default.useMemo(() => values.reduce((a, b) => a + b, 0) || 1, [values]);
|
|
568
584
|
const size = Math.min(width, height);
|
|
569
585
|
const cx = size / 2;
|
|
570
586
|
const cy = size / 2;
|
|
@@ -572,10 +588,10 @@ var PieDonutChart = import_react.default.memo(
|
|
|
572
588
|
const innerR = isDoughnut ? r * 0.5 : 0;
|
|
573
589
|
const firstKey = entries[0]?.[0] ?? "";
|
|
574
590
|
const colorOffset = hashString(firstKey);
|
|
575
|
-
const maskRef =
|
|
591
|
+
const maskRef = import_react2.default.useRef(null);
|
|
576
592
|
const maskR = r + 10;
|
|
577
593
|
const maskCircumference = 2 * Math.PI * maskR;
|
|
578
|
-
|
|
594
|
+
import_react2.default.useEffect(() => {
|
|
579
595
|
if (!animate || !maskRef.current) return;
|
|
580
596
|
const el = maskRef.current;
|
|
581
597
|
el.style.strokeDasharray = `${maskCircumference}`;
|
|
@@ -585,7 +601,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
585
601
|
el.style.strokeDashoffset = "0";
|
|
586
602
|
});
|
|
587
603
|
}, [animate, maskCircumference]);
|
|
588
|
-
const sliceData =
|
|
604
|
+
const sliceData = import_react2.default.useMemo(() => {
|
|
589
605
|
let angle0 = -Math.PI / 2;
|
|
590
606
|
let cumulativeAngle = 0;
|
|
591
607
|
return values.map((v, i) => {
|
|
@@ -619,8 +635,8 @@ var PieDonutChart = import_react.default.memo(
|
|
|
619
635
|
});
|
|
620
636
|
}, [values, total, cx, cy, r, innerR, labels]);
|
|
621
637
|
const maskId = `pie-mask-${isDoughnut ? "d" : "p"}`;
|
|
622
|
-
return /* @__PURE__ */ (0,
|
|
623
|
-
animate && /* @__PURE__ */ (0,
|
|
638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { viewBox: `0 0 ${size} ${size}`, className: "chart-svg chart-pie", children: [
|
|
639
|
+
animate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("mask", { id: maskId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
624
640
|
"circle",
|
|
625
641
|
{
|
|
626
642
|
ref: maskRef,
|
|
@@ -633,7 +649,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
633
649
|
transform: `rotate(-90 ${cx} ${cy})`
|
|
634
650
|
}
|
|
635
651
|
) }) }),
|
|
636
|
-
/* @__PURE__ */ (0,
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { mask: animate ? `url(#${maskId})` : void 0, children: sliceData.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
637
653
|
"path",
|
|
638
654
|
{
|
|
639
655
|
d: s.d,
|
|
@@ -644,7 +660,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
644
660
|
onMouseLeave: onLeave
|
|
645
661
|
}
|
|
646
662
|
) }, i)) }),
|
|
647
|
-
sliceData.map((s, i) => s.angle > 0.2 && /* @__PURE__ */ (0,
|
|
663
|
+
sliceData.map((s, i) => s.angle > 0.2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
648
664
|
"text",
|
|
649
665
|
{
|
|
650
666
|
x: s.lx,
|
|
@@ -662,9 +678,9 @@ var PieDonutChart = import_react.default.memo(
|
|
|
662
678
|
);
|
|
663
679
|
PieDonutChart.displayName = "PieDonutChart";
|
|
664
680
|
var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
665
|
-
const ref =
|
|
666
|
-
const [pos, setPos] =
|
|
667
|
-
|
|
681
|
+
const ref = import_react2.default.useRef(null);
|
|
682
|
+
const [pos, setPos] = import_react2.default.useState({ left: 0, top: 0 });
|
|
683
|
+
import_react2.default.useLayoutEffect(() => {
|
|
668
684
|
const el = ref.current;
|
|
669
685
|
if (!el) return;
|
|
670
686
|
const w = el.offsetWidth;
|
|
@@ -677,7 +693,7 @@ var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
|
677
693
|
if (left < 8) left = 8;
|
|
678
694
|
setPos({ left, top });
|
|
679
695
|
}, [clientX, clientY]);
|
|
680
|
-
return /* @__PURE__ */ (0,
|
|
696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
681
697
|
"div",
|
|
682
698
|
{
|
|
683
699
|
ref,
|
|
@@ -694,14 +710,14 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
694
710
|
const total = values.reduce((a, b) => a + b, 0) || 1;
|
|
695
711
|
const firstKey = entries[0]?.[0] ?? "";
|
|
696
712
|
const colorOffset = hashString(firstKey);
|
|
697
|
-
return /* @__PURE__ */ (0,
|
|
713
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "chart-legend", children: values.map((v, i) => {
|
|
698
714
|
const pct = Math.round(v / total * 100);
|
|
699
715
|
const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
|
|
700
|
-
return /* @__PURE__ */ (0,
|
|
701
|
-
/* @__PURE__ */ (0,
|
|
702
|
-
/* @__PURE__ */ (0,
|
|
703
|
-
/* @__PURE__ */ (0,
|
|
704
|
-
/* @__PURE__ */ (0,
|
|
716
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "chart-legend-item", children: [
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
718
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "chart-legend-text", children: [
|
|
719
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "chart-legend-value", children: [
|
|
705
721
|
v.toLocaleString(),
|
|
706
722
|
"(",
|
|
707
723
|
pct,
|
|
@@ -711,37 +727,37 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
711
727
|
] }, i);
|
|
712
728
|
}) });
|
|
713
729
|
}
|
|
714
|
-
return /* @__PURE__ */ (0,
|
|
730
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "chart-legend", children: entries.map(([key], di) => {
|
|
715
731
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
716
732
|
const color = palette[2];
|
|
717
733
|
const values = entries[di][1];
|
|
718
734
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
719
|
-
return /* @__PURE__ */ (0,
|
|
720
|
-
/* @__PURE__ */ (0,
|
|
721
|
-
/* @__PURE__ */ (0,
|
|
722
|
-
/* @__PURE__ */ (0,
|
|
723
|
-
/* @__PURE__ */ (0,
|
|
735
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "chart-legend-item", children: [
|
|
736
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "chart-legend-text", children: [
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "chart-legend-label", children: key }),
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "chart-legend-value", children: sum.toLocaleString() })
|
|
724
740
|
] })
|
|
725
741
|
] }, di);
|
|
726
742
|
}) });
|
|
727
743
|
};
|
|
728
|
-
var Chart =
|
|
744
|
+
var Chart = import_react2.default.memo((props) => {
|
|
729
745
|
const { type, data, labels, tooltip: showTooltip = true } = props;
|
|
730
746
|
const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
731
747
|
const { width, height } = useChartSize(containerRef);
|
|
732
|
-
const stableData =
|
|
733
|
-
const stableLabels =
|
|
734
|
-
const dataKey =
|
|
748
|
+
const stableData = import_react2.default.useMemo(() => data, [JSON.stringify(data)]);
|
|
749
|
+
const stableLabels = import_react2.default.useMemo(() => labels, [JSON.stringify(labels)]);
|
|
750
|
+
const dataKey = import_react2.default.useMemo(() => JSON.stringify(labels), [labels]);
|
|
735
751
|
const animate = useChartAnimation(containerRef, dataKey);
|
|
736
752
|
const ready = width > 0 && height > 0;
|
|
737
|
-
return /* @__PURE__ */ (0,
|
|
738
|
-
ready && type === "line" && /* @__PURE__ */ (0,
|
|
739
|
-
ready && type === "curve" && /* @__PURE__ */ (0,
|
|
740
|
-
ready && type === "bar" && /* @__PURE__ */ (0,
|
|
741
|
-
ready && type === "pie" && /* @__PURE__ */ (0,
|
|
742
|
-
ready && type === "doughnut" && /* @__PURE__ */ (0,
|
|
743
|
-
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0,
|
|
744
|
-
tooltip.content && /* @__PURE__ */ (0,
|
|
753
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lib-xplat-chart", ref: containerRef, children: [
|
|
754
|
+
ready && type === "line" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LineChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
755
|
+
ready && type === "curve" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CurveChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
756
|
+
ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
757
|
+
ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
758
|
+
ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
|
|
759
|
+
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChartLegend, { data: stableData, labels: stableLabels, type }),
|
|
760
|
+
tooltip.content && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content }) })
|
|
745
761
|
] });
|
|
746
762
|
});
|
|
747
763
|
Chart.displayName = "Chart";
|
|
@@ -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
|
}
|