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