@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
|
@@ -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,27 +163,29 @@ 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;
|
|
154
170
|
setAnimate(false);
|
|
155
|
-
requestAnimationFrame(() =>
|
|
171
|
+
requestAnimationFrame(() => {
|
|
172
|
+
requestAnimationFrame(() => setAnimate(true));
|
|
173
|
+
});
|
|
156
174
|
}
|
|
157
175
|
}, [dataKey]);
|
|
158
176
|
return animate || prefersReducedMotion();
|
|
159
177
|
};
|
|
160
178
|
var TOOLTIP_OFFSET = 12;
|
|
161
179
|
var useChartTooltip = (enabled) => {
|
|
162
|
-
const [tooltip, setTooltip] =
|
|
180
|
+
const [tooltip, setTooltip] = import_react2.default.useState({
|
|
163
181
|
visible: false,
|
|
164
182
|
clientX: 0,
|
|
165
183
|
clientY: 0,
|
|
166
184
|
content: ""
|
|
167
185
|
});
|
|
168
|
-
const containerRef =
|
|
169
|
-
const rafRef =
|
|
170
|
-
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) => {
|
|
171
189
|
if (!enabled) return;
|
|
172
190
|
const cx = e.clientX;
|
|
173
191
|
const cy = e.clientY;
|
|
@@ -176,22 +194,22 @@ var useChartTooltip = (enabled) => {
|
|
|
176
194
|
setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
|
|
177
195
|
});
|
|
178
196
|
}, [enabled]);
|
|
179
|
-
const show =
|
|
197
|
+
const show = import_react2.default.useCallback((e, content) => {
|
|
180
198
|
if (!enabled) return;
|
|
181
199
|
setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
|
|
182
200
|
}, [enabled]);
|
|
183
|
-
const hide =
|
|
201
|
+
const hide = import_react2.default.useCallback(() => {
|
|
184
202
|
cancelAnimationFrame(rafRef.current);
|
|
185
203
|
setTooltip((prev) => ({ ...prev, visible: false }));
|
|
186
204
|
}, []);
|
|
187
205
|
return { tooltip, show, hide, move, containerRef };
|
|
188
206
|
};
|
|
189
|
-
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) => {
|
|
190
208
|
const y = PADDING.top + (1 - ratio) * chartH;
|
|
191
209
|
const val = Math.round(maxVal * ratio);
|
|
192
|
-
return /* @__PURE__ */ (0,
|
|
193
|
-
/* @__PURE__ */ (0,
|
|
194
|
-
/* @__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 })
|
|
195
213
|
] }, ratio);
|
|
196
214
|
}) }));
|
|
197
215
|
GridLines.displayName = "GridLines";
|
|
@@ -201,18 +219,18 @@ var getLabelStep = (count, chartW) => {
|
|
|
201
219
|
if (count <= maxLabels) return 1;
|
|
202
220
|
return Math.ceil(count / maxLabels);
|
|
203
221
|
};
|
|
204
|
-
var AxisLabels =
|
|
222
|
+
var AxisLabels = import_react2.default.memo(({ labels, count, chartW, height }) => {
|
|
205
223
|
const step = getLabelStep(count, chartW);
|
|
206
|
-
return /* @__PURE__ */ (0,
|
|
224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: labels.map((label, i) => {
|
|
207
225
|
if (i % step !== 0) return null;
|
|
208
226
|
const x = PADDING.left + i / (count - 1 || 1) * chartW;
|
|
209
|
-
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);
|
|
210
228
|
}) });
|
|
211
229
|
});
|
|
212
230
|
AxisLabels.displayName = "AxisLabels";
|
|
213
231
|
var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
214
|
-
const [activeIndex, setActiveIndex] =
|
|
215
|
-
const handleMouseMove =
|
|
232
|
+
const [activeIndex, setActiveIndex] = import_react2.default.useState(null);
|
|
233
|
+
const handleMouseMove = import_react2.default.useCallback((e) => {
|
|
216
234
|
const svg = e.currentTarget;
|
|
217
235
|
const rect = svg.getBoundingClientRect();
|
|
218
236
|
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
@@ -231,17 +249,17 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
231
249
|
}
|
|
232
250
|
setActiveIndex(minDist <= threshold ? closest : null);
|
|
233
251
|
}, [seriesPoints]);
|
|
234
|
-
const handleMouseLeave =
|
|
252
|
+
const handleMouseLeave = import_react2.default.useCallback(() => {
|
|
235
253
|
setActiveIndex(null);
|
|
236
254
|
}, []);
|
|
237
|
-
const tooltipContent =
|
|
255
|
+
const tooltipContent = import_react2.default.useMemo(() => {
|
|
238
256
|
if (activeIndex === null) return "";
|
|
239
257
|
return entries.map(([key], di) => {
|
|
240
258
|
const p = seriesPoints[di]?.[activeIndex];
|
|
241
259
|
return p ? `${key}: ${p.v}` : "";
|
|
242
260
|
}).filter(Boolean).join(" / ");
|
|
243
261
|
}, [activeIndex, entries, seriesPoints]);
|
|
244
|
-
const getTooltipAt =
|
|
262
|
+
const getTooltipAt = import_react2.default.useCallback((idx) => {
|
|
245
263
|
return entries.map(([key], di) => {
|
|
246
264
|
const p = seriesPoints[di]?.[idx];
|
|
247
265
|
return p ? `${key}: ${p.v}` : "";
|
|
@@ -249,16 +267,16 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
249
267
|
}, [entries, seriesPoints]);
|
|
250
268
|
return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
|
|
251
269
|
};
|
|
252
|
-
var LineChart =
|
|
253
|
-
const entries =
|
|
254
|
-
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(() => {
|
|
255
273
|
const allValues = entries.flatMap(([, v]) => v);
|
|
256
274
|
return Math.max(...allValues) * 1.2 || 1;
|
|
257
275
|
}, [entries]);
|
|
258
276
|
const count = labels.length;
|
|
259
277
|
const chartW = width - PADDING.left - PADDING.right;
|
|
260
278
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
261
|
-
const seriesPoints =
|
|
279
|
+
const seriesPoints = import_react2.default.useMemo(
|
|
262
280
|
() => entries.map(
|
|
263
281
|
([, values]) => values.map((v, i) => ({
|
|
264
282
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -268,34 +286,21 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
268
286
|
),
|
|
269
287
|
[entries, count, chartW, chartH, maxVal]
|
|
270
288
|
);
|
|
271
|
-
const
|
|
272
|
-
const clipRef = import_react.default.useRef(null);
|
|
289
|
+
const clipRef = import_react2.default.useRef(null);
|
|
273
290
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
274
|
-
|
|
275
|
-
if (!animate) return;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
|
|
283
|
-
el.style.strokeDashoffset = "0";
|
|
284
|
-
});
|
|
291
|
+
import_react2.default.useEffect(() => {
|
|
292
|
+
if (!animate || !clipRef.current) return;
|
|
293
|
+
clipRef.current.setAttribute("width", "0");
|
|
294
|
+
requestAnimationFrame(() => {
|
|
295
|
+
if (clipRef.current) {
|
|
296
|
+
clipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
297
|
+
clipRef.current.setAttribute("width", `${width}`);
|
|
298
|
+
}
|
|
285
299
|
});
|
|
286
|
-
|
|
287
|
-
clipRef.current.setAttribute("width", "0");
|
|
288
|
-
requestAnimationFrame(() => {
|
|
289
|
-
if (clipRef.current) {
|
|
290
|
-
clipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
291
|
-
clipRef.current.setAttribute("width", `${width}`);
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}, [animate, seriesPoints, width]);
|
|
300
|
+
}, [animate, width]);
|
|
296
301
|
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
297
302
|
const lineClipId = "line-area-clip";
|
|
298
|
-
return /* @__PURE__ */ (0,
|
|
303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
299
304
|
"svg",
|
|
300
305
|
{
|
|
301
306
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -328,9 +333,9 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
328
333
|
onLeave();
|
|
329
334
|
},
|
|
330
335
|
children: [
|
|
331
|
-
animate && /* @__PURE__ */ (0,
|
|
332
|
-
/* @__PURE__ */ (0,
|
|
333
|
-
/* @__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 }),
|
|
334
339
|
entries.map(([key], di) => {
|
|
335
340
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
336
341
|
const color = palette[2];
|
|
@@ -339,32 +344,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
339
344
|
const gradientId = `line-gradient-${di}`;
|
|
340
345
|
const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
341
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`;
|
|
342
|
-
return /* @__PURE__ */ (0,
|
|
343
|
-
/* @__PURE__ */ (0,
|
|
344
|
-
/* @__PURE__ */ (0,
|
|
345
|
-
/* @__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" })
|
|
346
351
|
] }) }),
|
|
347
|
-
/* @__PURE__ */ (0,
|
|
348
|
-
"path",
|
|
349
|
-
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
clipPath: animate ? `url(#${lineClipId})` : void 0
|
|
353
|
-
}
|
|
354
|
-
),
|
|
355
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
356
|
-
"polyline",
|
|
357
|
-
{
|
|
358
|
-
ref: (el) => {
|
|
359
|
-
lineRefs.current[di] = el;
|
|
360
|
-
},
|
|
361
|
-
points: polyPoints,
|
|
362
|
-
fill: "none",
|
|
363
|
-
stroke: color,
|
|
364
|
-
strokeWidth: "2"
|
|
365
|
-
}
|
|
366
|
-
),
|
|
367
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
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" })
|
|
355
|
+
] }),
|
|
356
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
368
357
|
"circle",
|
|
369
358
|
{
|
|
370
359
|
cx: points[activeIndex].x,
|
|
@@ -376,7 +365,7 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
376
365
|
)
|
|
377
366
|
] }, di);
|
|
378
367
|
}),
|
|
379
|
-
activeX !== null && /* @__PURE__ */ (0,
|
|
368
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
380
369
|
"line",
|
|
381
370
|
{
|
|
382
371
|
x1: activeX,
|
|
@@ -386,7 +375,7 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
386
375
|
className: "chart-crosshair"
|
|
387
376
|
}
|
|
388
377
|
),
|
|
389
|
-
/* @__PURE__ */ (0,
|
|
378
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
390
379
|
"rect",
|
|
391
380
|
{
|
|
392
381
|
x: PADDING.left,
|
|
@@ -402,16 +391,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
402
391
|
);
|
|
403
392
|
});
|
|
404
393
|
LineChart.displayName = "LineChart";
|
|
405
|
-
var CurveChart =
|
|
406
|
-
const entries =
|
|
407
|
-
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(() => {
|
|
408
397
|
const allValues = entries.flatMap(([, v]) => v);
|
|
409
398
|
return Math.max(...allValues) * 1.2 || 1;
|
|
410
399
|
}, [entries]);
|
|
411
400
|
const count = labels.length;
|
|
412
401
|
const chartW = width - PADDING.left - PADDING.right;
|
|
413
402
|
const chartH = height - PADDING.top - PADDING.bottom;
|
|
414
|
-
const seriesPoints =
|
|
403
|
+
const seriesPoints = import_react2.default.useMemo(
|
|
415
404
|
() => entries.map(
|
|
416
405
|
([, values]) => values.map((v, i) => ({
|
|
417
406
|
x: PADDING.left + i / (count - 1 || 1) * chartW,
|
|
@@ -421,34 +410,21 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
421
410
|
),
|
|
422
411
|
[entries, count, chartW, chartH, maxVal]
|
|
423
412
|
);
|
|
424
|
-
const
|
|
425
|
-
const curveClipRef = import_react.default.useRef(null);
|
|
413
|
+
const curveClipRef = import_react2.default.useRef(null);
|
|
426
414
|
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
427
|
-
|
|
428
|
-
if (!animate) return;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
|
|
436
|
-
el.style.strokeDashoffset = "0";
|
|
437
|
-
});
|
|
415
|
+
import_react2.default.useEffect(() => {
|
|
416
|
+
if (!animate || !curveClipRef.current) return;
|
|
417
|
+
curveClipRef.current.setAttribute("width", "0");
|
|
418
|
+
requestAnimationFrame(() => {
|
|
419
|
+
if (curveClipRef.current) {
|
|
420
|
+
curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
421
|
+
curveClipRef.current.setAttribute("width", `${width}`);
|
|
422
|
+
}
|
|
438
423
|
});
|
|
439
|
-
|
|
440
|
-
curveClipRef.current.setAttribute("width", "0");
|
|
441
|
-
requestAnimationFrame(() => {
|
|
442
|
-
if (curveClipRef.current) {
|
|
443
|
-
curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
444
|
-
curveClipRef.current.setAttribute("width", `${width}`);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}, [animate, seriesPoints, width]);
|
|
424
|
+
}, [animate, width]);
|
|
449
425
|
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
450
426
|
const curveClipId = "curve-area-clip";
|
|
451
|
-
return /* @__PURE__ */ (0,
|
|
427
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
452
428
|
"svg",
|
|
453
429
|
{
|
|
454
430
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -481,9 +457,9 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
481
457
|
onLeave();
|
|
482
458
|
},
|
|
483
459
|
children: [
|
|
484
|
-
animate && /* @__PURE__ */ (0,
|
|
485
|
-
/* @__PURE__ */ (0,
|
|
486
|
-
/* @__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 }),
|
|
487
463
|
entries.map(([key], di) => {
|
|
488
464
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
489
465
|
const color = palette[2];
|
|
@@ -492,32 +468,16 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
492
468
|
const gradientId = `curve-gradient-${di}`;
|
|
493
469
|
const linePath = toSmoothPath(points);
|
|
494
470
|
const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
|
|
495
|
-
return /* @__PURE__ */ (0,
|
|
496
|
-
/* @__PURE__ */ (0,
|
|
497
|
-
/* @__PURE__ */ (0,
|
|
498
|
-
/* @__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" })
|
|
499
475
|
] }) }),
|
|
500
|
-
/* @__PURE__ */ (0,
|
|
501
|
-
"path",
|
|
502
|
-
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
clipPath: animate ? `url(#${curveClipId})` : void 0
|
|
506
|
-
}
|
|
507
|
-
),
|
|
508
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
509
|
-
"path",
|
|
510
|
-
{
|
|
511
|
-
ref: (el) => {
|
|
512
|
-
lineRefs.current[di] = el;
|
|
513
|
-
},
|
|
514
|
-
d: linePath,
|
|
515
|
-
fill: "none",
|
|
516
|
-
stroke: color,
|
|
517
|
-
strokeWidth: "2"
|
|
518
|
-
}
|
|
519
|
-
),
|
|
520
|
-
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
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" })
|
|
479
|
+
] }),
|
|
480
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
521
481
|
"circle",
|
|
522
482
|
{
|
|
523
483
|
cx: points[activeIndex].x,
|
|
@@ -529,7 +489,7 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
529
489
|
)
|
|
530
490
|
] }, di);
|
|
531
491
|
}),
|
|
532
|
-
activeX !== null && /* @__PURE__ */ (0,
|
|
492
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
533
493
|
"line",
|
|
534
494
|
{
|
|
535
495
|
x1: activeX,
|
|
@@ -539,7 +499,7 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
539
499
|
className: "chart-crosshair"
|
|
540
500
|
}
|
|
541
501
|
),
|
|
542
|
-
/* @__PURE__ */ (0,
|
|
502
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
543
503
|
"rect",
|
|
544
504
|
{
|
|
545
505
|
x: PADDING.left,
|
|
@@ -555,9 +515,9 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
555
515
|
);
|
|
556
516
|
});
|
|
557
517
|
CurveChart.displayName = "CurveChart";
|
|
558
|
-
var BarChart =
|
|
559
|
-
const entries =
|
|
560
|
-
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(() => {
|
|
561
521
|
const allValues = entries.flatMap(([, v]) => v);
|
|
562
522
|
return Math.max(...allValues) * 1.2 || 1;
|
|
563
523
|
}, [entries]);
|
|
@@ -569,7 +529,7 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
569
529
|
const barGap = groupCount > 1 ? 2 : 0;
|
|
570
530
|
const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
|
|
571
531
|
const baseline = PADDING.top + chartH;
|
|
572
|
-
const bars =
|
|
532
|
+
const bars = import_react2.default.useMemo(
|
|
573
533
|
() => entries.map(
|
|
574
534
|
([, values], di) => values.map((v, i) => {
|
|
575
535
|
const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
|
|
@@ -582,11 +542,11 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
582
542
|
[entries, maxVal, chartH, groupW, barW, barGap, groupCount]
|
|
583
543
|
);
|
|
584
544
|
const barLabelStep = getLabelStep(count, chartW);
|
|
585
|
-
return /* @__PURE__ */ (0,
|
|
586
|
-
/* @__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 }),
|
|
587
547
|
labels.map((label, i) => {
|
|
588
548
|
if (i % barLabelStep !== 0) return null;
|
|
589
|
-
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);
|
|
590
550
|
}),
|
|
591
551
|
entries.map(([key], di) => {
|
|
592
552
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
@@ -595,7 +555,7 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
595
555
|
const r = Math.min(4, b.w / 2);
|
|
596
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`;
|
|
597
557
|
const delay = 100 + i * 80;
|
|
598
|
-
return /* @__PURE__ */ (0,
|
|
558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
599
559
|
"path",
|
|
600
560
|
{
|
|
601
561
|
d,
|
|
@@ -616,11 +576,11 @@ var BarChart = import_react.default.memo(({ data, labels, width, height, animate
|
|
|
616
576
|
] });
|
|
617
577
|
});
|
|
618
578
|
BarChart.displayName = "BarChart";
|
|
619
|
-
var PieDonutChart =
|
|
579
|
+
var PieDonutChart = import_react2.default.memo(
|
|
620
580
|
({ data, labels, width, height, animate, isDoughnut, onHover, onMove, onLeave }) => {
|
|
621
|
-
const entries =
|
|
622
|
-
const values =
|
|
623
|
-
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]);
|
|
624
584
|
const size = Math.min(width, height);
|
|
625
585
|
const cx = size / 2;
|
|
626
586
|
const cy = size / 2;
|
|
@@ -628,10 +588,10 @@ var PieDonutChart = import_react.default.memo(
|
|
|
628
588
|
const innerR = isDoughnut ? r * 0.5 : 0;
|
|
629
589
|
const firstKey = entries[0]?.[0] ?? "";
|
|
630
590
|
const colorOffset = hashString(firstKey);
|
|
631
|
-
const maskRef =
|
|
591
|
+
const maskRef = import_react2.default.useRef(null);
|
|
632
592
|
const maskR = r + 10;
|
|
633
593
|
const maskCircumference = 2 * Math.PI * maskR;
|
|
634
|
-
|
|
594
|
+
import_react2.default.useEffect(() => {
|
|
635
595
|
if (!animate || !maskRef.current) return;
|
|
636
596
|
const el = maskRef.current;
|
|
637
597
|
el.style.strokeDasharray = `${maskCircumference}`;
|
|
@@ -641,7 +601,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
641
601
|
el.style.strokeDashoffset = "0";
|
|
642
602
|
});
|
|
643
603
|
}, [animate, maskCircumference]);
|
|
644
|
-
const sliceData =
|
|
604
|
+
const sliceData = import_react2.default.useMemo(() => {
|
|
645
605
|
let angle0 = -Math.PI / 2;
|
|
646
606
|
let cumulativeAngle = 0;
|
|
647
607
|
return values.map((v, i) => {
|
|
@@ -675,8 +635,8 @@ var PieDonutChart = import_react.default.memo(
|
|
|
675
635
|
});
|
|
676
636
|
}, [values, total, cx, cy, r, innerR, labels]);
|
|
677
637
|
const maskId = `pie-mask-${isDoughnut ? "d" : "p"}`;
|
|
678
|
-
return /* @__PURE__ */ (0,
|
|
679
|
-
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)(
|
|
680
640
|
"circle",
|
|
681
641
|
{
|
|
682
642
|
ref: maskRef,
|
|
@@ -689,7 +649,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
689
649
|
transform: `rotate(-90 ${cx} ${cy})`
|
|
690
650
|
}
|
|
691
651
|
) }) }),
|
|
692
|
-
/* @__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)(
|
|
693
653
|
"path",
|
|
694
654
|
{
|
|
695
655
|
d: s.d,
|
|
@@ -700,7 +660,7 @@ var PieDonutChart = import_react.default.memo(
|
|
|
700
660
|
onMouseLeave: onLeave
|
|
701
661
|
}
|
|
702
662
|
) }, i)) }),
|
|
703
|
-
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)(
|
|
704
664
|
"text",
|
|
705
665
|
{
|
|
706
666
|
x: s.lx,
|
|
@@ -718,9 +678,9 @@ var PieDonutChart = import_react.default.memo(
|
|
|
718
678
|
);
|
|
719
679
|
PieDonutChart.displayName = "PieDonutChart";
|
|
720
680
|
var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
721
|
-
const ref =
|
|
722
|
-
const [pos, setPos] =
|
|
723
|
-
|
|
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(() => {
|
|
724
684
|
const el = ref.current;
|
|
725
685
|
if (!el) return;
|
|
726
686
|
const w = el.offsetWidth;
|
|
@@ -733,7 +693,7 @@ var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
|
733
693
|
if (left < 8) left = 8;
|
|
734
694
|
setPos({ left, top });
|
|
735
695
|
}, [clientX, clientY]);
|
|
736
|
-
return /* @__PURE__ */ (0,
|
|
696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
737
697
|
"div",
|
|
738
698
|
{
|
|
739
699
|
ref,
|
|
@@ -750,14 +710,14 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
750
710
|
const total = values.reduce((a, b) => a + b, 0) || 1;
|
|
751
711
|
const firstKey = entries[0]?.[0] ?? "";
|
|
752
712
|
const colorOffset = hashString(firstKey);
|
|
753
|
-
return /* @__PURE__ */ (0,
|
|
713
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "chart-legend", children: values.map((v, i) => {
|
|
754
714
|
const pct = Math.round(v / total * 100);
|
|
755
715
|
const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
|
|
756
|
-
return /* @__PURE__ */ (0,
|
|
757
|
-
/* @__PURE__ */ (0,
|
|
758
|
-
/* @__PURE__ */ (0,
|
|
759
|
-
/* @__PURE__ */ (0,
|
|
760
|
-
/* @__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: [
|
|
761
721
|
v.toLocaleString(),
|
|
762
722
|
"(",
|
|
763
723
|
pct,
|
|
@@ -767,37 +727,37 @@ var ChartLegend = ({ data, labels, type }) => {
|
|
|
767
727
|
] }, i);
|
|
768
728
|
}) });
|
|
769
729
|
}
|
|
770
|
-
return /* @__PURE__ */ (0,
|
|
730
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "chart-legend", children: entries.map(([key], di) => {
|
|
771
731
|
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
772
732
|
const color = palette[2];
|
|
773
733
|
const values = entries[di][1];
|
|
774
734
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
775
|
-
return /* @__PURE__ */ (0,
|
|
776
|
-
/* @__PURE__ */ (0,
|
|
777
|
-
/* @__PURE__ */ (0,
|
|
778
|
-
/* @__PURE__ */ (0,
|
|
779
|
-
/* @__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() })
|
|
780
740
|
] })
|
|
781
741
|
] }, di);
|
|
782
742
|
}) });
|
|
783
743
|
};
|
|
784
|
-
var Chart =
|
|
744
|
+
var Chart = import_react2.default.memo((props) => {
|
|
785
745
|
const { type, data, labels, tooltip: showTooltip = true } = props;
|
|
786
746
|
const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
787
747
|
const { width, height } = useChartSize(containerRef);
|
|
788
|
-
const stableData =
|
|
789
|
-
const stableLabels =
|
|
790
|
-
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]);
|
|
791
751
|
const animate = useChartAnimation(containerRef, dataKey);
|
|
792
752
|
const ready = width > 0 && height > 0;
|
|
793
|
-
return /* @__PURE__ */ (0,
|
|
794
|
-
ready && type === "line" && /* @__PURE__ */ (0,
|
|
795
|
-
ready && type === "curve" && /* @__PURE__ */ (0,
|
|
796
|
-
ready && type === "bar" && /* @__PURE__ */ (0,
|
|
797
|
-
ready && type === "pie" && /* @__PURE__ */ (0,
|
|
798
|
-
ready && type === "doughnut" && /* @__PURE__ */ (0,
|
|
799
|
-
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0,
|
|
800
|
-
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 }) })
|
|
801
761
|
] });
|
|
802
762
|
});
|
|
803
763
|
Chart.displayName = "Chart";
|