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