@underverse-ui/underverse 1.0.14 → 1.0.15

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/index.cjs CHANGED
@@ -17244,29 +17244,52 @@ var import_react25 = require("react");
17244
17244
  var import_react24 = require("react");
17245
17245
  var import_react_dom6 = require("react-dom");
17246
17246
  var import_jsx_runtime55 = require("react/jsx-runtime");
17247
- function ChartTooltip({ x, y, visible, label, value, color, secondaryLabel, secondaryValue, items, containerRef }) {
17247
+ function ChartTooltip({
17248
+ x,
17249
+ y,
17250
+ visible,
17251
+ label,
17252
+ value,
17253
+ color,
17254
+ secondaryLabel,
17255
+ secondaryValue,
17256
+ items,
17257
+ containerRef,
17258
+ formatter
17259
+ }) {
17248
17260
  const [isMounted, setIsMounted] = (0, import_react24.useState)(false);
17249
17261
  const [position, setPosition] = (0, import_react24.useState)(null);
17262
+ const tooltipRef = (0, import_react24.useRef)(null);
17250
17263
  (0, import_react24.useEffect)(() => {
17251
17264
  setIsMounted(true);
17252
17265
  }, []);
17253
17266
  (0, import_react24.useEffect)(() => {
17254
17267
  if (visible && containerRef?.current) {
17255
17268
  const rect = containerRef.current.getBoundingClientRect();
17256
- setPosition({
17257
- top: rect.top + y,
17258
- left: rect.left + x
17259
- });
17269
+ const tw = tooltipRef.current?.offsetWidth ?? 160;
17270
+ const th = tooltipRef.current?.offsetHeight ?? 80;
17271
+ let left = rect.left + x + 12;
17272
+ let top = rect.top + y - th / 2;
17273
+ if (left + tw > window.innerWidth - 8) {
17274
+ left = rect.left + x - tw - 12;
17275
+ }
17276
+ if (top + th > window.innerHeight - 8) {
17277
+ top = window.innerHeight - th - 8;
17278
+ }
17279
+ if (top < 8) top = 8;
17280
+ if (left < 8) left = 8;
17281
+ setPosition({ top, left });
17260
17282
  }
17261
17283
  }, [visible, x, y, containerRef]);
17262
17284
  if (!visible || !isMounted || !position) return null;
17263
17285
  const tooltipContent = /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
17264
17286
  "div",
17265
17287
  {
17288
+ ref: tooltipRef,
17266
17289
  style: {
17267
17290
  position: "fixed",
17268
17291
  top: position.top,
17269
- left: position.left + 12,
17292
+ left: position.left,
17270
17293
  zIndex: 99999,
17271
17294
  pointerEvents: "none",
17272
17295
  animation: "chartTooltipFadeIn 0.15s ease-out"
@@ -17289,11 +17312,11 @@ function ChartTooltip({ x, y, visible, label, value, color, secondaryLabel, seco
17289
17312
  item.label,
17290
17313
  ":"
17291
17314
  ] }),
17292
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "font-semibold ml-auto", children: item.value })
17315
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "font-semibold ml-auto", children: formatter ? formatter(item.value) : item.value })
17293
17316
  ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_jsx_runtime55.Fragment, { children: [
17294
17317
  /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center gap-2", children: [
17295
17318
  color && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "w-2 h-2 rounded-full shrink-0", style: { backgroundColor: color } }),
17296
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "font-semibold", children: value })
17319
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "font-semibold", children: formatter && value != null ? formatter(value) : value })
17297
17320
  ] }),
17298
17321
  secondaryLabel && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "text-muted-foreground text-xs mt-1", children: [
17299
17322
  secondaryLabel,
@@ -17316,10 +17339,39 @@ function ChartTooltip({ x, y, visible, label, value, color, secondaryLabel, seco
17316
17339
  return (0, import_react_dom6.createPortal)(tooltipContent, document.body);
17317
17340
  }
17318
17341
 
17342
+ // ../../components/ui/chart-utils.ts
17343
+ function getCatmullRomSpline(points, tension = 0.5) {
17344
+ if (points.length < 2) return "";
17345
+ if (points.length === 2) {
17346
+ return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y}`;
17347
+ }
17348
+ let path = `M ${points[0].x} ${points[0].y}`;
17349
+ for (let i = 0; i < points.length - 1; i++) {
17350
+ const p0 = points[Math.max(0, i - 1)];
17351
+ const p1 = points[i];
17352
+ const p2 = points[i + 1];
17353
+ const p3 = points[Math.min(points.length - 1, i + 2)];
17354
+ const cp1x = p1.x + (p2.x - p0.x) * tension / 6;
17355
+ const cp1y = p1.y + (p2.y - p0.y) * tension / 6;
17356
+ const cp2x = p2.x - (p3.x - p1.x) * tension / 6;
17357
+ const cp2y = p2.y - (p3.y - p1.y) * tension / 6;
17358
+ path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
17359
+ }
17360
+ return path;
17361
+ }
17362
+ function getPathLength(points) {
17363
+ return points.reduce((acc, p, i) => {
17364
+ if (i === 0) return 0;
17365
+ const prev = points[i - 1];
17366
+ return acc + Math.sqrt((p.x - prev.x) ** 2 + (p.y - prev.y) ** 2);
17367
+ }, 0);
17368
+ }
17369
+
17319
17370
  // ../../components/ui/LineChart.tsx
17320
17371
  var import_jsx_runtime56 = require("react/jsx-runtime");
17321
17372
  function LineChart({
17322
17373
  data,
17374
+ series,
17323
17375
  width = 400,
17324
17376
  height = 200,
17325
17377
  color = "currentColor",
@@ -17328,8 +17380,11 @@ function LineChart({
17328
17380
  showGrid = true,
17329
17381
  showLabels = true,
17330
17382
  showValues = false,
17383
+ showLegend,
17331
17384
  animated = true,
17332
17385
  curved = true,
17386
+ formatValue,
17387
+ referenceLines = [],
17333
17388
  className = ""
17334
17389
  }) {
17335
17390
  const svgRef = (0, import_react25.useRef)(null);
@@ -17337,41 +17392,50 @@ function LineChart({
17337
17392
  const chartWidth = width - padding.left - padding.right;
17338
17393
  const chartHeight = height - padding.top - padding.bottom;
17339
17394
  const [hoveredPoint, setHoveredPoint] = (0, import_react25.useState)(null);
17340
- const { minValue, maxValue, points, linePath, areaPath } = (0, import_react25.useMemo)(() => {
17341
- if (!data.length) return { minValue: 0, maxValue: 0, points: [], linePath: "", areaPath: "" };
17342
- const values = data.map((d) => d.value);
17343
- const min = Math.min(...values);
17344
- const max = Math.max(...values);
17395
+ const [hiddenSeries, setHiddenSeries] = (0, import_react25.useState)(/* @__PURE__ */ new Set());
17396
+ const toggleSeries = (0, import_react25.useCallback)((name) => {
17397
+ setHiddenSeries((prev) => {
17398
+ const next = new Set(prev);
17399
+ if (next.has(name)) next.delete(name);
17400
+ else next.add(name);
17401
+ return next;
17402
+ });
17403
+ }, []);
17404
+ const normalizedSeries = (0, import_react25.useMemo)(() => {
17405
+ if (series && series.length > 0) return series;
17406
+ if (data && data.length > 0) return [{ name: "", data, color, fillColor }];
17407
+ return [];
17408
+ }, [series, data, color, fillColor]);
17409
+ const isMultiSeries = normalizedSeries.length > 1;
17410
+ const { minValue, maxValue, processedSeries, labels } = (0, import_react25.useMemo)(() => {
17411
+ if (!normalizedSeries.length || !normalizedSeries[0]?.data?.length) {
17412
+ return { minValue: 0, maxValue: 0, processedSeries: [], labels: [] };
17413
+ }
17414
+ const allLabels = normalizedSeries[0].data.map((d) => d.label);
17415
+ const allValues = normalizedSeries.flatMap((s) => s.data.map((d) => d.value));
17416
+ const min = Math.min(...allValues);
17417
+ const max = Math.max(...allValues);
17345
17418
  const range = max - min || 1;
17346
- const pts = data.map((d, i) => ({
17347
- x: padding.left + i / (data.length - 1 || 1) * chartWidth,
17348
- y: padding.top + chartHeight - (d.value - min) / range * chartHeight,
17349
- ...d
17350
- }));
17351
- let path = "";
17352
- let area = "";
17353
- if (curved && pts.length > 2) {
17354
- path = `M ${pts[0].x} ${pts[0].y}`;
17355
- area = `M ${pts[0].x} ${padding.top + chartHeight} L ${pts[0].x} ${pts[0].y}`;
17356
- for (let i = 0; i < pts.length - 1; i++) {
17357
- const p0 = pts[Math.max(0, i - 1)];
17358
- const p1 = pts[i];
17359
- const p2 = pts[i + 1];
17360
- const p3 = pts[Math.min(pts.length - 1, i + 2)];
17361
- const cp1x = p1.x + (p2.x - p0.x) / 6;
17362
- const cp1y = p1.y + (p2.y - p0.y) / 6;
17363
- const cp2x = p2.x - (p3.x - p1.x) / 6;
17364
- const cp2y = p2.y - (p3.y - p1.y) / 6;
17365
- path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
17366
- area += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
17367
- }
17368
- area += ` L ${pts[pts.length - 1].x} ${padding.top + chartHeight} Z`;
17369
- } else {
17370
- path = pts.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
17371
- area = `M ${pts[0].x} ${padding.top + chartHeight} ` + pts.map((p) => `L ${p.x} ${p.y}`).join(" ") + ` L ${pts[pts.length - 1].x} ${padding.top + chartHeight} Z`;
17372
- }
17373
- return { minValue: min, maxValue: max, points: pts, linePath: path, areaPath: area };
17374
- }, [data, chartWidth, chartHeight, curved, padding.left, padding.top]);
17419
+ const processed = normalizedSeries.map((s) => {
17420
+ const pts = s.data.map((d, i) => ({
17421
+ x: padding.left + i / (s.data.length - 1 || 1) * chartWidth,
17422
+ y: padding.top + chartHeight - (d.value - min) / range * chartHeight,
17423
+ ...d
17424
+ }));
17425
+ let linePath = "";
17426
+ let areaPath = "";
17427
+ if (curved && pts.length > 2) {
17428
+ linePath = getCatmullRomSpline(pts);
17429
+ areaPath = `${linePath} L ${pts[pts.length - 1].x} ${padding.top + chartHeight} L ${pts[0].x} ${padding.top + chartHeight} Z`;
17430
+ } else {
17431
+ linePath = pts.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
17432
+ areaPath = `M ${pts[0].x} ${padding.top + chartHeight} ` + pts.map((p) => `L ${p.x} ${p.y}`).join(" ") + ` L ${pts[pts.length - 1].x} ${padding.top + chartHeight} Z`;
17433
+ }
17434
+ const lineLength = getPathLength(pts);
17435
+ return { ...s, points: pts, linePath, areaPath, lineLength };
17436
+ });
17437
+ return { minValue: min, maxValue: max, processedSeries: processed, labels: allLabels };
17438
+ }, [normalizedSeries, chartWidth, chartHeight, curved, padding.left, padding.top]);
17375
17439
  const gridLines = (0, import_react25.useMemo)(() => {
17376
17440
  const lines = [];
17377
17441
  const steps = 5;
@@ -17386,82 +17450,129 @@ function LineChart({
17386
17450
  /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("svg", { ref: svgRef, width, height, className: `overflow-visible ${className}`, style: { fontFamily: "inherit" }, children: [
17387
17451
  showGrid && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("g", { className: "text-muted-foreground/20", children: gridLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("g", { children: [
17388
17452
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("line", { x1: padding.left, y1: line.y, x2: width - padding.right, y2: line.y, stroke: "currentColor", strokeDasharray: "4 4" }),
17389
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", fill: "currentColor", className: "text-muted-foreground", children: line.value.toFixed(0) })
17453
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", fill: "currentColor", className: "text-muted-foreground", children: formatValue ? formatValue(line.value) : line.value.toFixed(0) })
17390
17454
  ] }, i)) }),
17391
- fillColor && areaPath && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("path", { d: areaPath, fill: fillColor, opacity: 0.2, className: animated ? "animate-[fadeIn_0.6s_ease-out]" : "" }),
17392
- linePath && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17455
+ processedSeries.map(
17456
+ (s, si) => s.fillColor && s.areaPath && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17457
+ "path",
17458
+ {
17459
+ d: s.areaPath,
17460
+ fill: s.fillColor,
17461
+ className: "transition-opacity duration-300",
17462
+ opacity: hiddenSeries.has(s.name) ? 0 : 0.15,
17463
+ style: animated ? { opacity: 0, animation: `fadeIn 0.6s ease-out ${si * 0.1}s forwards` } : void 0
17464
+ },
17465
+ `area-${si}`
17466
+ )
17467
+ ),
17468
+ processedSeries.map((s, si) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17393
17469
  "path",
17394
17470
  {
17395
- d: linePath,
17471
+ d: s.linePath,
17396
17472
  fill: "none",
17397
- stroke: color,
17473
+ stroke: s.color,
17398
17474
  strokeWidth: 2,
17399
17475
  strokeLinecap: "round",
17400
17476
  strokeLinejoin: "round",
17401
- className: animated ? "animate-[drawLine_1s_ease-out]" : "",
17477
+ className: "transition-opacity duration-300",
17478
+ opacity: hiddenSeries.has(s.name) ? 0 : 1,
17402
17479
  style: animated ? {
17403
- strokeDasharray: 1e3,
17404
- strokeDashoffset: 0,
17405
- animation: "drawLine 1s ease-out forwards"
17480
+ strokeDasharray: s.lineLength,
17481
+ strokeDashoffset: s.lineLength,
17482
+ animation: `drawLine 1s ease-out ${si * 0.15}s forwards`
17406
17483
  } : void 0
17407
- }
17408
- ),
17409
- showDots && points.map((point, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
17410
- "g",
17411
- {
17412
- onMouseEnter: () => setHoveredPoint({ x: point.x, y: point.y, label: point.label, value: point.value }),
17413
- onMouseLeave: () => setHoveredPoint(null),
17414
- className: "cursor-pointer",
17415
- children: [
17416
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17417
- "circle",
17418
- {
17419
- cx: point.x,
17420
- cy: point.y,
17421
- r: hoveredPoint?.x === point.x ? 6 : 4,
17422
- fill: color,
17423
- className: `transition-all duration-150 ${animated ? "animate-[scaleIn_0.3s_ease-out]" : ""}`,
17424
- style: animated ? { animationDelay: `${i * 0.05}s`, animationFillMode: "both" } : void 0
17425
- }
17426
- ),
17427
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("circle", { cx: point.x, cy: point.y, r: 16, fill: "transparent" }),
17428
- showValues && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x: point.x, y: point.y - 12, textAnchor: "middle", fontSize: "10", fontWeight: "500", className: "text-foreground", fill: "currentColor", children: point.value })
17429
- ]
17430
17484
  },
17431
- i
17485
+ `line-${si}`
17432
17486
  )),
17433
- hoveredPoint && /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("g", { className: "pointer-events-none", children: [
17434
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17435
- "line",
17436
- {
17437
- x1: hoveredPoint.x,
17438
- y1: padding.top,
17439
- x2: hoveredPoint.x,
17440
- y2: padding.top + chartHeight,
17441
- stroke: color,
17442
- strokeWidth: 1,
17443
- strokeDasharray: "4 4",
17444
- opacity: 0.5
17445
- }
17446
- ),
17447
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17448
- "line",
17487
+ showDots && processedSeries.map(
17488
+ (s, si) => !hiddenSeries.has(s.name) && s.points.map((point, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
17489
+ "g",
17449
17490
  {
17450
- x1: padding.left,
17451
- y1: hoveredPoint.y,
17452
- x2: padding.left + chartWidth,
17453
- y2: hoveredPoint.y,
17454
- stroke: color,
17455
- strokeWidth: 1,
17456
- strokeDasharray: "4 4",
17457
- opacity: 0.5
17458
- }
17459
- )
17460
- ] }),
17461
- showLabels && points.map((point, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x: point.x, y: height - 10, textAnchor: "middle", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: point.label }, i)),
17491
+ onMouseEnter: () => {
17492
+ if (isMultiSeries) {
17493
+ const items = processedSeries.filter((ps) => !hiddenSeries.has(ps.name)).map((ps) => ({
17494
+ label: ps.name,
17495
+ value: ps.points[i]?.value ?? 0,
17496
+ color: ps.color
17497
+ }));
17498
+ setHoveredPoint({ x: point.x, y: point.y, label: point.label, value: point.value, items });
17499
+ } else {
17500
+ setHoveredPoint({ x: point.x, y: point.y, label: point.label, value: point.value });
17501
+ }
17502
+ },
17503
+ onMouseLeave: () => setHoveredPoint(null),
17504
+ className: "cursor-pointer",
17505
+ children: [
17506
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17507
+ "circle",
17508
+ {
17509
+ cx: point.x,
17510
+ cy: point.y,
17511
+ r: hoveredPoint?.x === point.x ? 6 : 4,
17512
+ fill: s.color,
17513
+ className: `transition-all duration-150 ${animated ? "animate-[scaleIn_0.3s_ease-out]" : ""}`,
17514
+ style: animated ? { animationDelay: `${si * 0.15 + i * 0.05}s`, animationFillMode: "both" } : void 0
17515
+ }
17516
+ ),
17517
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("circle", { cx: point.x, cy: point.y, r: 16, fill: "transparent" }),
17518
+ showValues && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17519
+ "text",
17520
+ {
17521
+ x: point.x,
17522
+ y: point.y - 12,
17523
+ textAnchor: "middle",
17524
+ fontSize: "10",
17525
+ fontWeight: "500",
17526
+ className: "text-foreground",
17527
+ fill: "currentColor",
17528
+ children: formatValue ? formatValue(point.value) : point.value
17529
+ }
17530
+ )
17531
+ ]
17532
+ },
17533
+ `dot-${si}-${i}`
17534
+ ))
17535
+ ),
17536
+ referenceLines.map((ref, i) => {
17537
+ const range = maxValue - minValue || 1;
17538
+ const y = padding.top + chartHeight - (ref.value - minValue) / range * chartHeight;
17539
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("g", { className: "pointer-events-none", children: [
17540
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17541
+ "line",
17542
+ {
17543
+ x1: padding.left,
17544
+ y1: y,
17545
+ x2: padding.left + chartWidth,
17546
+ y2: y,
17547
+ stroke: ref.color || "#ef4444",
17548
+ strokeWidth: 1.5,
17549
+ strokeDasharray: ref.strokeDasharray || "6 4",
17550
+ opacity: 0.7
17551
+ }
17552
+ ),
17553
+ ref.label && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x: padding.left + chartWidth + 4, y: y + 4, fontSize: "10", fill: ref.color || "#ef4444", fontWeight: "500", children: ref.label })
17554
+ ] }, `ref-${i}`);
17555
+ }),
17556
+ hoveredPoint && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("g", { className: "pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17557
+ "line",
17558
+ {
17559
+ x1: hoveredPoint.x,
17560
+ y1: padding.top,
17561
+ x2: hoveredPoint.x,
17562
+ y2: padding.top + chartHeight,
17563
+ stroke: "currentColor",
17564
+ strokeWidth: 1,
17565
+ strokeDasharray: "4 4",
17566
+ opacity: 0.3,
17567
+ className: "text-foreground"
17568
+ }
17569
+ ) }),
17570
+ showLabels && labels.map((lbl, i) => {
17571
+ const x = padding.left + i / (labels.length - 1 || 1) * chartWidth;
17572
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("text", { x, y: height - 10, textAnchor: "middle", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: lbl }, i);
17573
+ }),
17462
17574
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("style", { children: `
17463
17575
  @keyframes drawLine {
17464
- from { stroke-dashoffset: 1000; }
17465
17576
  to { stroke-dashoffset: 0; }
17466
17577
  }
17467
17578
  @keyframes scaleIn {
@@ -17470,10 +17581,20 @@ function LineChart({
17470
17581
  }
17471
17582
  @keyframes fadeIn {
17472
17583
  from { opacity: 0; }
17473
- to { opacity: 0.2; }
17584
+ to { opacity: 0.15; }
17474
17585
  }
17475
17586
  ` })
17476
17587
  ] }),
17588
+ (showLegend ?? isMultiSeries) && isMultiSeries && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "flex items-center justify-center gap-6 mt-2", children: normalizedSeries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex items-center gap-2 text-sm cursor-pointer select-none", onClick: () => toggleSeries(s.name), children: [
17589
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17590
+ "div",
17591
+ {
17592
+ className: "w-3 h-3 rounded-md transition-opacity",
17593
+ style: { backgroundColor: s.color, opacity: hiddenSeries.has(s.name) ? 0.3 : 1 }
17594
+ }
17595
+ ),
17596
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: hiddenSeries.has(s.name) ? "text-muted-foreground/40 line-through" : "text-muted-foreground", children: s.name })
17597
+ ] }, i)) }),
17477
17598
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
17478
17599
  ChartTooltip,
17479
17600
  {
@@ -17481,9 +17602,11 @@ function LineChart({
17481
17602
  y: hoveredPoint?.y ?? 0,
17482
17603
  visible: !!hoveredPoint,
17483
17604
  label: hoveredPoint?.label,
17484
- value: hoveredPoint?.value,
17485
- color,
17486
- containerRef: svgRef
17605
+ value: !isMultiSeries ? hoveredPoint?.value : void 0,
17606
+ items: isMultiSeries ? hoveredPoint?.items : void 0,
17607
+ color: !isMultiSeries ? processedSeries[0]?.color || color : void 0,
17608
+ containerRef: svgRef,
17609
+ formatter: formatValue ? (v) => formatValue(Number(v)) : void 0
17487
17610
  }
17488
17611
  )
17489
17612
  ] });
@@ -17494,16 +17617,20 @@ var import_react26 = require("react");
17494
17617
  var import_jsx_runtime57 = require("react/jsx-runtime");
17495
17618
  function BarChart({
17496
17619
  data,
17620
+ series,
17497
17621
  width = 400,
17498
17622
  height = 200,
17499
17623
  color = "currentColor",
17500
17624
  showLabels = true,
17501
17625
  showValues = true,
17502
17626
  showGrid = true,
17627
+ showLegend,
17503
17628
  horizontal = false,
17504
17629
  animated = true,
17630
+ stacked = false,
17505
17631
  barRadius = 4,
17506
17632
  barGap = 0.3,
17633
+ formatValue,
17507
17634
  className = ""
17508
17635
  }) {
17509
17636
  const svgRef = (0, import_react26.useRef)(null);
@@ -17511,31 +17638,109 @@ function BarChart({
17511
17638
  const chartWidth = width - padding.left - padding.right;
17512
17639
  const chartHeight = height - padding.top - padding.bottom;
17513
17640
  const [hoveredBar, setHoveredBar] = (0, import_react26.useState)(null);
17514
- const { maxValue, bars, gridLines } = (0, import_react26.useMemo)(() => {
17515
- if (!data.length) return { maxValue: 0, bars: [], gridLines: [] };
17516
- const max = Math.max(...data.map((d) => d.value));
17517
- const barCount = data.length;
17518
- const barsData = data.map((d, i) => {
17641
+ const normalizedSeries = (0, import_react26.useMemo)(() => {
17642
+ if (series && series.length > 0) return series;
17643
+ if (data && data.length > 0) return [{ name: "", data, color }];
17644
+ return [];
17645
+ }, [series, data, color]);
17646
+ const isMultiSeries = normalizedSeries.length > 1;
17647
+ const { maxValue, barGroups, gridLines } = (0, import_react26.useMemo)(() => {
17648
+ if (!normalizedSeries.length || !normalizedSeries[0]?.data?.length) {
17649
+ return { maxValue: 0, barGroups: [], gridLines: [] };
17650
+ }
17651
+ const allLabels = normalizedSeries[0].data.map((d) => d.label);
17652
+ const seriesCount = normalizedSeries.length;
17653
+ const labelCount = allLabels.length;
17654
+ let max = 0;
17655
+ if (stacked) {
17656
+ for (let li = 0; li < labelCount; li++) {
17657
+ const stackVal = normalizedSeries.reduce((sum, s) => sum + (s.data[li]?.value || 0), 0);
17658
+ max = Math.max(max, stackVal);
17659
+ }
17660
+ } else {
17661
+ max = Math.max(...normalizedSeries.flatMap((s) => s.data.map((d) => d.value)));
17662
+ }
17663
+ const groups = allLabels.map((label, li) => {
17519
17664
  if (horizontal) {
17520
- const barHeight = chartHeight / barCount * (1 - barGap);
17521
- const gap = chartHeight / barCount * barGap;
17522
- return {
17523
- x: padding.left,
17524
- y: padding.top + i * (barHeight + gap) + gap / 2,
17525
- width: d.value / max * chartWidth,
17526
- height: barHeight,
17527
- ...d
17528
- };
17665
+ const groupH = chartHeight / labelCount;
17666
+ const gap = groupH * barGap;
17667
+ const usable = groupH - gap;
17668
+ if (stacked) {
17669
+ let cum = 0;
17670
+ const bars = normalizedSeries.map((s) => {
17671
+ const val = s.data[li]?.value || 0;
17672
+ const w = val / max * chartWidth;
17673
+ const bar = {
17674
+ x: padding.left + cum,
17675
+ y: padding.top + li * groupH + gap / 2,
17676
+ width: w,
17677
+ height: usable,
17678
+ value: val,
17679
+ color: s.data[li]?.color || s.color,
17680
+ seriesName: s.name,
17681
+ label
17682
+ };
17683
+ cum += w;
17684
+ return bar;
17685
+ });
17686
+ return { label, bars };
17687
+ } else {
17688
+ const bH = usable / seriesCount;
17689
+ const bars = normalizedSeries.map((s, si) => {
17690
+ const val = s.data[li]?.value || 0;
17691
+ return {
17692
+ x: padding.left,
17693
+ y: padding.top + li * groupH + gap / 2 + si * bH,
17694
+ width: val / max * chartWidth,
17695
+ height: bH,
17696
+ value: val,
17697
+ color: s.data[li]?.color || s.color,
17698
+ seriesName: s.name,
17699
+ label
17700
+ };
17701
+ });
17702
+ return { label, bars };
17703
+ }
17529
17704
  } else {
17530
- const barWidth = chartWidth / barCount * (1 - barGap);
17531
- const gap = chartWidth / barCount * barGap;
17532
- return {
17533
- x: padding.left + i * (barWidth + gap) + gap / 2,
17534
- y: padding.top + chartHeight - d.value / max * chartHeight,
17535
- width: barWidth,
17536
- height: d.value / max * chartHeight,
17537
- ...d
17538
- };
17705
+ const groupW = chartWidth / labelCount;
17706
+ const gap = groupW * barGap;
17707
+ const usable = groupW - gap;
17708
+ if (stacked) {
17709
+ let cum = 0;
17710
+ const bars = normalizedSeries.map((s) => {
17711
+ const val = s.data[li]?.value || 0;
17712
+ const h = val / max * chartHeight;
17713
+ const bar = {
17714
+ x: padding.left + li * groupW + gap / 2,
17715
+ y: padding.top + chartHeight - cum - h,
17716
+ width: usable,
17717
+ height: h,
17718
+ value: val,
17719
+ color: s.data[li]?.color || s.color,
17720
+ seriesName: s.name,
17721
+ label
17722
+ };
17723
+ cum += h;
17724
+ return bar;
17725
+ });
17726
+ return { label, bars };
17727
+ } else {
17728
+ const bW = usable / seriesCount;
17729
+ const bars = normalizedSeries.map((s, si) => {
17730
+ const val = s.data[li]?.value || 0;
17731
+ return {
17732
+ x: padding.left + li * groupW + gap / 2 + si * bW,
17733
+ y: padding.top + chartHeight - val / max * chartHeight,
17734
+ width: bW,
17735
+ height: val / max * chartHeight,
17736
+ value: val,
17737
+ color: s.data[li]?.color || s.color,
17738
+ seriesName: s.name,
17739
+ label
17740
+ };
17741
+ });
17742
+ return { label, bars };
17743
+ }
17539
17744
  }
17540
17745
  });
17541
17746
  const lines = [];
@@ -17543,106 +17748,102 @@ function BarChart({
17543
17748
  for (let i = 0; i <= steps; i++) {
17544
17749
  const value = i / steps * max;
17545
17750
  if (horizontal) {
17546
- lines.push({
17547
- x: padding.left + i / steps * chartWidth,
17548
- y1: padding.top,
17549
- y2: height - padding.bottom,
17550
- value
17551
- });
17751
+ lines.push({ x: padding.left + i / steps * chartWidth, y1: padding.top, y2: height - padding.bottom, value });
17552
17752
  } else {
17553
- lines.push({
17554
- y: padding.top + chartHeight - i / steps * chartHeight,
17555
- x1: padding.left,
17556
- x2: width - padding.right,
17557
- value
17558
- });
17753
+ lines.push({ y: padding.top + chartHeight - i / steps * chartHeight, x1: padding.left, x2: width - padding.right, value });
17559
17754
  }
17560
17755
  }
17561
- return { maxValue: max, bars: barsData, gridLines: lines };
17562
- }, [data, chartWidth, chartHeight, horizontal, barGap, padding, width, height]);
17756
+ return { maxValue: max, barGroups: groups, gridLines: lines };
17757
+ }, [normalizedSeries, chartWidth, chartHeight, horizontal, stacked, barGap, padding, width, height]);
17563
17758
  return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
17564
17759
  /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("svg", { ref: svgRef, width, height, className: `overflow-visible ${className}`, style: { fontFamily: "inherit" }, children: [
17565
17760
  showGrid && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("g", { className: "text-muted-foreground/20", children: gridLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("g", { children: horizontal ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
17566
17761
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("line", { x1: line.x, y1: line.y1, x2: line.x, y2: line.y2, stroke: "currentColor", strokeDasharray: "4 4" }),
17567
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("text", { x: line.x, y: height - 8, textAnchor: "middle", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: line.value.toFixed(0) })
17762
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("text", { x: line.x, y: height - 8, textAnchor: "middle", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: formatValue ? formatValue(line.value) : line.value.toFixed(0) })
17568
17763
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
17569
17764
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("line", { x1: line.x1, y1: line.y, x2: line.x2, y2: line.y, stroke: "currentColor", strokeDasharray: "4 4" }),
17570
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: line.value.toFixed(0) })
17765
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: formatValue ? formatValue(line.value) : line.value.toFixed(0) })
17571
17766
  ] }) }, i)) }),
17572
- bars.map((bar, i) => /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
17573
- "g",
17574
- {
17575
- onMouseEnter: () => setHoveredBar({
17576
- x: horizontal ? bar.x + bar.width : bar.x + bar.width / 2,
17577
- y: horizontal ? bar.y + bar.height / 2 : bar.y,
17578
- label: bar.label,
17579
- value: bar.value,
17580
- color: bar.color || color
17581
- }),
17582
- onMouseLeave: () => setHoveredBar(null),
17583
- className: "cursor-pointer",
17584
- children: [
17585
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
17586
- "rect",
17587
- {
17588
- x: bar.x,
17589
- y: horizontal ? bar.y : bar.y,
17590
- width: animated && !horizontal ? 0 : bar.width,
17591
- height: animated && horizontal ? bar.height : horizontal ? bar.height : 0,
17592
- rx: barRadius,
17593
- ry: barRadius,
17594
- fill: bar.color || color,
17595
- className: `transition-all duration-150 ${hoveredBar?.label === bar.label ? "opacity-80" : ""}`,
17596
- style: animated ? {
17597
- animation: horizontal ? `growWidth 0.5s ease-out ${i * 0.1}s forwards` : `growHeight 0.5s ease-out ${i * 0.1}s forwards`,
17598
- ...horizontal ? { width: 0 } : { height: 0, y: padding.top + chartHeight }
17599
- } : void 0,
17600
- children: [
17601
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17602
- "animate",
17603
- {
17604
- attributeName: horizontal ? "width" : "height",
17605
- from: "0",
17606
- to: horizontal ? bar.width : bar.height,
17607
- dur: "0.5s",
17608
- begin: `${i * 0.1}s`,
17609
- fill: "freeze"
17610
- }
17611
- ),
17612
- !horizontal && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("animate", { attributeName: "y", from: padding.top + chartHeight, to: bar.y, dur: "0.5s", begin: `${i * 0.1}s`, fill: "freeze" })
17613
- ]
17614
- }
17615
- ),
17616
- showValues && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17617
- "text",
17618
- {
17619
- x: horizontal ? bar.x + bar.width + 8 : bar.x + bar.width / 2,
17620
- y: horizontal ? bar.y + bar.height / 2 + 4 : bar.y - 8,
17621
- textAnchor: horizontal ? "start" : "middle",
17622
- fontSize: "11",
17623
- fontWeight: "500",
17624
- className: "text-foreground",
17625
- fill: "currentColor",
17626
- style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.1 + 0.3}s forwards` } : void 0,
17627
- children: bar.value
17628
- }
17629
- ),
17630
- showLabels && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17631
- "text",
17632
- {
17633
- x: horizontal ? padding.left - 8 : bar.x + bar.width / 2,
17634
- y: horizontal ? bar.y + bar.height / 2 + 4 : height - 10,
17635
- textAnchor: horizontal ? "end" : "middle",
17636
- fontSize: "10",
17637
- className: "text-muted-foreground",
17638
- fill: "currentColor",
17639
- children: bar.label
17640
- }
17641
- )
17642
- ]
17643
- },
17644
- i
17645
- )),
17767
+ barGroups.map((group, gi) => /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("g", { children: [
17768
+ group.bars.map((bar, bi) => {
17769
+ const animDelay = gi * 0.08 + bi * 0.04;
17770
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
17771
+ "g",
17772
+ {
17773
+ onMouseEnter: () => setHoveredBar({
17774
+ x: horizontal ? bar.x + bar.width : bar.x + bar.width / 2,
17775
+ y: horizontal ? bar.y + bar.height / 2 : bar.y,
17776
+ label: bar.label,
17777
+ value: bar.value,
17778
+ color: bar.color,
17779
+ seriesName: bar.seriesName
17780
+ }),
17781
+ onMouseLeave: () => setHoveredBar(null),
17782
+ className: "cursor-pointer",
17783
+ children: [
17784
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
17785
+ "rect",
17786
+ {
17787
+ x: bar.x,
17788
+ y: bar.y,
17789
+ width: bar.width,
17790
+ height: bar.height,
17791
+ rx: barRadius,
17792
+ ry: barRadius,
17793
+ fill: bar.color,
17794
+ className: `transition-all duration-150 ${hoveredBar?.label === bar.label && hoveredBar?.seriesName === bar.seriesName ? "opacity-80" : ""}`,
17795
+ style: animated ? {
17796
+ animation: horizontal ? `growWidth 0.5s ease-out ${animDelay}s forwards` : `growHeight 0.5s ease-out ${animDelay}s forwards`,
17797
+ ...horizontal ? { width: 0 } : { height: 0, y: padding.top + chartHeight }
17798
+ } : void 0,
17799
+ children: [
17800
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17801
+ "animate",
17802
+ {
17803
+ attributeName: horizontal ? "width" : "height",
17804
+ from: "0",
17805
+ to: horizontal ? bar.width : bar.height,
17806
+ dur: "0.5s",
17807
+ begin: `${animDelay}s`,
17808
+ fill: "freeze"
17809
+ }
17810
+ ),
17811
+ !horizontal && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("animate", { attributeName: "y", from: padding.top + chartHeight, to: bar.y, dur: "0.5s", begin: `${animDelay}s`, fill: "freeze" })
17812
+ ]
17813
+ }
17814
+ ),
17815
+ showValues && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17816
+ "text",
17817
+ {
17818
+ x: horizontal ? bar.x + bar.width + 8 : bar.x + bar.width / 2,
17819
+ y: horizontal ? bar.y + bar.height / 2 + 4 : bar.y - 8,
17820
+ textAnchor: horizontal ? "start" : "middle",
17821
+ fontSize: "11",
17822
+ fontWeight: "500",
17823
+ className: "text-foreground",
17824
+ fill: "currentColor",
17825
+ style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${animDelay + 0.3}s forwards` } : void 0,
17826
+ children: formatValue ? formatValue(bar.value) : bar.value
17827
+ }
17828
+ )
17829
+ ]
17830
+ },
17831
+ bi
17832
+ );
17833
+ }),
17834
+ showLabels && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17835
+ "text",
17836
+ {
17837
+ x: horizontal ? padding.left - 8 : padding.left + (gi + 0.5) * (chartWidth / barGroups.length),
17838
+ y: horizontal ? padding.top + (gi + 0.5) * (chartHeight / barGroups.length) + 4 : height - 10,
17839
+ textAnchor: horizontal ? "end" : "middle",
17840
+ fontSize: "10",
17841
+ className: "text-muted-foreground",
17842
+ fill: "currentColor",
17843
+ children: group.label
17844
+ }
17845
+ )
17846
+ ] }, gi)),
17646
17847
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("style", { children: `
17647
17848
  @keyframes growHeight {
17648
17849
  from { height: 0; }
@@ -17656,16 +17857,21 @@ function BarChart({
17656
17857
  }
17657
17858
  ` })
17658
17859
  ] }),
17860
+ (showLegend ?? isMultiSeries) && isMultiSeries && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("div", { className: "flex items-center justify-center gap-6 mt-2", children: normalizedSeries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
17861
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("div", { className: "w-3 h-3 rounded-md", style: { backgroundColor: s.color } }),
17862
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: "text-muted-foreground", children: s.name })
17863
+ ] }, i)) }),
17659
17864
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
17660
17865
  ChartTooltip,
17661
17866
  {
17662
17867
  x: hoveredBar?.x ?? 0,
17663
17868
  y: hoveredBar?.y ?? 0,
17664
17869
  visible: !!hoveredBar,
17665
- label: hoveredBar?.label,
17870
+ label: hoveredBar?.seriesName ? `${hoveredBar.label} \u2014 ${hoveredBar.seriesName}` : hoveredBar?.label,
17666
17871
  value: hoveredBar?.value,
17667
17872
  color: hoveredBar?.color,
17668
- containerRef: svgRef
17873
+ containerRef: svgRef,
17874
+ formatter: formatValue ? (v) => formatValue(Number(v)) : void 0
17669
17875
  }
17670
17876
  )
17671
17877
  ] });
@@ -17684,6 +17890,8 @@ function PieChart({
17684
17890
  showPercentage = true,
17685
17891
  animated = true,
17686
17892
  startAngle = -90,
17893
+ renderCenter,
17894
+ formatValue,
17687
17895
  className = ""
17688
17896
  }) {
17689
17897
  const containerRef = (0, import_react27.useRef)(null);
@@ -17779,17 +17987,17 @@ function PieChart({
17779
17987
  className: "text-foreground",
17780
17988
  fill: "currentColor",
17781
17989
  style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.1 + 0.3}s forwards` } : void 0,
17782
- children: showPercentage ? `${(seg.percentage * 100).toFixed(0)}%` : seg.value
17990
+ children: showPercentage ? `${(seg.percentage * 100).toFixed(0)}%` : formatValue ? formatValue(seg.value) : seg.value
17783
17991
  }
17784
17992
  )
17785
17993
  ]
17786
17994
  },
17787
17995
  i
17788
17996
  )),
17789
- donut && /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)("g", { children: [
17997
+ donut && (renderCenter ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("foreignObject", { x: center - donutWidth, y: center - donutWidth / 2, width: donutWidth * 2, height: donutWidth, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%" }, children: renderCenter(total) }) }) : /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)("g", { children: [
17790
17998
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("text", { x: center, y: center - 5, textAnchor: "middle", fontSize: "12", className: "text-muted-foreground", fill: "currentColor", children: "Total" }),
17791
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("text", { x: center, y: center + 15, textAnchor: "middle", fontSize: "18", fontWeight: "600", className: "text-foreground", fill: "currentColor", children: total })
17792
- ] })
17999
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("text", { x: center, y: center + 15, textAnchor: "middle", fontSize: "18", fontWeight: "600", className: "text-foreground", fill: "currentColor", children: formatValue ? formatValue(total) : total })
18000
+ ] }))
17793
18001
  ] }),
17794
18002
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("style", { children: `
17795
18003
  @keyframes pieSlice {
@@ -17816,7 +18024,7 @@ function PieChart({
17816
18024
  children: [
17817
18025
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { className: "w-3 h-3 rounded-md shrink-0", style: { backgroundColor: seg.color } }),
17818
18026
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("span", { className: "text-muted-foreground", children: seg.label }),
17819
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("span", { className: "text-foreground font-medium ml-auto", children: showPercentage ? `${(seg.percentage * 100).toFixed(0)}%` : seg.value })
18027
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("span", { className: "text-foreground font-medium ml-auto", children: showPercentage ? `${(seg.percentage * 100).toFixed(0)}%` : formatValue ? formatValue(seg.value) : seg.value })
17820
18028
  ]
17821
18029
  },
17822
18030
  i
@@ -17839,26 +18047,6 @@ function PieChart({
17839
18047
  // ../../components/ui/AreaChart.tsx
17840
18048
  var import_react28 = require("react");
17841
18049
  var import_jsx_runtime59 = require("react/jsx-runtime");
17842
- function getCatmullRomSpline(points) {
17843
- if (points.length < 2) return "";
17844
- if (points.length === 2) {
17845
- return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y}`;
17846
- }
17847
- let path = `M ${points[0].x} ${points[0].y}`;
17848
- for (let i = 0; i < points.length - 1; i++) {
17849
- const p0 = points[Math.max(0, i - 1)];
17850
- const p1 = points[i];
17851
- const p2 = points[i + 1];
17852
- const p3 = points[Math.min(points.length - 1, i + 2)];
17853
- const tension = 0.5;
17854
- const cp1x = p1.x + (p2.x - p0.x) * tension / 6;
17855
- const cp1y = p1.y + (p2.y - p0.y) * tension / 6;
17856
- const cp2x = p2.x - (p3.x - p1.x) * tension / 6;
17857
- const cp2y = p2.y - (p3.y - p1.y) * tension / 6;
17858
- path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
17859
- }
17860
- return path;
17861
- }
17862
18050
  function AreaChart({
17863
18051
  series,
17864
18052
  width = 400,
@@ -17870,6 +18058,8 @@ function AreaChart({
17870
18058
  animated = true,
17871
18059
  stacked = false,
17872
18060
  curved = true,
18061
+ formatValue,
18062
+ emptyText = "No data",
17873
18063
  className = ""
17874
18064
  }) {
17875
18065
  const containerRef = (0, import_react28.useRef)(null);
@@ -17877,6 +18067,15 @@ function AreaChart({
17877
18067
  const chartWidth = width - padding.left - padding.right;
17878
18068
  const chartHeight = height - padding.top - padding.bottom;
17879
18069
  const [hoveredPoint, setHoveredPoint] = (0, import_react28.useState)(null);
18070
+ const [hiddenSeries, setHiddenSeries] = (0, import_react28.useState)(/* @__PURE__ */ new Set());
18071
+ const toggleSeries = (0, import_react28.useCallback)((name) => {
18072
+ setHiddenSeries((prev) => {
18073
+ const next = new Set(prev);
18074
+ if (next.has(name)) next.delete(name);
18075
+ else next.add(name);
18076
+ return next;
18077
+ });
18078
+ }, []);
17880
18079
  const { processedSeries, gridLines, maxValue, labels } = (0, import_react28.useMemo)(() => {
17881
18080
  if (!series.length || !series[0]?.data?.length) {
17882
18081
  return { processedSeries: [], gridLines: [], maxValue: 0, labels: [] };
@@ -17949,14 +18148,14 @@ function AreaChart({
17949
18148
  /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("svg", { width, height, className: "overflow-visible", style: { fontFamily: "inherit" }, children: [
17950
18149
  showGrid && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("g", { className: "text-muted-foreground/20", children: gridLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("g", { children: [
17951
18150
  /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("line", { x1: padding.left, y1: line.y, x2: width - padding.right, y2: line.y, stroke: "currentColor", strokeDasharray: "4 4" }),
17952
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: line.value.toFixed(0) })
18151
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("text", { x: padding.left - 8, y: line.y + 4, textAnchor: "end", fontSize: "10", className: "text-muted-foreground", fill: "currentColor", children: formatValue ? formatValue(line.value) : line.value.toFixed(0) })
17953
18152
  ] }, i)) }),
17954
18153
  [...processedSeries].reverse().map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
17955
18154
  "path",
17956
18155
  {
17957
18156
  d: s.areaPath,
17958
18157
  fill: s.color,
17959
- fillOpacity: s.fillOpacity ?? 0.3,
18158
+ fillOpacity: hiddenSeries.has(s.name) ? 0 : s.fillOpacity ?? 0.3,
17960
18159
  className: "transition-all duration-300",
17961
18160
  style: animated ? {
17962
18161
  opacity: 0,
@@ -17974,6 +18173,8 @@ function AreaChart({
17974
18173
  strokeWidth: 2,
17975
18174
  strokeLinecap: "round",
17976
18175
  strokeLinejoin: "round",
18176
+ className: "transition-opacity duration-300",
18177
+ opacity: hiddenSeries.has(s.name) ? 0 : 1,
17977
18178
  style: animated ? {
17978
18179
  strokeDasharray: s.lineLength,
17979
18180
  strokeDashoffset: s.lineLength,
@@ -17983,7 +18184,7 @@ function AreaChart({
17983
18184
  `line-${i}`
17984
18185
  )),
17985
18186
  showDots && processedSeries.map(
17986
- (s, seriesIdx) => s.points.map((point, i) => /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
18187
+ (s, seriesIdx) => !hiddenSeries.has(s.name) && s.points.map((point, i) => /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
17987
18188
  "g",
17988
18189
  {
17989
18190
  onMouseEnter: () => {
@@ -18067,11 +18268,18 @@ function AreaChart({
18067
18268
  showLegend && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "flex items-center justify-center gap-6", children: series.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
18068
18269
  "div",
18069
18270
  {
18070
- className: "flex items-center gap-2 text-sm",
18271
+ className: "flex items-center gap-2 text-sm cursor-pointer select-none",
18071
18272
  style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.1 + 0.5}s forwards` } : void 0,
18273
+ onClick: () => toggleSeries(s.name),
18072
18274
  children: [
18073
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "w-3 h-3 rounded-md", style: { backgroundColor: s.color } }),
18074
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: "text-muted-foreground", children: s.name })
18275
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
18276
+ "div",
18277
+ {
18278
+ className: "w-3 h-3 rounded-md transition-opacity",
18279
+ style: { backgroundColor: s.color, opacity: hiddenSeries.has(s.name) ? 0.3 : 1 }
18280
+ }
18281
+ ),
18282
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: hiddenSeries.has(s.name) ? "text-muted-foreground/40 line-through" : "text-muted-foreground", children: s.name })
18075
18283
  ]
18076
18284
  },
18077
18285
  i
@@ -18093,26 +18301,6 @@ function AreaChart({
18093
18301
  // ../../components/ui/Sparkline.tsx
18094
18302
  var import_react29 = require("react");
18095
18303
  var import_jsx_runtime60 = require("react/jsx-runtime");
18096
- function getCatmullRomSpline2(points) {
18097
- if (points.length < 2) return "";
18098
- if (points.length === 2) {
18099
- return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y}`;
18100
- }
18101
- let path = `M ${points[0].x} ${points[0].y}`;
18102
- for (let i = 0; i < points.length - 1; i++) {
18103
- const p0 = points[Math.max(0, i - 1)];
18104
- const p1 = points[i];
18105
- const p2 = points[i + 1];
18106
- const p3 = points[Math.min(points.length - 1, i + 2)];
18107
- const tension = 0.5;
18108
- const cp1x = p1.x + (p2.x - p0.x) * tension / 6;
18109
- const cp1y = p1.y + (p2.y - p0.y) * tension / 6;
18110
- const cp2x = p2.x - (p3.x - p1.x) * tension / 6;
18111
- const cp2y = p2.y - (p3.y - p1.y) * tension / 6;
18112
- path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
18113
- }
18114
- return path;
18115
- }
18116
18304
  function Sparkline({
18117
18305
  data,
18118
18306
  width = 100,
@@ -18143,7 +18331,7 @@ function Sparkline({
18143
18331
  y: padding + chartHeight - (value - min) / range * chartHeight,
18144
18332
  value
18145
18333
  }));
18146
- const line = curved ? getCatmullRomSpline2(pts) : `M ${pts.map((p) => `${p.x} ${p.y}`).join(" L ")}`;
18334
+ const line = curved ? getCatmullRomSpline(pts) : `M ${pts.map((p) => `${p.x} ${p.y}`).join(" L ")}`;
18147
18335
  const area = `${line} L ${padding + chartWidth} ${padding + chartHeight} L ${padding} ${padding + chartHeight} Z`;
18148
18336
  const length = pts.reduce((acc, p, i) => {
18149
18337
  if (i === 0) return 0;
@@ -18244,12 +18432,22 @@ function RadarChart({
18244
18432
  showLegend = true,
18245
18433
  showValues = false,
18246
18434
  animated = true,
18435
+ formatValue,
18247
18436
  className = ""
18248
18437
  }) {
18249
18438
  const containerRef = (0, import_react30.useRef)(null);
18250
18439
  const center = size / 2;
18251
18440
  const radius = size / 2 - 40;
18252
18441
  const [hoveredPoint, setHoveredPoint] = (0, import_react30.useState)(null);
18442
+ const [hiddenSeries, setHiddenSeries] = (0, import_react30.useState)(/* @__PURE__ */ new Set());
18443
+ const toggleSeries = (0, import_react30.useCallback)((name) => {
18444
+ setHiddenSeries((prev) => {
18445
+ const next = new Set(prev);
18446
+ if (next.has(name)) next.delete(name);
18447
+ else next.add(name);
18448
+ return next;
18449
+ });
18450
+ }, []);
18253
18451
  const { axes, processedSeries, levelPaths } = (0, import_react30.useMemo)(() => {
18254
18452
  if (!series.length || !series[0]?.data?.length) {
18255
18453
  return { axes: [], processedSeries: [], levelPaths: [] };
@@ -18304,81 +18502,83 @@ function RadarChart({
18304
18502
  /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("svg", { width: size, height: size, className: "overflow-visible", style: { fontFamily: "inherit" }, children: [
18305
18503
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("g", { className: "text-muted-foreground/20", children: levelPaths.map((level, i) => /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("path", { d: level.path, fill: "none", stroke: "currentColor", strokeWidth: 1 }, i)) }),
18306
18504
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("g", { className: "text-muted-foreground/30", children: axes.map((axis, i) => /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("line", { x1: center, y1: center, x2: axis.x, y2: axis.y, stroke: "currentColor", strokeWidth: 1 }, i)) }),
18307
- processedSeries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("g", { children: [
18308
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18309
- "path",
18310
- {
18311
- d: s.path,
18312
- fill: s.color,
18313
- fillOpacity: s.fillOpacity ?? 0.2,
18314
- stroke: s.color,
18315
- strokeWidth: 2,
18316
- strokeLinejoin: "round",
18317
- className: "transition-all duration-300",
18318
- style: animated ? {
18319
- opacity: 0,
18320
- transform: "scale(0)",
18321
- transformOrigin: `${center}px ${center}px`,
18322
- animation: `radarPop 0.5s ease-out ${i * 0.15}s forwards`
18323
- } : void 0
18324
- }
18325
- ),
18326
- s.points.map((point, j) => /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(
18327
- "g",
18328
- {
18329
- onMouseEnter: () => {
18330
- const items = processedSeries.map((ps) => ({
18331
- label: ps.name,
18332
- value: ps.points[j]?.value ?? 0,
18333
- color: ps.color
18334
- }));
18335
- setHoveredPoint({
18336
- x: point.x,
18337
- y: point.y,
18338
- axis: series[0]?.data[j]?.axis ?? "",
18339
- items
18340
- });
18505
+ processedSeries.map(
18506
+ (s, i) => !hiddenSeries.has(s.name) && /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("g", { children: [
18507
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18508
+ "path",
18509
+ {
18510
+ d: s.path,
18511
+ fill: s.color,
18512
+ fillOpacity: s.fillOpacity ?? 0.2,
18513
+ stroke: s.color,
18514
+ strokeWidth: 2,
18515
+ strokeLinejoin: "round",
18516
+ className: "transition-all duration-300",
18517
+ style: animated ? {
18518
+ opacity: 0,
18519
+ transform: "scale(0)",
18520
+ transformOrigin: `${center}px ${center}px`,
18521
+ animation: `radarPop 0.5s ease-out ${i * 0.15}s forwards`
18522
+ } : void 0
18523
+ }
18524
+ ),
18525
+ s.points.map((point, j) => /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(
18526
+ "g",
18527
+ {
18528
+ onMouseEnter: () => {
18529
+ const items = processedSeries.map((ps) => ({
18530
+ label: ps.name,
18531
+ value: ps.points[j]?.value ?? 0,
18532
+ color: ps.color
18533
+ }));
18534
+ setHoveredPoint({
18535
+ x: point.x,
18536
+ y: point.y,
18537
+ axis: series[0]?.data[j]?.axis ?? "",
18538
+ items
18539
+ });
18540
+ },
18541
+ onMouseLeave: () => setHoveredPoint(null),
18542
+ className: "cursor-pointer",
18543
+ children: [
18544
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18545
+ "circle",
18546
+ {
18547
+ cx: point.x,
18548
+ cy: point.y,
18549
+ r: hoveredPoint?.axis === series[0]?.data[j]?.axis ? 6 : 4,
18550
+ fill: s.color,
18551
+ className: "transition-all duration-150",
18552
+ style: animated ? {
18553
+ opacity: 0,
18554
+ transform: "scale(0)",
18555
+ transformOrigin: `${point.x}px ${point.y}px`,
18556
+ animation: `dotPop 0.3s ease-out ${i * 0.15 + j * 0.05 + 0.3}s forwards`
18557
+ } : void 0
18558
+ }
18559
+ ),
18560
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("circle", { cx: point.x, cy: point.y, r: 12, fill: "transparent" })
18561
+ ]
18341
18562
  },
18342
- onMouseLeave: () => setHoveredPoint(null),
18343
- className: "cursor-pointer",
18344
- children: [
18345
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18346
- "circle",
18347
- {
18348
- cx: point.x,
18349
- cy: point.y,
18350
- r: hoveredPoint?.axis === series[0]?.data[j]?.axis ? 6 : 4,
18351
- fill: s.color,
18352
- className: "transition-all duration-150",
18353
- style: animated ? {
18354
- opacity: 0,
18355
- transform: "scale(0)",
18356
- transformOrigin: `${point.x}px ${point.y}px`,
18357
- animation: `dotPop 0.3s ease-out ${i * 0.15 + j * 0.05 + 0.3}s forwards`
18358
- } : void 0
18359
- }
18360
- ),
18361
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("circle", { cx: point.x, cy: point.y, r: 12, fill: "transparent" })
18362
- ]
18363
- },
18364
- j
18365
- )),
18366
- showValues && s.points.map((point, j) => /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18367
- "text",
18368
- {
18369
- x: point.x,
18370
- y: point.y - 10,
18371
- textAnchor: "middle",
18372
- fontSize: "10",
18373
- fontWeight: "500",
18374
- className: "text-foreground",
18375
- fill: "currentColor",
18376
- style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.15 + 0.5}s forwards` } : void 0,
18377
- children: point.value
18378
- },
18379
- `val-${j}`
18380
- ))
18381
- ] }, i)),
18563
+ j
18564
+ )),
18565
+ showValues && s.points.map((point, j) => /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18566
+ "text",
18567
+ {
18568
+ x: point.x,
18569
+ y: point.y - 10,
18570
+ textAnchor: "middle",
18571
+ fontSize: "10",
18572
+ fontWeight: "500",
18573
+ className: "text-foreground",
18574
+ fill: "currentColor",
18575
+ style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.15 + 0.5}s forwards` } : void 0,
18576
+ children: point.value
18577
+ },
18578
+ `val-${j}`
18579
+ ))
18580
+ ] }, i)
18581
+ ),
18382
18582
  showLabels && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("g", { className: "text-muted-foreground", children: axes.map((axis, i) => /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("text", { x: axis.labelX, y: axis.labelY, textAnchor: "middle", dominantBaseline: "middle", fontSize: "11", fill: "currentColor", children: axis.label }, i)) }),
18383
18583
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("style", { children: `
18384
18584
  @keyframes radarPop {
@@ -18410,11 +18610,18 @@ function RadarChart({
18410
18610
  showLegend && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: "flex items-center justify-center gap-6", children: series.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(
18411
18611
  "div",
18412
18612
  {
18413
- className: "flex items-center gap-2 text-sm",
18613
+ className: "flex items-center gap-2 text-sm cursor-pointer select-none",
18414
18614
  style: animated ? { opacity: 0, animation: `fadeIn 0.3s ease-out ${i * 0.1 + 0.5}s forwards` } : void 0,
18615
+ onClick: () => toggleSeries(s.name),
18415
18616
  children: [
18416
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: "w-3 h-3 rounded-md", style: { backgroundColor: s.color } }),
18417
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("span", { className: "text-muted-foreground", children: s.name })
18617
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
18618
+ "div",
18619
+ {
18620
+ className: "w-3 h-3 rounded-md transition-opacity",
18621
+ style: { backgroundColor: s.color, opacity: hiddenSeries.has(s.name) ? 0.3 : 1 }
18622
+ }
18623
+ ),
18624
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("span", { className: hiddenSeries.has(s.name) ? "text-muted-foreground/40 line-through" : "text-muted-foreground", children: s.name })
18418
18625
  ]
18419
18626
  },
18420
18627
  i
@@ -18450,11 +18657,13 @@ function GaugeChart({
18450
18657
  animated = true,
18451
18658
  startAngle = -135,
18452
18659
  endAngle = 135,
18660
+ zones,
18661
+ formatValue,
18453
18662
  className = ""
18454
18663
  }) {
18455
18664
  const center = size / 2;
18456
18665
  const radius = center - thickness / 2 - 10;
18457
- const { backgroundPath, valuePath, percentage, needleAngle } = (0, import_react31.useMemo)(() => {
18666
+ const { backgroundPath, valuePath, percentage, needleAngle, zonePaths } = (0, import_react31.useMemo)(() => {
18458
18667
  const normalizedValue = Math.min(Math.max(value, min), max);
18459
18668
  const pct = (normalizedValue - min) / (max - min);
18460
18669
  const totalAngle = endAngle - startAngle;
@@ -18472,13 +18681,19 @@ function GaugeChart({
18472
18681
  const largeArc = Math.abs(end - start) > 180 ? 1 : 0;
18473
18682
  return `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 1 ${endPoint.x} ${endPoint.y}`;
18474
18683
  };
18684
+ const zonePaths2 = (zones ?? []).map((zone) => {
18685
+ const zoneStart = startAngle + (Math.max(zone.min, min) - min) / (max - min) * totalAngle;
18686
+ const zoneEnd = startAngle + (Math.min(zone.max, max) - min) / (max - min) * totalAngle;
18687
+ return { path: createArc(zoneStart, zoneEnd), color: zone.color };
18688
+ });
18475
18689
  return {
18476
18690
  backgroundPath: createArc(startAngle, endAngle),
18477
18691
  valuePath: createArc(startAngle, currentAngle),
18478
18692
  percentage: pct,
18479
- needleAngle: currentAngle
18693
+ needleAngle: currentAngle,
18694
+ zonePaths: zonePaths2
18480
18695
  };
18481
- }, [value, min, max, center, radius, startAngle, endAngle]);
18696
+ }, [value, min, max, center, radius, startAngle, endAngle, zones]);
18482
18697
  const needleLength = radius - 10;
18483
18698
  const needleAngleRad = needleAngle * Math.PI / 180;
18484
18699
  const needleX = center + needleLength * Math.cos(needleAngleRad);
@@ -18495,6 +18710,7 @@ function GaugeChart({
18495
18710
  className: !backgroundColor ? "text-muted-foreground/20" : ""
18496
18711
  }
18497
18712
  ),
18713
+ zonePaths.map((zone, i) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("path", { d: zone.path, fill: "none", stroke: zone.color, strokeWidth: thickness, strokeLinecap: "round", opacity: 0.35 }, `zone-${i}`)),
18498
18714
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
18499
18715
  "path",
18500
18716
  {
@@ -18565,7 +18781,7 @@ function GaugeChart({
18565
18781
  className: "text-foreground",
18566
18782
  fill: "currentColor",
18567
18783
  style: animated ? { opacity: 0, animation: "fadeIn 0.5s ease-out 0.5s forwards" } : void 0,
18568
- children: value
18784
+ children: formatValue ? formatValue(value) : value
18569
18785
  }
18570
18786
  ),
18571
18787
  label && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(