@x-plat/design-system 0.5.31 → 0.5.33
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 +326 -132
- package/dist/components/Chart/index.css +70 -8
- package/dist/components/Chart/index.js +326 -132
- package/dist/components/Table/index.css +4 -0
- package/dist/components/index.cjs +326 -132
- package/dist/components/index.css +74 -8
- package/dist/components/index.js +326 -132
- package/dist/index.cjs +326 -132
- package/dist/index.css +74 -8
- package/dist/index.js +326 -132
- package/package.json +1 -1
|
@@ -1999,19 +1999,43 @@
|
|
|
1999
1999
|
.lib-xplat-chart .chart-area {
|
|
2000
2000
|
opacity: 1;
|
|
2001
2001
|
}
|
|
2002
|
+
.lib-xplat-chart .chart-crosshair {
|
|
2003
|
+
stroke: var(--semantic-border-strong);
|
|
2004
|
+
stroke-width: 1;
|
|
2005
|
+
stroke-dasharray: 4 3;
|
|
2006
|
+
pointer-events: none;
|
|
2007
|
+
}
|
|
2008
|
+
.lib-xplat-chart .chart-point-active {
|
|
2009
|
+
pointer-events: none;
|
|
2010
|
+
transition: cx 0.05s, cy 0.05s;
|
|
2011
|
+
}
|
|
2012
|
+
.lib-xplat-chart .chart-crosshair-label {
|
|
2013
|
+
font-size: 11px;
|
|
2014
|
+
font-weight: 500;
|
|
2015
|
+
color: var(--semantic-text-strong);
|
|
2016
|
+
text-align: center;
|
|
2017
|
+
white-space: nowrap;
|
|
2018
|
+
overflow: visible;
|
|
2019
|
+
}
|
|
2002
2020
|
.lib-xplat-chart .chart-tooltip {
|
|
2003
|
-
|
|
2004
|
-
transform: translate(-50%, -100%);
|
|
2005
|
-
padding: var(--spacing-space-2) var(--spacing-space-3);
|
|
2021
|
+
padding: var(--spacing-space-3);
|
|
2006
2022
|
background-color: var(--semantic-surface-neutral-strong);
|
|
2007
2023
|
color: var(--semantic-text-inverse);
|
|
2008
2024
|
font-size: 12px;
|
|
2025
|
+
line-height: 18px;
|
|
2009
2026
|
font-weight: 500;
|
|
2010
2027
|
border-radius: var(--spacing-radius-md);
|
|
2011
|
-
|
|
2028
|
+
max-width: 240px;
|
|
2012
2029
|
pointer-events: none;
|
|
2013
|
-
|
|
2014
|
-
|
|
2030
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
2031
|
+
}
|
|
2032
|
+
.lib-xplat-chart .chart-tooltip.chart-tooltip-show {
|
|
2033
|
+
opacity: 1;
|
|
2034
|
+
animation: chart-tooltip-in 120ms ease-out;
|
|
2035
|
+
}
|
|
2036
|
+
.lib-xplat-chart .chart-tooltip.chart-tooltip-hide {
|
|
2037
|
+
opacity: 0;
|
|
2038
|
+
animation: chart-tooltip-out 80ms ease-in;
|
|
2015
2039
|
}
|
|
2016
2040
|
.lib-xplat-chart .chart-bar-animate {
|
|
2017
2041
|
animation: chart-bar-grow 800ms ease-out both;
|
|
@@ -2023,6 +2047,38 @@
|
|
|
2023
2047
|
.lib-xplat-chart .chart-area[style*=animationDelay] {
|
|
2024
2048
|
animation: chart-fade-in 800ms ease-out both;
|
|
2025
2049
|
}
|
|
2050
|
+
.lib-xplat-chart .chart-legend {
|
|
2051
|
+
display: flex;
|
|
2052
|
+
flex-wrap: wrap;
|
|
2053
|
+
justify-content: center;
|
|
2054
|
+
gap: var(--spacing-space-4);
|
|
2055
|
+
padding: var(--spacing-space-3) 0;
|
|
2056
|
+
}
|
|
2057
|
+
.lib-xplat-chart .chart-legend-item {
|
|
2058
|
+
display: flex;
|
|
2059
|
+
align-items: flex-start;
|
|
2060
|
+
gap: var(--spacing-space-2);
|
|
2061
|
+
}
|
|
2062
|
+
.lib-xplat-chart .chart-legend-dot {
|
|
2063
|
+
flex-shrink: 0;
|
|
2064
|
+
width: 10px;
|
|
2065
|
+
height: 10px;
|
|
2066
|
+
border-radius: 50%;
|
|
2067
|
+
margin-top: 3px;
|
|
2068
|
+
}
|
|
2069
|
+
.lib-xplat-chart .chart-legend-text {
|
|
2070
|
+
display: flex;
|
|
2071
|
+
flex-direction: column;
|
|
2072
|
+
}
|
|
2073
|
+
.lib-xplat-chart .chart-legend-label {
|
|
2074
|
+
font-size: 12px;
|
|
2075
|
+
color: var(--semantic-text-muted);
|
|
2076
|
+
}
|
|
2077
|
+
.lib-xplat-chart .chart-legend-value {
|
|
2078
|
+
font-size: 14px;
|
|
2079
|
+
font-weight: 600;
|
|
2080
|
+
color: var(--semantic-text-strong);
|
|
2081
|
+
}
|
|
2026
2082
|
@keyframes chart-bar-grow {
|
|
2027
2083
|
from {
|
|
2028
2084
|
transform: scaleY(0);
|
|
@@ -2042,11 +2098,17 @@
|
|
|
2042
2098
|
@keyframes chart-tooltip-in {
|
|
2043
2099
|
from {
|
|
2044
2100
|
opacity: 0;
|
|
2045
|
-
transform: translate(-50%, -90%);
|
|
2046
2101
|
}
|
|
2047
2102
|
to {
|
|
2048
2103
|
opacity: 1;
|
|
2049
|
-
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
@keyframes chart-tooltip-out {
|
|
2107
|
+
from {
|
|
2108
|
+
opacity: 1;
|
|
2109
|
+
}
|
|
2110
|
+
to {
|
|
2111
|
+
opacity: 0;
|
|
2050
2112
|
}
|
|
2051
2113
|
}
|
|
2052
2114
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -3844,6 +3906,10 @@
|
|
|
3844
3906
|
height: 100%;
|
|
3845
3907
|
position: relative;
|
|
3846
3908
|
overflow: auto;
|
|
3909
|
+
scrollbar-width: none;
|
|
3910
|
+
}
|
|
3911
|
+
.lib-xplat-table-wrapper::-webkit-scrollbar {
|
|
3912
|
+
display: none;
|
|
3847
3913
|
}
|
|
3848
3914
|
.lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > th,
|
|
3849
3915
|
.lib-xplat-table-wrapper.sm > .lib-xplat-table > thead > tr > td,
|
package/dist/components/index.js
CHANGED
|
@@ -2261,40 +2261,28 @@ var useChartAnimation = (containerRef, dataKey) => {
|
|
|
2261
2261
|
}, [dataKey]);
|
|
2262
2262
|
return animate || prefersReducedMotion();
|
|
2263
2263
|
};
|
|
2264
|
+
var TOOLTIP_OFFSET = 12;
|
|
2264
2265
|
var useChartTooltip = (enabled) => {
|
|
2265
2266
|
const [tooltip, setTooltip] = React6.useState({
|
|
2266
2267
|
visible: false,
|
|
2267
|
-
|
|
2268
|
-
|
|
2268
|
+
clientX: 0,
|
|
2269
|
+
clientY: 0,
|
|
2269
2270
|
content: ""
|
|
2270
2271
|
});
|
|
2271
2272
|
const containerRef = React6.useRef(null);
|
|
2272
2273
|
const rafRef = React6.useRef(0);
|
|
2273
2274
|
const move = React6.useCallback((e) => {
|
|
2274
2275
|
if (!enabled) return;
|
|
2275
|
-
const
|
|
2276
|
-
const
|
|
2276
|
+
const cx = e.clientX;
|
|
2277
|
+
const cy = e.clientY;
|
|
2277
2278
|
cancelAnimationFrame(rafRef.current);
|
|
2278
2279
|
rafRef.current = requestAnimationFrame(() => {
|
|
2279
|
-
|
|
2280
|
-
if (!rect) return;
|
|
2281
|
-
setTooltip((prev) => ({
|
|
2282
|
-
...prev,
|
|
2283
|
-
x: clientX - rect.left,
|
|
2284
|
-
y: clientY - rect.top - 12
|
|
2285
|
-
}));
|
|
2280
|
+
setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
|
|
2286
2281
|
});
|
|
2287
2282
|
}, [enabled]);
|
|
2288
2283
|
const show = React6.useCallback((e, content) => {
|
|
2289
2284
|
if (!enabled) return;
|
|
2290
|
-
|
|
2291
|
-
if (!rect) return;
|
|
2292
|
-
setTooltip({
|
|
2293
|
-
visible: true,
|
|
2294
|
-
x: e.clientX - rect.left,
|
|
2295
|
-
y: e.clientY - rect.top - 12,
|
|
2296
|
-
content
|
|
2297
|
-
});
|
|
2285
|
+
setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
|
|
2298
2286
|
}, [enabled]);
|
|
2299
2287
|
const hide = React6.useCallback(() => {
|
|
2300
2288
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -2326,6 +2314,45 @@ var AxisLabels = React6.memo(({ labels, count, chartW, height }) => {
|
|
|
2326
2314
|
}) });
|
|
2327
2315
|
});
|
|
2328
2316
|
AxisLabels.displayName = "AxisLabels";
|
|
2317
|
+
var useCrosshair = (seriesPoints, entries, labels, chartH) => {
|
|
2318
|
+
const [activeIndex, setActiveIndex] = React6.useState(null);
|
|
2319
|
+
const handleMouseMove = React6.useCallback((e) => {
|
|
2320
|
+
const svg = e.currentTarget;
|
|
2321
|
+
const rect = svg.getBoundingClientRect();
|
|
2322
|
+
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
2323
|
+
if (seriesPoints.length === 0 || seriesPoints[0].length === 0) return;
|
|
2324
|
+
const points = seriesPoints[0];
|
|
2325
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
2326
|
+
const threshold = step / 2;
|
|
2327
|
+
let closest = 0;
|
|
2328
|
+
let minDist = Math.abs(points[0].x - mx);
|
|
2329
|
+
for (let i = 1; i < points.length; i++) {
|
|
2330
|
+
const dist = Math.abs(points[i].x - mx);
|
|
2331
|
+
if (dist < minDist) {
|
|
2332
|
+
minDist = dist;
|
|
2333
|
+
closest = i;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
setActiveIndex(minDist <= threshold ? closest : null);
|
|
2337
|
+
}, [seriesPoints]);
|
|
2338
|
+
const handleMouseLeave = React6.useCallback(() => {
|
|
2339
|
+
setActiveIndex(null);
|
|
2340
|
+
}, []);
|
|
2341
|
+
const tooltipContent = React6.useMemo(() => {
|
|
2342
|
+
if (activeIndex === null) return "";
|
|
2343
|
+
return entries.map(([key], di) => {
|
|
2344
|
+
const p = seriesPoints[di]?.[activeIndex];
|
|
2345
|
+
return p ? `${key}: ${p.v}` : "";
|
|
2346
|
+
}).filter(Boolean).join(" / ");
|
|
2347
|
+
}, [activeIndex, entries, seriesPoints]);
|
|
2348
|
+
const getTooltipAt = React6.useCallback((idx) => {
|
|
2349
|
+
return entries.map(([key], di) => {
|
|
2350
|
+
const p = seriesPoints[di]?.[idx];
|
|
2351
|
+
return p ? `${key}: ${p.v}` : "";
|
|
2352
|
+
}).filter(Boolean).join(" / ");
|
|
2353
|
+
}, [entries, seriesPoints]);
|
|
2354
|
+
return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
|
|
2355
|
+
};
|
|
2329
2356
|
var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
2330
2357
|
const entries = React6.useMemo(() => Object.entries(data), [data]);
|
|
2331
2358
|
const maxVal = React6.useMemo(() => {
|
|
@@ -2345,8 +2372,9 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
2345
2372
|
),
|
|
2346
2373
|
[entries, count, chartW, chartH, maxVal]
|
|
2347
2374
|
);
|
|
2348
|
-
const showPoints = count <= 100;
|
|
2349
2375
|
const lineRefs = React6.useRef([]);
|
|
2376
|
+
const clipRef = React6.useRef(null);
|
|
2377
|
+
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
2350
2378
|
React6.useEffect(() => {
|
|
2351
2379
|
if (!animate) return;
|
|
2352
2380
|
lineRefs.current.forEach((el) => {
|
|
@@ -2359,61 +2387,123 @@ var LineChart = React6.memo(({ data, labels, width, height, animate, onHover, on
|
|
|
2359
2387
|
el.style.strokeDashoffset = "0";
|
|
2360
2388
|
});
|
|
2361
2389
|
});
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2390
|
+
if (clipRef.current) {
|
|
2391
|
+
clipRef.current.setAttribute("width", "0");
|
|
2392
|
+
requestAnimationFrame(() => {
|
|
2393
|
+
if (clipRef.current) {
|
|
2394
|
+
clipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
2395
|
+
clipRef.current.setAttribute("width", `${width}`);
|
|
2396
|
+
}
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
}, [animate, seriesPoints, width]);
|
|
2400
|
+
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
2401
|
+
const lineClipId = "line-area-clip";
|
|
2402
|
+
return /* @__PURE__ */ jsxs197(
|
|
2403
|
+
"svg",
|
|
2404
|
+
{
|
|
2405
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
2406
|
+
className: "chart-svg",
|
|
2407
|
+
onMouseMove: (e) => {
|
|
2408
|
+
handleMouseMove(e);
|
|
2409
|
+
const svg = e.currentTarget;
|
|
2410
|
+
const rect = svg.getBoundingClientRect();
|
|
2411
|
+
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
2412
|
+
const points = seriesPoints[0];
|
|
2413
|
+
if (!points || points.length === 0) return;
|
|
2414
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
2415
|
+
let closest = 0;
|
|
2416
|
+
let minDist = Math.abs(points[0].x - mx);
|
|
2417
|
+
for (let i = 1; i < points.length; i++) {
|
|
2418
|
+
const dist = Math.abs(points[i].x - mx);
|
|
2419
|
+
if (dist < minDist) {
|
|
2420
|
+
minDist = dist;
|
|
2421
|
+
closest = i;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
if (minDist <= step / 2) {
|
|
2425
|
+
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
2426
|
+
} else {
|
|
2427
|
+
onLeave();
|
|
2428
|
+
}
|
|
2429
|
+
},
|
|
2430
|
+
onMouseLeave: () => {
|
|
2431
|
+
handleMouseLeave();
|
|
2432
|
+
onLeave();
|
|
2433
|
+
},
|
|
2434
|
+
children: [
|
|
2435
|
+
animate && /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsx307("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx307("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
2436
|
+
/* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
|
|
2437
|
+
/* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
|
|
2438
|
+
entries.map(([key], di) => {
|
|
2439
|
+
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
2440
|
+
const color = palette[2];
|
|
2441
|
+
const areaColor = palette[0];
|
|
2442
|
+
const points = seriesPoints[di];
|
|
2443
|
+
const gradientId = `line-gradient-${di}`;
|
|
2444
|
+
const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
2445
|
+
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`;
|
|
2446
|
+
return /* @__PURE__ */ jsxs197("g", { children: [
|
|
2447
|
+
/* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
2448
|
+
/* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
|
|
2449
|
+
/* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
|
|
2450
|
+
] }) }),
|
|
2451
|
+
/* @__PURE__ */ jsx307(
|
|
2452
|
+
"path",
|
|
2453
|
+
{
|
|
2454
|
+
d: areaD,
|
|
2455
|
+
fill: `url(#${gradientId})`,
|
|
2456
|
+
clipPath: animate ? `url(#${lineClipId})` : void 0
|
|
2457
|
+
}
|
|
2458
|
+
),
|
|
2459
|
+
/* @__PURE__ */ jsx307(
|
|
2460
|
+
"polyline",
|
|
2461
|
+
{
|
|
2462
|
+
ref: (el) => {
|
|
2463
|
+
lineRefs.current[di] = el;
|
|
2464
|
+
},
|
|
2465
|
+
points: polyPoints,
|
|
2466
|
+
fill: "none",
|
|
2467
|
+
stroke: color,
|
|
2468
|
+
strokeWidth: "2"
|
|
2469
|
+
}
|
|
2470
|
+
),
|
|
2471
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
|
|
2472
|
+
"circle",
|
|
2473
|
+
{
|
|
2474
|
+
cx: points[activeIndex].x,
|
|
2475
|
+
cy: points[activeIndex].y,
|
|
2476
|
+
r: "5",
|
|
2477
|
+
fill: color,
|
|
2478
|
+
className: "chart-point-active"
|
|
2479
|
+
}
|
|
2480
|
+
)
|
|
2481
|
+
] }, di);
|
|
2482
|
+
}),
|
|
2483
|
+
activeX !== null && /* @__PURE__ */ jsx307(
|
|
2484
|
+
"line",
|
|
2381
2485
|
{
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2486
|
+
x1: activeX,
|
|
2487
|
+
y1: PADDING.top,
|
|
2488
|
+
x2: activeX,
|
|
2489
|
+
y2: PADDING.top + chartH,
|
|
2490
|
+
className: "chart-crosshair"
|
|
2386
2491
|
}
|
|
2387
2492
|
),
|
|
2388
2493
|
/* @__PURE__ */ jsx307(
|
|
2389
|
-
"
|
|
2494
|
+
"rect",
|
|
2390
2495
|
{
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
fill: "
|
|
2396
|
-
|
|
2397
|
-
strokeWidth: "2"
|
|
2496
|
+
x: PADDING.left,
|
|
2497
|
+
y: PADDING.top,
|
|
2498
|
+
width: chartW,
|
|
2499
|
+
height: chartH,
|
|
2500
|
+
fill: "transparent",
|
|
2501
|
+
style: { cursor: "crosshair" }
|
|
2398
2502
|
}
|
|
2399
|
-
)
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
cx: p.x,
|
|
2404
|
-
cy: p.y,
|
|
2405
|
-
r: "4",
|
|
2406
|
-
fill: color,
|
|
2407
|
-
className: "chart-point",
|
|
2408
|
-
onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
|
|
2409
|
-
onMouseMove: onMove,
|
|
2410
|
-
onMouseLeave: onLeave
|
|
2411
|
-
},
|
|
2412
|
-
i
|
|
2413
|
-
))
|
|
2414
|
-
] }, di);
|
|
2415
|
-
})
|
|
2416
|
-
] });
|
|
2503
|
+
)
|
|
2504
|
+
]
|
|
2505
|
+
}
|
|
2506
|
+
);
|
|
2417
2507
|
});
|
|
2418
2508
|
LineChart.displayName = "LineChart";
|
|
2419
2509
|
var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
@@ -2435,8 +2525,9 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
2435
2525
|
),
|
|
2436
2526
|
[entries, count, chartW, chartH, maxVal]
|
|
2437
2527
|
);
|
|
2438
|
-
const showPoints = count <= 100;
|
|
2439
2528
|
const lineRefs = React6.useRef([]);
|
|
2529
|
+
const curveClipRef = React6.useRef(null);
|
|
2530
|
+
const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
|
|
2440
2531
|
React6.useEffect(() => {
|
|
2441
2532
|
if (!animate) return;
|
|
2442
2533
|
lineRefs.current.forEach((el) => {
|
|
@@ -2449,61 +2540,123 @@ var CurveChart = React6.memo(({ data, labels, width, height, animate, onHover, o
|
|
|
2449
2540
|
el.style.strokeDashoffset = "0";
|
|
2450
2541
|
});
|
|
2451
2542
|
});
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2543
|
+
if (curveClipRef.current) {
|
|
2544
|
+
curveClipRef.current.setAttribute("width", "0");
|
|
2545
|
+
requestAnimationFrame(() => {
|
|
2546
|
+
if (curveClipRef.current) {
|
|
2547
|
+
curveClipRef.current.style.transition = "width 1200ms ease-out 200ms";
|
|
2548
|
+
curveClipRef.current.setAttribute("width", `${width}`);
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
}, [animate, seriesPoints, width]);
|
|
2553
|
+
const activeX = activeIndex !== null ? seriesPoints[0]?.[activeIndex]?.x ?? null : null;
|
|
2554
|
+
const curveClipId = "curve-area-clip";
|
|
2555
|
+
return /* @__PURE__ */ jsxs197(
|
|
2556
|
+
"svg",
|
|
2557
|
+
{
|
|
2558
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
2559
|
+
className: "chart-svg",
|
|
2560
|
+
onMouseMove: (e) => {
|
|
2561
|
+
handleMouseMove(e);
|
|
2562
|
+
const svg = e.currentTarget;
|
|
2563
|
+
const rect = svg.getBoundingClientRect();
|
|
2564
|
+
const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
|
|
2565
|
+
const points = seriesPoints[0];
|
|
2566
|
+
if (!points || points.length === 0) return;
|
|
2567
|
+
const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
|
|
2568
|
+
let closest = 0;
|
|
2569
|
+
let minDist = Math.abs(points[0].x - mx);
|
|
2570
|
+
for (let i = 1; i < points.length; i++) {
|
|
2571
|
+
const dist = Math.abs(points[i].x - mx);
|
|
2572
|
+
if (dist < minDist) {
|
|
2573
|
+
minDist = dist;
|
|
2574
|
+
closest = i;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
if (minDist <= step / 2) {
|
|
2578
|
+
onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
|
|
2579
|
+
} else {
|
|
2580
|
+
onLeave();
|
|
2581
|
+
}
|
|
2582
|
+
},
|
|
2583
|
+
onMouseLeave: () => {
|
|
2584
|
+
handleMouseLeave();
|
|
2585
|
+
onLeave();
|
|
2586
|
+
},
|
|
2587
|
+
children: [
|
|
2588
|
+
animate && /* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsx307("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx307("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
|
|
2589
|
+
/* @__PURE__ */ jsx307(GridLines, { width, height, chartH, maxVal }),
|
|
2590
|
+
/* @__PURE__ */ jsx307(AxisLabels, { labels, count, chartW, height }),
|
|
2591
|
+
entries.map(([key], di) => {
|
|
2592
|
+
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
2593
|
+
const color = palette[2];
|
|
2594
|
+
const areaColor = palette[0];
|
|
2595
|
+
const points = seriesPoints[di];
|
|
2596
|
+
const gradientId = `curve-gradient-${di}`;
|
|
2597
|
+
const linePath = toSmoothPath(points);
|
|
2598
|
+
const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
|
|
2599
|
+
return /* @__PURE__ */ jsxs197("g", { children: [
|
|
2600
|
+
/* @__PURE__ */ jsx307("defs", { children: /* @__PURE__ */ jsxs197("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
2601
|
+
/* @__PURE__ */ jsx307("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
|
|
2602
|
+
/* @__PURE__ */ jsx307("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
|
|
2603
|
+
] }) }),
|
|
2604
|
+
/* @__PURE__ */ jsx307(
|
|
2605
|
+
"path",
|
|
2606
|
+
{
|
|
2607
|
+
d: areaPath,
|
|
2608
|
+
fill: `url(#${gradientId})`,
|
|
2609
|
+
clipPath: animate ? `url(#${curveClipId})` : void 0
|
|
2610
|
+
}
|
|
2611
|
+
),
|
|
2612
|
+
/* @__PURE__ */ jsx307(
|
|
2613
|
+
"path",
|
|
2614
|
+
{
|
|
2615
|
+
ref: (el) => {
|
|
2616
|
+
lineRefs.current[di] = el;
|
|
2617
|
+
},
|
|
2618
|
+
d: linePath,
|
|
2619
|
+
fill: "none",
|
|
2620
|
+
stroke: color,
|
|
2621
|
+
strokeWidth: "2"
|
|
2622
|
+
}
|
|
2623
|
+
),
|
|
2624
|
+
activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx307(
|
|
2625
|
+
"circle",
|
|
2626
|
+
{
|
|
2627
|
+
cx: points[activeIndex].x,
|
|
2628
|
+
cy: points[activeIndex].y,
|
|
2629
|
+
r: "5",
|
|
2630
|
+
fill: color,
|
|
2631
|
+
className: "chart-point-active"
|
|
2632
|
+
}
|
|
2633
|
+
)
|
|
2634
|
+
] }, di);
|
|
2635
|
+
}),
|
|
2636
|
+
activeX !== null && /* @__PURE__ */ jsx307(
|
|
2637
|
+
"line",
|
|
2471
2638
|
{
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2639
|
+
x1: activeX,
|
|
2640
|
+
y1: PADDING.top,
|
|
2641
|
+
x2: activeX,
|
|
2642
|
+
y2: PADDING.top + chartH,
|
|
2643
|
+
className: "chart-crosshair"
|
|
2476
2644
|
}
|
|
2477
2645
|
),
|
|
2478
2646
|
/* @__PURE__ */ jsx307(
|
|
2479
|
-
"
|
|
2647
|
+
"rect",
|
|
2480
2648
|
{
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
fill: "
|
|
2486
|
-
|
|
2487
|
-
strokeWidth: "2"
|
|
2649
|
+
x: PADDING.left,
|
|
2650
|
+
y: PADDING.top,
|
|
2651
|
+
width: chartW,
|
|
2652
|
+
height: chartH,
|
|
2653
|
+
fill: "transparent",
|
|
2654
|
+
style: { cursor: "crosshair" }
|
|
2488
2655
|
}
|
|
2489
|
-
)
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
cx: p.x,
|
|
2494
|
-
cy: p.y,
|
|
2495
|
-
r: "4",
|
|
2496
|
-
fill: color,
|
|
2497
|
-
className: "chart-point",
|
|
2498
|
-
onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
|
|
2499
|
-
onMouseMove: onMove,
|
|
2500
|
-
onMouseLeave: onLeave
|
|
2501
|
-
},
|
|
2502
|
-
i
|
|
2503
|
-
))
|
|
2504
|
-
] }, di);
|
|
2505
|
-
})
|
|
2506
|
-
] });
|
|
2656
|
+
)
|
|
2657
|
+
]
|
|
2658
|
+
}
|
|
2659
|
+
);
|
|
2507
2660
|
});
|
|
2508
2661
|
CurveChart.displayName = "CurveChart";
|
|
2509
2662
|
var BarChart = React6.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
|
|
@@ -2668,30 +2821,70 @@ var PieDonutChart = React6.memo(
|
|
|
2668
2821
|
}
|
|
2669
2822
|
);
|
|
2670
2823
|
PieDonutChart.displayName = "PieDonutChart";
|
|
2671
|
-
var
|
|
2824
|
+
var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
|
|
2672
2825
|
const ref = React6.useRef(null);
|
|
2673
|
-
const [
|
|
2674
|
-
React6.
|
|
2826
|
+
const [pos, setPos] = React6.useState({ left: 0, top: 0 });
|
|
2827
|
+
React6.useLayoutEffect(() => {
|
|
2675
2828
|
const el = ref.current;
|
|
2676
2829
|
if (!el) return;
|
|
2677
2830
|
const w = el.offsetWidth;
|
|
2678
|
-
const
|
|
2679
|
-
const
|
|
2680
|
-
let
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2831
|
+
const h = el.offsetHeight;
|
|
2832
|
+
const vw = window.innerWidth;
|
|
2833
|
+
let left = clientX + TOOLTIP_OFFSET;
|
|
2834
|
+
let top = clientY - h - TOOLTIP_OFFSET;
|
|
2835
|
+
if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
|
|
2836
|
+
if (top < 8) top = clientY + TOOLTIP_OFFSET;
|
|
2837
|
+
if (left < 8) left = 8;
|
|
2838
|
+
setPos({ left, top });
|
|
2839
|
+
}, [clientX, clientY]);
|
|
2685
2840
|
return /* @__PURE__ */ jsx307(
|
|
2686
2841
|
"div",
|
|
2687
2842
|
{
|
|
2688
2843
|
ref,
|
|
2689
|
-
className: "chart-tooltip"
|
|
2690
|
-
style: { left:
|
|
2844
|
+
className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
|
|
2845
|
+
style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
|
|
2691
2846
|
children
|
|
2692
2847
|
}
|
|
2693
2848
|
);
|
|
2694
2849
|
};
|
|
2850
|
+
var ChartLegend = ({ data, labels, type }) => {
|
|
2851
|
+
const entries = Object.entries(data);
|
|
2852
|
+
if (type === "pie" || type === "doughnut") {
|
|
2853
|
+
const values = entries.flatMap(([, v]) => v);
|
|
2854
|
+
const total = values.reduce((a, b) => a + b, 0) || 1;
|
|
2855
|
+
const firstKey = entries[0]?.[0] ?? "";
|
|
2856
|
+
const colorOffset = hashString(firstKey);
|
|
2857
|
+
return /* @__PURE__ */ jsx307("div", { className: "chart-legend", children: values.map((v, i) => {
|
|
2858
|
+
const pct = Math.round(v / total * 100);
|
|
2859
|
+
const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
|
|
2860
|
+
return /* @__PURE__ */ jsxs197("div", { className: "chart-legend-item", children: [
|
|
2861
|
+
/* @__PURE__ */ jsx307("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
2862
|
+
/* @__PURE__ */ jsxs197("div", { className: "chart-legend-text", children: [
|
|
2863
|
+
/* @__PURE__ */ jsx307("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
|
|
2864
|
+
/* @__PURE__ */ jsxs197("span", { className: "chart-legend-value", children: [
|
|
2865
|
+
v.toLocaleString(),
|
|
2866
|
+
"(",
|
|
2867
|
+
pct,
|
|
2868
|
+
"%)"
|
|
2869
|
+
] })
|
|
2870
|
+
] })
|
|
2871
|
+
] }, i);
|
|
2872
|
+
}) });
|
|
2873
|
+
}
|
|
2874
|
+
return /* @__PURE__ */ jsx307("div", { className: "chart-legend", children: entries.map(([key], di) => {
|
|
2875
|
+
const palette = getPalette(LINE_BAR_PALETTES, di, key);
|
|
2876
|
+
const color = palette[2];
|
|
2877
|
+
const values = entries[di][1];
|
|
2878
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
2879
|
+
return /* @__PURE__ */ jsxs197("div", { className: "chart-legend-item", children: [
|
|
2880
|
+
/* @__PURE__ */ jsx307("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
|
|
2881
|
+
/* @__PURE__ */ jsxs197("div", { className: "chart-legend-text", children: [
|
|
2882
|
+
/* @__PURE__ */ jsx307("span", { className: "chart-legend-label", children: key }),
|
|
2883
|
+
/* @__PURE__ */ jsx307("span", { className: "chart-legend-value", children: sum.toLocaleString() })
|
|
2884
|
+
] })
|
|
2885
|
+
] }, di);
|
|
2886
|
+
}) });
|
|
2887
|
+
};
|
|
2695
2888
|
var Chart = React6.memo((props) => {
|
|
2696
2889
|
const { type, data, labels, tooltip: showTooltip = true } = props;
|
|
2697
2890
|
const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
|
|
@@ -2707,7 +2900,8 @@ var Chart = React6.memo((props) => {
|
|
|
2707
2900
|
ready && type === "bar" && /* @__PURE__ */ jsx307(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
2708
2901
|
ready && type === "pie" && /* @__PURE__ */ jsx307(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
|
|
2709
2902
|
ready && type === "doughnut" && /* @__PURE__ */ jsx307(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
|
|
2710
|
-
|
|
2903
|
+
ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ jsx307(ChartLegend, { data: stableData, labels: stableLabels, type }),
|
|
2904
|
+
tooltip.content && /* @__PURE__ */ jsx307(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content })
|
|
2711
2905
|
] });
|
|
2712
2906
|
});
|
|
2713
2907
|
Chart.displayName = "Chart";
|