@x-plat/design-system 0.5.32 → 0.5.34
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 +148 -144
- package/dist/components/Chart/index.css +54 -10
- package/dist/components/Chart/index.js +148 -144
- package/dist/components/index.cjs +148 -144
- package/dist/components/index.css +54 -10
- package/dist/components/index.js +148 -144
- package/dist/index.cjs +148 -144
- package/dist/index.css +54 -10
- package/dist/index.js +148 -144
- package/package.json +1 -1
|
@@ -152,45 +152,35 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
152
152
|
prevDataKey.current = dataKey;
|
|
153
153
|
if (prefersReducedMotion()) return;
|
|
154
154
|
setAnimate(false);
|
|
155
|
-
requestAnimationFrame(() =>
|
|
155
|
+
requestAnimationFrame(() => {
|
|
156
|
+
requestAnimationFrame(() => setAnimate(true));
|
|
157
|
+
});
|
|
156
158
|
}
|
|
157
159
|
}, [dataKey]);
|
|
158
160
|
return animate || prefersReducedMotion();
|
|
159
161
|
};
|
|
162
|
+
var TOOLTIP_OFFSET = 12;
|
|
160
163
|
var useChartTooltip = (enabled) => {
|
|
161
164
|
const [tooltip, setTooltip] = import_react.default.useState({
|
|
162
165
|
visible: false,
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
clientX: 0,
|
|
167
|
+
clientY: 0,
|
|
165
168
|
content: ""
|
|
166
169
|
});
|
|
167
170
|
const containerRef = import_react.default.useRef(null);
|
|
168
171
|
const rafRef = import_react.default.useRef(0);
|
|
169
172
|
const move = import_react.default.useCallback((e) => {
|
|
170
173
|
if (!enabled) return;
|
|
171
|
-
const
|
|
172
|
-
const
|
|
174
|
+
const cx = e.clientX;
|
|
175
|
+
const cy = e.clientY;
|
|
173
176
|
cancelAnimationFrame(rafRef.current);
|
|
174
177
|
rafRef.current = requestAnimationFrame(() => {
|
|
175
|
-
|
|
176
|
-
if (!rect) return;
|
|
177
|
-
setTooltip((prev) => ({
|
|
178
|
-
...prev,
|
|
179
|
-
x: clientX - rect.left,
|
|
180
|
-
y: clientY - rect.top - 12
|
|
181
|
-
}));
|
|
178
|
+
setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
|
|
182
179
|
});
|
|
183
180
|
}, [enabled]);
|
|
184
181
|
const show = import_react.default.useCallback((e, content) => {
|
|
185
182
|
if (!enabled) return;
|
|
186
|
-
|
|
187
|
-
if (!rect) return;
|
|
188
|
-
setTooltip({
|
|
189
|
-
visible: true,
|
|
190
|
-
x: e.clientX - rect.left,
|
|
191
|
-
y: e.clientY - rect.top - 12,
|
|
192
|
-
content
|
|
193
|
-
});
|
|
183
|
+
setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
|
|
194
184
|
}, [enabled]);
|
|
195
185
|
const hide = import_react.default.useCallback(() => {
|
|
196
186
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -224,14 +214,14 @@ var AxisLabels = import_react.default.memo(({ labels, count, chartW, height }) =
|
|
|
224
214
|
AxisLabels.displayName = "AxisLabels";
|
|
225
215
|
var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
226
216
|
const [activeIndex, setActiveIndex] = import_react.default.useState(null);
|
|
227
|
-
const [mouseX, setMouseX] = import_react.default.useState(null);
|
|
228
217
|
const handleMouseMove = import_react.default.useCallback((e) => {
|
|
229
218
|
const svg = e.currentTarget;
|
|
230
219
|
const rect = svg.getBoundingClientRect();
|
|
231
220
|
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
232
|
-
setMouseX(mx);
|
|
233
221
|
if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
|
|
234
222
|
const points = seriesPoints[0];
|
|
223
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
224
|
+
const threshold = step / 2;
|
|
235
225
|
let closest = 0;
|
|
236
226
|
let minDist = Math.abs(points[0].x - mx);
|
|
237
227
|
for (let i = 1; i < points.length; i++) {
|
|
@@ -241,11 +231,10 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
241
231
|
closest = i;
|
|
242
232
|
}
|
|
243
233
|
}
|
|
244
|
-
setActiveIndex(closest);
|
|
234
|
+
setActiveIndex(minDist <= threshold ? closest : null);
|
|
245
235
|
}, [seriesPoints]);
|
|
246
236
|
const handleMouseLeave = import_react.default.useCallback(() => {
|
|
247
237
|
setActiveIndex(null);
|
|
248
|
-
setMouseX(null);
|
|
249
238
|
}, []);
|
|
250
239
|
const tooltipContent = import_react.default.useMemo(() => {
|
|
251
240
|
if (activeIndex === null) return "";
|
|
@@ -254,7 +243,13 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
|
254
243
|
return p ? `${key}: ${p.v}` : "";
|
|
255
244
|
}).filter(Boolean).join(" / ");
|
|
256
245
|
}, [activeIndex, entries, seriesPoints]);
|
|
257
|
-
|
|
246
|
+
const getTooltipAt = import_react.default.useCallback((idx) => {
|
|
247
|
+
return entries.map(([key], di) => {
|
|
248
|
+
const p = seriesPoints[di]?.[idx];
|
|
249
|
+
return p ? `${key}: ${p.v}` : "";
|
|
250
|
+
}).filter(Boolean).join(" / ");
|
|
251
|
+
}, [entries, seriesPoints]);
|
|
252
|
+
return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
|
|
258
253
|
};
|
|
259
254
|
var LineChart = import_react.default.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
260
255
|
const entries = import_react.default.useMemo(() => Object.entries(data), [data]);
|
|
@@ -275,33 +270,19 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
275
270
|
),
|
|
276
271
|
[entries, count, chartW, chartH, maxVal]
|
|
277
272
|
);
|
|
278
|
-
const lineRefs = import_react.default.useRef([]);
|
|
279
273
|
const clipRef = import_react.default.useRef(null);
|
|
280
|
-
const { activeIndex,
|
|
274
|
+
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
281
275
|
import_react.default.useEffect(() => {
|
|
282
|
-
if (!animate) return;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
|
|
290
|
-
el.style.strokeDashoffset = "0";
|
|
291
|
-
});
|
|
276
|
+
if (!animate || !clipRef.current) return;
|
|
277
|
+
clipRef.current.setAttribute("width", "0");
|
|
278
|
+
requestAnimationFrame(() => {
|
|
279
|
+
if (clipRef.current) {
|
|
280
|
+
clipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
281
|
+
clipRef.current.setAttribute("width", `${width}`);
|
|
282
|
+
}
|
|
292
283
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
requestAnimationFrame(() => {
|
|
296
|
-
if (clipRef.current) {
|
|
297
|
-
clipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
298
|
-
clipRef.current.setAttribute("width", `${width}`);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}, [animate, seriesPoints, width]);
|
|
303
|
-
const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
|
|
304
|
-
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
|
|
284
|
+
}, [animate, width]);
|
|
285
|
+
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
305
286
|
const lineClipId = "line-area-clip";
|
|
306
287
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
307
288
|
"svg",
|
|
@@ -310,7 +291,26 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
310
291
|
className: "chart-svg",
|
|
311
292
|
onMouseMove: (e) => {
|
|
312
293
|
handleMouseMove(e);
|
|
313
|
-
|
|
294
|
+
const svg = e.currentTarget;
|
|
295
|
+
const rect = svg.getBoundingClientRect();
|
|
296
|
+
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
297
|
+
const points = seriesPoints[0];
|
|
298
|
+
if (!points || points.length === 0) return;
|
|
299
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
300
|
+
let closest = 0;
|
|
301
|
+
let minDist = Math.abs(points[0].x - mx);
|
|
302
|
+
for (let i = 1; i < points.length; i++) {
|
|
303
|
+
const dist = Math.abs(points[i].x - mx);
|
|
304
|
+
if (dist < minDist) {
|
|
305
|
+
minDist = dist;
|
|
306
|
+
closest = i;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (minDist <= step / 2) {
|
|
310
|
+
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
311
|
+
} else {
|
|
312
|
+
onLeave();
|
|
313
|
+
}
|
|
314
314
|
},
|
|
315
315
|
onMouseLeave: () => {
|
|
316
316
|
handleMouseLeave();
|
|
@@ -333,26 +333,10 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
333
333
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
|
|
334
334
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
|
|
335
335
|
] }) }),
|
|
336
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
337
|
-
"path",
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
fill: `url(#${gradientId})`,
|
|
341
|
-
clipPath: animate ? `url(#${lineClipId})` : void 0
|
|
342
|
-
}
|
|
343
|
-
),
|
|
344
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
345
|
-
"polyline",
|
|
346
|
-
{
|
|
347
|
-
ref: (el) => {
|
|
348
|
-
lineRefs.current[di] = el;
|
|
349
|
-
},
|
|
350
|
-
points: polyPoints,
|
|
351
|
-
fill: "none",
|
|
352
|
-
stroke: color,
|
|
353
|
-
strokeWidth: "2"
|
|
354
|
-
}
|
|
355
|
-
),
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: areaD, fill: `url(#${gradientId})` }),
|
|
338
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
|
|
339
|
+
] }),
|
|
356
340
|
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
357
341
|
"circle",
|
|
358
342
|
{
|
|
@@ -365,21 +349,16 @@ var LineChart = import_react.default.memo(({ data, labels, width, height, animat
|
|
|
365
349
|
)
|
|
366
350
|
] }, di);
|
|
367
351
|
}),
|
|
368
|
-
|
|
352
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
369
353
|
"line",
|
|
370
354
|
{
|
|
371
|
-
x1:
|
|
355
|
+
x1: activeX,
|
|
372
356
|
y1: PADDING.top,
|
|
373
|
-
x2:
|
|
357
|
+
x2: activeX,
|
|
374
358
|
y2: PADDING.top + chartH,
|
|
375
359
|
className: "chart-crosshair"
|
|
376
360
|
}
|
|
377
361
|
),
|
|
378
|
-
activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-crosshair-label", children: [
|
|
379
|
-
labels[activeIndex],
|
|
380
|
-
" \u2014 ",
|
|
381
|
-
tooltipContent
|
|
382
|
-
] }) }),
|
|
383
362
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
384
363
|
"rect",
|
|
385
364
|
{
|
|
@@ -415,33 +394,19 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
415
394
|
),
|
|
416
395
|
[entries, count, chartW, chartH, maxVal]
|
|
417
396
|
);
|
|
418
|
-
const lineRefs = import_react.default.useRef([]);
|
|
419
397
|
const curveClipRef = import_react.default.useRef(null);
|
|
420
|
-
const { activeIndex,
|
|
398
|
+
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
421
399
|
import_react.default.useEffect(() => {
|
|
422
|
-
if (!animate) return;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
el.style.transition = "stroke-dashoffset 1200ms ease-out 200ms";
|
|
430
|
-
el.style.strokeDashoffset = "0";
|
|
431
|
-
});
|
|
400
|
+
if (!animate || !curveClipRef.current) return;
|
|
401
|
+
curveClipRef.current.setAttribute("width", "0");
|
|
402
|
+
requestAnimationFrame(() => {
|
|
403
|
+
if (curveClipRef.current) {
|
|
404
|
+
curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
405
|
+
curveClipRef.current.setAttribute("width", `${width}`);
|
|
406
|
+
}
|
|
432
407
|
});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
requestAnimationFrame(() => {
|
|
436
|
-
if (curveClipRef.current) {
|
|
437
|
-
curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
438
|
-
curveClipRef.current.setAttribute("width", `${width}`);
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
}, [animate, seriesPoints, width]);
|
|
443
|
-
const guideX = mouseX != null && mouseX >= PADDING.left && mouseX <= width - PADDING.right ? mouseX : null;
|
|
444
|
-
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x : null;
|
|
408
|
+
}, [animate, width]);
|
|
409
|
+
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
445
410
|
const curveClipId = "curve-area-clip";
|
|
446
411
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
447
412
|
"svg",
|
|
@@ -450,7 +415,26 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
450
415
|
className: "chart-svg",
|
|
451
416
|
onMouseMove: (e) => {
|
|
452
417
|
handleMouseMove(e);
|
|
453
|
-
|
|
418
|
+
const svg = e.currentTarget;
|
|
419
|
+
const rect = svg.getBoundingClientRect();
|
|
420
|
+
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
421
|
+
const points = seriesPoints[0];
|
|
422
|
+
if (!points || points.length === 0) return;
|
|
423
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
424
|
+
let closest = 0;
|
|
425
|
+
let minDist = Math.abs(points[0].x - mx);
|
|
426
|
+
for (let i = 1; i < points.length; i++) {
|
|
427
|
+
const dist = Math.abs(points[i].x - mx);
|
|
428
|
+
if (dist < minDist) {
|
|
429
|
+
minDist = dist;
|
|
430
|
+
closest = i;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (minDist <= step / 2) {
|
|
434
|
+
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
435
|
+
} else {
|
|
436
|
+
onLeave();
|
|
437
|
+
}
|
|
454
438
|
},
|
|
455
439
|
onMouseLeave: () => {
|
|
456
440
|
handleMouseLeave();
|
|
@@ -473,26 +457,10 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
473
457
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
|
|
474
458
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
|
|
475
459
|
] }) }),
|
|
476
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
477
|
-
"path",
|
|
478
|
-
{
|
|
479
|
-
|
|
480
|
-
fill: `url(#${gradientId})`,
|
|
481
|
-
clipPath: animate ? `url(#${curveClipId})` : void 0
|
|
482
|
-
}
|
|
483
|
-
),
|
|
484
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
485
|
-
"path",
|
|
486
|
-
{
|
|
487
|
-
ref: (el) => {
|
|
488
|
-
lineRefs.current[di] = el;
|
|
489
|
-
},
|
|
490
|
-
d: linePath,
|
|
491
|
-
fill: "none",
|
|
492
|
-
stroke: color,
|
|
493
|
-
strokeWidth: "2"
|
|
494
|
-
}
|
|
495
|
-
),
|
|
460
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: areaPath, fill: `url(#${gradientId})` }),
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
|
|
463
|
+
] }),
|
|
496
464
|
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
497
465
|
"circle",
|
|
498
466
|
{
|
|
@@ -505,21 +473,16 @@ var CurveChart = import_react.default.memo(({ data, labels, width, height, anima
|
|
|
505
473
|
)
|
|
506
474
|
] }, di);
|
|
507
475
|
}),
|
|
508
|
-
|
|
476
|
+
activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
509
477
|
"line",
|
|
510
478
|
{
|
|
511
|
-
x1:
|
|
479
|
+
x1: activeX,
|
|
512
480
|
y1: PADDING.top,
|
|
513
|
-
x2:
|
|
481
|
+
x2: activeX,
|
|
514
482
|
y2: PADDING.top + chartH,
|
|
515
483
|
className: "chart-crosshair"
|
|
516
484
|
}
|
|
517
485
|
),
|
|
518
|
-
activeIndex !== null && activeX !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("foreignObject", { x: activeX - 100, y: 0, width: "200", height: PADDING.top, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-crosshair-label", children: [
|
|
519
|
-
labels[activeIndex],
|
|
520
|
-
" \u2014 ",
|
|
521
|
-
tooltipContent
|
|
522
|
-
] }) }),
|
|
523
486
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
524
487
|
"rect",
|
|
525
488
|
{
|
|
@@ -698,30 +661,70 @@ var PieDonutChart = import_react.default.memo(
|
|
|
698
661
|
}
|
|
699
662
|
);
|
|
700
663
|
PieDonutChart.displayName = "PieDonutChart";
|
|
701
|
-
var
|
|
664
|
+
var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
702
665
|
const ref = import_react.default.useRef(null);
|
|
703
|
-
const [
|
|
704
|
-
import_react.default.
|
|
666
|
+
const [pos, setPos] = import_react.default.useState({ left: 0, top: 0 });
|
|
667
|
+
import_react.default.useLayoutEffect(() => {
|
|
705
668
|
const el = ref.current;
|
|
706
669
|
if (!el) return;
|
|
707
670
|
const w = el.offsetWidth;
|
|
708
|
-
const
|
|
709
|
-
const
|
|
710
|
-
let
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
671
|
+
const h = el.offsetHeight;
|
|
672
|
+
const vw = window.innerWidth;
|
|
673
|
+
let left = clientX + TOOLTIP_OFFSET;
|
|
674
|
+
let top = clientY - h - TOOLTIP_OFFSET;
|
|
675
|
+
if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
|
|
676
|
+
if (top < 8) top = clientY + TOOLTIP_OFFSET;
|
|
677
|
+
if (left < 8) left = 8;
|
|
678
|
+
setPos({ left, top });
|
|
679
|
+
}, [clientX, clientY]);
|
|
715
680
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
716
681
|
"div",
|
|
717
682
|
{
|
|
718
683
|
ref,
|
|
719
|
-
className: "chart-tooltip"
|
|
720
|
-
style: { left:
|
|
684
|
+
className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
|
|
685
|
+
style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
|
|
721
686
|
children
|
|
722
687
|
}
|
|
723
688
|
);
|
|
724
689
|
};
|
|
690
|
+
var ChartLegend = ({ data, labels, type }) => {
|
|
691
|
+
const entries = Object.entries(data);
|
|
692
|
+
if (type === "pie" || type === "doughnut") {
|
|
693
|
+
const values = entries.flatMap(([, v]) => v);
|
|
694
|
+
const total = values.reduce((a, b) => a + b, 0) || 1;
|
|
695
|
+
const firstKey = entries[0]?.[0] ?? "";
|
|
696
|
+
const colorOffset = hashString(firstKey);
|
|
697
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "chart-legend", children: values.map((v, i) => {
|
|
698
|
+
const pct = Math.round(v / total * 100);
|
|
699
|
+
const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
|
|
700
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-item", children: [
|
|
701
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-text", children: [
|
|
703
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
|
|
704
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "chart-legend-value", children: [
|
|
705
|
+
v.toLocaleString(),
|
|
706
|
+
"(",
|
|
707
|
+
pct,
|
|
708
|
+
"%)"
|
|
709
|
+
] })
|
|
710
|
+
] })
|
|
711
|
+
] }, i);
|
|
712
|
+
}) });
|
|
713
|
+
}
|
|
714
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "chart-legend", children: entries.map(([key], di) => {
|
|
715
|
+
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
716
|
+
const color = palette[2];
|
|
717
|
+
const values = entries[di][1];
|
|
718
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
719
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-item", children: [
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
721
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "chart-legend-text", children: [
|
|
722
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-label", children: key }),
|
|
723
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "chart-legend-value", children: sum.toLocaleString() })
|
|
724
|
+
] })
|
|
725
|
+
] }, di);
|
|
726
|
+
}) });
|
|
727
|
+
};
|
|
725
728
|
var Chart = import_react.default.memo((props) => {
|
|
726
729
|
const { type, data, labels, tooltip: showTooltip = true } = props;
|
|
727
730
|
const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
@@ -737,7 +740,8 @@ var Chart = import_react.default.memo((props) => {
|
|
|
737
740
|
ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
738
741
|
ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
739
742
|
ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
|
|
740
|
-
|
|
743
|
+
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartLegend, { data: stableData, labels: stableLabels, type }),
|
|
744
|
+
tooltip.content && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content })
|
|
741
745
|
] });
|
|
742
746
|
});
|
|
743
747
|
Chart.displayName = "Chart";
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
opacity: 1;
|
|
76
76
|
}
|
|
77
77
|
.lib-xplat-chart .chart-crosshair {
|
|
78
|
-
stroke: var(--semantic-border-
|
|
78
|
+
stroke: var(--semantic-border-strong);
|
|
79
79
|
stroke-width: 1;
|
|
80
|
-
stroke-dasharray: 4
|
|
80
|
+
stroke-dasharray: 4 3;
|
|
81
81
|
pointer-events: none;
|
|
82
82
|
}
|
|
83
83
|
.lib-xplat-chart .chart-point-active {
|
|
@@ -93,18 +93,24 @@
|
|
|
93
93
|
overflow: visible;
|
|
94
94
|
}
|
|
95
95
|
.lib-xplat-chart .chart-tooltip {
|
|
96
|
-
|
|
97
|
-
transform: translate(-50%, -100%);
|
|
98
|
-
padding: var(--spacing-space-2) var(--spacing-space-3);
|
|
96
|
+
padding: var(--spacing-space-3);
|
|
99
97
|
background-color: var(--semantic-surface-neutral-strong);
|
|
100
98
|
color: var(--semantic-text-inverse);
|
|
101
99
|
font-size: 12px;
|
|
100
|
+
line-height: 18px;
|
|
102
101
|
font-weight: 500;
|
|
103
102
|
border-radius: var(--spacing-radius-md);
|
|
104
|
-
|
|
103
|
+
max-width: 240px;
|
|
105
104
|
pointer-events: none;
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
106
|
+
}
|
|
107
|
+
.lib-xplat-chart .chart-tooltip.chart-tooltip-show {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
animation: chart-tooltip-in 120ms ease-out;
|
|
110
|
+
}
|
|
111
|
+
.lib-xplat-chart .chart-tooltip.chart-tooltip-hide {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
animation: chart-tooltip-out 80ms ease-in;
|
|
108
114
|
}
|
|
109
115
|
.lib-xplat-chart .chart-bar-animate {
|
|
110
116
|
animation: chart-bar-grow 800ms ease-out both;
|
|
@@ -116,6 +122,38 @@
|
|
|
116
122
|
.lib-xplat-chart .chart-area[style*=animationDelay] {
|
|
117
123
|
animation: chart-fade-in 800ms ease-out both;
|
|
118
124
|
}
|
|
125
|
+
.lib-xplat-chart .chart-legend {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
gap: var(--spacing-space-4);
|
|
130
|
+
padding: var(--spacing-space-3) 0;
|
|
131
|
+
}
|
|
132
|
+
.lib-xplat-chart .chart-legend-item {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: flex-start;
|
|
135
|
+
gap: var(--spacing-space-2);
|
|
136
|
+
}
|
|
137
|
+
.lib-xplat-chart .chart-legend-dot {
|
|
138
|
+
flex-shrink: 0;
|
|
139
|
+
width: 10px;
|
|
140
|
+
height: 10px;
|
|
141
|
+
border-radius: 50%;
|
|
142
|
+
margin-top: 3px;
|
|
143
|
+
}
|
|
144
|
+
.lib-xplat-chart .chart-legend-text {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
}
|
|
148
|
+
.lib-xplat-chart .chart-legend-label {
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
color: var(--semantic-text-muted);
|
|
151
|
+
}
|
|
152
|
+
.lib-xplat-chart .chart-legend-value {
|
|
153
|
+
font-size: 14px;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
color: var(--semantic-text-strong);
|
|
156
|
+
}
|
|
119
157
|
@keyframes chart-bar-grow {
|
|
120
158
|
from {
|
|
121
159
|
transform: scaleY(0);
|
|
@@ -135,11 +173,17 @@
|
|
|
135
173
|
@keyframes chart-tooltip-in {
|
|
136
174
|
from {
|
|
137
175
|
opacity: 0;
|
|
138
|
-
transform: translate(-50%, -90%);
|
|
139
176
|
}
|
|
140
177
|
to {
|
|
141
178
|
opacity: 1;
|
|
142
|
-
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
@keyframes chart-tooltip-out {
|
|
182
|
+
from {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
to {
|
|
186
|
+
opacity: 0;
|
|
143
187
|
}
|
|
144
188
|
}
|
|
145
189
|
@media (prefers-reduced-motion: reduce) {
|