@marcoschwartz/lite-ui 0.20.0 → 0.22.0

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.js CHANGED
@@ -50,10 +50,14 @@ __export(index_exports, {
50
50
  BookIcon: () => BookIcon,
51
51
  BrainIcon: () => BrainIcon,
52
52
  Button: () => Button,
53
+ CHART_DEFAULTS: () => CHART_DEFAULTS,
53
54
  Calendar: () => Calendar,
55
+ CalendarHeatmap: () => CalendarHeatmap,
54
56
  CalendarIcon: () => CalendarIcon,
55
57
  CameraIcon: () => CameraIcon,
56
58
  Card: () => Card,
59
+ CartesianGrid: () => CartesianGrid,
60
+ ChartTooltip: () => ChartTooltip,
57
61
  ChatIcon: () => ChatIcon,
58
62
  CheckCircleIcon: () => CheckCircleIcon,
59
63
  CheckIcon: () => CheckIcon,
@@ -86,10 +90,12 @@ __export(index_exports, {
86
90
  GlobeIcon: () => GlobeIcon,
87
91
  GoogleIcon: () => GoogleIcon,
88
92
  HeartIcon: () => HeartIcon,
93
+ Heatmap: () => Heatmap,
89
94
  HomeIcon: () => HomeIcon,
90
95
  ImageIcon: () => ImageIcon,
91
96
  InfoCircleIcon: () => InfoCircleIcon,
92
97
  KeyIcon: () => KeyIcon,
98
+ Legend: () => Legend,
93
99
  LineChart: () => LineChart,
94
100
  LinkedInIcon: () => LinkedInIcon,
95
101
  LockIcon: () => LockIcon,
@@ -106,9 +112,13 @@ __export(index_exports, {
106
112
  PlusIcon: () => PlusIcon,
107
113
  ProgressBar: () => ProgressBar,
108
114
  Radio: () => Radio,
115
+ ReferenceArea: () => ReferenceArea,
116
+ ReferenceLine: () => ReferenceLine,
109
117
  RefreshIcon: () => RefreshIcon,
118
+ ResponsiveContainer: () => ResponsiveContainer,
110
119
  RichTextEditor: () => RichTextEditor,
111
120
  SaveIcon: () => SaveIcon,
121
+ ScatterChart: () => ScatterChart,
112
122
  SearchIcon: () => SearchIcon,
113
123
  Select: () => Select,
114
124
  SettingsIcon: () => SettingsIcon,
@@ -147,7 +157,8 @@ __export(index_exports, {
147
157
  toast: () => toast,
148
158
  useSidebar: () => useSidebar,
149
159
  useTheme: () => useTheme,
150
- useToast: () => useToast
160
+ useToast: () => useToast,
161
+ useTooltip: () => useTooltip
151
162
  });
152
163
  module.exports = __toCommonJS(index_exports);
153
164
 
@@ -2358,7 +2369,7 @@ var DatePicker = ({
2358
2369
  onChange?.(today);
2359
2370
  setIsOpen(false);
2360
2371
  };
2361
- const formatDate = (date) => {
2372
+ const formatDate2 = (date) => {
2362
2373
  if (!date) return "";
2363
2374
  return date.toLocaleDateString("en-US", {
2364
2375
  month: "short",
@@ -2451,7 +2462,7 @@ var DatePicker = ({
2451
2462
  onClick: () => !disabled && setIsOpen(!isOpen),
2452
2463
  className: `${baseStyles} ${errorStyles} ${disabledStyles} flex items-center justify-between`.trim(),
2453
2464
  children: [
2454
- /* @__PURE__ */ (0, import_jsx_runtime92.jsx)("span", { className: !value ? "text-gray-500 dark:text-gray-400" : "", children: value ? formatDate(value) : placeholder }),
2465
+ /* @__PURE__ */ (0, import_jsx_runtime92.jsx)("span", { className: !value ? "text-gray-500 dark:text-gray-400" : "", children: value ? formatDate2(value) : placeholder }),
2455
2466
  /* @__PURE__ */ (0, import_jsx_runtime92.jsx)("svg", { className: "w-5 h-5 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime92.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) })
2456
2467
  ]
2457
2468
  }
@@ -5382,367 +5393,2254 @@ var VideoPlayer = ({
5382
5393
  ] });
5383
5394
  };
5384
5395
 
5385
- // src/components/LineChart.tsx
5396
+ // src/charts/LineChart.tsx
5397
+ var import_react28 = __toESM(require("react"));
5398
+
5399
+ // src/charts/constants.ts
5400
+ var CHART_DEFAULTS = {
5401
+ viewBox: {
5402
+ width: 1e3,
5403
+ height: 600,
5404
+ pieSize: 600
5405
+ },
5406
+ padding: {
5407
+ top: 40,
5408
+ right: 40,
5409
+ bottom: 60,
5410
+ left: 60
5411
+ },
5412
+ fontSize: {
5413
+ gridLabel: 12,
5414
+ axisLabel: 14,
5415
+ title: 16,
5416
+ tooltip: 12
5417
+ },
5418
+ colors: [
5419
+ "#3b82f6",
5420
+ // blue-500
5421
+ "#10b981",
5422
+ // emerald-500
5423
+ "#f59e0b",
5424
+ // amber-500
5425
+ "#ef4444",
5426
+ // red-500
5427
+ "#8b5cf6",
5428
+ // violet-500
5429
+ "#ec4899",
5430
+ // pink-500
5431
+ "#06b6d4",
5432
+ // cyan-500
5433
+ "#f97316",
5434
+ // orange-500
5435
+ "#84cc16",
5436
+ // lime-500
5437
+ "#6366f1"
5438
+ // indigo-500
5439
+ ],
5440
+ animation: {
5441
+ duration: 300,
5442
+ easing: "ease-out"
5443
+ },
5444
+ grid: {
5445
+ strokeDasharray: "3 3",
5446
+ strokeWidth: 1
5447
+ },
5448
+ line: {
5449
+ strokeWidth: 2,
5450
+ dotRadius: 4,
5451
+ activeDotRadius: 6
5452
+ },
5453
+ bar: {
5454
+ radius: 4,
5455
+ gap: 0.1
5456
+ // 10% gap between bars
5457
+ },
5458
+ tooltip: {
5459
+ offset: 10
5460
+ }
5461
+ };
5462
+
5463
+ // src/charts/utils/scales.ts
5464
+ function scaleLinear({ domain, range, nice = false, clamp = false }) {
5465
+ let [d0, d1] = domain;
5466
+ const [r0, r1] = range;
5467
+ if (nice) {
5468
+ const span = d1 - d0;
5469
+ const step = Math.pow(10, Math.floor(Math.log10(span))) / 2;
5470
+ d0 = Math.floor(d0 / step) * step;
5471
+ d1 = Math.ceil(d1 / step) * step;
5472
+ }
5473
+ const domainSpan = d1 - d0;
5474
+ const rangeSpan = r1 - r0;
5475
+ return (value) => {
5476
+ if (domainSpan === 0) return (r0 + r1) / 2;
5477
+ let normalized = (value - d0) / domainSpan;
5478
+ if (clamp) {
5479
+ normalized = Math.max(0, Math.min(1, normalized));
5480
+ }
5481
+ return r0 + normalized * rangeSpan;
5482
+ };
5483
+ }
5484
+ function scaleBand({ domain, range, padding = 0.1, paddingInner, paddingOuter }) {
5485
+ const [r0, r1] = range;
5486
+ const n = domain.length;
5487
+ if (n === 0) {
5488
+ return {
5489
+ scale: (_value) => r0,
5490
+ bandwidth: () => 0,
5491
+ step: () => 0
5492
+ };
5493
+ }
5494
+ const inner = paddingInner ?? padding;
5495
+ const outer = paddingOuter ?? padding;
5496
+ const totalRange = r1 - r0;
5497
+ const step = totalRange / (n + outer * 2 - inner);
5498
+ const bandwidth = step * (1 - inner);
5499
+ const start = r0 + step * outer;
5500
+ const indexMap = new Map(domain.map((d, i) => [d, i]));
5501
+ return {
5502
+ scale: (value) => {
5503
+ const index = indexMap.get(value);
5504
+ if (index === void 0) return r0;
5505
+ return start + index * step;
5506
+ },
5507
+ bandwidth: () => bandwidth,
5508
+ step: () => step
5509
+ };
5510
+ }
5511
+ function getTicks(domain, count = 5) {
5512
+ const [min, max] = domain;
5513
+ const span = max - min;
5514
+ if (span === 0) return [min];
5515
+ const step = span / (count - 1);
5516
+ const ticks = [];
5517
+ for (let i = 0; i < count; i++) {
5518
+ ticks.push(min + step * i);
5519
+ }
5520
+ return ticks;
5521
+ }
5522
+ function calculateDomain(values, options = {}) {
5523
+ const { includeZero = true, padding = 0.1 } = options;
5524
+ if (values.length === 0) return [0, 1];
5525
+ let min = Math.min(...values);
5526
+ let max = Math.max(...values);
5527
+ if (includeZero) {
5528
+ min = Math.min(0, min);
5529
+ }
5530
+ const span = max - min;
5531
+ const pad = span * padding;
5532
+ return [min - (includeZero && min >= 0 ? 0 : pad), max + pad];
5533
+ }
5534
+
5535
+ // src/charts/utils/paths.ts
5536
+ function generateLinePath(points) {
5537
+ if (points.length === 0) return "";
5538
+ if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;
5539
+ return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
5540
+ }
5541
+ function generateMonotonePath(points) {
5542
+ if (points.length === 0) return "";
5543
+ if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;
5544
+ if (points.length === 2) return generateLinePath(points);
5545
+ const tangents = [];
5546
+ for (let i = 0; i < points.length; i++) {
5547
+ if (i === 0) {
5548
+ tangents.push((points[1].y - points[0].y) / (points[1].x - points[0].x));
5549
+ } else if (i === points.length - 1) {
5550
+ tangents.push((points[i].y - points[i - 1].y) / (points[i].x - points[i - 1].x));
5551
+ } else {
5552
+ const d0 = (points[i].y - points[i - 1].y) / (points[i].x - points[i - 1].x);
5553
+ const d1 = (points[i + 1].y - points[i].y) / (points[i + 1].x - points[i].x);
5554
+ tangents.push((d0 + d1) / 2);
5555
+ }
5556
+ }
5557
+ let path = `M ${points[0].x} ${points[0].y}`;
5558
+ for (let i = 0; i < points.length - 1; i++) {
5559
+ const p0 = points[i];
5560
+ const p1 = points[i + 1];
5561
+ const dx = (p1.x - p0.x) / 3;
5562
+ const cp1x = p0.x + dx;
5563
+ const cp1y = p0.y + tangents[i] * dx;
5564
+ const cp2x = p1.x - dx;
5565
+ const cp2y = p1.y - tangents[i + 1] * dx;
5566
+ path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p1.x} ${p1.y}`;
5567
+ }
5568
+ return path;
5569
+ }
5570
+ function generateStepPath(points, position = "after") {
5571
+ if (points.length === 0) return "";
5572
+ if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;
5573
+ let path = `M ${points[0].x} ${points[0].y}`;
5574
+ for (let i = 1; i < points.length; i++) {
5575
+ const prev = points[i - 1];
5576
+ const curr = points[i];
5577
+ if (position === "before") {
5578
+ path += ` V ${curr.y} H ${curr.x}`;
5579
+ } else if (position === "after") {
5580
+ path += ` H ${curr.x} V ${curr.y}`;
5581
+ } else {
5582
+ const midX = (prev.x + curr.x) / 2;
5583
+ path += ` H ${midX} V ${curr.y} H ${curr.x}`;
5584
+ }
5585
+ }
5586
+ return path;
5587
+ }
5588
+ function generateAreaPath(points, baseline, curved = false) {
5589
+ if (points.length === 0) return "";
5590
+ const linePath = curved ? generateMonotonePath(points) : generateLinePath(points);
5591
+ const lastPoint = points[points.length - 1];
5592
+ const firstPoint = points[0];
5593
+ return `${linePath} L ${lastPoint.x} ${baseline} L ${firstPoint.x} ${baseline} Z`;
5594
+ }
5595
+ function generateStackedAreaPath(topPoints, bottomPoints, curved = false) {
5596
+ if (topPoints.length === 0) return "";
5597
+ const topPath = curved ? generateMonotonePath(topPoints) : generateLinePath(topPoints);
5598
+ const reversedBottom = [...bottomPoints].reverse();
5599
+ const bottomPath = reversedBottom.map((p, i) => `${i === 0 ? "L" : "L"} ${p.x} ${p.y}`).join(" ");
5600
+ return `${topPath} ${bottomPath} Z`;
5601
+ }
5602
+ function generateArcPath(centerX, centerY, radius, startAngle, endAngle, innerRadius = 0) {
5603
+ const startRad = (startAngle - 90) * Math.PI / 180;
5604
+ const endRad = (endAngle - 90) * Math.PI / 180;
5605
+ const x1 = centerX + radius * Math.cos(startRad);
5606
+ const y1 = centerY + radius * Math.sin(startRad);
5607
+ const x2 = centerX + radius * Math.cos(endRad);
5608
+ const y2 = centerY + radius * Math.sin(endRad);
5609
+ const largeArc = endAngle - startAngle > 180 ? 1 : 0;
5610
+ if (innerRadius === 0) {
5611
+ return [
5612
+ `M ${centerX} ${centerY}`,
5613
+ `L ${x1} ${y1}`,
5614
+ `A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2}`,
5615
+ "Z"
5616
+ ].join(" ");
5617
+ } else {
5618
+ const ix1 = centerX + innerRadius * Math.cos(startRad);
5619
+ const iy1 = centerY + innerRadius * Math.sin(startRad);
5620
+ const ix2 = centerX + innerRadius * Math.cos(endRad);
5621
+ const iy2 = centerY + innerRadius * Math.sin(endRad);
5622
+ return [
5623
+ `M ${x1} ${y1}`,
5624
+ `A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2}`,
5625
+ `L ${ix2} ${iy2}`,
5626
+ `A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${ix1} ${iy1}`,
5627
+ "Z"
5628
+ ].join(" ");
5629
+ }
5630
+ }
5631
+
5632
+ // src/charts/utils/format.ts
5633
+ function formatNumber(value, options = {}) {
5634
+ const { precision = 2, compact = false, prefix = "", suffix = "" } = options;
5635
+ if (compact) {
5636
+ const absValue = Math.abs(value);
5637
+ if (absValue >= 1e9) {
5638
+ return `${prefix}${(value / 1e9).toFixed(1)}B${suffix}`;
5639
+ }
5640
+ if (absValue >= 1e6) {
5641
+ return `${prefix}${(value / 1e6).toFixed(1)}M${suffix}`;
5642
+ }
5643
+ if (absValue >= 1e3) {
5644
+ return `${prefix}${(value / 1e3).toFixed(1)}K${suffix}`;
5645
+ }
5646
+ }
5647
+ const formatted = value.toFixed(precision);
5648
+ const trimmed = parseFloat(formatted).toString();
5649
+ return `${prefix}${trimmed}${suffix}`;
5650
+ }
5651
+ function formatPercent(value, options = {}) {
5652
+ const { precision = 1, multiply = false } = options;
5653
+ const percent = multiply ? value * 100 : value;
5654
+ return `${percent.toFixed(precision)}%`;
5655
+ }
5656
+ function formatDate(date, rangeMs) {
5657
+ const minute = 60 * 1e3;
5658
+ const hour = 60 * minute;
5659
+ const day = 24 * hour;
5660
+ const month = 30 * day;
5661
+ const year = 365 * day;
5662
+ if (!rangeMs) {
5663
+ return date.toLocaleDateString("en-US", {
5664
+ month: "short",
5665
+ day: "numeric",
5666
+ year: "numeric"
5667
+ });
5668
+ }
5669
+ if (rangeMs < hour) {
5670
+ return date.toLocaleTimeString("en-US", {
5671
+ hour: "2-digit",
5672
+ minute: "2-digit",
5673
+ second: "2-digit"
5674
+ });
5675
+ } else if (rangeMs < day) {
5676
+ return date.toLocaleTimeString("en-US", {
5677
+ hour: "2-digit",
5678
+ minute: "2-digit"
5679
+ });
5680
+ } else if (rangeMs < month) {
5681
+ return date.toLocaleDateString("en-US", {
5682
+ month: "short",
5683
+ day: "numeric",
5684
+ hour: "2-digit",
5685
+ minute: "2-digit"
5686
+ });
5687
+ } else if (rangeMs < year) {
5688
+ return date.toLocaleDateString("en-US", {
5689
+ month: "short",
5690
+ day: "numeric"
5691
+ });
5692
+ } else {
5693
+ return date.toLocaleDateString("en-US", {
5694
+ month: "short",
5695
+ year: "numeric"
5696
+ });
5697
+ }
5698
+ }
5699
+ function createTickFormatter(domain) {
5700
+ const [min, max] = domain;
5701
+ const range = max - min;
5702
+ let precision = 0;
5703
+ if (range < 1) {
5704
+ precision = 2;
5705
+ } else if (range < 10) {
5706
+ precision = 1;
5707
+ }
5708
+ const compact = Math.abs(max) >= 1e3 || Math.abs(min) >= 1e3;
5709
+ return (value) => formatNumber(value, { precision, compact });
5710
+ }
5711
+
5712
+ // src/charts/components/ChartTooltip.tsx
5386
5713
  var import_react26 = __toESM(require("react"));
5387
5714
  var import_jsx_runtime108 = require("react/jsx-runtime");
5388
- var defaultColors = [
5389
- "#3b82f6",
5390
- // blue-500
5391
- "#10b981",
5392
- // green-500
5393
- "#f59e0b",
5394
- // amber-500
5395
- "#ef4444",
5396
- // red-500
5397
- "#8b5cf6",
5398
- // violet-500
5399
- "#ec4899"
5400
- // pink-500
5401
- ];
5715
+ var DefaultTooltipContent = ({
5716
+ active,
5717
+ label,
5718
+ payload,
5719
+ formatter,
5720
+ labelFormatter
5721
+ }) => {
5722
+ if (!active || !payload || payload.length === 0) {
5723
+ return null;
5724
+ }
5725
+ const formattedLabel = labelFormatter ? labelFormatter(label ?? "") : String(label ?? "");
5726
+ return /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "bg-gray-900 dark:bg-gray-800 text-white rounded-lg shadow-xl border border-gray-700 overflow-hidden min-w-[120px]", children: [
5727
+ formattedLabel && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "px-3 py-2 bg-gray-800 dark:bg-gray-700 border-b border-gray-700 dark:border-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-sm font-medium text-gray-200", children: formattedLabel }) }),
5728
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "px-3 py-2 space-y-1", children: payload.map((item, index) => {
5729
+ const formattedValue = formatter ? formatter(item.value, item.name, item) : String(item.value);
5730
+ return /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "flex items-center justify-between gap-4", children: [
5731
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "flex items-center gap-2", children: [
5732
+ item.color && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5733
+ "span",
5734
+ {
5735
+ className: "w-2.5 h-2.5 rounded-full flex-shrink-0",
5736
+ style: { backgroundColor: item.color }
5737
+ }
5738
+ ),
5739
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-xs text-gray-300", children: item.name })
5740
+ ] }),
5741
+ /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-sm font-semibold text-white", children: formattedValue })
5742
+ ] }, `${item.name}-${index}`);
5743
+ }) })
5744
+ ] });
5745
+ };
5746
+ var ChartTooltip = ({
5747
+ active = false,
5748
+ label,
5749
+ payload,
5750
+ content,
5751
+ x = 0,
5752
+ y = 0,
5753
+ offset = 12,
5754
+ formatter,
5755
+ labelFormatter,
5756
+ className = "",
5757
+ containerBounds,
5758
+ animationDuration = 150
5759
+ }) => {
5760
+ const tooltipRef = (0, import_react26.useRef)(null);
5761
+ const [position, setPosition] = (0, import_react26.useState)({ x: 0, y: 0 });
5762
+ const [isVisible, setIsVisible] = (0, import_react26.useState)(false);
5763
+ (0, import_react26.useEffect)(() => {
5764
+ if (!active || !tooltipRef.current) {
5765
+ setIsVisible(false);
5766
+ return;
5767
+ }
5768
+ const tooltip = tooltipRef.current;
5769
+ const tooltipRect = tooltip.getBoundingClientRect();
5770
+ const tooltipWidth = tooltipRect.width || 150;
5771
+ const tooltipHeight = tooltipRect.height || 80;
5772
+ let newX = x + offset;
5773
+ let newY = y - tooltipHeight / 2;
5774
+ if (containerBounds && newX + tooltipWidth > containerBounds.width) {
5775
+ newX = x - tooltipWidth - offset;
5776
+ }
5777
+ if (newX < 0) {
5778
+ newX = offset;
5779
+ }
5780
+ if (newY < 0) {
5781
+ newY = offset;
5782
+ }
5783
+ if (containerBounds && newY + tooltipHeight > containerBounds.height) {
5784
+ newY = containerBounds.height - tooltipHeight - offset;
5785
+ }
5786
+ setPosition({ x: newX, y: newY });
5787
+ setIsVisible(true);
5788
+ }, [active, x, y, offset, containerBounds]);
5789
+ if (!active) {
5790
+ return null;
5791
+ }
5792
+ const tooltipContent = content ? import_react26.default.isValidElement(content) ? import_react26.default.cloneElement(content, {
5793
+ active,
5794
+ label,
5795
+ payload,
5796
+ formatter,
5797
+ labelFormatter
5798
+ }) : import_react26.default.createElement(content, {
5799
+ active,
5800
+ label,
5801
+ payload,
5802
+ formatter,
5803
+ labelFormatter
5804
+ }) : /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5805
+ DefaultTooltipContent,
5806
+ {
5807
+ active,
5808
+ label,
5809
+ payload,
5810
+ formatter,
5811
+ labelFormatter
5812
+ }
5813
+ );
5814
+ return /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5815
+ "div",
5816
+ {
5817
+ ref: tooltipRef,
5818
+ className: `absolute pointer-events-none z-50 ${className}`,
5819
+ style: {
5820
+ left: `${position.x}px`,
5821
+ top: `${position.y}px`,
5822
+ opacity: isVisible ? 1 : 0,
5823
+ transform: `translateZ(0)`,
5824
+ transition: animationDuration > 0 ? `opacity ${animationDuration}ms ease-out` : void 0
5825
+ },
5826
+ children: tooltipContent
5827
+ }
5828
+ );
5829
+ };
5830
+ function useTooltip() {
5831
+ const [tooltipData, setTooltipData] = (0, import_react26.useState)({
5832
+ active: false,
5833
+ x: 0,
5834
+ y: 0,
5835
+ label: void 0,
5836
+ payload: void 0
5837
+ });
5838
+ const showTooltip = import_react26.default.useCallback((data) => {
5839
+ setTooltipData({
5840
+ active: true,
5841
+ ...data
5842
+ });
5843
+ }, []);
5844
+ const hideTooltip = import_react26.default.useCallback(() => {
5845
+ setTooltipData((prev) => ({
5846
+ ...prev,
5847
+ active: false
5848
+ }));
5849
+ }, []);
5850
+ const updatePosition = import_react26.default.useCallback((x, y) => {
5851
+ setTooltipData((prev) => ({
5852
+ ...prev,
5853
+ x,
5854
+ y
5855
+ }));
5856
+ }, []);
5857
+ return {
5858
+ tooltipData,
5859
+ showTooltip,
5860
+ hideTooltip,
5861
+ updatePosition
5862
+ };
5863
+ }
5864
+
5865
+ // src/charts/components/Legend.tsx
5866
+ var import_jsx_runtime109 = require("react/jsx-runtime");
5867
+ var Legend = ({
5868
+ items = [],
5869
+ layout = "horizontal",
5870
+ align = "center",
5871
+ verticalAlign = "bottom",
5872
+ iconSize = 12,
5873
+ formatter,
5874
+ onClick,
5875
+ onMouseEnter,
5876
+ onMouseLeave,
5877
+ className = "",
5878
+ wrapperStyle
5879
+ }) => {
5880
+ const alignClass = {
5881
+ left: "justify-start",
5882
+ center: "justify-center",
5883
+ right: "justify-end"
5884
+ }[align];
5885
+ const layoutClass = layout === "horizontal" ? "flex-row flex-wrap" : "flex-col";
5886
+ const renderIcon = (item) => {
5887
+ const style = { backgroundColor: item.color };
5888
+ switch (item.type) {
5889
+ case "line":
5890
+ return /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5891
+ "div",
5892
+ {
5893
+ className: "flex-shrink-0",
5894
+ style: {
5895
+ width: iconSize,
5896
+ height: 2,
5897
+ backgroundColor: item.color
5898
+ }
5899
+ }
5900
+ );
5901
+ case "circle":
5902
+ return /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5903
+ "div",
5904
+ {
5905
+ className: "rounded-full flex-shrink-0",
5906
+ style: {
5907
+ width: iconSize,
5908
+ height: iconSize,
5909
+ ...style
5910
+ }
5911
+ }
5912
+ );
5913
+ case "rect":
5914
+ case "square":
5915
+ default:
5916
+ return /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5917
+ "div",
5918
+ {
5919
+ className: "rounded-sm flex-shrink-0",
5920
+ style: {
5921
+ width: iconSize,
5922
+ height: iconSize,
5923
+ ...style
5924
+ }
5925
+ }
5926
+ );
5927
+ }
5928
+ };
5929
+ return /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5930
+ "div",
5931
+ {
5932
+ className: `flex gap-4 px-4 py-2 ${layoutClass} ${alignClass} ${className}`,
5933
+ style: wrapperStyle,
5934
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
5935
+ "button",
5936
+ {
5937
+ type: "button",
5938
+ className: `
5939
+ flex items-center gap-2 text-sm transition-opacity
5940
+ ${onClick ? "cursor-pointer hover:opacity-80" : "cursor-default"}
5941
+ ${item.inactive ? "opacity-40" : "opacity-100"}
5942
+ `,
5943
+ onClick: () => onClick?.(item, index),
5944
+ onMouseEnter: () => onMouseEnter?.(item, index),
5945
+ onMouseLeave,
5946
+ children: [
5947
+ renderIcon(item),
5948
+ /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: formatter ? formatter(item.name, item, index) : item.name })
5949
+ ]
5950
+ },
5951
+ `${item.name}-${index}`
5952
+ ))
5953
+ }
5954
+ );
5955
+ };
5956
+
5957
+ // src/charts/components/ReferenceLine.tsx
5958
+ var import_jsx_runtime110 = require("react/jsx-runtime");
5959
+ var ReferenceLine = ({
5960
+ x,
5961
+ y,
5962
+ stroke = "#94a3b8",
5963
+ strokeWidth = 1,
5964
+ strokeDasharray = "4 4",
5965
+ label,
5966
+ labelPosition = "end",
5967
+ className = "",
5968
+ ifOverflow = "hidden",
5969
+ _chartDimensions,
5970
+ _scales
5971
+ }) => {
5972
+ if (!_chartDimensions || !_scales) {
5973
+ return null;
5974
+ }
5975
+ const { width, height } = _chartDimensions;
5976
+ const { xScale, yScale } = _scales;
5977
+ const isHorizontal = y !== void 0;
5978
+ const isVertical = x !== void 0;
5979
+ if (!isHorizontal && !isVertical) {
5980
+ return null;
5981
+ }
5982
+ let x1, y1, x2, y2;
5983
+ let labelX, labelY;
5984
+ let textAnchor = "middle";
5985
+ let dominantBaseline = "middle";
5986
+ if (isHorizontal && y !== void 0) {
5987
+ const scaledY = yScale(y);
5988
+ if (ifOverflow === "hidden" && (scaledY < 0 || scaledY > height)) {
5989
+ return null;
5990
+ }
5991
+ x1 = 0;
5992
+ y1 = scaledY;
5993
+ x2 = width;
5994
+ y2 = scaledY;
5995
+ switch (labelPosition) {
5996
+ case "start":
5997
+ labelX = 0;
5998
+ labelY = scaledY - 8;
5999
+ textAnchor = "start";
6000
+ break;
6001
+ case "middle":
6002
+ labelX = width / 2;
6003
+ labelY = scaledY - 8;
6004
+ break;
6005
+ case "end":
6006
+ labelX = width;
6007
+ labelY = scaledY - 8;
6008
+ textAnchor = "end";
6009
+ break;
6010
+ case "insideStart":
6011
+ labelX = 8;
6012
+ labelY = scaledY - 8;
6013
+ textAnchor = "start";
6014
+ break;
6015
+ case "insideEnd":
6016
+ labelX = width - 8;
6017
+ labelY = scaledY - 8;
6018
+ textAnchor = "end";
6019
+ break;
6020
+ default:
6021
+ labelX = width;
6022
+ labelY = scaledY - 8;
6023
+ textAnchor = "end";
6024
+ }
6025
+ } else if (isVertical && x !== void 0) {
6026
+ const scaledX = xScale(x);
6027
+ if (ifOverflow === "hidden" && (scaledX < 0 || scaledX > width)) {
6028
+ return null;
6029
+ }
6030
+ x1 = scaledX;
6031
+ y1 = 0;
6032
+ x2 = scaledX;
6033
+ y2 = height;
6034
+ switch (labelPosition) {
6035
+ case "start":
6036
+ labelX = scaledX;
6037
+ labelY = -8;
6038
+ dominantBaseline = "auto";
6039
+ break;
6040
+ case "middle":
6041
+ labelX = scaledX + 8;
6042
+ labelY = height / 2;
6043
+ textAnchor = "start";
6044
+ break;
6045
+ case "end":
6046
+ labelX = scaledX;
6047
+ labelY = height + 16;
6048
+ dominantBaseline = "hanging";
6049
+ break;
6050
+ case "insideStart":
6051
+ labelX = scaledX + 8;
6052
+ labelY = 16;
6053
+ textAnchor = "start";
6054
+ dominantBaseline = "hanging";
6055
+ break;
6056
+ case "insideEnd":
6057
+ labelX = scaledX + 8;
6058
+ labelY = height - 16;
6059
+ textAnchor = "start";
6060
+ dominantBaseline = "auto";
6061
+ break;
6062
+ default:
6063
+ labelX = scaledX;
6064
+ labelY = -8;
6065
+ dominantBaseline = "auto";
6066
+ }
6067
+ } else {
6068
+ return null;
6069
+ }
6070
+ return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { className: `reference-line ${className}`, children: [
6071
+ /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6072
+ "line",
6073
+ {
6074
+ x1,
6075
+ y1,
6076
+ x2,
6077
+ y2,
6078
+ stroke,
6079
+ strokeWidth,
6080
+ strokeDasharray
6081
+ }
6082
+ ),
6083
+ label && (typeof label === "string" ? /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6084
+ "text",
6085
+ {
6086
+ x: labelX,
6087
+ y: labelY,
6088
+ textAnchor,
6089
+ dominantBaseline,
6090
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6091
+ fontWeight: "500",
6092
+ className: "fill-gray-600 dark:fill-gray-400",
6093
+ children: label
6094
+ }
6095
+ ) : /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6096
+ "foreignObject",
6097
+ {
6098
+ x: labelX - 50,
6099
+ y: labelY - 12,
6100
+ width: 100,
6101
+ height: 24,
6102
+ children: label
6103
+ }
6104
+ ))
6105
+ ] });
6106
+ };
6107
+
6108
+ // src/charts/components/ReferenceArea.tsx
6109
+ var import_jsx_runtime111 = require("react/jsx-runtime");
6110
+ var ReferenceArea = ({
6111
+ x1,
6112
+ x2,
6113
+ y1,
6114
+ y2,
6115
+ fill = "#3b82f6",
6116
+ fillOpacity = 0.1,
6117
+ stroke,
6118
+ strokeWidth = 0,
6119
+ label,
6120
+ labelPosition = "center",
6121
+ className = "",
6122
+ ifOverflow = "hidden",
6123
+ _chartDimensions,
6124
+ _scales
6125
+ }) => {
6126
+ if (!_chartDimensions || !_scales) {
6127
+ return null;
6128
+ }
6129
+ const { width, height } = _chartDimensions;
6130
+ const { xScale, yScale } = _scales;
6131
+ let rectX, rectY, rectWidth, rectHeight;
6132
+ if (x1 !== void 0 && x2 !== void 0) {
6133
+ const scaledX1 = xScale(x1);
6134
+ const scaledX2 = xScale(x2);
6135
+ rectX = Math.min(scaledX1, scaledX2);
6136
+ rectWidth = Math.abs(scaledX2 - scaledX1);
6137
+ if (y1 !== void 0 && y2 !== void 0) {
6138
+ const scaledY1 = yScale(y1);
6139
+ const scaledY2 = yScale(y2);
6140
+ rectY = Math.min(scaledY1, scaledY2);
6141
+ rectHeight = Math.abs(scaledY2 - scaledY1);
6142
+ } else {
6143
+ rectY = 0;
6144
+ rectHeight = height;
6145
+ }
6146
+ } else if (y1 !== void 0 && y2 !== void 0) {
6147
+ const scaledY1 = yScale(y1);
6148
+ const scaledY2 = yScale(y2);
6149
+ rectY = Math.min(scaledY1, scaledY2);
6150
+ rectHeight = Math.abs(scaledY2 - scaledY1);
6151
+ rectX = 0;
6152
+ rectWidth = width;
6153
+ } else {
6154
+ return null;
6155
+ }
6156
+ if (ifOverflow === "hidden") {
6157
+ if (rectX < 0) {
6158
+ rectWidth += rectX;
6159
+ rectX = 0;
6160
+ }
6161
+ if (rectY < 0) {
6162
+ rectHeight += rectY;
6163
+ rectY = 0;
6164
+ }
6165
+ if (rectX + rectWidth > width) {
6166
+ rectWidth = width - rectX;
6167
+ }
6168
+ if (rectY + rectHeight > height) {
6169
+ rectHeight = height - rectY;
6170
+ }
6171
+ if (rectWidth <= 0 || rectHeight <= 0) {
6172
+ return null;
6173
+ }
6174
+ }
6175
+ let labelX, labelY;
6176
+ let textAnchor = "middle";
6177
+ let dominantBaseline = "middle";
6178
+ switch (labelPosition) {
6179
+ case "center":
6180
+ labelX = rectX + rectWidth / 2;
6181
+ labelY = rectY + rectHeight / 2;
6182
+ break;
6183
+ case "top":
6184
+ labelX = rectX + rectWidth / 2;
6185
+ labelY = rectY - 8;
6186
+ dominantBaseline = "auto";
6187
+ break;
6188
+ case "bottom":
6189
+ labelX = rectX + rectWidth / 2;
6190
+ labelY = rectY + rectHeight + 16;
6191
+ dominantBaseline = "hanging";
6192
+ break;
6193
+ case "left":
6194
+ labelX = rectX - 8;
6195
+ labelY = rectY + rectHeight / 2;
6196
+ textAnchor = "end";
6197
+ break;
6198
+ case "right":
6199
+ labelX = rectX + rectWidth + 8;
6200
+ labelY = rectY + rectHeight / 2;
6201
+ textAnchor = "start";
6202
+ break;
6203
+ case "insideTop":
6204
+ labelX = rectX + rectWidth / 2;
6205
+ labelY = rectY + 16;
6206
+ dominantBaseline = "hanging";
6207
+ break;
6208
+ case "insideBottom":
6209
+ labelX = rectX + rectWidth / 2;
6210
+ labelY = rectY + rectHeight - 16;
6211
+ dominantBaseline = "auto";
6212
+ break;
6213
+ default:
6214
+ labelX = rectX + rectWidth / 2;
6215
+ labelY = rectY + rectHeight / 2;
6216
+ }
6217
+ return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { className: `reference-area ${className}`, children: [
6218
+ /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6219
+ "rect",
6220
+ {
6221
+ x: rectX,
6222
+ y: rectY,
6223
+ width: rectWidth,
6224
+ height: rectHeight,
6225
+ fill,
6226
+ fillOpacity,
6227
+ stroke,
6228
+ strokeWidth
6229
+ }
6230
+ ),
6231
+ label && (typeof label === "string" ? /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6232
+ "text",
6233
+ {
6234
+ x: labelX,
6235
+ y: labelY,
6236
+ textAnchor,
6237
+ dominantBaseline,
6238
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6239
+ fontWeight: "500",
6240
+ className: "fill-gray-600 dark:fill-gray-400",
6241
+ children: label
6242
+ }
6243
+ ) : /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6244
+ "foreignObject",
6245
+ {
6246
+ x: labelX - 50,
6247
+ y: labelY - 12,
6248
+ width: 100,
6249
+ height: 24,
6250
+ children: label
6251
+ }
6252
+ ))
6253
+ ] });
6254
+ };
6255
+
6256
+ // src/charts/components/CartesianGrid.tsx
6257
+ var import_react27 = require("react");
6258
+ var import_jsx_runtime112 = require("react/jsx-runtime");
6259
+ var CartesianGrid = ({
6260
+ horizontal = true,
6261
+ vertical = true,
6262
+ stroke,
6263
+ strokeDasharray = CHART_DEFAULTS.grid.strokeDasharray,
6264
+ strokeWidth = CHART_DEFAULTS.grid.strokeWidth,
6265
+ strokeOpacity = 0.5,
6266
+ horizontalPoints,
6267
+ verticalPoints,
6268
+ className = "",
6269
+ _chartDimensions
6270
+ }) => {
6271
+ if (!_chartDimensions) {
6272
+ return null;
6273
+ }
6274
+ const { width, height } = _chartDimensions;
6275
+ const hPoints = (0, import_react27.useMemo)(() => {
6276
+ if (horizontalPoints) return horizontalPoints;
6277
+ const count = 5;
6278
+ return Array.from({ length: count + 1 }, (_, i) => height / count * i);
6279
+ }, [horizontalPoints, height]);
6280
+ const vPoints = (0, import_react27.useMemo)(() => {
6281
+ if (verticalPoints) return verticalPoints;
6282
+ const count = 6;
6283
+ return Array.from({ length: count + 1 }, (_, i) => width / count * i);
6284
+ }, [verticalPoints, width]);
6285
+ return /* @__PURE__ */ (0, import_jsx_runtime112.jsxs)("g", { className: `cartesian-grid ${className}`, children: [
6286
+ horizontal && hPoints.map((y, i) => /* @__PURE__ */ (0, import_jsx_runtime112.jsx)(
6287
+ "line",
6288
+ {
6289
+ x1: 0,
6290
+ y1: y,
6291
+ x2: width,
6292
+ y2: y,
6293
+ stroke,
6294
+ strokeDasharray,
6295
+ strokeWidth,
6296
+ strokeOpacity,
6297
+ className: !stroke ? "stroke-gray-200 dark:stroke-gray-700" : void 0
6298
+ },
6299
+ `h-${i}`
6300
+ )),
6301
+ vertical && vPoints.map((x, i) => /* @__PURE__ */ (0, import_jsx_runtime112.jsx)(
6302
+ "line",
6303
+ {
6304
+ x1: x,
6305
+ y1: 0,
6306
+ x2: x,
6307
+ y2: height,
6308
+ stroke,
6309
+ strokeDasharray,
6310
+ strokeWidth,
6311
+ strokeOpacity,
6312
+ className: !stroke ? "stroke-gray-200 dark:stroke-gray-700" : void 0
6313
+ },
6314
+ `v-${i}`
6315
+ ))
6316
+ ] });
6317
+ };
6318
+
6319
+ // src/charts/LineChart.tsx
6320
+ var import_jsx_runtime113 = require("react/jsx-runtime");
5402
6321
  var LineChart = ({
5403
6322
  data,
5404
6323
  width: providedWidth,
5405
6324
  height: providedHeight,
5406
- aspectRatio = 16 / 9,
5407
- responsive = true,
6325
+ padding: customPadding,
6326
+ showGrid = true,
6327
+ showXAxis = true,
6328
+ showYAxis = true,
6329
+ showLegend = true,
6330
+ showTooltip = true,
6331
+ showDots = true,
6332
+ animate = true,
6333
+ animationDuration = CHART_DEFAULTS.animation.duration,
6334
+ xAxisLabel,
6335
+ yAxisLabel,
6336
+ yAxisTickCount = 5,
6337
+ yDomain,
6338
+ formatXValue,
6339
+ formatYValue,
6340
+ tooltipFormatter,
6341
+ tooltipLabelFormatter,
6342
+ tooltipContent,
6343
+ onPointClick,
6344
+ onPointHover,
6345
+ showCrosshair = false,
6346
+ showValueLabels = false,
6347
+ className = "",
6348
+ children,
6349
+ colors = CHART_DEFAULTS.colors,
6350
+ ariaLabel
6351
+ }) => {
6352
+ const containerRef = (0, import_react28.useRef)(null);
6353
+ const svgRef = (0, import_react28.useRef)(null);
6354
+ const [hoveredPoint, setHoveredPoint] = (0, import_react28.useState)(null);
6355
+ const [crosshairX, setCrosshairX] = (0, import_react28.useState)(null);
6356
+ const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
6357
+ const width = providedWidth || 800;
6358
+ const height = providedHeight || 400;
6359
+ const padding = (0, import_react28.useMemo)(() => ({
6360
+ top: customPadding?.top ?? CHART_DEFAULTS.padding.top,
6361
+ right: customPadding?.right ?? (showValueLabels ? 80 : CHART_DEFAULTS.padding.right),
6362
+ bottom: customPadding?.bottom ?? (showXAxis ? 60 : CHART_DEFAULTS.padding.bottom),
6363
+ left: customPadding?.left ?? (showYAxis ? 60 : CHART_DEFAULTS.padding.left)
6364
+ }), [customPadding, showXAxis, showYAxis, showValueLabels]);
6365
+ const chartWidth = width - padding.left - padding.right;
6366
+ const chartHeight = height - padding.top - padding.bottom;
6367
+ const allPoints = (0, import_react28.useMemo)(
6368
+ () => data.flatMap((series) => series.data),
6369
+ [data]
6370
+ );
6371
+ const xValueType = (0, import_react28.useMemo)(() => {
6372
+ const firstX = data[0]?.data[0]?.x;
6373
+ if (firstX instanceof Date) return "date";
6374
+ if (typeof firstX === "number") return "number";
6375
+ return "string";
6376
+ }, [data]);
6377
+ const xDomainCalc = (0, import_react28.useMemo)(() => {
6378
+ if (xValueType === "date") {
6379
+ const dates = allPoints.map((p) => p.x.getTime());
6380
+ return [Math.min(...dates), Math.max(...dates)];
6381
+ }
6382
+ if (xValueType === "number") {
6383
+ const nums = allPoints.map((p) => p.x);
6384
+ return [Math.min(...nums), Math.max(...nums)];
6385
+ }
6386
+ const maxLen = Math.max(...data.map((s) => s.data.length));
6387
+ return [0, maxLen - 1];
6388
+ }, [allPoints, xValueType, data]);
6389
+ const yDomainCalc = (0, import_react28.useMemo)(() => {
6390
+ if (yDomain) return yDomain;
6391
+ const yValues = allPoints.map((p) => p.y);
6392
+ return calculateDomain(yValues, { includeZero: true, padding: 0.1 });
6393
+ }, [allPoints, yDomain]);
6394
+ const xScale = (0, import_react28.useMemo)(() => {
6395
+ if (xValueType === "date") {
6396
+ return (value) => {
6397
+ const time = value instanceof Date ? value.getTime() : Number(value);
6398
+ return scaleLinear({
6399
+ domain: xDomainCalc,
6400
+ range: [0, chartWidth]
6401
+ })(time);
6402
+ };
6403
+ }
6404
+ if (xValueType === "number") {
6405
+ const scale = scaleLinear({
6406
+ domain: xDomainCalc,
6407
+ range: [0, chartWidth]
6408
+ });
6409
+ return (value) => scale(Number(value));
6410
+ }
6411
+ return (_value, index = 0) => {
6412
+ const maxLen = Math.max(...data.map((s) => s.data.length)) - 1;
6413
+ if (maxLen === 0) return chartWidth / 2;
6414
+ return index / maxLen * chartWidth;
6415
+ };
6416
+ }, [xValueType, xDomainCalc, chartWidth, data]);
6417
+ const yScale = (0, import_react28.useMemo)(
6418
+ () => scaleLinear({
6419
+ domain: yDomainCalc,
6420
+ range: [chartHeight, 0]
6421
+ }),
6422
+ [yDomainCalc, chartHeight]
6423
+ );
6424
+ const yTicks = (0, import_react28.useMemo)(
6425
+ () => getTicks(yDomainCalc, yAxisTickCount),
6426
+ [yDomainCalc, yAxisTickCount]
6427
+ );
6428
+ const xTicks = (0, import_react28.useMemo)(() => {
6429
+ if (xValueType === "string") {
6430
+ return data[0]?.data.map((p, i) => ({ value: p.x, index: i })) || [];
6431
+ }
6432
+ const tickCount = Math.min(6, Math.max(2, data[0]?.data.length || 2));
6433
+ const ticks = getTicks(xDomainCalc, tickCount);
6434
+ return ticks.map((t) => ({ value: t, index: 0 }));
6435
+ }, [xValueType, xDomainCalc, data]);
6436
+ const xFormatter = (0, import_react28.useCallback)((value) => {
6437
+ if (formatXValue) return formatXValue(value);
6438
+ if (value instanceof Date) {
6439
+ const range = xDomainCalc[1] - xDomainCalc[0];
6440
+ return formatDate(value, range);
6441
+ }
6442
+ return String(value);
6443
+ }, [formatXValue, xDomainCalc]);
6444
+ const yFormatter = (0, import_react28.useMemo)(() => {
6445
+ if (formatYValue) return formatYValue;
6446
+ return createTickFormatter(yDomainCalc);
6447
+ }, [formatYValue, yDomainCalc]);
6448
+ const seriesPaths = (0, import_react28.useMemo)(() => {
6449
+ return data.map((series) => {
6450
+ const points = series.data.map((point, i) => ({
6451
+ x: xScale(point.x, i),
6452
+ y: yScale(point.y)
6453
+ }));
6454
+ const type = series.type || "linear";
6455
+ let path;
6456
+ switch (type) {
6457
+ case "monotone":
6458
+ path = generateMonotonePath(points);
6459
+ break;
6460
+ case "step":
6461
+ case "stepAfter":
6462
+ path = generateStepPath(points, "after");
6463
+ break;
6464
+ case "stepBefore":
6465
+ path = generateStepPath(points, "before");
6466
+ break;
6467
+ default:
6468
+ path = generateLinePath(points);
6469
+ }
6470
+ return { points, path };
6471
+ });
6472
+ }, [data, xScale, yScale]);
6473
+ const handlePointEnter = (0, import_react28.useCallback)((e, seriesIndex, pointIndex) => {
6474
+ const series = data[seriesIndex];
6475
+ const point = series.data[pointIndex];
6476
+ const scaledPoint = seriesPaths[seriesIndex].points[pointIndex];
6477
+ setHoveredPoint({ seriesIndex, pointIndex });
6478
+ if (showCrosshair) {
6479
+ setCrosshairX(scaledPoint.x);
6480
+ }
6481
+ if (showTooltip && containerRef.current) {
6482
+ const rect = containerRef.current.getBoundingClientRect();
6483
+ const mouseX = e.clientX - rect.left;
6484
+ const mouseY = e.clientY - rect.top;
6485
+ const payload = [{
6486
+ name: series.name,
6487
+ value: point.y,
6488
+ color: series.color || colors[seriesIndex % colors.length],
6489
+ payload: point
6490
+ }];
6491
+ showTooltipFn({
6492
+ x: mouseX,
6493
+ y: mouseY,
6494
+ label: xFormatter(point.x),
6495
+ payload
6496
+ });
6497
+ }
6498
+ onPointHover?.(point, seriesIndex, pointIndex);
6499
+ }, [data, seriesPaths, showCrosshair, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
6500
+ const handlePointLeave = (0, import_react28.useCallback)(() => {
6501
+ setHoveredPoint(null);
6502
+ setCrosshairX(null);
6503
+ hideTooltip();
6504
+ onPointHover?.(null, -1, -1);
6505
+ }, [hideTooltip, onPointHover]);
6506
+ const handlePointClick = (0, import_react28.useCallback)((seriesIndex, pointIndex) => {
6507
+ const point = data[seriesIndex].data[pointIndex];
6508
+ onPointClick?.(point, seriesIndex, pointIndex);
6509
+ }, [data, onPointClick]);
6510
+ const legendItems = (0, import_react28.useMemo)(
6511
+ () => data.map((series, i) => ({
6512
+ name: series.name,
6513
+ color: series.color || colors[i % colors.length],
6514
+ type: "line"
6515
+ })),
6516
+ [data, colors]
6517
+ );
6518
+ const referenceElements = (0, import_react28.useMemo)(() => {
6519
+ if (!children) return null;
6520
+ const chartDimensions = {
6521
+ width: chartWidth,
6522
+ height: chartHeight,
6523
+ padding
6524
+ };
6525
+ const scales = {
6526
+ xScale: (value) => {
6527
+ if (xValueType === "string") {
6528
+ const index = data[0]?.data.findIndex((p) => p.x === value) ?? 0;
6529
+ return xScale(value, index);
6530
+ }
6531
+ return xScale(value, 0);
6532
+ },
6533
+ yScale
6534
+ };
6535
+ return import_react28.default.Children.map(children, (child) => {
6536
+ if (!import_react28.default.isValidElement(child)) return child;
6537
+ if (child.type === ReferenceLine || child.type === ReferenceArea || child.type === CartesianGrid) {
6538
+ return import_react28.default.cloneElement(child, {
6539
+ _chartDimensions: chartDimensions,
6540
+ _scales: scales
6541
+ });
6542
+ }
6543
+ return child;
6544
+ });
6545
+ }, [children, chartWidth, chartHeight, padding, xScale, yScale, xValueType, data]);
6546
+ const accessibleDescription = (0, import_react28.useMemo)(() => {
6547
+ if (ariaLabel) return ariaLabel;
6548
+ const seriesNames = data.map((s) => s.name).join(", ");
6549
+ return `Line chart with ${data.length} series: ${seriesNames}`;
6550
+ }, [data, ariaLabel]);
6551
+ return /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)(
6552
+ "div",
6553
+ {
6554
+ ref: containerRef,
6555
+ className: `relative ${className}`,
6556
+ style: { width, height: "auto" },
6557
+ children: [
6558
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)(
6559
+ "svg",
6560
+ {
6561
+ ref: svgRef,
6562
+ width,
6563
+ height,
6564
+ viewBox: `0 0 ${width} ${height}`,
6565
+ role: "img",
6566
+ "aria-label": accessibleDescription,
6567
+ className: "bg-white dark:bg-gray-900",
6568
+ children: [
6569
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)("defs", { children: animate && data.map((series, i) => /* @__PURE__ */ (0, import_jsx_runtime113.jsx)("style", { children: `
6570
+ @keyframes drawLine${i} {
6571
+ from { stroke-dashoffset: 2000; }
6572
+ to { stroke-dashoffset: 0; }
6573
+ }
6574
+ ` }, `anim-${i}`)) }),
6575
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
6576
+ showGrid && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6577
+ CartesianGrid,
6578
+ {
6579
+ _chartDimensions: { width: chartWidth, height: chartHeight },
6580
+ horizontalPoints: yTicks.map((t) => yScale(t))
6581
+ }
6582
+ ),
6583
+ referenceElements,
6584
+ showCrosshair && crosshairX !== null && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6585
+ "line",
6586
+ {
6587
+ x1: crosshairX,
6588
+ y1: 0,
6589
+ x2: crosshairX,
6590
+ y2: chartHeight,
6591
+ stroke: "currentColor",
6592
+ strokeWidth: 1,
6593
+ strokeDasharray: "4 4",
6594
+ className: "text-gray-400 dark:text-gray-500",
6595
+ pointerEvents: "none"
6596
+ }
6597
+ ),
6598
+ data.map((series, seriesIndex) => {
6599
+ const { path } = seriesPaths[seriesIndex];
6600
+ const color = series.color || colors[seriesIndex % colors.length];
6601
+ const strokeWidth = series.strokeWidth || CHART_DEFAULTS.line.strokeWidth;
6602
+ return /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6603
+ "path",
6604
+ {
6605
+ d: path,
6606
+ fill: "none",
6607
+ stroke: color,
6608
+ strokeWidth,
6609
+ strokeLinecap: "round",
6610
+ strokeLinejoin: "round",
6611
+ style: animate ? {
6612
+ strokeDasharray: 2e3,
6613
+ animation: `drawLine${seriesIndex} ${animationDuration}ms ease-out forwards`
6614
+ } : void 0
6615
+ },
6616
+ `line-${seriesIndex}`
6617
+ );
6618
+ }),
6619
+ showDots && data.map((series, seriesIndex) => {
6620
+ const { points } = seriesPaths[seriesIndex];
6621
+ const color = series.color || colors[seriesIndex % colors.length];
6622
+ const showSeriesDots = series.dot !== false;
6623
+ if (!showSeriesDots) return null;
6624
+ return points.map((point, pointIndex) => {
6625
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
6626
+ return /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6627
+ "circle",
6628
+ {
6629
+ cx: point.x,
6630
+ cy: point.y,
6631
+ r: isHovered ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
6632
+ fill: color,
6633
+ stroke: "white",
6634
+ strokeWidth: 2,
6635
+ className: "cursor-pointer transition-all duration-150",
6636
+ style: {
6637
+ opacity: animate ? 0 : 1,
6638
+ animation: animate ? `fadeIn 200ms ease-out ${animationDuration + pointIndex * 20}ms forwards` : void 0
6639
+ },
6640
+ onMouseEnter: (e) => handlePointEnter(e, seriesIndex, pointIndex),
6641
+ onMouseLeave: handlePointLeave,
6642
+ onClick: () => handlePointClick(seriesIndex, pointIndex)
6643
+ },
6644
+ `dot-${seriesIndex}-${pointIndex}`
6645
+ );
6646
+ });
6647
+ }),
6648
+ showValueLabels && data.map((series, seriesIndex) => {
6649
+ const lastPoint = series.data[series.data.length - 1];
6650
+ const { points } = seriesPaths[seriesIndex];
6651
+ const lastScaled = points[points.length - 1];
6652
+ const color = series.color || colors[seriesIndex % colors.length];
6653
+ if (!lastPoint || !lastScaled) return null;
6654
+ return /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { children: [
6655
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6656
+ "circle",
6657
+ {
6658
+ cx: chartWidth + 12,
6659
+ cy: lastScaled.y,
6660
+ r: 4,
6661
+ fill: color
6662
+ }
6663
+ ),
6664
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6665
+ "text",
6666
+ {
6667
+ x: chartWidth + 22,
6668
+ y: lastScaled.y,
6669
+ dominantBaseline: "middle",
6670
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6671
+ fontWeight: "600",
6672
+ className: "fill-gray-700 dark:fill-gray-300",
6673
+ children: yFormatter(lastPoint.y)
6674
+ }
6675
+ )
6676
+ ] }, `label-${seriesIndex}`);
6677
+ }),
6678
+ showXAxis && /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { transform: `translate(0, ${chartHeight})`, children: [
6679
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6680
+ "line",
6681
+ {
6682
+ x1: 0,
6683
+ y1: 0,
6684
+ x2: chartWidth,
6685
+ y2: 0,
6686
+ className: "stroke-gray-300 dark:stroke-gray-600",
6687
+ strokeWidth: 1
6688
+ }
6689
+ ),
6690
+ xTicks.map((tick, i) => {
6691
+ const x = xValueType === "string" ? xScale(tick.value, tick.index) : xScale(xValueType === "date" ? new Date(tick.value) : tick.value, 0);
6692
+ return /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { transform: `translate(${x}, 0)`, children: [
6693
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-600" }),
6694
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6695
+ "text",
6696
+ {
6697
+ y: 20,
6698
+ textAnchor: "middle",
6699
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6700
+ className: "fill-gray-600 dark:fill-gray-400",
6701
+ children: xFormatter(xValueType === "date" ? new Date(tick.value) : tick.value)
6702
+ }
6703
+ )
6704
+ ] }, `x-tick-${i}`);
6705
+ }),
6706
+ xAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6707
+ "text",
6708
+ {
6709
+ x: chartWidth / 2,
6710
+ y: 50,
6711
+ textAnchor: "middle",
6712
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
6713
+ fontWeight: "500",
6714
+ className: "fill-gray-700 dark:fill-gray-300",
6715
+ children: xAxisLabel
6716
+ }
6717
+ )
6718
+ ] }),
6719
+ showYAxis && /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { children: [
6720
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6721
+ "line",
6722
+ {
6723
+ x1: 0,
6724
+ y1: 0,
6725
+ x2: 0,
6726
+ y2: chartHeight,
6727
+ className: "stroke-gray-300 dark:stroke-gray-600",
6728
+ strokeWidth: 1
6729
+ }
6730
+ ),
6731
+ yTicks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime113.jsxs)("g", { transform: `translate(0, ${yScale(tick)})`, children: [
6732
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)("line", { x2: -6, className: "stroke-gray-300 dark:stroke-gray-600" }),
6733
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6734
+ "text",
6735
+ {
6736
+ x: -12,
6737
+ textAnchor: "end",
6738
+ dominantBaseline: "middle",
6739
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6740
+ className: "fill-gray-600 dark:fill-gray-400",
6741
+ children: yFormatter(tick)
6742
+ }
6743
+ )
6744
+ ] }, `y-tick-${i}`)),
6745
+ yAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6746
+ "text",
6747
+ {
6748
+ x: -chartHeight / 2,
6749
+ y: -45,
6750
+ textAnchor: "middle",
6751
+ transform: "rotate(-90)",
6752
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
6753
+ fontWeight: "500",
6754
+ className: "fill-gray-700 dark:fill-gray-300",
6755
+ children: yAxisLabel
6756
+ }
6757
+ )
6758
+ ] })
6759
+ ] }),
6760
+ /* @__PURE__ */ (0, import_jsx_runtime113.jsx)("style", { children: `
6761
+ @keyframes fadeIn {
6762
+ from { opacity: 0; }
6763
+ to { opacity: 1; }
6764
+ }
6765
+ ` })
6766
+ ]
6767
+ }
6768
+ ),
6769
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6770
+ Legend,
6771
+ {
6772
+ items: legendItems,
6773
+ layout: "horizontal",
6774
+ align: "center"
6775
+ }
6776
+ ),
6777
+ showTooltip && /* @__PURE__ */ (0, import_jsx_runtime113.jsx)(
6778
+ ChartTooltip,
6779
+ {
6780
+ ...tooltipData,
6781
+ content: tooltipContent,
6782
+ formatter: tooltipFormatter,
6783
+ labelFormatter: tooltipLabelFormatter,
6784
+ containerBounds: { width, height }
6785
+ }
6786
+ )
6787
+ ]
6788
+ }
6789
+ );
6790
+ };
6791
+
6792
+ // src/charts/BarChart.tsx
6793
+ var import_react29 = __toESM(require("react"));
6794
+ var import_jsx_runtime114 = require("react/jsx-runtime");
6795
+ var BarChart = ({
6796
+ data,
6797
+ width: providedWidth,
6798
+ height: providedHeight,
6799
+ padding: customPadding,
5408
6800
  showGrid = true,
5409
6801
  showXAxis = true,
5410
6802
  showYAxis = true,
5411
6803
  showLegend = true,
5412
6804
  showTooltip = true,
5413
- showDots = true,
5414
- curved = false,
5415
- strokeWidth = 2,
5416
- className = "",
6805
+ showValues = false,
6806
+ stacked = false,
6807
+ horizontal = false,
6808
+ barRadius = CHART_DEFAULTS.bar.radius,
6809
+ barGap = 0.1,
6810
+ barCategoryGap = 0.2,
6811
+ animate = true,
6812
+ animationDuration = CHART_DEFAULTS.animation.duration,
5417
6813
  xAxisLabel,
5418
6814
  yAxisLabel,
5419
- baseFontSize = 14
6815
+ yAxisTickCount = 5,
6816
+ yDomain,
6817
+ formatYValue,
6818
+ tooltipFormatter,
6819
+ onBarClick,
6820
+ onBarHover,
6821
+ className = "",
6822
+ children,
6823
+ colors = CHART_DEFAULTS.colors,
6824
+ ariaLabel
5420
6825
  }) => {
5421
- const viewBoxWidth = 1e3;
5422
- const viewBoxHeight = 600;
5423
- const containerRef = import_react26.default.useRef(null);
5424
- const svgRef = import_react26.default.useRef(null);
5425
- const [tooltipPosition, setTooltipPosition] = import_react26.default.useState(null);
5426
- const [hoveredPoint, setHoveredPoint] = import_react26.default.useState(null);
5427
- const chartId = import_react26.default.useId();
5428
- const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5429
- const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5430
- const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5431
- const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
5432
- const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
5433
- const padding = {
5434
- top: 50,
5435
- right: 50,
5436
- bottom: showXAxis ? 120 : 50,
5437
- left: showYAxis ? 130 : 50
5438
- };
5439
- const chartWidth = viewBoxWidth - padding.left - padding.right;
5440
- const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5441
- const gridLabelFontSize = viewBoxWidth * 0.045;
5442
- const axisLabelFontSize = viewBoxWidth * 0.05;
5443
- const titleFontSize = viewBoxWidth * 0.055;
5444
- const allPoints = data.flatMap((series) => series.data);
5445
- const allYValues = allPoints.map((p) => p.y);
5446
- const firstXValue = data[0]?.data[0]?.x;
5447
- const isStringX = typeof firstXValue === "string";
5448
- const uniqueXValues = isStringX ? Array.from(new Set(allPoints.map((p) => p.x))) : [];
5449
- const xValueToIndex = isStringX ? Object.fromEntries(uniqueXValues.map((val, idx) => [val, idx])) : {};
5450
- const allXNumbers = isStringX ? uniqueXValues.map((_, idx) => idx) : allPoints.map((p) => p.x);
5451
- const minX = Math.min(...allXNumbers);
5452
- const maxX = Math.max(...allXNumbers);
5453
- const minY = Math.min(0, ...allYValues);
5454
- const maxY = Math.max(...allYValues);
5455
- const yRange = maxY - minY;
5456
- const yMin = Math.max(0, minY - yRange * 0.1);
5457
- const yMax = maxY + yRange * 0.1;
5458
- const scaleX = (x, index) => {
5459
- const numX = isStringX ? index : x;
5460
- if (maxX === minX) return chartWidth / 2;
5461
- return (numX - minX) / (maxX - minX) * chartWidth;
5462
- };
5463
- const scaleY = (y) => {
5464
- if (yMax === yMin) return chartHeight / 2;
5465
- return chartHeight - (y - yMin) / (yMax - yMin) * chartHeight;
5466
- };
5467
- const generatePath = (series) => {
5468
- if (series.data.length === 0) return "";
5469
- const points = series.data.map((point, i) => {
5470
- const x = scaleX(point.x, i);
5471
- const y = scaleY(point.y);
5472
- return { x, y, originalIndex: i };
5473
- });
5474
- if (curved) {
5475
- let path = `M ${points[0].x} ${points[0].y}`;
5476
- for (let i = 0; i < points.length - 1; i++) {
5477
- const current = points[i];
5478
- const next = points[i + 1];
5479
- const controlPointX = (current.x + next.x) / 2;
5480
- path += ` C ${controlPointX} ${current.y}, ${controlPointX} ${next.y}, ${next.x} ${next.y}`;
5481
- }
5482
- return path;
5483
- } else {
5484
- return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
6826
+ const containerRef = (0, import_react29.useRef)(null);
6827
+ const [hoveredBar, setHoveredBar] = (0, import_react29.useState)(null);
6828
+ const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
6829
+ const width = providedWidth || 800;
6830
+ const height = providedHeight || 400;
6831
+ const padding = (0, import_react29.useMemo)(() => ({
6832
+ top: customPadding?.top ?? CHART_DEFAULTS.padding.top,
6833
+ right: customPadding?.right ?? CHART_DEFAULTS.padding.right,
6834
+ bottom: customPadding?.bottom ?? (showXAxis ? 70 : CHART_DEFAULTS.padding.bottom),
6835
+ left: customPadding?.left ?? (showYAxis ? 60 : CHART_DEFAULTS.padding.left)
6836
+ }), [customPadding, showXAxis, showYAxis]);
6837
+ const chartWidth = width - padding.left - padding.right;
6838
+ const chartHeight = height - padding.top - padding.bottom;
6839
+ const categories = (0, import_react29.useMemo)(() => {
6840
+ const cats = data[0]?.data.map((d) => String(d.x)) || [];
6841
+ return [...new Set(cats)];
6842
+ }, [data]);
6843
+ const yDomainCalc = (0, import_react29.useMemo)(() => {
6844
+ if (yDomain) return yDomain;
6845
+ if (stacked) {
6846
+ const maxStacked = categories.map((_, catIndex) => {
6847
+ return data.reduce((sum, series) => {
6848
+ return sum + (series.data[catIndex]?.y || 0);
6849
+ }, 0);
6850
+ });
6851
+ return calculateDomain([0, ...maxStacked], { includeZero: true, padding: 0.1 });
5485
6852
  }
5486
- };
5487
- const gridLines = [];
5488
- const numGridLines = 5;
5489
- if (showGrid) {
5490
- for (let i = 0; i <= numGridLines; i++) {
5491
- const y = chartHeight / numGridLines * i;
5492
- const yValue = yMax - (yMax - yMin) / numGridLines * i;
5493
- gridLines.push(
5494
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { children: [
5495
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5496
- "line",
5497
- {
5498
- x1: 0,
5499
- y1: y,
5500
- x2: chartWidth,
5501
- y2: y,
5502
- stroke: "currentColor",
5503
- strokeWidth: "0.5",
5504
- className: "text-gray-200 dark:text-gray-700",
5505
- strokeDasharray: "4,4"
5506
- }
5507
- ),
5508
- showYAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5509
- "text",
5510
- {
5511
- x: -25,
5512
- y,
5513
- textAnchor: "end",
5514
- dominantBaseline: "middle",
5515
- fontSize: gridLabelFontSize,
5516
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5517
- children: yValue.toFixed(1)
5518
- }
5519
- )
5520
- ] }, `grid-h-${i}`)
5521
- );
6853
+ const allY = data.flatMap((s) => s.data.map((d) => d.y));
6854
+ return calculateDomain(allY, { includeZero: true, padding: 0.1 });
6855
+ }, [data, categories, stacked, yDomain]);
6856
+ const xBandScale = (0, import_react29.useMemo)(
6857
+ () => scaleBand({
6858
+ domain: categories,
6859
+ range: horizontal ? [chartHeight, 0] : [0, chartWidth],
6860
+ padding: barCategoryGap
6861
+ }),
6862
+ [categories, chartWidth, chartHeight, horizontal, barCategoryGap]
6863
+ );
6864
+ const yScale = (0, import_react29.useMemo)(
6865
+ () => scaleLinear({
6866
+ domain: yDomainCalc,
6867
+ range: horizontal ? [0, chartWidth] : [chartHeight, 0]
6868
+ }),
6869
+ [yDomainCalc, chartWidth, chartHeight, horizontal]
6870
+ );
6871
+ const yTicks = (0, import_react29.useMemo)(
6872
+ () => getTicks(yDomainCalc, yAxisTickCount),
6873
+ [yDomainCalc, yAxisTickCount]
6874
+ );
6875
+ const yFormatter = (0, import_react29.useMemo)(() => {
6876
+ if (formatYValue) return formatYValue;
6877
+ return createTickFormatter(yDomainCalc);
6878
+ }, [formatYValue, yDomainCalc]);
6879
+ const numSeries = data.length;
6880
+ const bandwidth = xBandScale.bandwidth();
6881
+ const barWidth = stacked ? bandwidth : (bandwidth - bandwidth * barGap * (numSeries - 1)) / numSeries;
6882
+ const handleBarEnter = (0, import_react29.useCallback)((e, seriesIndex, barIndex) => {
6883
+ const series = data[seriesIndex];
6884
+ const point = series.data[barIndex];
6885
+ setHoveredBar({ seriesIndex, barIndex });
6886
+ if (showTooltip && containerRef.current) {
6887
+ const rect = containerRef.current.getBoundingClientRect();
6888
+ const mouseX = e.clientX - rect.left;
6889
+ const mouseY = e.clientY - rect.top;
6890
+ const payload = [{
6891
+ name: series.name,
6892
+ value: point.y,
6893
+ color: series.color || colors[seriesIndex % colors.length],
6894
+ payload: point
6895
+ }];
6896
+ showTooltipFn({
6897
+ x: mouseX,
6898
+ y: mouseY,
6899
+ label: String(point.x),
6900
+ payload
6901
+ });
5522
6902
  }
5523
- const numXGridLines = Math.max(2, Math.min(allPoints.length, 6));
5524
- const xGridStep = numXGridLines > 1 ? 1 / (numXGridLines - 1) : 0;
5525
- for (let i = 0; i < numXGridLines; i++) {
5526
- const x = chartWidth * (i * xGridStep);
5527
- const dataIndex = Math.floor((data[0]?.data.length - 1) * (i * xGridStep));
5528
- const xValue = data[0]?.data[Math.max(0, Math.min(dataIndex, data[0].data.length - 1))]?.x || "";
5529
- gridLines.push(
5530
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { children: [
5531
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5532
- "line",
5533
- {
5534
- x1: x,
5535
- y1: 0,
5536
- x2: x,
5537
- y2: chartHeight,
5538
- stroke: "currentColor",
5539
- strokeWidth: "0.5",
5540
- className: "text-gray-200 dark:text-gray-700",
5541
- strokeDasharray: "4,4"
5542
- }
5543
- ),
5544
- showXAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5545
- "text",
6903
+ onBarHover?.(point, seriesIndex, barIndex);
6904
+ }, [data, showTooltip, colors, showTooltipFn, onBarHover]);
6905
+ const handleBarLeave = (0, import_react29.useCallback)(() => {
6906
+ setHoveredBar(null);
6907
+ hideTooltip();
6908
+ onBarHover?.(null, -1, -1);
6909
+ }, [hideTooltip, onBarHover]);
6910
+ const handleBarClick = (0, import_react29.useCallback)((seriesIndex, barIndex) => {
6911
+ const point = data[seriesIndex].data[barIndex];
6912
+ onBarClick?.(point, seriesIndex, barIndex);
6913
+ }, [data, onBarClick]);
6914
+ const legendItems = (0, import_react29.useMemo)(
6915
+ () => data.map((series, i) => ({
6916
+ name: series.name,
6917
+ color: series.color || colors[i % colors.length],
6918
+ type: "square"
6919
+ })),
6920
+ [data, colors]
6921
+ );
6922
+ const bars = (0, import_react29.useMemo)(() => {
6923
+ const result = [];
6924
+ const cumulativeValues = {};
6925
+ data.forEach((series, seriesIndex) => {
6926
+ const color = series.color || colors[seriesIndex % colors.length];
6927
+ const radius = series.radius ?? barRadius;
6928
+ series.data.forEach((point, barIndex) => {
6929
+ const category = String(point.x);
6930
+ const baseX = xBandScale.scale(category);
6931
+ let barX, barY, barH, barW;
6932
+ if (stacked) {
6933
+ const prevValue = cumulativeValues[category] || 0;
6934
+ cumulativeValues[category] = prevValue + point.y;
6935
+ if (horizontal) {
6936
+ barX = yScale(prevValue);
6937
+ barY = baseX;
6938
+ barW = yScale(point.y) - yScale(0);
6939
+ barH = bandwidth;
6940
+ } else {
6941
+ barX = baseX;
6942
+ barY = yScale(prevValue + point.y);
6943
+ barW = bandwidth;
6944
+ barH = yScale(prevValue) - yScale(prevValue + point.y);
6945
+ }
6946
+ } else {
6947
+ const offset = seriesIndex * (barWidth + bandwidth * barGap / numSeries);
6948
+ if (horizontal) {
6949
+ barX = yScale(0);
6950
+ barY = baseX + offset;
6951
+ barW = yScale(point.y) - yScale(0);
6952
+ barH = barWidth;
6953
+ } else {
6954
+ barX = baseX + offset;
6955
+ barY = yScale(Math.max(0, point.y));
6956
+ barW = barWidth;
6957
+ barH = Math.abs(yScale(point.y) - yScale(0));
6958
+ }
6959
+ }
6960
+ const isHovered = hoveredBar?.seriesIndex === seriesIndex && hoveredBar?.barIndex === barIndex;
6961
+ result.push(
6962
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
6963
+ "rect",
5546
6964
  {
5547
- x,
5548
- y: chartHeight + 35,
5549
- textAnchor: "middle",
5550
- dominantBaseline: "hanging",
5551
- fontSize: gridLabelFontSize,
5552
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5553
- children: xValue
5554
- }
6965
+ x: barX,
6966
+ y: barY,
6967
+ width: Math.max(0, barW),
6968
+ height: Math.max(0, barH),
6969
+ rx: radius,
6970
+ ry: radius,
6971
+ fill: color,
6972
+ opacity: isHovered ? 0.8 : 1,
6973
+ className: "cursor-pointer transition-opacity duration-150",
6974
+ style: animate ? {
6975
+ transform: horizontal ? "scaleX(0)" : "scaleY(0)",
6976
+ transformOrigin: horizontal ? "left" : "bottom",
6977
+ animation: `${horizontal ? "growX" : "growY"} ${animationDuration}ms ease-out ${barIndex * 50}ms forwards`
6978
+ } : void 0,
6979
+ onMouseEnter: (e) => handleBarEnter(e, seriesIndex, barIndex),
6980
+ onMouseLeave: handleBarLeave,
6981
+ onClick: () => handleBarClick(seriesIndex, barIndex)
6982
+ },
6983
+ `bar-${seriesIndex}-${barIndex}`
5555
6984
  )
5556
- ] }, `grid-v-${i}`)
5557
- );
5558
- }
5559
- }
5560
- const getTooltipPosition = import_react26.default.useCallback((x, y) => {
5561
- if (!containerRef.current) return { x, y };
5562
- const rect = containerRef.current.getBoundingClientRect();
5563
- const tooltipWidth = 120;
5564
- const tooltipHeight = 80;
5565
- let tooltipX = x + 10;
5566
- let tooltipY = y - 30;
5567
- if (tooltipX + tooltipWidth > rect.width) {
5568
- tooltipX = x - tooltipWidth - 10;
5569
- }
5570
- if (tooltipY + tooltipHeight > rect.height) {
5571
- tooltipY = y + 30;
5572
- }
5573
- return { x: tooltipX, y: tooltipY };
5574
- }, []);
5575
- const containerStyle = {
5576
- "--font-size-base": `${baseFontSize}px`,
5577
- "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
5578
- "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
5579
- "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
5580
- };
5581
- return /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)(
6985
+ );
6986
+ if (showValues) {
6987
+ const labelX = horizontal ? barX + barW + 8 : barX + barW / 2;
6988
+ const labelY = horizontal ? barY + barH / 2 : barY - 8;
6989
+ result.push(
6990
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
6991
+ "text",
6992
+ {
6993
+ x: labelX,
6994
+ y: labelY,
6995
+ textAnchor: horizontal ? "start" : "middle",
6996
+ dominantBaseline: horizontal ? "middle" : "auto",
6997
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
6998
+ fontWeight: "600",
6999
+ className: "fill-gray-700 dark:fill-gray-300",
7000
+ style: animate ? {
7001
+ opacity: 0,
7002
+ animation: `fadeIn 200ms ease-out ${animationDuration + barIndex * 50}ms forwards`
7003
+ } : void 0,
7004
+ children: yFormatter(point.y)
7005
+ },
7006
+ `value-${seriesIndex}-${barIndex}`
7007
+ )
7008
+ );
7009
+ }
7010
+ });
7011
+ });
7012
+ return result;
7013
+ }, [
7014
+ data,
7015
+ colors,
7016
+ barRadius,
7017
+ xBandScale,
7018
+ yScale,
7019
+ stacked,
7020
+ horizontal,
7021
+ bandwidth,
7022
+ barWidth,
7023
+ barGap,
7024
+ numSeries,
7025
+ hoveredBar,
7026
+ animate,
7027
+ animationDuration,
7028
+ showValues,
7029
+ yFormatter,
7030
+ handleBarEnter,
7031
+ handleBarLeave,
7032
+ handleBarClick
7033
+ ]);
7034
+ const referenceElements = (0, import_react29.useMemo)(() => {
7035
+ if (!children) return null;
7036
+ const chartDimensions = { width: chartWidth, height: chartHeight, padding };
7037
+ const scales = {
7038
+ xScale: (value) => xBandScale.scale(String(value)) + bandwidth / 2,
7039
+ yScale
7040
+ };
7041
+ return import_react29.default.Children.map(children, (child) => {
7042
+ if (!import_react29.default.isValidElement(child)) return child;
7043
+ if (child.type === ReferenceLine || child.type === ReferenceArea || child.type === CartesianGrid) {
7044
+ return import_react29.default.cloneElement(child, {
7045
+ _chartDimensions: chartDimensions,
7046
+ _scales: scales
7047
+ });
7048
+ }
7049
+ return child;
7050
+ });
7051
+ }, [children, chartWidth, chartHeight, padding, xBandScale, bandwidth, yScale]);
7052
+ const accessibleDescription = (0, import_react29.useMemo)(() => {
7053
+ if (ariaLabel) return ariaLabel;
7054
+ const seriesNames = data.map((s) => s.name).join(", ");
7055
+ return `Bar chart with ${data.length} series: ${seriesNames}`;
7056
+ }, [data, ariaLabel]);
7057
+ return /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)(
5582
7058
  "div",
5583
7059
  {
5584
7060
  ref: containerRef,
5585
- className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
5586
- style: {
5587
- ...containerStyle
5588
- },
7061
+ className: `relative ${className}`,
7062
+ style: { width, height: "auto" },
5589
7063
  children: [
5590
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("style", { children: `
5591
- /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
5592
- .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
5593
- .${axisLabelClass} { display: none; }
5594
- .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
5595
- .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
5596
-
5597
- /* Tablet: Medium fonts, show axis titles, medium lines */
5598
- @media (min-width: 640px) {
5599
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
5600
- .${axisLabelClass} {
5601
- font-size: ${axisLabelFontSize * 0.7}px !important;
5602
- display: block;
7064
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)(
7065
+ "svg",
7066
+ {
7067
+ width,
7068
+ height,
7069
+ viewBox: `0 0 ${width} ${height}`,
7070
+ role: "img",
7071
+ "aria-label": accessibleDescription,
7072
+ className: "bg-white dark:bg-gray-900",
7073
+ children: [
7074
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime114.jsx)("style", { children: `
7075
+ @keyframes growY {
7076
+ from { transform: scaleY(0); }
7077
+ to { transform: scaleY(1); }
7078
+ }
7079
+ @keyframes growX {
7080
+ from { transform: scaleX(0); }
7081
+ to { transform: scaleX(1); }
7082
+ }
7083
+ @keyframes fadeIn {
7084
+ from { opacity: 0; }
7085
+ to { opacity: 1; }
7086
+ }
7087
+ ` }) }),
7088
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
7089
+ showGrid && /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7090
+ CartesianGrid,
7091
+ {
7092
+ _chartDimensions: { width: chartWidth, height: chartHeight },
7093
+ horizontal: !horizontal,
7094
+ vertical: horizontal,
7095
+ horizontalPoints: horizontal ? void 0 : yTicks.map((t) => yScale(t)),
7096
+ verticalPoints: horizontal ? yTicks.map((t) => yScale(t)) : void 0
7097
+ }
7098
+ ),
7099
+ referenceElements,
7100
+ bars,
7101
+ showXAxis && /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)("g", { transform: `translate(0, ${chartHeight})`, children: [
7102
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7103
+ "line",
7104
+ {
7105
+ x1: 0,
7106
+ y1: 0,
7107
+ x2: chartWidth,
7108
+ y2: 0,
7109
+ className: "stroke-gray-300 dark:stroke-gray-600"
7110
+ }
7111
+ ),
7112
+ categories.map((cat, i) => {
7113
+ const x = xBandScale.scale(cat) + bandwidth / 2;
7114
+ return /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)("g", { transform: `translate(${x}, 0)`, children: [
7115
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-600" }),
7116
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7117
+ "text",
7118
+ {
7119
+ y: 20,
7120
+ textAnchor: "middle",
7121
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7122
+ className: "fill-gray-600 dark:fill-gray-400",
7123
+ children: cat
7124
+ }
7125
+ )
7126
+ ] }, `x-tick-${i}`);
7127
+ }),
7128
+ xAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7129
+ "text",
7130
+ {
7131
+ x: chartWidth / 2,
7132
+ y: 50,
7133
+ textAnchor: "middle",
7134
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
7135
+ fontWeight: "500",
7136
+ className: "fill-gray-700 dark:fill-gray-300",
7137
+ children: xAxisLabel
7138
+ }
7139
+ )
7140
+ ] }),
7141
+ showYAxis && /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)("g", { children: [
7142
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7143
+ "line",
7144
+ {
7145
+ x1: 0,
7146
+ y1: 0,
7147
+ x2: 0,
7148
+ y2: chartHeight,
7149
+ className: "stroke-gray-300 dark:stroke-gray-600"
7150
+ }
7151
+ ),
7152
+ yTicks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime114.jsxs)("g", { transform: `translate(0, ${yScale(tick)})`, children: [
7153
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)("line", { x2: -6, className: "stroke-gray-300 dark:stroke-gray-600" }),
7154
+ /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7155
+ "text",
7156
+ {
7157
+ x: -12,
7158
+ textAnchor: "end",
7159
+ dominantBaseline: "middle",
7160
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7161
+ className: "fill-gray-600 dark:fill-gray-400",
7162
+ children: yFormatter(tick)
7163
+ }
7164
+ )
7165
+ ] }, `y-tick-${i}`)),
7166
+ yAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7167
+ "text",
7168
+ {
7169
+ x: -chartHeight / 2,
7170
+ y: -45,
7171
+ textAnchor: "middle",
7172
+ transform: "rotate(-90)",
7173
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
7174
+ fontWeight: "500",
7175
+ className: "fill-gray-700 dark:fill-gray-300",
7176
+ children: yAxisLabel
7177
+ }
7178
+ )
7179
+ ] })
7180
+ ] })
7181
+ ]
5603
7182
  }
5604
- .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
5605
- .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
5606
- }
5607
-
5608
- /* Desktop: Small fonts, show axis titles, normal lines */
5609
- @media (min-width: 1024px) {
5610
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
5611
- .${axisLabelClass} {
5612
- font-size: ${axisLabelFontSize * 0.4}px !important;
5613
- display: block;
7183
+ ),
7184
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(Legend, { items: legendItems, layout: "horizontal", align: "center" }),
7185
+ showTooltip && /* @__PURE__ */ (0, import_jsx_runtime114.jsx)(
7186
+ ChartTooltip,
7187
+ {
7188
+ ...tooltipData,
7189
+ formatter: tooltipFormatter,
7190
+ containerBounds: { width, height }
5614
7191
  }
5615
- .${lineClass} { stroke-width: ${strokeWidth} !important; }
5616
- .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
7192
+ )
7193
+ ]
7194
+ }
7195
+ );
7196
+ };
7197
+
7198
+ // src/charts/AreaChart.tsx
7199
+ var import_react30 = __toESM(require("react"));
7200
+ var import_jsx_runtime115 = require("react/jsx-runtime");
7201
+ var AreaChart = ({
7202
+ data,
7203
+ width: providedWidth,
7204
+ height: providedHeight,
7205
+ padding: customPadding,
7206
+ showGrid = true,
7207
+ showXAxis = true,
7208
+ showYAxis = true,
7209
+ showLegend = true,
7210
+ showTooltip = true,
7211
+ showDots = false,
7212
+ stacked = false,
7213
+ fillOpacity = 0.3,
7214
+ curved = true,
7215
+ animate = true,
7216
+ animationDuration = CHART_DEFAULTS.animation.duration,
7217
+ xAxisLabel,
7218
+ yAxisLabel,
7219
+ yAxisTickCount = 5,
7220
+ yDomain,
7221
+ formatXValue,
7222
+ formatYValue,
7223
+ tooltipFormatter,
7224
+ onPointClick,
7225
+ onPointHover,
7226
+ className = "",
7227
+ children,
7228
+ colors = CHART_DEFAULTS.colors,
7229
+ ariaLabel
7230
+ }) => {
7231
+ const containerRef = (0, import_react30.useRef)(null);
7232
+ const [hoveredPoint, setHoveredPoint] = (0, import_react30.useState)(null);
7233
+ const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
7234
+ const width = providedWidth || 800;
7235
+ const height = providedHeight || 400;
7236
+ const padding = (0, import_react30.useMemo)(() => ({
7237
+ top: customPadding?.top ?? CHART_DEFAULTS.padding.top,
7238
+ right: customPadding?.right ?? CHART_DEFAULTS.padding.right,
7239
+ bottom: customPadding?.bottom ?? (showXAxis ? 60 : CHART_DEFAULTS.padding.bottom),
7240
+ left: customPadding?.left ?? (showYAxis ? 60 : CHART_DEFAULTS.padding.left)
7241
+ }), [customPadding, showXAxis, showYAxis]);
7242
+ const chartWidth = width - padding.left - padding.right;
7243
+ const chartHeight = height - padding.top - padding.bottom;
7244
+ const allPoints = (0, import_react30.useMemo)(
7245
+ () => data.flatMap((series) => series.data),
7246
+ [data]
7247
+ );
7248
+ const xValueType = (0, import_react30.useMemo)(() => {
7249
+ const firstX = data[0]?.data[0]?.x;
7250
+ if (firstX instanceof Date) return "date";
7251
+ if (typeof firstX === "number") return "number";
7252
+ return "string";
7253
+ }, [data]);
7254
+ const xDomainCalc = (0, import_react30.useMemo)(() => {
7255
+ if (xValueType === "date") {
7256
+ const dates = allPoints.map((p) => p.x.getTime());
7257
+ return [Math.min(...dates), Math.max(...dates)];
7258
+ }
7259
+ if (xValueType === "number") {
7260
+ const nums = allPoints.map((p) => p.x);
7261
+ return [Math.min(...nums), Math.max(...nums)];
7262
+ }
7263
+ const maxLen = Math.max(...data.map((s) => s.data.length));
7264
+ return [0, maxLen - 1];
7265
+ }, [allPoints, xValueType, data]);
7266
+ const yDomainCalc = (0, import_react30.useMemo)(() => {
7267
+ if (yDomain) return yDomain;
7268
+ if (stacked) {
7269
+ const maxPoints = Math.max(...data.map((s) => s.data.length));
7270
+ const maxStacked = Array.from({ length: maxPoints }, (_, i) => {
7271
+ return data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0);
7272
+ });
7273
+ return calculateDomain([0, ...maxStacked], { includeZero: true, padding: 0.1 });
7274
+ }
7275
+ const yValues = allPoints.map((p) => p.y);
7276
+ return calculateDomain(yValues, { includeZero: true, padding: 0.1 });
7277
+ }, [allPoints, stacked, data, yDomain]);
7278
+ const xScale = (0, import_react30.useMemo)(() => {
7279
+ if (xValueType === "date") {
7280
+ return (value) => {
7281
+ const time = value instanceof Date ? value.getTime() : Number(value);
7282
+ return scaleLinear({
7283
+ domain: xDomainCalc,
7284
+ range: [0, chartWidth]
7285
+ })(time);
7286
+ };
7287
+ }
7288
+ if (xValueType === "number") {
7289
+ const scale = scaleLinear({
7290
+ domain: xDomainCalc,
7291
+ range: [0, chartWidth]
7292
+ });
7293
+ return (value) => scale(Number(value));
7294
+ }
7295
+ return (_value, index = 0) => {
7296
+ const maxLen = Math.max(...data.map((s) => s.data.length)) - 1;
7297
+ if (maxLen === 0) return chartWidth / 2;
7298
+ return index / maxLen * chartWidth;
7299
+ };
7300
+ }, [xValueType, xDomainCalc, chartWidth, data]);
7301
+ const yScale = (0, import_react30.useMemo)(
7302
+ () => scaleLinear({
7303
+ domain: yDomainCalc,
7304
+ range: [chartHeight, 0]
7305
+ }),
7306
+ [yDomainCalc, chartHeight]
7307
+ );
7308
+ const yTicks = (0, import_react30.useMemo)(
7309
+ () => getTicks(yDomainCalc, yAxisTickCount),
7310
+ [yDomainCalc, yAxisTickCount]
7311
+ );
7312
+ const xTicks = (0, import_react30.useMemo)(() => {
7313
+ if (xValueType === "string") {
7314
+ return data[0]?.data.map((p, i) => ({ value: p.x, index: i })) || [];
7315
+ }
7316
+ const tickCount = Math.min(6, Math.max(2, data[0]?.data.length || 2));
7317
+ const ticks = getTicks(xDomainCalc, tickCount);
7318
+ return ticks.map((t) => ({ value: t, index: 0 }));
7319
+ }, [xValueType, xDomainCalc, data]);
7320
+ const xFormatter = (0, import_react30.useCallback)((value) => {
7321
+ if (formatXValue) return formatXValue(value);
7322
+ if (value instanceof Date) {
7323
+ const range = xDomainCalc[1] - xDomainCalc[0];
7324
+ return formatDate(value, range);
7325
+ }
7326
+ return String(value);
7327
+ }, [formatXValue, xDomainCalc]);
7328
+ const yFormatter = (0, import_react30.useMemo)(() => {
7329
+ if (formatYValue) return formatYValue;
7330
+ return createTickFormatter(yDomainCalc);
7331
+ }, [formatYValue, yDomainCalc]);
7332
+ const areaPaths = (0, import_react30.useMemo)(() => {
7333
+ const paths = [];
7334
+ const cumulativeY = Array(data[0]?.data.length || 0).fill(0);
7335
+ data.forEach((series) => {
7336
+ const seriesType = series.type;
7337
+ const isCurved = seriesType !== void 0 ? seriesType === "monotone" : curved;
7338
+ const points = series.data.map((point, i) => {
7339
+ const x = xScale(point.x, i);
7340
+ const baseY = stacked ? cumulativeY[i] : 0;
7341
+ const y = yScale(baseY + point.y);
7342
+ return { x, y, baseY: yScale(baseY) };
7343
+ });
7344
+ if (stacked) {
7345
+ series.data.forEach((point, i) => {
7346
+ cumulativeY[i] += point.y;
7347
+ });
7348
+ }
7349
+ const linePoints = points.map((p) => ({ x: p.x, y: p.y }));
7350
+ const linePath = isCurved ? generateMonotonePath(linePoints) : generateLinePath(linePoints);
7351
+ let areaPath;
7352
+ if (stacked && paths.length > 0) {
7353
+ const prevPoints = paths[paths.length - 1].points;
7354
+ areaPath = generateStackedAreaPath(linePoints, prevPoints, isCurved);
7355
+ } else {
7356
+ areaPath = generateAreaPath(linePoints, yScale(0), isCurved);
7357
+ }
7358
+ paths.push({
7359
+ areaPath,
7360
+ linePath,
7361
+ points: linePoints
7362
+ });
7363
+ });
7364
+ return paths;
7365
+ }, [data, xScale, yScale, stacked, curved]);
7366
+ const handlePointEnter = (0, import_react30.useCallback)((e, seriesIndex, pointIndex) => {
7367
+ const series = data[seriesIndex];
7368
+ const point = series.data[pointIndex];
7369
+ setHoveredPoint({ seriesIndex, pointIndex });
7370
+ if (showTooltip && containerRef.current) {
7371
+ const rect = containerRef.current.getBoundingClientRect();
7372
+ const mouseX = e.clientX - rect.left;
7373
+ const mouseY = e.clientY - rect.top;
7374
+ const payload = [{
7375
+ name: series.name,
7376
+ value: point.y,
7377
+ color: series.color || colors[seriesIndex % colors.length],
7378
+ payload: point
7379
+ }];
7380
+ showTooltipFn({
7381
+ x: mouseX,
7382
+ y: mouseY,
7383
+ label: xFormatter(point.x),
7384
+ payload
7385
+ });
7386
+ }
7387
+ onPointHover?.(point, seriesIndex, pointIndex);
7388
+ }, [data, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
7389
+ const handlePointLeave = (0, import_react30.useCallback)(() => {
7390
+ setHoveredPoint(null);
7391
+ hideTooltip();
7392
+ onPointHover?.(null, -1, -1);
7393
+ }, [hideTooltip, onPointHover]);
7394
+ const handlePointClick = (0, import_react30.useCallback)((seriesIndex, pointIndex) => {
7395
+ const point = data[seriesIndex].data[pointIndex];
7396
+ onPointClick?.(point, seriesIndex, pointIndex);
7397
+ }, [data, onPointClick]);
7398
+ const legendItems = (0, import_react30.useMemo)(
7399
+ () => data.map((series, i) => ({
7400
+ name: series.name,
7401
+ color: series.color || colors[i % colors.length],
7402
+ type: "square"
7403
+ })),
7404
+ [data, colors]
7405
+ );
7406
+ const referenceElements = (0, import_react30.useMemo)(() => {
7407
+ if (!children) return null;
7408
+ const chartDimensions = { width: chartWidth, height: chartHeight, padding };
7409
+ const scales = {
7410
+ xScale: (value) => {
7411
+ if (xValueType === "string") {
7412
+ const index = data[0]?.data.findIndex((p) => p.x === value) ?? 0;
7413
+ return xScale(value, index);
5617
7414
  }
5618
- ` }),
5619
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
7415
+ return xScale(value, 0);
7416
+ },
7417
+ yScale
7418
+ };
7419
+ return import_react30.default.Children.map(children, (child) => {
7420
+ if (!import_react30.default.isValidElement(child)) return child;
7421
+ if (child.type === ReferenceLine || child.type === ReferenceArea || child.type === CartesianGrid) {
7422
+ return import_react30.default.cloneElement(child, {
7423
+ _chartDimensions: chartDimensions,
7424
+ _scales: scales
7425
+ });
7426
+ }
7427
+ return child;
7428
+ });
7429
+ }, [children, chartWidth, chartHeight, padding, xScale, yScale, xValueType, data]);
7430
+ const accessibleDescription = (0, import_react30.useMemo)(() => {
7431
+ if (ariaLabel) return ariaLabel;
7432
+ const seriesNames = data.map((s) => s.name).join(", ");
7433
+ return `Area chart with ${data.length} series: ${seriesNames}`;
7434
+ }, [data, ariaLabel]);
7435
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)(
7436
+ "div",
7437
+ {
7438
+ ref: containerRef,
7439
+ className: `relative ${className}`,
7440
+ style: { width, height: "auto" },
7441
+ children: [
7442
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)(
5620
7443
  "svg",
5621
7444
  {
5622
- ref: svgRef,
5623
- width: "100%",
5624
- viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
5625
- preserveAspectRatio: "xMidYMid meet",
5626
- className: "bg-white dark:bg-gray-800 block w-full",
5627
- style: { height: "auto", overflow: "visible" },
5628
- children: /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
5629
- gridLines,
5630
- data.map((series, seriesIndex) => /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5631
- "path",
5632
- {
5633
- d: generatePath(series),
5634
- fill: "none",
5635
- stroke: series.color || defaultColors[seriesIndex % defaultColors.length],
5636
- strokeWidth,
5637
- strokeLinecap: "round",
5638
- strokeLinejoin: "round",
5639
- className: lineClass
5640
- },
5641
- `line-${seriesIndex}`
5642
- )),
5643
- showDots && data.map(
5644
- (series, seriesIndex) => series.data.map((point, pointIndex) => {
5645
- const x = scaleX(point.x, pointIndex);
5646
- const y = scaleY(point.y);
5647
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
5648
- return /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5649
- "circle",
7445
+ width,
7446
+ height,
7447
+ viewBox: `0 0 ${width} ${height}`,
7448
+ role: "img",
7449
+ "aria-label": accessibleDescription,
7450
+ className: "bg-white dark:bg-gray-900",
7451
+ children: [
7452
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("defs", { children: [
7453
+ data.map((series, i) => {
7454
+ const color = series.color || colors[i % colors.length];
7455
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)(
7456
+ "linearGradient",
5650
7457
  {
5651
- cx: x,
5652
- cy: y,
5653
- r: isHovered ? 5 : 3,
5654
- fill: series.color || defaultColors[seriesIndex % defaultColors.length],
5655
- stroke: "white",
5656
- strokeWidth: "1.5",
5657
- className: `cursor-pointer transition-all ${pointClass}`,
5658
- onMouseEnter: (e) => {
5659
- if (showTooltip && containerRef.current) {
5660
- setHoveredPoint({ seriesIndex, pointIndex, x, y });
5661
- const containerRect = containerRef.current.getBoundingClientRect();
5662
- const mouseX = e.clientX - containerRect.left;
5663
- const mouseY = e.clientY - containerRect.top;
5664
- setTooltipPosition(getTooltipPosition(mouseX, mouseY));
5665
- }
7458
+ id: `area-gradient-${i}`,
7459
+ x1: "0",
7460
+ y1: "0",
7461
+ x2: "0",
7462
+ y2: "1",
7463
+ children: [
7464
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("stop", { offset: "0%", stopColor: color, stopOpacity: series.fillOpacity ?? fillOpacity }),
7465
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("stop", { offset: "100%", stopColor: color, stopOpacity: 0.05 })
7466
+ ]
7467
+ },
7468
+ `gradient-${i}`
7469
+ );
7470
+ }),
7471
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("style", { children: `
7472
+ @keyframes fadeIn {
7473
+ from { opacity: 0; }
7474
+ to { opacity: 1; }
7475
+ }
7476
+ @keyframes drawPath {
7477
+ from { stroke-dashoffset: 2000; }
7478
+ to { stroke-dashoffset: 0; }
7479
+ }
7480
+ ` })
7481
+ ] }),
7482
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
7483
+ showGrid && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7484
+ CartesianGrid,
7485
+ {
7486
+ _chartDimensions: { width: chartWidth, height: chartHeight },
7487
+ horizontalPoints: yTicks.map((t) => yScale(t))
7488
+ }
7489
+ ),
7490
+ referenceElements,
7491
+ [...data].reverse().map((series, reversedIndex) => {
7492
+ const seriesIndex = data.length - 1 - reversedIndex;
7493
+ const { areaPath, linePath } = areaPaths[seriesIndex];
7494
+ const color = series.color || colors[seriesIndex % colors.length];
7495
+ const strokeWidth = series.strokeWidth || CHART_DEFAULTS.line.strokeWidth;
7496
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { children: [
7497
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7498
+ "path",
7499
+ {
7500
+ d: areaPath,
7501
+ fill: `url(#area-gradient-${seriesIndex})`,
7502
+ style: animate ? {
7503
+ opacity: 0,
7504
+ animation: `fadeIn ${animationDuration}ms ease-out forwards`
7505
+ } : void 0
7506
+ }
7507
+ ),
7508
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7509
+ "path",
7510
+ {
7511
+ d: linePath,
7512
+ fill: "none",
7513
+ stroke: color,
7514
+ strokeWidth,
7515
+ strokeLinecap: "round",
7516
+ strokeLinejoin: "round",
7517
+ style: animate ? {
7518
+ strokeDasharray: 2e3,
7519
+ animation: `drawPath ${animationDuration}ms ease-out forwards`
7520
+ } : void 0
7521
+ }
7522
+ )
7523
+ ] }, `area-${seriesIndex}`);
7524
+ }),
7525
+ showDots && data.map((series, seriesIndex) => {
7526
+ const { points } = areaPaths[seriesIndex];
7527
+ const color = series.color || colors[seriesIndex % colors.length];
7528
+ const showSeriesDots = series.dot !== false;
7529
+ if (!showSeriesDots) return null;
7530
+ return points.map((point, pointIndex) => {
7531
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
7532
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7533
+ "circle",
7534
+ {
7535
+ cx: point.x,
7536
+ cy: point.y,
7537
+ r: isHovered ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
7538
+ fill: color,
7539
+ stroke: "white",
7540
+ strokeWidth: 2,
7541
+ className: "cursor-pointer transition-all duration-150",
7542
+ style: animate ? {
7543
+ opacity: 0,
7544
+ animation: `fadeIn 200ms ease-out ${animationDuration + pointIndex * 20}ms forwards`
7545
+ } : void 0,
7546
+ onMouseEnter: (e) => handlePointEnter(e, seriesIndex, pointIndex),
7547
+ onMouseLeave: handlePointLeave,
7548
+ onClick: () => handlePointClick(seriesIndex, pointIndex)
5666
7549
  },
5667
- onMouseMove: (e) => {
5668
- if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
5669
- const containerRect = containerRef.current.getBoundingClientRect();
5670
- const mouseX = e.clientX - containerRect.left;
5671
- const mouseY = e.clientY - containerRect.top;
5672
- setTooltipPosition(getTooltipPosition(mouseX, mouseY));
7550
+ `dot-${seriesIndex}-${pointIndex}`
7551
+ );
7552
+ });
7553
+ }),
7554
+ showXAxis && /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { transform: `translate(0, ${chartHeight})`, children: [
7555
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7556
+ "line",
7557
+ {
7558
+ x1: 0,
7559
+ y1: 0,
7560
+ x2: chartWidth,
7561
+ y2: 0,
7562
+ className: "stroke-gray-300 dark:stroke-gray-600"
7563
+ }
7564
+ ),
7565
+ xTicks.map((tick, i) => {
7566
+ const x = xValueType === "string" ? xScale(tick.value, tick.index) : xScale(xValueType === "date" ? new Date(tick.value) : tick.value, 0);
7567
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { transform: `translate(${x}, 0)`, children: [
7568
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-600" }),
7569
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7570
+ "text",
7571
+ {
7572
+ y: 20,
7573
+ textAnchor: "middle",
7574
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7575
+ className: "fill-gray-600 dark:fill-gray-400",
7576
+ children: xFormatter(xValueType === "date" ? new Date(tick.value) : tick.value)
5673
7577
  }
5674
- },
5675
- onMouseLeave: () => {
5676
- setHoveredPoint(null);
5677
- setTooltipPosition(null);
7578
+ )
7579
+ ] }, `x-tick-${i}`);
7580
+ }),
7581
+ xAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7582
+ "text",
7583
+ {
7584
+ x: chartWidth / 2,
7585
+ y: 50,
7586
+ textAnchor: "middle",
7587
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
7588
+ fontWeight: "500",
7589
+ className: "fill-gray-700 dark:fill-gray-300",
7590
+ children: xAxisLabel
7591
+ }
7592
+ )
7593
+ ] }),
7594
+ showYAxis && /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { children: [
7595
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7596
+ "line",
7597
+ {
7598
+ x1: 0,
7599
+ y1: 0,
7600
+ x2: 0,
7601
+ y2: chartHeight,
7602
+ className: "stroke-gray-300 dark:stroke-gray-600"
7603
+ }
7604
+ ),
7605
+ yTicks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("g", { transform: `translate(0, ${yScale(tick)})`, children: [
7606
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("line", { x2: -6, className: "stroke-gray-300 dark:stroke-gray-600" }),
7607
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7608
+ "text",
7609
+ {
7610
+ x: -12,
7611
+ textAnchor: "end",
7612
+ dominantBaseline: "middle",
7613
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7614
+ className: "fill-gray-600 dark:fill-gray-400",
7615
+ children: yFormatter(tick)
5678
7616
  }
5679
- },
5680
- `point-${seriesIndex}-${pointIndex}`
5681
- );
5682
- })
5683
- ),
5684
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5685
- "text",
5686
- {
5687
- x: chartWidth / 2,
5688
- y: chartHeight + 80,
5689
- textAnchor: "middle",
5690
- dominantBaseline: "hanging",
5691
- fontSize: axisLabelFontSize,
5692
- fontWeight: "600",
5693
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5694
- children: xAxisLabel
5695
- }
5696
- ),
5697
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5698
- "text",
5699
- {
5700
- x: -chartHeight / 2,
5701
- y: -100,
5702
- textAnchor: "middle",
5703
- dominantBaseline: "middle",
5704
- fontSize: axisLabelFontSize,
5705
- fontWeight: "600",
5706
- transform: "rotate(-90)",
5707
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
5708
- children: yAxisLabel
5709
- }
5710
- )
5711
- ] })
7617
+ )
7618
+ ] }, `y-tick-${i}`)),
7619
+ yAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7620
+ "text",
7621
+ {
7622
+ x: -chartHeight / 2,
7623
+ y: -45,
7624
+ textAnchor: "middle",
7625
+ transform: "rotate(-90)",
7626
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
7627
+ fontWeight: "500",
7628
+ className: "fill-gray-700 dark:fill-gray-300",
7629
+ children: yAxisLabel
7630
+ }
7631
+ )
7632
+ ] })
7633
+ ] })
7634
+ ]
5712
7635
  }
5713
7636
  ),
5714
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
5715
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)(
5716
- "div",
5717
- {
5718
- className: "w-3 h-3 rounded-sm flex-shrink-0",
5719
- style: {
5720
- backgroundColor: series.color || defaultColors[index % defaultColors.length]
5721
- }
5722
- }
5723
- ),
5724
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
5725
- ] }, `legend-${index}`)) }),
5726
- showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)(
5727
- "div",
7637
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(Legend, { items: legendItems, layout: "horizontal", align: "center" }),
7638
+ showTooltip && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(
7639
+ ChartTooltip,
5728
7640
  {
5729
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
5730
- style: {
5731
- left: `${tooltipPosition.x}px`,
5732
- top: `${tooltipPosition.y}px`,
5733
- transform: "translateZ(0)"
5734
- },
5735
- children: [
5736
- /* @__PURE__ */ (0, import_jsx_runtime108.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
5737
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "text-xs opacity-90", children: [
5738
- "x: ",
5739
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
5740
- ] }),
5741
- /* @__PURE__ */ (0, import_jsx_runtime108.jsxs)("div", { className: "text-xs opacity-90", children: [
5742
- "y: ",
5743
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
5744
- ] })
5745
- ]
7641
+ ...tooltipData,
7642
+ formatter: tooltipFormatter,
7643
+ containerBounds: { width, height }
5746
7644
  }
5747
7645
  )
5748
7646
  ]
@@ -5750,797 +7648,718 @@ var LineChart = ({
5750
7648
  );
5751
7649
  };
5752
7650
 
5753
- // src/components/BarChart.tsx
5754
- var import_react27 = __toESM(require("react"));
5755
- var import_jsx_runtime109 = require("react/jsx-runtime");
5756
- var defaultColors2 = [
5757
- "#3b82f6",
5758
- // blue-500
5759
- "#10b981",
5760
- // green-500
5761
- "#f59e0b",
5762
- // amber-500
5763
- "#ef4444",
5764
- // red-500
5765
- "#8b5cf6",
5766
- // violet-500
5767
- "#ec4899"
5768
- // pink-500
5769
- ];
5770
- var BarChart = ({
7651
+ // src/charts/PieChart.tsx
7652
+ var import_react31 = require("react");
7653
+ var import_jsx_runtime116 = require("react/jsx-runtime");
7654
+ var PieChart = ({
5771
7655
  data,
5772
7656
  width: providedWidth,
5773
7657
  height: providedHeight,
5774
- aspectRatio = 16 / 9,
5775
- responsive = true,
5776
- showGrid = true,
5777
- showXAxis = true,
5778
- showYAxis = true,
7658
+ innerRadius: providedInnerRadius,
7659
+ outerRadius: providedOuterRadius,
7660
+ donut = false,
7661
+ paddingAngle = 0,
7662
+ startAngle = 0,
7663
+ endAngle = 360,
5779
7664
  showLegend = true,
5780
7665
  showTooltip = true,
5781
- showValues = false,
5782
- stacked = false,
5783
- horizontal = false,
5784
- barWidth = 0.8,
7666
+ showLabels = true,
7667
+ labelType = "percent",
7668
+ labelFormatter,
7669
+ showPercentages = false,
7670
+ minLabelPercent = 5,
7671
+ animate = true,
7672
+ animationDuration = CHART_DEFAULTS.animation.duration,
7673
+ centerLabel,
7674
+ centerValue,
7675
+ tooltipFormatter,
7676
+ onSliceClick,
7677
+ onSliceHover,
5785
7678
  className = "",
5786
- xAxisLabel,
5787
- yAxisLabel,
5788
- baseFontSize = 14,
5789
- colors = defaultColors2
7679
+ colors = CHART_DEFAULTS.colors,
7680
+ ariaLabel
5790
7681
  }) => {
5791
- const viewBoxWidth = 1e3;
5792
- const viewBoxHeight = 600;
5793
- const containerRef = import_react27.default.useRef(null);
5794
- const [tooltipPosition, setTooltipPosition] = import_react27.default.useState(null);
5795
- const [hoveredBar, setHoveredBar] = import_react27.default.useState(null);
5796
- const chartId = import_react27.default.useId();
5797
- const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
5798
- const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
5799
- const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
5800
- const barClass = `chart-bar-${chartId.replace(/:/g, "-")}`;
5801
- const padding = {
5802
- top: 50,
5803
- right: 50,
5804
- bottom: showXAxis ? horizontal ? 120 : 140 : 50,
5805
- left: showYAxis ? horizontal ? 140 : 130 : 50
5806
- };
5807
- const chartWidth = viewBoxWidth - padding.left - padding.right;
5808
- const chartHeight = viewBoxHeight - padding.top - padding.bottom;
5809
- const gridLabelFontSize = viewBoxWidth * 0.045;
5810
- const axisLabelFontSize = viewBoxWidth * 0.05;
5811
- const titleFontSize = viewBoxWidth * 0.055;
5812
- const allYValues = stacked ? data[0]?.data.map(
5813
- (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
5814
- ) || [] : data.flatMap((series) => series.data.map((p) => p.y));
5815
- const minY = Math.min(0, ...allYValues);
5816
- const maxY = Math.max(...allYValues);
5817
- const yRange = maxY - minY;
5818
- const yMin = Math.max(0, minY - yRange * 0.1);
5819
- const yMax = maxY + yRange * 0.1;
5820
- const numBars = data[0]?.data.length || 0;
5821
- const numSeries = data.length;
5822
- const scaleY = (y) => {
5823
- if (yMax === yMin) return chartHeight / 2;
5824
- return chartHeight - (y - yMin) / (yMax - yMin) * chartHeight;
5825
- };
5826
- const barGroupWidth = chartWidth / numBars;
5827
- const barPadding = barGroupWidth * (1 - barWidth) / 2;
5828
- const actualBarWidth = stacked ? barGroupWidth * barWidth : barGroupWidth * barWidth / numSeries;
5829
- const gridLines = [];
5830
- const numGridLines = 5;
5831
- if (showGrid) {
5832
- for (let i = 0; i <= numGridLines; i++) {
5833
- const y = chartHeight / numGridLines * i;
5834
- const yValue = yMax - (yMax - yMin) / numGridLines * i;
5835
- gridLines.push(
5836
- /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("g", { children: [
5837
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5838
- "line",
5839
- {
5840
- x1: 0,
5841
- y1: y,
5842
- x2: chartWidth,
5843
- y2: y,
5844
- stroke: "currentColor",
5845
- strokeWidth: "0.5",
5846
- className: "text-gray-200 dark:text-gray-700",
5847
- strokeDasharray: "4,4"
5848
- }
5849
- ),
5850
- showYAxis && !horizontal && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5851
- "text",
5852
- {
5853
- x: -25,
5854
- y,
5855
- textAnchor: "end",
5856
- dominantBaseline: "middle",
5857
- fontSize: gridLabelFontSize,
5858
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5859
- children: yValue.toFixed(0)
5860
- }
5861
- )
5862
- ] }, `grid-h-${i}`)
7682
+ const containerRef = (0, import_react31.useRef)(null);
7683
+ const [hoveredSlice, setHoveredSlice] = (0, import_react31.useState)(null);
7684
+ const [activeSlice, setActiveSlice] = (0, import_react31.useState)(null);
7685
+ const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
7686
+ const width = providedWidth || 400;
7687
+ const height = providedHeight || 400;
7688
+ const size = Math.min(width, height);
7689
+ const centerX = width / 2;
7690
+ const centerY = height / 2;
7691
+ const maxRadius = size / 2 - 20;
7692
+ const outerRadius = providedOuterRadius || maxRadius;
7693
+ const innerRadius = providedInnerRadius ?? (donut ? outerRadius * 0.6 : 0);
7694
+ const isDonut = innerRadius > 0;
7695
+ const total = (0, import_react31.useMemo)(
7696
+ () => data.reduce((sum, d) => sum + d.value, 0),
7697
+ [data]
7698
+ );
7699
+ const slices = (0, import_react31.useMemo)(() => {
7700
+ const angleRange = endAngle - startAngle;
7701
+ let currentAngle = startAngle;
7702
+ return data.map((item, index) => {
7703
+ const percent = total > 0 ? item.value / total * 100 : 0;
7704
+ const sliceAngle = item.value / total * angleRange;
7705
+ const actualStartAngle = currentAngle + paddingAngle / 2;
7706
+ const actualEndAngle = currentAngle + sliceAngle - paddingAngle / 2;
7707
+ const path = generateArcPath(
7708
+ centerX,
7709
+ centerY,
7710
+ outerRadius,
7711
+ actualStartAngle,
7712
+ actualEndAngle,
7713
+ innerRadius
5863
7714
  );
7715
+ const midAngle = currentAngle + sliceAngle / 2;
7716
+ const labelRadius = isDonut ? innerRadius + (outerRadius - innerRadius) / 2 : outerRadius * 0.65;
7717
+ const labelRad = (midAngle - 90) * Math.PI / 180;
7718
+ const labelX = centerX + labelRadius * Math.cos(labelRad);
7719
+ const labelY = centerY + labelRadius * Math.sin(labelRad);
7720
+ currentAngle += sliceAngle;
7721
+ return {
7722
+ ...item,
7723
+ index,
7724
+ path,
7725
+ percent,
7726
+ startAngle: actualStartAngle,
7727
+ endAngle: actualEndAngle,
7728
+ midAngle,
7729
+ labelX,
7730
+ labelY,
7731
+ color: item.color || colors[index % colors.length]
7732
+ };
7733
+ });
7734
+ }, [data, total, startAngle, endAngle, paddingAngle, centerX, centerY, outerRadius, innerRadius, isDonut, colors]);
7735
+ const handleSliceEnter = (0, import_react31.useCallback)((e, index) => {
7736
+ const slice = slices[index];
7737
+ setHoveredSlice(index);
7738
+ if (showTooltip && containerRef.current) {
7739
+ const rect = containerRef.current.getBoundingClientRect();
7740
+ const mouseX = e.clientX - rect.left;
7741
+ const mouseY = e.clientY - rect.top;
7742
+ const payload = [{
7743
+ name: slice.label,
7744
+ value: slice.value,
7745
+ color: slice.color,
7746
+ payload: data[index]
7747
+ }];
7748
+ showTooltipFn({
7749
+ x: mouseX,
7750
+ y: mouseY,
7751
+ label: slice.label,
7752
+ payload
7753
+ });
5864
7754
  }
5865
- if (horizontal) {
5866
- for (let i = 0; i <= numGridLines; i++) {
5867
- const x = chartWidth / numGridLines * i;
5868
- gridLines.push(
5869
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5870
- "line",
5871
- {
5872
- x1: x,
5873
- y1: 0,
5874
- x2: x,
5875
- y2: chartHeight,
5876
- stroke: "currentColor",
5877
- strokeWidth: "1",
5878
- className: "text-gray-200 dark:text-gray-700",
5879
- strokeDasharray: "2,2"
5880
- },
5881
- `grid-v-${i}`
5882
- )
5883
- );
5884
- }
7755
+ onSliceHover?.(data[index], index);
7756
+ }, [slices, data, showTooltip, showTooltipFn, onSliceHover]);
7757
+ const handleSliceLeave = (0, import_react31.useCallback)(() => {
7758
+ setHoveredSlice(null);
7759
+ hideTooltip();
7760
+ onSliceHover?.(null, -1);
7761
+ }, [hideTooltip, onSliceHover]);
7762
+ const handleSliceClick = (0, import_react31.useCallback)((index) => {
7763
+ setActiveSlice((prev) => prev === index ? null : index);
7764
+ onSliceClick?.(data[index], index);
7765
+ }, [data, onSliceClick]);
7766
+ const getLabelText = (0, import_react31.useCallback)((slice) => {
7767
+ if (labelFormatter) {
7768
+ return labelFormatter(data[slice.index], slice.percent);
5885
7769
  }
5886
- }
5887
- const renderBars = () => {
5888
- const bars = [];
5889
- if (stacked) {
5890
- for (let barIndex = 0; barIndex < numBars; barIndex++) {
5891
- let cumulativeY = 0;
5892
- const xPos = barIndex * barGroupWidth + barPadding;
5893
- const barW = barGroupWidth * barWidth;
5894
- for (let seriesIndex = 0; seriesIndex < numSeries; seriesIndex++) {
5895
- const point = data[seriesIndex].data[barIndex];
5896
- if (!point) continue;
5897
- const barHeight = Math.abs(scaleY(point.y) - scaleY(0));
5898
- const yPos = scaleY(cumulativeY + point.y);
5899
- const isHovered = hoveredBar?.seriesIndex === seriesIndex && hoveredBar?.barIndex === barIndex;
5900
- bars.push(
5901
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5902
- "rect",
5903
- {
5904
- x: xPos,
5905
- y: yPos,
5906
- width: barW,
5907
- height: Math.abs(scaleY(0) - yPos),
5908
- fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5909
- className: `cursor-pointer transition-opacity ${barClass}`,
5910
- opacity: isHovered ? 0.8 : 1,
5911
- onMouseEnter: () => {
5912
- if (showTooltip) {
5913
- setHoveredBar({ seriesIndex, barIndex, x: xPos + barW / 2, y: yPos });
5914
- setTooltipPosition(getTooltipPosition(padding.left + xPos + barW / 2, padding.top + yPos));
5915
- }
5916
- },
5917
- onMouseLeave: () => {
5918
- setHoveredBar(null);
5919
- setTooltipPosition(null);
5920
- }
5921
- },
5922
- `bar-${seriesIndex}-${barIndex}`
5923
- )
5924
- );
5925
- cumulativeY += point.y;
5926
- }
5927
- }
5928
- } else {
5929
- for (let barIndex = 0; barIndex < numBars; barIndex++) {
5930
- for (let seriesIndex = 0; seriesIndex < numSeries; seriesIndex++) {
5931
- const point = data[seriesIndex].data[barIndex];
5932
- if (!point) continue;
5933
- const xPos = barIndex * barGroupWidth + barPadding + seriesIndex * actualBarWidth;
5934
- const barHeight = Math.abs(scaleY(point.y) - scaleY(0));
5935
- const yPos = scaleY(Math.max(0, point.y));
5936
- const isHovered = hoveredBar?.seriesIndex === seriesIndex && hoveredBar?.barIndex === barIndex;
5937
- bars.push(
5938
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5939
- "rect",
5940
- {
5941
- x: xPos,
5942
- y: yPos,
5943
- width: actualBarWidth,
5944
- height: barHeight,
5945
- fill: data[seriesIndex].color || colors[seriesIndex % colors.length],
5946
- className: `cursor-pointer transition-opacity ${barClass}`,
5947
- opacity: isHovered ? 0.8 : 1,
5948
- onMouseEnter: () => showTooltip && setHoveredBar({ seriesIndex, barIndex, x: xPos + actualBarWidth / 2, y: yPos }),
5949
- onMouseLeave: () => setHoveredBar(null)
5950
- },
5951
- `bar-${seriesIndex}-${barIndex}`
5952
- )
5953
- );
5954
- if (showValues) {
5955
- bars.push(
5956
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5957
- "text",
5958
- {
5959
- x: xPos + actualBarWidth / 2,
5960
- y: yPos - 10,
5961
- textAnchor: "middle",
5962
- dominantBaseline: "middle",
5963
- fontSize: "11",
5964
- fontWeight: "600",
5965
- className: "fill-gray-700 dark:fill-gray-300",
5966
- children: point.y
5967
- },
5968
- `value-${seriesIndex}-${barIndex}`
5969
- )
5970
- );
5971
- }
5972
- }
5973
- }
7770
+ switch (labelType) {
7771
+ case "percent":
7772
+ return `${slice.percent.toFixed(1)}%`;
7773
+ case "value":
7774
+ return formatNumber(slice.value);
7775
+ case "name":
7776
+ return slice.label;
7777
+ default:
7778
+ return `${slice.percent.toFixed(1)}%`;
5974
7779
  }
5975
- return bars;
5976
- };
5977
- const xAxisLabels = data[0]?.data.map((point, i) => {
5978
- const xPos = i * barGroupWidth + barGroupWidth / 2;
5979
- return /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
5980
- "text",
5981
- {
5982
- x: xPos,
5983
- y: chartHeight + 35,
5984
- textAnchor: "middle",
5985
- dominantBaseline: "hanging",
5986
- fontSize: gridLabelFontSize,
5987
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
5988
- children: point.x
5989
- },
5990
- `x-label-${i}`
5991
- );
5992
- });
5993
- const getTooltipPosition = import_react27.default.useCallback((x, y) => {
5994
- if (!containerRef.current) return { x, y };
5995
- const rect = containerRef.current.getBoundingClientRect();
5996
- const tooltipWidth = 140;
5997
- const tooltipHeight = 80;
5998
- let tooltipX = x + 10;
5999
- let tooltipY = y - 30;
6000
- if (tooltipX + tooltipWidth > rect.width) {
6001
- tooltipX = x - tooltipWidth - 10;
6002
- }
6003
- if (tooltipY + tooltipHeight > rect.height) {
6004
- tooltipY = y + 30;
6005
- }
6006
- return { x: tooltipX, y: tooltipY };
6007
- }, []);
6008
- const containerStyle = {
6009
- "--font-size-base": `${baseFontSize}px`,
6010
- "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6011
- "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6012
- "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
6013
- };
6014
- return /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
7780
+ }, [labelType, labelFormatter, data]);
7781
+ const legendItems = (0, import_react31.useMemo)(
7782
+ () => data.map((item, i) => ({
7783
+ name: `${item.label}${showPercentages ? ` (${(item.value / total * 100).toFixed(1)}%)` : ""}`,
7784
+ color: item.color || colors[i % colors.length],
7785
+ type: "square"
7786
+ })),
7787
+ [data, colors, total, showPercentages]
7788
+ );
7789
+ const accessibleDescription = (0, import_react31.useMemo)(() => {
7790
+ if (ariaLabel) return ariaLabel;
7791
+ const sliceNames = data.map((d) => `${d.label}: ${d.value}`).join(", ");
7792
+ return `${isDonut ? "Donut" : "Pie"} chart with ${data.length} slices: ${sliceNames}`;
7793
+ }, [data, isDonut, ariaLabel]);
7794
+ return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
6015
7795
  "div",
6016
7796
  {
6017
7797
  ref: containerRef,
6018
- className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6019
- style: {
6020
- ...containerStyle
6021
- },
7798
+ className: `relative inline-flex flex-col items-center ${className}`,
7799
+ style: { width },
6022
7800
  children: [
6023
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("style", { children: `
6024
- /* Mobile: Large fonts, hidden axis titles, thicker bars (default) */
6025
- .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6026
- .${axisLabelClass} { display: none; }
6027
-
6028
- /* Tablet: Medium fonts, show axis titles */
6029
- @media (min-width: 640px) {
6030
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6031
- .${axisLabelClass} {
6032
- font-size: ${axisLabelFontSize * 0.7}px !important;
6033
- display: block;
6034
- }
6035
- }
6036
-
6037
- /* Desktop: Small fonts, show axis titles */
6038
- @media (min-width: 1024px) {
6039
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6040
- .${axisLabelClass} {
6041
- font-size: ${axisLabelFontSize * 0.4}px !important;
6042
- display: block;
6043
- }
6044
- }
6045
- ` }),
6046
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
7801
+ /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
6047
7802
  "svg",
6048
7803
  {
6049
- width: "100%",
6050
- viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6051
- preserveAspectRatio: "xMidYMid meet",
6052
- className: "bg-white dark:bg-gray-800 block w-full",
6053
- style: { height: "auto", overflow: "visible" },
6054
- children: /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
6055
- gridLines,
6056
- renderBars(),
6057
- showXAxis && xAxisLabels,
6058
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
6059
- "text",
6060
- {
6061
- x: chartWidth / 2,
6062
- y: chartHeight + 80,
6063
- textAnchor: "middle",
6064
- dominantBaseline: "hanging",
6065
- fontSize: axisLabelFontSize,
6066
- fontWeight: "600",
6067
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6068
- children: xAxisLabel
6069
- }
6070
- ),
6071
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
6072
- "text",
6073
- {
6074
- x: -chartHeight / 2,
6075
- y: -100,
6076
- textAnchor: "middle",
6077
- dominantBaseline: "middle",
6078
- fontSize: axisLabelFontSize,
6079
- fontWeight: "600",
6080
- transform: "rotate(-90)",
6081
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6082
- children: yAxisLabel
6083
- }
6084
- )
6085
- ] })
6086
- }
6087
- ),
6088
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
6089
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)(
6090
- "div",
6091
- {
6092
- className: "w-3 h-3 rounded-sm flex-shrink-0",
6093
- style: {
6094
- backgroundColor: series.color || colors[index % colors.length]
7804
+ width,
7805
+ height,
7806
+ viewBox: `0 0 ${width} ${height}`,
7807
+ role: "img",
7808
+ "aria-label": accessibleDescription,
7809
+ className: "bg-white dark:bg-gray-900",
7810
+ children: [
7811
+ /* @__PURE__ */ (0, import_jsx_runtime116.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime116.jsx)("style", { children: `
7812
+ @keyframes rotateIn {
7813
+ from {
7814
+ transform: rotate(-90deg);
7815
+ opacity: 0;
7816
+ }
7817
+ to {
7818
+ transform: rotate(0deg);
7819
+ opacity: 1;
6095
7820
  }
6096
7821
  }
6097
- ),
6098
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
6099
- ] }, `legend-${index}`)) }),
6100
- showTooltip && hoveredBar && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(
6101
- "div",
6102
- {
6103
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
6104
- style: {
6105
- left: `${tooltipPosition.x}px`,
6106
- top: `${tooltipPosition.y}px`,
6107
- transform: "translateZ(0)"
6108
- },
6109
- children: [
6110
- /* @__PURE__ */ (0, import_jsx_runtime109.jsx)("div", { className: "font-semibold", children: data[hoveredBar.seriesIndex].name }),
6111
- /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("div", { className: "text-xs opacity-90", children: [
6112
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].x,
6113
- ": ",
6114
- data[hoveredBar.seriesIndex].data[hoveredBar.barIndex].y
7822
+ @keyframes fadeIn {
7823
+ from { opacity: 0; }
7824
+ to { opacity: 1; }
7825
+ }
7826
+ ` }) }),
7827
+ /* @__PURE__ */ (0, import_jsx_runtime116.jsx)("g", { style: animate ? {
7828
+ transformOrigin: `${centerX}px ${centerY}px`,
7829
+ animation: `rotateIn ${animationDuration}ms ease-out forwards`
7830
+ } : void 0, children: slices.map((slice) => {
7831
+ const isHovered = hoveredSlice === slice.index;
7832
+ const isActive = activeSlice === slice.index;
7833
+ const scale = isHovered || isActive ? 1.03 : 1;
7834
+ const transform = `translate(${centerX}px, ${centerY}px) scale(${scale}) translate(${-centerX}px, ${-centerY}px)`;
7835
+ return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)("g", { children: [
7836
+ /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7837
+ "path",
7838
+ {
7839
+ d: slice.path,
7840
+ fill: slice.color,
7841
+ stroke: "white",
7842
+ strokeWidth: 2,
7843
+ className: "cursor-pointer transition-transform duration-150",
7844
+ style: { transform },
7845
+ onMouseEnter: (e) => handleSliceEnter(e, slice.index),
7846
+ onMouseLeave: handleSliceLeave,
7847
+ onClick: () => handleSliceClick(slice.index)
7848
+ }
7849
+ ),
7850
+ showLabels && slice.percent >= minLabelPercent && /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7851
+ "text",
7852
+ {
7853
+ x: slice.labelX,
7854
+ y: slice.labelY,
7855
+ textAnchor: "middle",
7856
+ dominantBaseline: "middle",
7857
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7858
+ fontWeight: "600",
7859
+ className: "fill-white dark:fill-white pointer-events-none",
7860
+ style: animate ? {
7861
+ opacity: 0,
7862
+ animation: `fadeIn 200ms ease-out ${animationDuration}ms forwards`
7863
+ } : void 0,
7864
+ children: getLabelText(slice)
7865
+ }
7866
+ )
7867
+ ] }, slice.index);
7868
+ }) }),
7869
+ isDonut && (centerLabel || centerValue !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)("g", { children: [
7870
+ centerValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7871
+ "text",
7872
+ {
7873
+ x: centerX,
7874
+ y: centerY - (centerLabel ? 8 : 0),
7875
+ textAnchor: "middle",
7876
+ dominantBaseline: "middle",
7877
+ fontSize: CHART_DEFAULTS.fontSize.title,
7878
+ fontWeight: "700",
7879
+ className: "fill-gray-900 dark:fill-gray-100",
7880
+ children: typeof centerValue === "number" ? formatNumber(centerValue) : centerValue
7881
+ }
7882
+ ),
7883
+ centerLabel && (typeof centerLabel === "string" ? /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7884
+ "text",
7885
+ {
7886
+ x: centerX,
7887
+ y: centerY + (centerValue !== void 0 ? 16 : 0),
7888
+ textAnchor: "middle",
7889
+ dominantBaseline: "middle",
7890
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
7891
+ className: "fill-gray-500 dark:fill-gray-400",
7892
+ children: centerLabel
7893
+ }
7894
+ ) : /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7895
+ "foreignObject",
7896
+ {
7897
+ x: centerX - innerRadius * 0.7,
7898
+ y: centerY - innerRadius * 0.3,
7899
+ width: innerRadius * 1.4,
7900
+ height: innerRadius * 0.6,
7901
+ children: centerLabel
7902
+ }
7903
+ ))
6115
7904
  ] })
6116
7905
  ]
6117
7906
  }
7907
+ ),
7908
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7909
+ Legend,
7910
+ {
7911
+ items: legendItems,
7912
+ layout: "horizontal",
7913
+ align: "center",
7914
+ onClick: (item, index) => handleSliceClick(index),
7915
+ onMouseEnter: (item, index) => setHoveredSlice(index),
7916
+ onMouseLeave: () => setHoveredSlice(null)
7917
+ }
7918
+ ),
7919
+ showTooltip && /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
7920
+ ChartTooltip,
7921
+ {
7922
+ ...tooltipData,
7923
+ formatter: tooltipFormatter || ((value) => `${formatNumber(value)} (${formatPercent(value / total * 100)})`),
7924
+ containerBounds: { width, height }
7925
+ }
6118
7926
  )
6119
7927
  ]
6120
7928
  }
6121
7929
  );
6122
7930
  };
6123
7931
 
6124
- // src/components/AreaChart.tsx
6125
- var import_react28 = __toESM(require("react"));
6126
- var import_jsx_runtime110 = require("react/jsx-runtime");
6127
- var defaultColors3 = [
6128
- "#3b82f6",
6129
- // blue-500
6130
- "#10b981",
6131
- // green-500
6132
- "#f59e0b",
6133
- // amber-500
6134
- "#ef4444",
6135
- // red-500
6136
- "#8b5cf6",
6137
- // violet-500
6138
- "#ec4899"
6139
- // pink-500
6140
- ];
6141
- var AreaChart = ({
7932
+ // src/charts/ScatterChart.tsx
7933
+ var import_react32 = __toESM(require("react"));
7934
+ var import_jsx_runtime117 = require("react/jsx-runtime");
7935
+ function linearRegression(points) {
7936
+ const n = points.length;
7937
+ if (n === 0) return { slope: 0, intercept: 0 };
7938
+ let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
7939
+ for (const p of points) {
7940
+ sumX += p.x;
7941
+ sumY += p.y;
7942
+ sumXY += p.x * p.y;
7943
+ sumXX += p.x * p.x;
7944
+ }
7945
+ const denominator = n * sumXX - sumX * sumX;
7946
+ if (denominator === 0) return { slope: 0, intercept: sumY / n };
7947
+ const slope = (n * sumXY - sumX * sumY) / denominator;
7948
+ const intercept = (sumY - slope * sumX) / n;
7949
+ return { slope, intercept };
7950
+ }
7951
+ var renderShape = (shape, x, y, size, color, commonProps) => {
7952
+ const { className, stroke, strokeWidth, onMouseEnter, onMouseLeave, onClick } = commonProps;
7953
+ switch (shape) {
7954
+ case "square":
7955
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
7956
+ "rect",
7957
+ {
7958
+ x: x - size / 2,
7959
+ y: y - size / 2,
7960
+ width: size,
7961
+ height: size,
7962
+ fill: color,
7963
+ className,
7964
+ stroke,
7965
+ strokeWidth,
7966
+ onMouseEnter,
7967
+ onMouseLeave,
7968
+ onClick
7969
+ }
7970
+ );
7971
+ case "triangle": {
7972
+ const h = size * 0.866;
7973
+ const points = `${x},${y - h / 2} ${x - size / 2},${y + h / 2} ${x + size / 2},${y + h / 2}`;
7974
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
7975
+ "polygon",
7976
+ {
7977
+ points,
7978
+ fill: color,
7979
+ className,
7980
+ stroke,
7981
+ strokeWidth,
7982
+ onMouseEnter,
7983
+ onMouseLeave,
7984
+ onClick
7985
+ }
7986
+ );
7987
+ }
7988
+ case "diamond": {
7989
+ const d = size / 1.414;
7990
+ const diamondPoints = `${x},${y - d} ${x + d},${y} ${x},${y + d} ${x - d},${y}`;
7991
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
7992
+ "polygon",
7993
+ {
7994
+ points: diamondPoints,
7995
+ fill: color,
7996
+ className,
7997
+ stroke,
7998
+ strokeWidth,
7999
+ onMouseEnter,
8000
+ onMouseLeave,
8001
+ onClick
8002
+ }
8003
+ );
8004
+ }
8005
+ case "circle":
8006
+ default:
8007
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8008
+ "circle",
8009
+ {
8010
+ cx: x,
8011
+ cy: y,
8012
+ r: size / 2,
8013
+ fill: color,
8014
+ className,
8015
+ stroke,
8016
+ strokeWidth,
8017
+ onMouseEnter,
8018
+ onMouseLeave,
8019
+ onClick
8020
+ }
8021
+ );
8022
+ }
8023
+ };
8024
+ var ScatterChart = ({
6142
8025
  data,
6143
8026
  width: providedWidth,
6144
8027
  height: providedHeight,
6145
- aspectRatio = 16 / 9,
6146
- responsive = true,
8028
+ padding: customPadding,
6147
8029
  showGrid = true,
6148
8030
  showXAxis = true,
6149
8031
  showYAxis = true,
6150
8032
  showLegend = true,
6151
8033
  showTooltip = true,
6152
- showDots = false,
6153
- curved = true,
6154
- stacked = false,
6155
- fillOpacity = 0.3,
6156
- strokeWidth = 2,
6157
- className = "",
8034
+ bubble = false,
8035
+ minBubbleSize = 8,
8036
+ maxBubbleSize = 40,
8037
+ pointSize = 8,
8038
+ animate = true,
8039
+ animationDuration = CHART_DEFAULTS.animation.duration,
6158
8040
  xAxisLabel,
6159
8041
  yAxisLabel,
6160
- baseFontSize = 14
8042
+ xAxisTickCount = 5,
8043
+ yAxisTickCount = 5,
8044
+ xDomain,
8045
+ yDomain,
8046
+ formatXValue,
8047
+ formatYValue,
8048
+ tooltipFormatter,
8049
+ onPointClick,
8050
+ onPointHover,
8051
+ showTrendLine = false,
8052
+ className = "",
8053
+ children,
8054
+ colors = CHART_DEFAULTS.colors,
8055
+ ariaLabel
6161
8056
  }) => {
6162
- const viewBoxWidth = 1e3;
6163
- const viewBoxHeight = 600;
6164
- const containerRef = import_react28.default.useRef(null);
6165
- const svgRef = import_react28.default.useRef(null);
6166
- const [tooltipPosition, setTooltipPosition] = import_react28.default.useState(null);
6167
- const [hoveredPoint, setHoveredPoint] = import_react28.default.useState(null);
6168
- const chartId = import_react28.default.useId();
6169
- const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
6170
- const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
6171
- const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
6172
- const lineClass = `chart-line-${chartId.replace(/:/g, "-")}`;
6173
- const pointClass = `chart-point-${chartId.replace(/:/g, "-")}`;
6174
- const padding = {
6175
- top: 50,
6176
- right: 50,
6177
- bottom: showXAxis ? 120 : 50,
6178
- left: showYAxis ? 130 : 50
6179
- };
6180
- const chartWidth = viewBoxWidth - padding.left - padding.right;
6181
- const chartHeight = viewBoxHeight - padding.top - padding.bottom;
6182
- const gridLabelFontSize = viewBoxWidth * 0.045;
6183
- const axisLabelFontSize = viewBoxWidth * 0.05;
6184
- const titleFontSize = viewBoxWidth * 0.055;
6185
- const allPoints = data.flatMap((series) => series.data);
6186
- const allYValues = stacked ? data[0]?.data.map(
6187
- (_, i) => data.reduce((sum, series) => sum + (series.data[i]?.y || 0), 0)
6188
- ) || [] : allPoints.map((p) => p.y);
6189
- const firstXValue = data[0]?.data[0]?.x;
6190
- const isStringX = typeof firstXValue === "string";
6191
- const uniqueXValues = isStringX ? Array.from(new Set(allPoints.map((p) => p.x))) : [];
6192
- const allXNumbers = isStringX ? uniqueXValues.map((_, idx) => idx) : allPoints.map((p) => p.x);
6193
- const minX = Math.min(...allXNumbers);
6194
- const maxX = Math.max(...allXNumbers);
6195
- const minY = Math.min(0, ...allYValues);
6196
- const maxY = Math.max(...allYValues);
6197
- const yRange = maxY - minY;
6198
- const yMin = Math.max(0, minY - yRange * 0.1);
6199
- const yMax = maxY + yRange * 0.1;
6200
- const scaleX = (x, index) => {
6201
- const numX = isStringX ? index : x;
6202
- if (maxX === minX) return chartWidth / 2;
6203
- return (numX - minX) / (maxX - minX) * chartWidth;
6204
- };
6205
- const scaleY = (y) => {
6206
- if (yMax === yMin) return chartHeight / 2;
6207
- return chartHeight - (y - yMin) / (yMax - yMin) * chartHeight;
6208
- };
6209
- const generateAreaPath = (series, baselineYValues) => {
6210
- if (series.data.length === 0) return "";
6211
- const points = series.data.map((point, i) => {
6212
- const x = scaleX(point.x, i);
6213
- const baseY = baselineYValues ? scaleY(baselineYValues[i]) : scaleY(0);
6214
- const y = baselineYValues ? scaleY(baselineYValues[i] + point.y) : scaleY(point.y);
6215
- return { x, y, baseY, originalIndex: i };
8057
+ const containerRef = (0, import_react32.useRef)(null);
8058
+ const [hoveredPoint, setHoveredPoint] = (0, import_react32.useState)(null);
8059
+ const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
8060
+ const width = providedWidth || 800;
8061
+ const height = providedHeight || 400;
8062
+ const padding = (0, import_react32.useMemo)(() => ({
8063
+ top: customPadding?.top ?? CHART_DEFAULTS.padding.top,
8064
+ right: customPadding?.right ?? CHART_DEFAULTS.padding.right,
8065
+ bottom: customPadding?.bottom ?? (showXAxis ? 60 : CHART_DEFAULTS.padding.bottom),
8066
+ left: customPadding?.left ?? (showYAxis ? 60 : CHART_DEFAULTS.padding.left)
8067
+ }), [customPadding, showXAxis, showYAxis]);
8068
+ const chartWidth = width - padding.left - padding.right;
8069
+ const chartHeight = height - padding.top - padding.bottom;
8070
+ const allPoints = (0, import_react32.useMemo)(
8071
+ () => data.flatMap((series) => series.data),
8072
+ [data]
8073
+ );
8074
+ const xDomainCalc = (0, import_react32.useMemo)(() => {
8075
+ if (xDomain) return xDomain;
8076
+ const xValues = allPoints.map((p) => p.x);
8077
+ return calculateDomain(xValues, { includeZero: false, padding: 0.1 });
8078
+ }, [allPoints, xDomain]);
8079
+ const yDomainCalc = (0, import_react32.useMemo)(() => {
8080
+ if (yDomain) return yDomain;
8081
+ const yValues = allPoints.map((p) => p.y);
8082
+ return calculateDomain(yValues, { includeZero: false, padding: 0.1 });
8083
+ }, [allPoints, yDomain]);
8084
+ const zDomain = (0, import_react32.useMemo)(() => {
8085
+ if (!bubble) return [0, 1];
8086
+ const zValues = allPoints.map((p) => p.z || 0).filter((z) => z > 0);
8087
+ if (zValues.length === 0) return [0, 1];
8088
+ return [Math.min(...zValues), Math.max(...zValues)];
8089
+ }, [allPoints, bubble]);
8090
+ const xScale = (0, import_react32.useMemo)(
8091
+ () => scaleLinear({
8092
+ domain: xDomainCalc,
8093
+ range: [0, chartWidth]
8094
+ }),
8095
+ [xDomainCalc, chartWidth]
8096
+ );
8097
+ const yScale = (0, import_react32.useMemo)(
8098
+ () => scaleLinear({
8099
+ domain: yDomainCalc,
8100
+ range: [chartHeight, 0]
8101
+ }),
8102
+ [yDomainCalc, chartHeight]
8103
+ );
8104
+ const sizeScale = (0, import_react32.useMemo)(() => {
8105
+ if (!bubble) return () => pointSize;
8106
+ return scaleLinear({
8107
+ domain: zDomain,
8108
+ range: [minBubbleSize, maxBubbleSize]
6216
8109
  });
6217
- if (curved) {
6218
- let topPath = `M ${points[0].x} ${points[0].y}`;
6219
- for (let i = 0; i < points.length - 1; i++) {
6220
- const current = points[i];
6221
- const next = points[i + 1];
6222
- const controlPointX = (current.x + next.x) / 2;
6223
- topPath += ` C ${controlPointX} ${current.y}, ${controlPointX} ${next.y}, ${next.x} ${next.y}`;
6224
- }
6225
- let bottomPath = "";
6226
- for (let i = points.length - 1; i >= 0; i--) {
6227
- const point = points[i];
6228
- if (i === points.length - 1) {
6229
- bottomPath += ` L ${point.x} ${point.baseY}`;
6230
- } else {
6231
- const next = points[i + 1];
6232
- const controlPointX = (point.x + next.x) / 2;
6233
- bottomPath += ` C ${controlPointX} ${next.baseY}, ${controlPointX} ${point.baseY}, ${point.x} ${point.baseY}`;
6234
- }
6235
- }
6236
- return topPath + bottomPath + " Z";
6237
- } else {
6238
- const topPath = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
6239
- const bottomPath = points.slice().reverse().map((p) => `L ${p.x} ${p.baseY}`).join(" ");
6240
- return topPath + " " + bottomPath + " Z";
6241
- }
6242
- };
6243
- const generateLinePath = (series, baselineYValues) => {
6244
- if (series.data.length === 0) return "";
6245
- const points = series.data.map((point, i) => {
6246
- const x = scaleX(point.x, i);
6247
- const y = baselineYValues ? scaleY(baselineYValues[i] + point.y) : scaleY(point.y);
6248
- return { x, y };
8110
+ }, [bubble, zDomain, minBubbleSize, maxBubbleSize, pointSize]);
8111
+ const xTicks = (0, import_react32.useMemo)(() => getTicks(xDomainCalc, xAxisTickCount), [xDomainCalc, xAxisTickCount]);
8112
+ const yTicks = (0, import_react32.useMemo)(() => getTicks(yDomainCalc, yAxisTickCount), [yDomainCalc, yAxisTickCount]);
8113
+ const xFormatter = (0, import_react32.useMemo)(() => formatXValue || createTickFormatter(xDomainCalc), [formatXValue, xDomainCalc]);
8114
+ const yFormatter = (0, import_react32.useMemo)(() => formatYValue || createTickFormatter(yDomainCalc), [formatYValue, yDomainCalc]);
8115
+ const trendLines = (0, import_react32.useMemo)(() => {
8116
+ if (!showTrendLine) return [];
8117
+ return data.map((series) => {
8118
+ const regression = linearRegression(series.data);
8119
+ const x1 = xDomainCalc[0];
8120
+ const x2 = xDomainCalc[1];
8121
+ const y1 = regression.slope * x1 + regression.intercept;
8122
+ const y2 = regression.slope * x2 + regression.intercept;
8123
+ return {
8124
+ x1: xScale(x1),
8125
+ y1: yScale(y1),
8126
+ x2: xScale(x2),
8127
+ y2: yScale(y2)
8128
+ };
6249
8129
  });
6250
- if (curved) {
6251
- let path = `M ${points[0].x} ${points[0].y}`;
6252
- for (let i = 0; i < points.length - 1; i++) {
6253
- const current = points[i];
6254
- const next = points[i + 1];
6255
- const controlPointX = (current.x + next.x) / 2;
6256
- path += ` C ${controlPointX} ${current.y}, ${controlPointX} ${next.y}, ${next.x} ${next.y}`;
8130
+ }, [data, showTrendLine, xDomainCalc, xScale, yScale]);
8131
+ const handlePointEnter = (0, import_react32.useCallback)((e, seriesIndex, pointIndex) => {
8132
+ const series = data[seriesIndex];
8133
+ const point = series.data[pointIndex];
8134
+ setHoveredPoint({ seriesIndex, pointIndex });
8135
+ if (showTooltip && containerRef.current) {
8136
+ const rect = containerRef.current.getBoundingClientRect();
8137
+ const mouseX = e.clientX - rect.left;
8138
+ const mouseY = e.clientY - rect.top;
8139
+ const payload = [
8140
+ { name: "X", value: point.x, color: series.color || colors[seriesIndex % colors.length] },
8141
+ { name: "Y", value: point.y, color: series.color || colors[seriesIndex % colors.length] }
8142
+ ];
8143
+ if (bubble && point.z !== void 0) {
8144
+ payload.push({ name: "Size", value: point.z, color: series.color || colors[seriesIndex % colors.length] });
6257
8145
  }
6258
- return path;
6259
- } else {
6260
- return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
6261
- }
6262
- };
6263
- const gridLines = [];
6264
- const numGridLines = 5;
6265
- if (showGrid) {
6266
- for (let i = 0; i <= numGridLines; i++) {
6267
- const y = chartHeight / numGridLines * i;
6268
- const yValue = yMax - (yMax - yMin) / numGridLines * i;
6269
- gridLines.push(
6270
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { children: [
6271
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6272
- "line",
6273
- {
6274
- x1: 0,
6275
- y1: y,
6276
- x2: chartWidth,
6277
- y2: y,
6278
- stroke: "currentColor",
6279
- strokeWidth: "0.5",
6280
- className: "text-gray-200 dark:text-gray-700",
6281
- strokeDasharray: "4,4"
6282
- }
6283
- ),
6284
- showYAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6285
- "text",
6286
- {
6287
- x: -25,
6288
- y,
6289
- textAnchor: "end",
6290
- dominantBaseline: "middle",
6291
- fontSize: gridLabelFontSize,
6292
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6293
- children: yValue.toFixed(1)
6294
- }
6295
- )
6296
- ] }, `grid-h-${i}`)
6297
- );
6298
- }
6299
- const numXGridLines = Math.min(allPoints.length, 6);
6300
- for (let i = 0; i < numXGridLines; i++) {
6301
- const x = chartWidth / (numXGridLines - 1) * i;
6302
- const xValue = data[0]?.data[Math.floor((data[0].data.length - 1) * (i / (numXGridLines - 1)))]?.x || "";
6303
- gridLines.push(
6304
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { children: [
6305
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6306
- "line",
6307
- {
6308
- x1: x,
6309
- y1: 0,
6310
- x2: x,
6311
- y2: chartHeight,
6312
- stroke: "currentColor",
6313
- strokeWidth: "0.5",
6314
- className: "text-gray-200 dark:text-gray-700",
6315
- strokeDasharray: "4,4"
6316
- }
6317
- ),
6318
- showXAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6319
- "text",
6320
- {
6321
- x,
6322
- y: chartHeight + 35,
6323
- textAnchor: "middle",
6324
- dominantBaseline: "hanging",
6325
- fontSize: gridLabelFontSize,
6326
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6327
- children: xValue
6328
- }
6329
- )
6330
- ] }, `grid-v-${i}`)
6331
- );
8146
+ showTooltipFn({
8147
+ x: mouseX,
8148
+ y: mouseY,
8149
+ label: point.label || series.name,
8150
+ payload
8151
+ });
6332
8152
  }
6333
- }
6334
- let cumulativeValues = [];
6335
- if (stacked) {
6336
- cumulativeValues = Array(data[0]?.data.length || 0).fill(0);
6337
- }
6338
- const getTooltipPosition = import_react28.default.useCallback((x, y) => {
6339
- if (!containerRef.current) return { x, y };
6340
- const rect = containerRef.current.getBoundingClientRect();
6341
- const tooltipWidth = 120;
6342
- const tooltipHeight = 80;
6343
- let tooltipX = x + 10;
6344
- let tooltipY = y - 30;
6345
- if (tooltipX + tooltipWidth > rect.width) {
6346
- tooltipX = x - tooltipWidth - 10;
6347
- }
6348
- if (tooltipY + tooltipHeight > rect.height) {
6349
- tooltipY = y + 30;
6350
- }
6351
- return { x: tooltipX, y: tooltipY };
6352
- }, []);
6353
- const containerStyle = {
6354
- "--font-size-base": `${baseFontSize}px`,
6355
- "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6356
- "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6357
- "--font-size-xs": `calc(var(--font-size-base) * 0.75)`
6358
- };
6359
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)(
8153
+ onPointHover?.(point, seriesIndex, pointIndex);
8154
+ }, [data, showTooltip, colors, bubble, showTooltipFn, onPointHover]);
8155
+ const handlePointLeave = (0, import_react32.useCallback)(() => {
8156
+ setHoveredPoint(null);
8157
+ hideTooltip();
8158
+ onPointHover?.(null, -1, -1);
8159
+ }, [hideTooltip, onPointHover]);
8160
+ const handlePointClick = (0, import_react32.useCallback)((seriesIndex, pointIndex) => {
8161
+ const point = data[seriesIndex].data[pointIndex];
8162
+ onPointClick?.(point, seriesIndex, pointIndex);
8163
+ }, [data, onPointClick]);
8164
+ const legendItems = (0, import_react32.useMemo)(
8165
+ () => data.map((series, i) => ({
8166
+ name: series.name,
8167
+ color: series.color || colors[i % colors.length],
8168
+ type: series.shape || "circle"
8169
+ })),
8170
+ [data, colors]
8171
+ );
8172
+ const referenceElements = (0, import_react32.useMemo)(() => {
8173
+ if (!children) return null;
8174
+ const chartDimensions = { width: chartWidth, height: chartHeight, padding };
8175
+ const scales = { xScale, yScale };
8176
+ return import_react32.default.Children.map(children, (child) => {
8177
+ if (!import_react32.default.isValidElement(child)) return child;
8178
+ if (child.type === ReferenceLine || child.type === ReferenceArea || child.type === CartesianGrid) {
8179
+ return import_react32.default.cloneElement(child, {
8180
+ _chartDimensions: chartDimensions,
8181
+ _scales: scales
8182
+ });
8183
+ }
8184
+ return child;
8185
+ });
8186
+ }, [children, chartWidth, chartHeight, padding, xScale, yScale]);
8187
+ const accessibleDescription = (0, import_react32.useMemo)(() => {
8188
+ if (ariaLabel) return ariaLabel;
8189
+ const totalPoints = data.reduce((sum, s) => sum + s.data.length, 0);
8190
+ return `Scatter chart with ${data.length} series and ${totalPoints} data points`;
8191
+ }, [data, ariaLabel]);
8192
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)(
6360
8193
  "div",
6361
8194
  {
6362
8195
  ref: containerRef,
6363
- className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
6364
- style: containerStyle,
8196
+ className: `relative ${className}`,
8197
+ style: { width, height: "auto" },
6365
8198
  children: [
6366
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("style", { children: `
6367
- /* Mobile: Large fonts, hidden axis titles, thicker lines (default) */
6368
- .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6369
- .${axisLabelClass} { display: none; }
6370
- .${lineClass} { stroke-width: ${strokeWidth * 2.5} !important; }
6371
- .${pointClass} { r: 6 !important; stroke-width: 3 !important; }
6372
-
6373
- /* Tablet: Medium fonts, show axis titles, medium lines */
6374
- @media (min-width: 640px) {
6375
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6376
- .${axisLabelClass} {
6377
- font-size: ${axisLabelFontSize * 0.7}px !important;
6378
- display: block;
6379
- }
6380
- .${lineClass} { stroke-width: ${strokeWidth * 1.5} !important; }
6381
- .${pointClass} { r: 4 !important; stroke-width: 2 !important; }
6382
- }
6383
-
6384
- /* Desktop: Small fonts, show axis titles, normal lines */
6385
- @media (min-width: 1024px) {
6386
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6387
- .${axisLabelClass} {
6388
- font-size: ${axisLabelFontSize * 0.4}px !important;
6389
- display: block;
6390
- }
6391
- .${lineClass} { stroke-width: ${strokeWidth} !important; }
6392
- .${pointClass} { r: 3 !important; stroke-width: 1.5 !important; }
6393
- }
6394
- ` }),
6395
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
8199
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)(
6396
8200
  "svg",
6397
8201
  {
6398
- ref: svgRef,
6399
- width: "100%",
6400
- viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6401
- preserveAspectRatio: "xMidYMid meet",
6402
- className: "bg-white dark:bg-gray-800 block w-full",
6403
- style: { height: "auto", overflow: "visible" },
6404
- children: /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
6405
- gridLines,
6406
- data.map((series, seriesIndex) => {
6407
- const baselineYValues = stacked ? [...cumulativeValues] : void 0;
6408
- const areaPath = generateAreaPath(series, baselineYValues);
6409
- const linePath = generateLinePath(series, baselineYValues);
6410
- if (stacked) {
6411
- series.data.forEach((point, i) => {
6412
- cumulativeValues[i] += point.y;
8202
+ width,
8203
+ height,
8204
+ viewBox: `0 0 ${width} ${height}`,
8205
+ role: "img",
8206
+ "aria-label": accessibleDescription,
8207
+ className: "bg-white dark:bg-gray-900",
8208
+ children: [
8209
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime117.jsx)("style", { children: `
8210
+ @keyframes popIn {
8211
+ from {
8212
+ transform: scale(0);
8213
+ opacity: 0;
8214
+ }
8215
+ to {
8216
+ transform: scale(1);
8217
+ opacity: 1;
8218
+ }
8219
+ }
8220
+ ` }) }),
8221
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)("g", { transform: `translate(${padding.left}, ${padding.top})`, children: [
8222
+ showGrid && /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8223
+ CartesianGrid,
8224
+ {
8225
+ _chartDimensions: { width: chartWidth, height: chartHeight },
8226
+ horizontalPoints: yTicks.map((t) => yScale(t)),
8227
+ verticalPoints: xTicks.map((t) => xScale(t))
8228
+ }
8229
+ ),
8230
+ referenceElements,
8231
+ showTrendLine && trendLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8232
+ "line",
8233
+ {
8234
+ x1: line.x1,
8235
+ y1: line.y1,
8236
+ x2: line.x2,
8237
+ y2: line.y2,
8238
+ stroke: data[i].color || colors[i % colors.length],
8239
+ strokeWidth: 2,
8240
+ strokeDasharray: "5 5",
8241
+ opacity: 0.5
8242
+ },
8243
+ `trend-${i}`
8244
+ )),
8245
+ data.map((series, seriesIndex) => {
8246
+ const color = series.color || colors[seriesIndex % colors.length];
8247
+ const shape = series.shape || "circle";
8248
+ const baseSize = series.size || pointSize;
8249
+ return series.data.map((point, pointIndex) => {
8250
+ const x = xScale(point.x);
8251
+ const y = yScale(point.y);
8252
+ const size = bubble && point.z !== void 0 ? sizeScale(point.z) : baseSize;
8253
+ const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
8254
+ const displaySize = isHovered ? size * 1.3 : size;
8255
+ return /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8256
+ "g",
8257
+ {
8258
+ style: animate ? {
8259
+ transformOrigin: `${x}px ${y}px`,
8260
+ animation: `popIn 200ms ease-out ${pointIndex * 30}ms forwards`,
8261
+ opacity: 0
8262
+ } : void 0,
8263
+ children: renderShape(shape, x, y, displaySize, color, {
8264
+ className: "cursor-pointer transition-all duration-150",
8265
+ stroke: "white",
8266
+ strokeWidth: 2,
8267
+ onMouseEnter: (e) => handlePointEnter(e, seriesIndex, pointIndex),
8268
+ onMouseLeave: handlePointLeave,
8269
+ onClick: () => handlePointClick(seriesIndex, pointIndex)
8270
+ })
8271
+ },
8272
+ `point-${seriesIndex}-${pointIndex}`
8273
+ );
6413
8274
  });
6414
- }
6415
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("g", { children: [
6416
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6417
- "path",
8275
+ }),
8276
+ showXAxis && /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)("g", { transform: `translate(0, ${chartHeight})`, children: [
8277
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8278
+ "line",
6418
8279
  {
6419
- d: areaPath,
6420
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6421
- opacity: fillOpacity
8280
+ x1: 0,
8281
+ y1: 0,
8282
+ x2: chartWidth,
8283
+ y2: 0,
8284
+ className: "stroke-gray-300 dark:stroke-gray-600"
6422
8285
  }
6423
8286
  ),
6424
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6425
- "path",
8287
+ xTicks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)("g", { transform: `translate(${xScale(tick)}, 0)`, children: [
8288
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-600" }),
8289
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8290
+ "text",
8291
+ {
8292
+ y: 20,
8293
+ textAnchor: "middle",
8294
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
8295
+ className: "fill-gray-600 dark:fill-gray-400",
8296
+ children: xFormatter(tick)
8297
+ }
8298
+ )
8299
+ ] }, `x-tick-${i}`)),
8300
+ xAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8301
+ "text",
6426
8302
  {
6427
- d: linePath,
6428
- fill: "none",
6429
- stroke: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6430
- strokeWidth,
6431
- strokeLinecap: "round",
6432
- strokeLinejoin: "round",
6433
- className: lineClass
8303
+ x: chartWidth / 2,
8304
+ y: 50,
8305
+ textAnchor: "middle",
8306
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
8307
+ fontWeight: "500",
8308
+ className: "fill-gray-700 dark:fill-gray-300",
8309
+ children: xAxisLabel
6434
8310
  }
6435
8311
  )
6436
- ] }, `area-${seriesIndex}`);
6437
- }),
6438
- showDots && data.map((series, seriesIndex) => {
6439
- const baselineYValues = stacked ? data.slice(0, seriesIndex).reduce((acc, s) => {
6440
- return acc.map((val, i) => val + (s.data[i]?.y || 0));
6441
- }, Array(series.data.length).fill(0)) : void 0;
6442
- return series.data.map((point, pointIndex) => {
6443
- const x = scaleX(point.x, pointIndex);
6444
- const y = baselineYValues ? scaleY(baselineYValues[pointIndex] + point.y) : scaleY(point.y);
6445
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
6446
- return /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6447
- "circle",
8312
+ ] }),
8313
+ showYAxis && /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)("g", { children: [
8314
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8315
+ "line",
6448
8316
  {
6449
- cx: x,
6450
- cy: y,
6451
- r: isHovered ? 6 : 4,
6452
- fill: series.color || defaultColors3[seriesIndex % defaultColors3.length],
6453
- stroke: "white",
6454
- strokeWidth: "2",
6455
- className: `cursor-pointer transition-all ${pointClass}`,
6456
- onMouseEnter: (e) => {
6457
- if (showTooltip && containerRef.current) {
6458
- setHoveredPoint({ seriesIndex, pointIndex, x, y });
6459
- const containerRect = containerRef.current.getBoundingClientRect();
6460
- const mouseX = e.clientX - containerRect.left;
6461
- const mouseY = e.clientY - containerRect.top;
6462
- setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6463
- }
6464
- },
6465
- onMouseMove: (e) => {
6466
- if (showTooltip && hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex && containerRef.current) {
6467
- const containerRect = containerRef.current.getBoundingClientRect();
6468
- const mouseX = e.clientX - containerRect.left;
6469
- const mouseY = e.clientY - containerRect.top;
6470
- setTooltipPosition(getTooltipPosition(mouseX, mouseY));
6471
- }
6472
- },
6473
- onMouseLeave: () => {
6474
- setHoveredPoint(null);
6475
- setTooltipPosition(null);
8317
+ x1: 0,
8318
+ y1: 0,
8319
+ x2: 0,
8320
+ y2: chartHeight,
8321
+ className: "stroke-gray-300 dark:stroke-gray-600"
8322
+ }
8323
+ ),
8324
+ yTicks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime117.jsxs)("g", { transform: `translate(0, ${yScale(tick)})`, children: [
8325
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)("line", { x2: -6, className: "stroke-gray-300 dark:stroke-gray-600" }),
8326
+ /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8327
+ "text",
8328
+ {
8329
+ x: -12,
8330
+ textAnchor: "end",
8331
+ dominantBaseline: "middle",
8332
+ fontSize: CHART_DEFAULTS.fontSize.gridLabel,
8333
+ className: "fill-gray-600 dark:fill-gray-400",
8334
+ children: yFormatter(tick)
6476
8335
  }
6477
- },
6478
- `point-${seriesIndex}-${pointIndex}`
6479
- );
6480
- });
6481
- }),
6482
- xAxisLabel && showXAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6483
- "text",
6484
- {
6485
- x: chartWidth / 2,
6486
- y: chartHeight + 80,
6487
- textAnchor: "middle",
6488
- dominantBaseline: "hanging",
6489
- fontSize: axisLabelFontSize,
6490
- fontWeight: "600",
6491
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6492
- children: xAxisLabel
6493
- }
6494
- ),
6495
- yAxisLabel && showYAxis && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6496
- "text",
6497
- {
6498
- x: -chartHeight / 2,
6499
- y: -100,
6500
- textAnchor: "middle",
6501
- dominantBaseline: "middle",
6502
- fontSize: axisLabelFontSize,
6503
- fontWeight: "600",
6504
- transform: "rotate(-90)",
6505
- className: `fill-gray-700 dark:fill-gray-300 ${axisLabelClass}`,
6506
- children: yAxisLabel
6507
- }
6508
- )
6509
- ] })
8336
+ )
8337
+ ] }, `y-tick-${i}`)),
8338
+ yAxisLabel && /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8339
+ "text",
8340
+ {
8341
+ x: -chartHeight / 2,
8342
+ y: -45,
8343
+ textAnchor: "middle",
8344
+ transform: "rotate(-90)",
8345
+ fontSize: CHART_DEFAULTS.fontSize.axisLabel,
8346
+ fontWeight: "500",
8347
+ className: "fill-gray-700 dark:fill-gray-300",
8348
+ children: yAxisLabel
8349
+ }
8350
+ )
8351
+ ] })
8352
+ ] })
8353
+ ]
6510
8354
  }
6511
8355
  ),
6512
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((series, index) => /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "flex items-center gap-2 text-sm", children: [
6513
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)(
6514
- "div",
6515
- {
6516
- className: "w-3 h-3 rounded-sm flex-shrink-0",
6517
- style: {
6518
- backgroundColor: series.color || defaultColors3[index % defaultColors3.length]
6519
- }
6520
- }
6521
- ),
6522
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("span", { className: "text-gray-700 dark:text-gray-300", children: series.name })
6523
- ] }, `legend-${index}`)) }),
6524
- showTooltip && hoveredPoint && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)(
6525
- "div",
8356
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(Legend, { items: legendItems, layout: "horizontal", align: "center" }),
8357
+ showTooltip && /* @__PURE__ */ (0, import_jsx_runtime117.jsx)(
8358
+ ChartTooltip,
6526
8359
  {
6527
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
6528
- style: {
6529
- left: `${tooltipPosition.x}px`,
6530
- top: `${tooltipPosition.y}px`,
6531
- transform: "translateZ(0)"
6532
- },
6533
- children: [
6534
- /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("div", { className: "font-semibold", children: data[hoveredPoint.seriesIndex].name }),
6535
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "text-xs opacity-90", children: [
6536
- "x: ",
6537
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].x
6538
- ] }),
6539
- /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "text-xs opacity-90", children: [
6540
- "y: ",
6541
- data[hoveredPoint.seriesIndex].data[hoveredPoint.pointIndex].y
6542
- ] })
6543
- ]
8360
+ ...tooltipData,
8361
+ formatter: tooltipFormatter,
8362
+ containerBounds: { width, height }
6544
8363
  }
6545
8364
  )
6546
8365
  ]
@@ -6548,299 +8367,494 @@ var AreaChart = ({
6548
8367
  );
6549
8368
  };
6550
8369
 
6551
- // src/components/PieChart.tsx
6552
- var import_react29 = __toESM(require("react"));
6553
- var import_jsx_runtime111 = require("react/jsx-runtime");
6554
- var defaultColors4 = [
6555
- "#3b82f6",
6556
- // blue-500
6557
- "#10b981",
6558
- // green-500
6559
- "#f59e0b",
6560
- // amber-500
6561
- "#ef4444",
6562
- // red-500
6563
- "#8b5cf6",
6564
- // violet-500
6565
- "#ec4899",
6566
- // pink-500
6567
- "#06b6d4",
6568
- // cyan-500
6569
- "#f97316"
6570
- // orange-500
6571
- ];
6572
- var PieChart = ({
6573
- data,
6574
- width: providedWidth,
6575
- height: providedHeight,
6576
- aspectRatio = 1,
6577
- responsive = true,
6578
- showLegend = true,
6579
- showLabels = true,
6580
- showValues = false,
6581
- showPercentages = false,
6582
- donut = false,
6583
- donutWidth = 60,
8370
+ // src/charts/components/ResponsiveContainer.tsx
8371
+ var import_react33 = __toESM(require("react"));
8372
+ var import_jsx_runtime118 = require("react/jsx-runtime");
8373
+ var ResponsiveContainer = ({
8374
+ width = "100%",
8375
+ height,
8376
+ aspect,
8377
+ minWidth = 0,
8378
+ minHeight = 0,
8379
+ maxWidth,
8380
+ maxHeight,
8381
+ debounce = 0,
6584
8382
  className = "",
6585
- baseFontSize = 14
8383
+ children,
8384
+ onResize
6586
8385
  }) => {
6587
- const viewBoxWidth = 600;
6588
- const viewBoxHeight = 600;
6589
- const containerRef = import_react29.default.useRef(null);
6590
- const [tooltipPosition, setTooltipPosition] = import_react29.default.useState(null);
6591
- const [hoveredSlice, setHoveredSlice] = import_react29.default.useState(null);
6592
- const chartId = import_react29.default.useId();
6593
- const wrapperClass = `chart-svg-wrapper-${chartId.replace(/:/g, "-")}`;
6594
- const gridLabelClass = `chart-grid-label-${chartId.replace(/:/g, "-")}`;
6595
- const axisLabelClass = `chart-axis-label-${chartId.replace(/:/g, "-")}`;
6596
- const sliceClass = `chart-slice-${chartId.replace(/:/g, "-")}`;
6597
- const total = data.reduce((sum, item) => sum + item.value, 0);
6598
- const centerX = viewBoxWidth / 2;
6599
- const centerY = viewBoxHeight / 2;
6600
- const radius = Math.min(viewBoxWidth, viewBoxHeight) / 2 - 40;
6601
- const innerRadius = donut ? radius - donutWidth : 0;
6602
- const gridLabelFontSize = viewBoxWidth * 0.045;
6603
- const axisLabelFontSize = viewBoxWidth * 0.05;
6604
- const titleFontSize = viewBoxWidth * 0.055;
6605
- let currentAngle = -90;
6606
- const slices = data.map((item, index) => {
6607
- const percentage = item.value / total * 100;
6608
- const angle = item.value / total * 360;
6609
- const startAngle = currentAngle;
6610
- const endAngle = currentAngle + angle;
6611
- const startRad = startAngle * Math.PI / 180;
6612
- const endRad = endAngle * Math.PI / 180;
6613
- const x1 = Math.round((centerX + radius * Math.cos(startRad)) * 100) / 100;
6614
- const y1 = Math.round((centerY + radius * Math.sin(startRad)) * 100) / 100;
6615
- const x2 = Math.round((centerX + radius * Math.cos(endRad)) * 100) / 100;
6616
- const y2 = Math.round((centerY + radius * Math.sin(endRad)) * 100) / 100;
6617
- const largeArc = angle > 180 ? 1 : 0;
6618
- let path;
6619
- if (donut) {
6620
- const ix1 = Math.round((centerX + innerRadius * Math.cos(startRad)) * 100) / 100;
6621
- const iy1 = Math.round((centerY + innerRadius * Math.sin(startRad)) * 100) / 100;
6622
- const ix2 = Math.round((centerX + innerRadius * Math.cos(endRad)) * 100) / 100;
6623
- const iy2 = Math.round((centerY + innerRadius * Math.sin(endRad)) * 100) / 100;
6624
- path = [
6625
- `M ${x1} ${y1}`,
6626
- `A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2}`,
6627
- `L ${ix2} ${iy2}`,
6628
- `A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${ix1} ${iy1}`,
6629
- "Z"
6630
- ].join(" ");
8386
+ const containerRef = (0, import_react33.useRef)(null);
8387
+ const [dimensions, setDimensions] = (0, import_react33.useState)({
8388
+ width: 0,
8389
+ height: 0
8390
+ });
8391
+ const debounceTimerRef = (0, import_react33.useRef)(null);
8392
+ const calculateDimensions = (0, import_react33.useCallback)((containerWidth, containerHeight) => {
8393
+ let finalWidth = containerWidth;
8394
+ let finalHeight = containerHeight;
8395
+ if (aspect && !height) {
8396
+ finalHeight = containerWidth / aspect;
8397
+ }
8398
+ finalWidth = Math.max(minWidth, finalWidth);
8399
+ finalHeight = Math.max(minHeight, finalHeight);
8400
+ if (maxWidth) {
8401
+ finalWidth = Math.min(maxWidth, finalWidth);
8402
+ }
8403
+ if (maxHeight) {
8404
+ finalHeight = Math.min(maxHeight, finalHeight);
8405
+ }
8406
+ return { width: finalWidth, height: finalHeight };
8407
+ }, [aspect, height, minWidth, minHeight, maxWidth, maxHeight]);
8408
+ const handleResize = (0, import_react33.useCallback)((entries) => {
8409
+ const entry = entries[0];
8410
+ if (!entry) return;
8411
+ const { width: containerWidth, height: containerHeight } = entry.contentRect;
8412
+ const newDimensions = calculateDimensions(containerWidth, containerHeight);
8413
+ const updateDimensions = () => {
8414
+ setDimensions(newDimensions);
8415
+ onResize?.(newDimensions.width, newDimensions.height);
8416
+ };
8417
+ if (debounce > 0) {
8418
+ if (debounceTimerRef.current) {
8419
+ clearTimeout(debounceTimerRef.current);
8420
+ }
8421
+ debounceTimerRef.current = setTimeout(updateDimensions, debounce);
6631
8422
  } else {
6632
- path = [
6633
- `M ${centerX} ${centerY}`,
6634
- `L ${x1} ${y1}`,
6635
- `A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2}`,
6636
- "Z"
6637
- ].join(" ");
6638
- }
6639
- const labelAngle = startAngle + angle / 2;
6640
- const labelRad = labelAngle * Math.PI / 180;
6641
- const labelRadius = donut ? innerRadius + (radius - innerRadius) / 2 : radius * 0.7;
6642
- const labelX = Math.round((centerX + labelRadius * Math.cos(labelRad)) * 100) / 100;
6643
- const labelY = Math.round((centerY + labelRadius * Math.sin(labelRad)) * 100) / 100;
6644
- currentAngle = endAngle;
6645
- return {
6646
- path,
6647
- color: item.color || defaultColors4[index % defaultColors4.length],
6648
- label: item.label,
6649
- value: item.value,
6650
- percentage,
6651
- labelX,
6652
- labelY,
6653
- index
8423
+ updateDimensions();
8424
+ }
8425
+ }, [calculateDimensions, debounce, onResize]);
8426
+ (0, import_react33.useEffect)(() => {
8427
+ const container = containerRef.current;
8428
+ if (!container) return;
8429
+ const observer = new ResizeObserver(handleResize);
8430
+ observer.observe(container);
8431
+ const rect = container.getBoundingClientRect();
8432
+ const initialDimensions = calculateDimensions(rect.width, rect.height);
8433
+ setDimensions(initialDimensions);
8434
+ return () => {
8435
+ observer.disconnect();
8436
+ if (debounceTimerRef.current) {
8437
+ clearTimeout(debounceTimerRef.current);
8438
+ }
6654
8439
  };
6655
- });
6656
- const getTooltipPosition = import_react29.default.useCallback((x, y) => {
6657
- if (!containerRef.current) return { x, y };
6658
- const rect = containerRef.current.getBoundingClientRect();
6659
- const tooltipWidth = 120;
6660
- const tooltipHeight = 80;
6661
- let tooltipX = x + 10;
6662
- let tooltipY = y - 30;
6663
- if (tooltipX + tooltipWidth > rect.width) {
6664
- tooltipX = x - tooltipWidth - 10;
6665
- }
6666
- if (tooltipY + tooltipHeight > rect.height) {
6667
- tooltipY = y + 30;
6668
- }
6669
- return { x: tooltipX, y: tooltipY };
6670
- }, []);
8440
+ }, [handleResize, calculateDimensions]);
6671
8441
  const containerStyle = {
6672
- "--font-size-base": `${baseFontSize}px`,
6673
- "--font-size-lg": `calc(var(--font-size-base) * 1.125)`,
6674
- "--font-size-sm": `calc(var(--font-size-base) * 0.875)`,
6675
- "--font-size-xs": `calc(var(--font-size-base) * 0.75)`,
6676
- "--font-size-xl": `calc(var(--font-size-base) * 1.5)`,
6677
- "--font-size-2xl": `calc(var(--font-size-base) * 2)`
8442
+ width: typeof width === "number" ? `${width}px` : width,
8443
+ height: height ? typeof height === "number" ? `${height}px` : height : "auto",
8444
+ minWidth: minWidth ? `${minWidth}px` : void 0,
8445
+ minHeight: minHeight ? `${minHeight}px` : void 0,
8446
+ maxWidth: maxWidth ? `${maxWidth}px` : void 0,
8447
+ maxHeight: maxHeight ? `${maxHeight}px` : void 0
6678
8448
  };
6679
- if (responsive) {
6680
- containerStyle["--font-size-base"] = `clamp(${baseFontSize * 0.8}px, 4vw, ${baseFontSize}px)`;
8449
+ if (dimensions.width === 0 || dimensions.height === 0) {
8450
+ return /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
8451
+ "div",
8452
+ {
8453
+ ref: containerRef,
8454
+ className: `relative ${className}`,
8455
+ style: containerStyle
8456
+ }
8457
+ );
6681
8458
  }
6682
- return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
8459
+ const chartElement = import_react33.default.cloneElement(
8460
+ children,
8461
+ {
8462
+ width: dimensions.width,
8463
+ height: dimensions.height
8464
+ }
8465
+ );
8466
+ return /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
6683
8467
  "div",
6684
8468
  {
6685
8469
  ref: containerRef,
6686
- className: `relative flex flex-col gap-4 ${responsive ? "w-full" : "inline-block"} ${className}`,
8470
+ className: `relative ${className}`,
6687
8471
  style: containerStyle,
6688
- children: [
6689
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("style", { children: `
6690
- /* Mobile: Large fonts (default) */
6691
- .${gridLabelClass} { font-size: ${gridLabelFontSize}px !important; }
6692
- .${axisLabelClass} { font-size: ${titleFontSize}px !important; }
6693
-
6694
- /* Tablet: Medium fonts */
6695
- @media (min-width: 640px) {
6696
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.7}px !important; }
6697
- .${axisLabelClass} { font-size: ${titleFontSize * 0.7}px !important; }
6698
- }
8472
+ children: chartElement
8473
+ }
8474
+ );
8475
+ };
6699
8476
 
6700
- /* Desktop: Small fonts */
6701
- @media (min-width: 1024px) {
6702
- .${gridLabelClass} { font-size: ${gridLabelFontSize * 0.4}px !important; }
6703
- .${axisLabelClass} { font-size: ${titleFontSize * 0.4}px !important; }
6704
- }
6705
- ` }),
6706
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6707
- "svg",
6708
- {
6709
- width: "100%",
6710
- viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
6711
- preserveAspectRatio: "xMidYMid meet",
6712
- className: "bg-white dark:bg-gray-800 block w-full",
6713
- style: { height: "auto", overflow: "visible" },
6714
- children: [
6715
- slices.map((slice) => {
6716
- const isHovered = hoveredSlice === slice.index;
6717
- return /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6718
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6719
- "path",
6720
- {
6721
- d: slice.path,
6722
- fill: slice.color,
6723
- stroke: "white",
6724
- strokeWidth: "2",
6725
- className: "cursor-pointer transition-opacity",
6726
- opacity: isHovered ? 0.8 : 1,
6727
- onMouseEnter: (e) => {
6728
- setHoveredSlice(slice.index);
6729
- if (containerRef.current) {
6730
- const rect = containerRef.current.getBoundingClientRect();
6731
- const svgX = e.clientX - rect.left;
6732
- const svgY = e.clientY - rect.top;
6733
- setTooltipPosition(getTooltipPosition(svgX, svgY));
6734
- }
6735
- },
6736
- onMouseLeave: () => {
6737
- setHoveredSlice(null);
6738
- setTooltipPosition(null);
6739
- }
6740
- }
6741
- ),
6742
- showLabels && slice.percentage > 5 && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6743
- "text",
6744
- {
6745
- x: slice.labelX,
6746
- y: slice.labelY,
6747
- textAnchor: "middle",
6748
- dominantBaseline: "middle",
6749
- fontSize: gridLabelFontSize,
6750
- fontWeight: "600",
6751
- className: `fill-gray-700 dark:fill-gray-200 pointer-events-none ${gridLabelClass}`,
6752
- children: [
6753
- showPercentages && `${slice.percentage.toFixed(1)}%`,
6754
- showPercentages && showValues && " ",
6755
- showValues && `(${slice.value})`,
6756
- !showPercentages && !showValues && slice.label
6757
- ]
6758
- }
6759
- )
6760
- ] }, slice.index);
6761
- }),
6762
- donut && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("g", { children: [
6763
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6764
- "text",
8477
+ // src/components/Heatmap.tsx
8478
+ var import_react34 = __toESM(require("react"));
8479
+ var import_jsx_runtime119 = require("react/jsx-runtime");
8480
+ var COLOR_SCALES = {
8481
+ blue: {
8482
+ light: ["#eff6ff", "#dbeafe", "#bfdbfe", "#93c5fd", "#60a5fa", "#3b82f6", "#2563eb", "#1d4ed8", "#1e40af"],
8483
+ dark: ["#1e3a5f", "#1e4976", "#1e5a8d", "#2563eb", "#3b82f6", "#60a5fa", "#93c5fd", "#bfdbfe", "#dbeafe"]
8484
+ },
8485
+ green: {
8486
+ light: ["#f0fdf4", "#dcfce7", "#bbf7d0", "#86efac", "#4ade80", "#22c55e", "#16a34a", "#15803d", "#166534"],
8487
+ dark: ["#14532d", "#166534", "#15803d", "#16a34a", "#22c55e", "#4ade80", "#86efac", "#bbf7d0", "#dcfce7"]
8488
+ },
8489
+ red: {
8490
+ light: ["#fef2f2", "#fee2e2", "#fecaca", "#fca5a5", "#f87171", "#ef4444", "#dc2626", "#b91c1c", "#991b1b"],
8491
+ dark: ["#450a0a", "#7f1d1d", "#991b1b", "#b91c1c", "#dc2626", "#ef4444", "#f87171", "#fca5a5", "#fecaca"]
8492
+ },
8493
+ purple: {
8494
+ light: ["#faf5ff", "#f3e8ff", "#e9d5ff", "#d8b4fe", "#c084fc", "#a855f7", "#9333ea", "#7e22ce", "#6b21a8"],
8495
+ dark: ["#3b0764", "#4c1d95", "#5b21b6", "#6d28d9", "#7c3aed", "#8b5cf6", "#a78bfa", "#c4b5fd", "#ddd6fe"]
8496
+ },
8497
+ orange: {
8498
+ light: ["#fff7ed", "#ffedd5", "#fed7aa", "#fdba74", "#fb923c", "#f97316", "#ea580c", "#c2410c", "#9a3412"],
8499
+ dark: ["#431407", "#7c2d12", "#9a3412", "#c2410c", "#ea580c", "#f97316", "#fb923c", "#fdba74", "#fed7aa"]
8500
+ },
8501
+ gray: {
8502
+ light: ["#f9fafb", "#f3f4f6", "#e5e7eb", "#d1d5db", "#9ca3af", "#6b7280", "#4b5563", "#374151", "#1f2937"],
8503
+ dark: ["#111827", "#1f2937", "#374151", "#4b5563", "#6b7280", "#9ca3af", "#d1d5db", "#e5e7eb", "#f3f4f6"]
8504
+ }
8505
+ };
8506
+ var Heatmap = ({
8507
+ data,
8508
+ xLabels,
8509
+ yLabels,
8510
+ colorScale = "blue",
8511
+ minValue: propMinValue,
8512
+ maxValue: propMaxValue,
8513
+ showValues = false,
8514
+ showTooltip = true,
8515
+ cellSize = 40,
8516
+ cellGap = 2,
8517
+ className = "",
8518
+ onCellClick,
8519
+ formatValue = (v) => v.toFixed(0),
8520
+ formatTooltip
8521
+ }) => {
8522
+ const [hoveredCell, setHoveredCell] = (0, import_react34.useState)(null);
8523
+ const [isDarkMode, setIsDarkMode] = (0, import_react34.useState)(false);
8524
+ import_react34.default.useEffect(() => {
8525
+ const checkDarkMode = () => {
8526
+ setIsDarkMode(document.documentElement.classList.contains("dark"));
8527
+ };
8528
+ checkDarkMode();
8529
+ const observer = new MutationObserver(checkDarkMode);
8530
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
8531
+ return () => observer.disconnect();
8532
+ }, []);
8533
+ const { uniqueX, uniqueY, dataMap, minValue, maxValue } = (0, import_react34.useMemo)(() => {
8534
+ const xSet = /* @__PURE__ */ new Set();
8535
+ const ySet = /* @__PURE__ */ new Set();
8536
+ const map = /* @__PURE__ */ new Map();
8537
+ let min = propMinValue ?? Infinity;
8538
+ let max = propMaxValue ?? -Infinity;
8539
+ data.forEach((point) => {
8540
+ const xKey = String(point.x);
8541
+ const yKey = String(point.y);
8542
+ xSet.add(xKey);
8543
+ ySet.add(yKey);
8544
+ map.set(`${xKey}:${yKey}`, point.value);
8545
+ if (propMinValue === void 0) min = Math.min(min, point.value);
8546
+ if (propMaxValue === void 0) max = Math.max(max, point.value);
8547
+ });
8548
+ return {
8549
+ uniqueX: xLabels || Array.from(xSet),
8550
+ uniqueY: yLabels || Array.from(ySet),
8551
+ dataMap: map,
8552
+ minValue: propMinValue ?? min,
8553
+ maxValue: propMaxValue ?? max
8554
+ };
8555
+ }, [data, xLabels, yLabels, propMinValue, propMaxValue]);
8556
+ const getColor = (value) => {
8557
+ if (value === void 0) {
8558
+ return isDarkMode ? "#374151" : "#f3f4f6";
8559
+ }
8560
+ const colors = isDarkMode ? COLOR_SCALES[colorScale].dark : COLOR_SCALES[colorScale].light;
8561
+ const range = maxValue - minValue;
8562
+ if (range === 0) return colors[4];
8563
+ const normalized = Math.max(0, Math.min(1, (value - minValue) / range));
8564
+ const index = Math.floor(normalized * (colors.length - 1));
8565
+ return colors[index];
8566
+ };
8567
+ const getTextColor = (value) => {
8568
+ if (value === void 0) return isDarkMode ? "#6b7280" : "#9ca3af";
8569
+ const range = maxValue - minValue;
8570
+ const normalized = range === 0 ? 0.5 : (value - minValue) / range;
8571
+ if (isDarkMode) {
8572
+ return normalized > 0.5 ? "#111827" : "#f9fafb";
8573
+ }
8574
+ return normalized > 0.5 ? "#ffffff" : "#111827";
8575
+ };
8576
+ const getTooltipContent = (point) => {
8577
+ if (formatTooltip) return formatTooltip(point);
8578
+ return `${point.x}, ${point.y}: ${formatValue(point.value)}`;
8579
+ };
8580
+ const labelWidth = 80;
8581
+ const labelHeight = 30;
8582
+ const width = labelWidth + uniqueX.length * (cellSize + cellGap);
8583
+ const height = labelHeight + uniqueY.length * (cellSize + cellGap);
8584
+ return /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { className: `relative inline-block ${className}`, children: [
8585
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)(
8586
+ "svg",
8587
+ {
8588
+ width,
8589
+ height,
8590
+ className: "select-none",
8591
+ children: [
8592
+ uniqueX.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8593
+ "text",
8594
+ {
8595
+ x: labelWidth + i * (cellSize + cellGap) + cellSize / 2,
8596
+ y: labelHeight - 8,
8597
+ textAnchor: "middle",
8598
+ className: "text-xs fill-gray-600 dark:fill-gray-400",
8599
+ style: { fontSize: "11px" },
8600
+ children: label
8601
+ },
8602
+ `x-${label}`
8603
+ )),
8604
+ uniqueY.map((label, j) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8605
+ "text",
8606
+ {
8607
+ x: labelWidth - 8,
8608
+ y: labelHeight + j * (cellSize + cellGap) + cellSize / 2 + 4,
8609
+ textAnchor: "end",
8610
+ className: "text-xs fill-gray-600 dark:fill-gray-400",
8611
+ style: { fontSize: "11px" },
8612
+ children: label
8613
+ },
8614
+ `y-${label}`
8615
+ )),
8616
+ uniqueY.map(
8617
+ (yLabel, j) => uniqueX.map((xLabel, i) => {
8618
+ const value = dataMap.get(`${xLabel}:${yLabel}`);
8619
+ const point = data.find((d) => String(d.x) === xLabel && String(d.y) === yLabel);
8620
+ const x = labelWidth + i * (cellSize + cellGap);
8621
+ const y = labelHeight + j * (cellSize + cellGap);
8622
+ return /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("g", { children: [
8623
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8624
+ "rect",
6765
8625
  {
6766
- x: centerX,
6767
- y: centerY - 10,
6768
- textAnchor: "middle",
6769
- dominantBaseline: "middle",
6770
- fontSize: titleFontSize,
6771
- fontWeight: "700",
6772
- className: `fill-gray-900 dark:fill-gray-100 ${axisLabelClass}`,
6773
- children: total
8626
+ x,
8627
+ y,
8628
+ width: cellSize,
8629
+ height: cellSize,
8630
+ rx: 4,
8631
+ fill: getColor(value),
8632
+ className: `transition-all duration-150 ${onCellClick && point ? "cursor-pointer" : ""}`,
8633
+ style: {
8634
+ stroke: hoveredCell?.x === i && hoveredCell?.y === j ? isDarkMode ? "#60a5fa" : "#3b82f6" : "transparent",
8635
+ strokeWidth: 2
8636
+ },
8637
+ onMouseEnter: () => {
8638
+ if (point) setHoveredCell({ x: i, y: j, point });
8639
+ },
8640
+ onMouseLeave: () => setHoveredCell(null),
8641
+ onClick: () => {
8642
+ if (point && onCellClick) onCellClick(point);
8643
+ }
6774
8644
  }
6775
8645
  ),
6776
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
8646
+ showValues && value !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
6777
8647
  "text",
6778
8648
  {
6779
- x: centerX,
6780
- y: centerY + 15,
8649
+ x: x + cellSize / 2,
8650
+ y: y + cellSize / 2 + 4,
6781
8651
  textAnchor: "middle",
6782
- dominantBaseline: "middle",
6783
- fontSize: axisLabelFontSize,
6784
- className: `fill-gray-600 dark:fill-gray-400 ${gridLabelClass}`,
6785
- children: "Total"
8652
+ fill: getTextColor(value),
8653
+ style: { fontSize: "11px", fontWeight: 500 },
8654
+ children: formatValue(value)
6786
8655
  }
6787
8656
  )
6788
- ] })
6789
- ]
6790
- }
6791
- ),
6792
- showLegend && /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "flex flex-wrap gap-3 justify-center px-4", children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6793
- "div",
6794
- {
6795
- className: "flex items-center gap-2 text-sm cursor-pointer",
6796
- onMouseEnter: () => setHoveredSlice(index),
6797
- onMouseLeave: () => setHoveredSlice(null),
6798
- children: [
6799
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)(
6800
- "div",
6801
- {
6802
- className: "w-3 h-3 rounded-sm flex-shrink-0",
6803
- style: {
6804
- backgroundColor: item.color || defaultColors4[index % defaultColors4.length]
6805
- }
6806
- }
6807
- ),
6808
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("span", { className: "text-gray-700 dark:text-gray-300", children: [
6809
- item.label,
6810
- ": ",
6811
- item.value,
6812
- showPercentages && ` (${(item.value / total * 100).toFixed(1)}%)`
6813
- ] })
6814
- ]
6815
- },
6816
- `legend-${index}`
6817
- )) }),
6818
- hoveredSlice !== null && tooltipPosition && /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)(
6819
- "div",
6820
- {
6821
- className: "absolute bg-gray-900 dark:bg-gray-700 text-white px-3 py-2 rounded shadow-lg pointer-events-none z-50 text-sm whitespace-nowrap",
6822
- style: {
6823
- left: `${tooltipPosition.x}px`,
6824
- top: `${tooltipPosition.y}px`,
6825
- transform: "translateZ(0)"
6826
- },
6827
- children: [
6828
- /* @__PURE__ */ (0, import_jsx_runtime111.jsx)("div", { className: "font-semibold", children: data[hoveredSlice].label }),
6829
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: "text-xs opacity-90", children: [
6830
- "Value: ",
6831
- data[hoveredSlice].value
6832
- ] }),
6833
- /* @__PURE__ */ (0, import_jsx_runtime111.jsxs)("div", { className: "text-xs opacity-90", children: [
6834
- "Percentage: ",
6835
- (data[hoveredSlice].value / total * 100).toFixed(1),
6836
- "%"
6837
- ] })
6838
- ]
6839
- }
6840
- )
6841
- ]
8657
+ ] }, `${xLabel}-${yLabel}`);
8658
+ })
8659
+ )
8660
+ ]
8661
+ }
8662
+ ),
8663
+ showTooltip && hoveredCell && /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)(
8664
+ "div",
8665
+ {
8666
+ className: "absolute z-50 px-3 py-2 text-sm bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 rounded-lg shadow-lg pointer-events-none whitespace-nowrap",
8667
+ style: {
8668
+ left: labelWidth + hoveredCell.x * (cellSize + cellGap) + cellSize / 2,
8669
+ top: labelHeight + hoveredCell.y * (cellSize + cellGap) - 8,
8670
+ transform: "translate(-50%, -100%)"
8671
+ },
8672
+ children: [
8673
+ getTooltipContent(hoveredCell.point),
8674
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8675
+ "div",
8676
+ {
8677
+ className: "absolute w-2 h-2 bg-gray-900 dark:bg-gray-100 rotate-45",
8678
+ style: {
8679
+ left: "50%",
8680
+ bottom: "-4px",
8681
+ transform: "translateX(-50%)"
8682
+ }
8683
+ }
8684
+ )
8685
+ ]
8686
+ }
8687
+ )
8688
+ ] });
8689
+ };
8690
+ var DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
8691
+ var MONTH_LABELS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
8692
+ var CalendarHeatmap = ({
8693
+ data,
8694
+ startDate: propStartDate,
8695
+ endDate: propEndDate,
8696
+ colorScale = "green",
8697
+ showMonthLabels = true,
8698
+ showDayLabels = true,
8699
+ cellSize = 12,
8700
+ cellGap = 2,
8701
+ className = "",
8702
+ onCellClick,
8703
+ formatTooltip
8704
+ }) => {
8705
+ const [hoveredCell, setHoveredCell] = (0, import_react34.useState)(null);
8706
+ const [isDarkMode, setIsDarkMode] = (0, import_react34.useState)(false);
8707
+ import_react34.default.useEffect(() => {
8708
+ const checkDarkMode = () => {
8709
+ setIsDarkMode(document.documentElement.classList.contains("dark"));
8710
+ };
8711
+ checkDarkMode();
8712
+ const observer = new MutationObserver(checkDarkMode);
8713
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
8714
+ return () => observer.disconnect();
8715
+ }, []);
8716
+ const { weeks, minValue, maxValue, dataMap, monthLabels } = (0, import_react34.useMemo)(() => {
8717
+ const map = /* @__PURE__ */ new Map();
8718
+ let min = Infinity;
8719
+ let max = -Infinity;
8720
+ data.forEach((item) => {
8721
+ const date = typeof item.date === "string" ? new Date(item.date) : item.date;
8722
+ const key = date.toISOString().split("T")[0];
8723
+ map.set(key, item.value);
8724
+ min = Math.min(min, item.value);
8725
+ max = Math.max(max, item.value);
8726
+ });
8727
+ if (min === Infinity) min = 0;
8728
+ if (max === -Infinity) max = 0;
8729
+ const endDate = propEndDate || /* @__PURE__ */ new Date();
8730
+ const startDate = propStartDate || new Date(endDate.getTime() - 364 * 24 * 60 * 60 * 1e3);
8731
+ const adjustedStart = new Date(startDate);
8732
+ adjustedStart.setDate(adjustedStart.getDate() - adjustedStart.getDay());
8733
+ const weeksArr = [];
8734
+ const currentDate = new Date(adjustedStart);
8735
+ const months = [];
8736
+ let lastMonth = -1;
8737
+ while (currentDate <= endDate || weeksArr.length === 0 || weeksArr[weeksArr.length - 1].length < 7) {
8738
+ if (weeksArr.length === 0 || weeksArr[weeksArr.length - 1].length === 7) {
8739
+ weeksArr.push([]);
8740
+ }
8741
+ if (currentDate.getMonth() !== lastMonth && currentDate <= endDate) {
8742
+ months.push({ label: MONTH_LABELS[currentDate.getMonth()], weekIndex: weeksArr.length - 1 });
8743
+ lastMonth = currentDate.getMonth();
8744
+ }
8745
+ weeksArr[weeksArr.length - 1].push(new Date(currentDate));
8746
+ currentDate.setDate(currentDate.getDate() + 1);
6842
8747
  }
6843
- );
8748
+ return {
8749
+ weeks: weeksArr,
8750
+ minValue: min,
8751
+ maxValue: max,
8752
+ dataMap: map,
8753
+ monthLabels: months
8754
+ };
8755
+ }, [data, propStartDate, propEndDate]);
8756
+ const getColor = (value) => {
8757
+ if (value === void 0 || value === 0) {
8758
+ return isDarkMode ? "#1f2937" : "#ebedf0";
8759
+ }
8760
+ const colors = isDarkMode ? COLOR_SCALES[colorScale].dark : COLOR_SCALES[colorScale].light;
8761
+ const range = maxValue - minValue;
8762
+ if (range === 0) return colors[4];
8763
+ const normalized = Math.max(0, Math.min(1, (value - minValue) / range));
8764
+ const index = Math.max(1, Math.floor(normalized * (colors.length - 1)));
8765
+ return colors[index];
8766
+ };
8767
+ const getTooltipContent = (date, value) => {
8768
+ if (formatTooltip) return formatTooltip(date, value);
8769
+ const dateStr = date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric" });
8770
+ return `${dateStr}: ${value} contribution${value !== 1 ? "s" : ""}`;
8771
+ };
8772
+ const dayLabelWidth = showDayLabels ? 30 : 0;
8773
+ const monthLabelHeight = showMonthLabels ? 20 : 0;
8774
+ const width = dayLabelWidth + weeks.length * (cellSize + cellGap);
8775
+ const height = monthLabelHeight + 7 * (cellSize + cellGap);
8776
+ return /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { className: `relative inline-block ${className}`, children: [
8777
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("svg", { width, height, className: "select-none", children: [
8778
+ showMonthLabels && monthLabels.map(({ label, weekIndex }, i) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8779
+ "text",
8780
+ {
8781
+ x: dayLabelWidth + weekIndex * (cellSize + cellGap),
8782
+ y: 12,
8783
+ className: "text-xs fill-gray-600 dark:fill-gray-400",
8784
+ style: { fontSize: "10px" },
8785
+ children: label
8786
+ },
8787
+ `month-${i}`
8788
+ )),
8789
+ showDayLabels && [1, 3, 5].map((dayIndex) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8790
+ "text",
8791
+ {
8792
+ x: dayLabelWidth - 6,
8793
+ y: monthLabelHeight + dayIndex * (cellSize + cellGap) + cellSize / 2 + 3,
8794
+ textAnchor: "end",
8795
+ className: "text-xs fill-gray-600 dark:fill-gray-400",
8796
+ style: { fontSize: "9px" },
8797
+ children: DAY_LABELS[dayIndex]
8798
+ },
8799
+ `day-${dayIndex}`
8800
+ )),
8801
+ weeks.map(
8802
+ (week, weekIndex) => week.map((date, dayIndex) => {
8803
+ const key = date.toISOString().split("T")[0];
8804
+ const value = dataMap.get(key);
8805
+ const x = dayLabelWidth + weekIndex * (cellSize + cellGap);
8806
+ const y = monthLabelHeight + dayIndex * (cellSize + cellGap);
8807
+ return /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8808
+ "rect",
8809
+ {
8810
+ x,
8811
+ y,
8812
+ width: cellSize,
8813
+ height: cellSize,
8814
+ rx: 2,
8815
+ fill: getColor(value),
8816
+ className: `transition-all duration-150 ${onCellClick ? "cursor-pointer" : ""}`,
8817
+ style: {
8818
+ stroke: hoveredCell?.weekIndex === weekIndex && hoveredCell?.dayIndex === dayIndex ? isDarkMode ? "#60a5fa" : "#3b82f6" : "transparent",
8819
+ strokeWidth: 1
8820
+ },
8821
+ onMouseEnter: () => setHoveredCell({ weekIndex, dayIndex, date, value: value || 0 }),
8822
+ onMouseLeave: () => setHoveredCell(null),
8823
+ onClick: () => {
8824
+ if (onCellClick) onCellClick(date, value || 0);
8825
+ }
8826
+ },
8827
+ key
8828
+ );
8829
+ })
8830
+ )
8831
+ ] }),
8832
+ hoveredCell && /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)(
8833
+ "div",
8834
+ {
8835
+ className: "absolute z-50 px-3 py-2 text-sm bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 rounded-lg shadow-lg pointer-events-none whitespace-nowrap",
8836
+ style: {
8837
+ left: dayLabelWidth + hoveredCell.weekIndex * (cellSize + cellGap) + cellSize / 2,
8838
+ top: monthLabelHeight + hoveredCell.dayIndex * (cellSize + cellGap) - 8,
8839
+ transform: "translate(-50%, -100%)"
8840
+ },
8841
+ children: [
8842
+ getTooltipContent(hoveredCell.date, hoveredCell.value),
8843
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
8844
+ "div",
8845
+ {
8846
+ className: "absolute w-2 h-2 bg-gray-900 dark:bg-gray-100 rotate-45",
8847
+ style: {
8848
+ left: "50%",
8849
+ bottom: "-4px",
8850
+ transform: "translateX(-50%)"
8851
+ }
8852
+ }
8853
+ )
8854
+ ]
8855
+ }
8856
+ )
8857
+ ] });
6844
8858
  };
6845
8859
 
6846
8860
  // src/utils/theme-script.ts
@@ -6896,10 +8910,14 @@ function getThemeScript() {
6896
8910
  BookIcon,
6897
8911
  BrainIcon,
6898
8912
  Button,
8913
+ CHART_DEFAULTS,
6899
8914
  Calendar,
8915
+ CalendarHeatmap,
6900
8916
  CalendarIcon,
6901
8917
  CameraIcon,
6902
8918
  Card,
8919
+ CartesianGrid,
8920
+ ChartTooltip,
6903
8921
  ChatIcon,
6904
8922
  CheckCircleIcon,
6905
8923
  CheckIcon,
@@ -6932,10 +8950,12 @@ function getThemeScript() {
6932
8950
  GlobeIcon,
6933
8951
  GoogleIcon,
6934
8952
  HeartIcon,
8953
+ Heatmap,
6935
8954
  HomeIcon,
6936
8955
  ImageIcon,
6937
8956
  InfoCircleIcon,
6938
8957
  KeyIcon,
8958
+ Legend,
6939
8959
  LineChart,
6940
8960
  LinkedInIcon,
6941
8961
  LockIcon,
@@ -6952,9 +8972,13 @@ function getThemeScript() {
6952
8972
  PlusIcon,
6953
8973
  ProgressBar,
6954
8974
  Radio,
8975
+ ReferenceArea,
8976
+ ReferenceLine,
6955
8977
  RefreshIcon,
8978
+ ResponsiveContainer,
6956
8979
  RichTextEditor,
6957
8980
  SaveIcon,
8981
+ ScatterChart,
6958
8982
  SearchIcon,
6959
8983
  Select,
6960
8984
  SettingsIcon,
@@ -6993,6 +9017,7 @@ function getThemeScript() {
6993
9017
  toast,
6994
9018
  useSidebar,
6995
9019
  useTheme,
6996
- useToast
9020
+ useToast,
9021
+ useTooltip
6997
9022
  });
6998
9023
  //# sourceMappingURL=index.js.map