@moontra/moonui-pro 2.4.4 → 2.4.6

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.
@@ -22,6 +22,19 @@ import {
22
22
  ReferenceLine,
23
23
  ReferenceArea,
24
24
  Brush,
25
+ ComposedChart,
26
+ RadarChart,
27
+ PolarGrid,
28
+ PolarAngleAxis,
29
+ PolarRadiusAxis,
30
+ Radar,
31
+ Treemap,
32
+ Funnel,
33
+ FunnelChart,
34
+ Sankey,
35
+ RadialBarChart,
36
+ RadialBar,
37
+ BarChart as RechartsBarChart,
25
38
  } from 'recharts'
26
39
  import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
27
40
  import { Button } from '../ui/button'
@@ -34,11 +47,42 @@ import {
34
47
  TrendingDown,
35
48
  Minus,
36
49
  Lock,
37
- Sparkles
50
+ Sparkles,
51
+ Eye,
52
+ EyeOff,
53
+ FileJson,
54
+ FileSpreadsheet,
55
+ Image,
56
+ ZoomIn,
57
+ ZoomOut,
58
+ Move,
59
+ Crosshair,
60
+ Palette,
61
+ Activity,
62
+ MoreVertical,
63
+ X,
64
+ Check
38
65
  } from 'lucide-react'
39
66
  import { cn } from '../../lib/utils'
67
+ import { motion, AnimatePresence, useMotionValue, useTransform } from 'framer-motion'
68
+ import {
69
+ DropdownMenu,
70
+ DropdownMenuContent,
71
+ DropdownMenuItem,
72
+ DropdownMenuTrigger,
73
+ DropdownMenuSeparator,
74
+ } from '../ui/dropdown-menu'
75
+ import { Skeleton } from '../ui/skeleton'
76
+ import { Switch } from '../ui/switch'
77
+ import { Label } from '../ui/label'
78
+ import { Slider } from '../ui/slider'
79
+ import {
80
+ Popover,
81
+ PopoverContent,
82
+ PopoverTrigger,
83
+ } from '../ui/popover'
40
84
 
41
- export type ChartType = 'line' | 'bar' | 'area' | 'pie' | 'scatter'
85
+ export type ChartType = 'line' | 'bar' | 'area' | 'pie' | 'scatter' | 'composed' | 'radar' | 'radialBar' | 'treemap' | 'funnel'
42
86
 
43
87
  interface ChartDataPoint {
44
88
  [key: string]: string | number | null
@@ -48,10 +92,17 @@ interface ChartSeries {
48
92
  dataKey: string
49
93
  name: string
50
94
  color: string
51
- type?: 'monotone' | 'linear' | 'step'
95
+ type?: 'monotone' | 'linear' | 'step' | 'basis' | 'basisClosed' | 'basisOpen' | 'natural'
52
96
  strokeWidth?: number
53
97
  fillOpacity?: number
54
98
  hide?: boolean
99
+ gradient?: boolean
100
+ strokeDasharray?: string
101
+ dot?: boolean | object
102
+ activeDot?: boolean | object
103
+ label?: boolean | object
104
+ stackId?: string
105
+ yAxisId?: string
55
106
  }
56
107
 
57
108
  interface AdvancedChartProps {
@@ -83,7 +134,7 @@ interface AdvancedChartProps {
83
134
  colors?: string[]
84
135
  className?: string
85
136
  onDataPointClick?: (data: ChartDataPoint) => void
86
- onExport?: (format: 'png' | 'svg' | 'pdf') => void
137
+ onExport?: (format: 'png' | 'svg' | 'pdf' | 'json' | 'csv') => void
87
138
  onRefresh?: () => void
88
139
  customTooltip?: React.ComponentType<any>
89
140
  customLegend?: React.ComponentType<any>
@@ -91,12 +142,144 @@ interface AdvancedChartProps {
91
142
  error?: string | null
92
143
  animated?: boolean
93
144
  responsive?: boolean
145
+ showCrosshair?: boolean
146
+ enableZoom?: boolean
147
+ enablePan?: boolean
148
+ showMiniMap?: boolean
149
+ darkMode?: boolean
150
+ gradientColors?: boolean
151
+ interactiveLegend?: boolean
152
+ showDataLabels?: boolean
153
+ sparklineMode?: boolean
154
+ animationDuration?: number
155
+ theme?: 'default' | 'vibrant' | 'pastel' | 'dark' | 'neon'
156
+ }
157
+
158
+ // Tema renk paletleri
159
+ const COLOR_THEMES = {
160
+ default: [
161
+ '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
162
+ '#06b6d4', '#f97316', '#84cc16', '#ec4899', '#6366f1'
163
+ ],
164
+ vibrant: [
165
+ '#FF006E', '#FB5607', '#FFBE0B', '#8338EC', '#3A86FF',
166
+ '#06FFB4', '#FF4365', '#00F5FF', '#FF124F', '#7209B7'
167
+ ],
168
+ pastel: [
169
+ '#B5E2FA', '#F7AEF8', '#FDC5F5', '#F8B7D3', '#FAC9B8',
170
+ '#C8E9A0', '#A7D2CB', '#F2D98D', '#E5B3BB', '#D6A2E8'
171
+ ],
172
+ dark: [
173
+ '#1e3a5f', '#4b0e0e', '#0e4b2b', '#4b3c0e', '#2e0e4b',
174
+ '#0e3c4b', '#4b2e0e', '#2b4b0e', '#4b0e3c', '#1e0e4b'
175
+ ],
176
+ neon: [
177
+ '#39FF14', '#FF10F0', '#00FFFF', '#FE019A', '#FFFF00',
178
+ '#FF073A', '#00FF00', '#FF0099', '#0FF0FC', '#FFE400'
179
+ ]
94
180
  }
95
181
 
96
- const DEFAULT_COLORS = [
97
- '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
98
- '#06b6d4', '#f97316', '#84cc16', '#ec4899', '#6366f1'
99
- ]
182
+ const DEFAULT_COLORS = COLOR_THEMES.default
183
+
184
+ // Özel Tooltip bileşeni
185
+ const CustomTooltip: React.FC<any> = ({ active, payload, label }) => {
186
+ if (!active || !payload || !payload.length) return null
187
+
188
+ return (
189
+ <motion.div
190
+ initial={{ opacity: 0, scale: 0.9 }}
191
+ animate={{ opacity: 1, scale: 1 }}
192
+ exit={{ opacity: 0, scale: 0.9 }}
193
+ className="backdrop-blur-xl bg-background/80 dark:bg-gray-900/90 p-3 rounded-xl shadow-2xl border border-border/50 dark:border-gray-700/50"
194
+ >
195
+ <p className="text-sm font-medium mb-2">{label}</p>
196
+ {payload.map((entry: any, index: number) => (
197
+ <div key={index} className="flex items-center gap-2 text-sm">
198
+ <div
199
+ className="w-3 h-3 rounded-full"
200
+ style={{ backgroundColor: entry.color }}
201
+ />
202
+ <span className="text-muted-foreground">{entry.name}:</span>
203
+ <span className="font-semibold">{entry.value}</span>
204
+ </div>
205
+ ))}
206
+ </motion.div>
207
+ )
208
+ }
209
+
210
+ // Özel Legend bileşeni
211
+ const CustomLegend: React.FC<any> = ({ payload, onItemClick }) => {
212
+ return (
213
+ <div className="flex flex-wrap items-center justify-center gap-4 mt-4">
214
+ {payload.map((entry: any, index: number) => (
215
+ <motion.button
216
+ key={`item-${index}`}
217
+ whileHover={{ scale: 1.05 }}
218
+ whileTap={{ scale: 0.95 }}
219
+ onClick={() => onItemClick && onItemClick(entry)}
220
+ className={cn(
221
+ "flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all",
222
+ "hover:bg-accent/10 dark:hover:bg-gray-800/50",
223
+ entry.inactive && "opacity-50"
224
+ )}
225
+ >
226
+ <motion.div
227
+ animate={{
228
+ scale: entry.inactive ? 0.8 : 1,
229
+ opacity: entry.inactive ? 0.5 : 1
230
+ }}
231
+ className="w-3 h-3 rounded-full"
232
+ style={{ backgroundColor: entry.color }}
233
+ />
234
+ <span className="text-sm font-medium">{entry.value}</span>
235
+ </motion.button>
236
+ ))}
237
+ </div>
238
+ )
239
+ }
240
+
241
+ // Mini sparkline bileşeni
242
+ const SparklinePreview = ({ data, dataKey, color }: any) => {
243
+ const values = data.map((d: any) => d[dataKey])
244
+ const min = Math.min(...values)
245
+ const max = Math.max(...values)
246
+ const range = max - min
247
+
248
+ return (
249
+ <svg width="60" height="20" className="ml-2">
250
+ <polyline
251
+ fill="none"
252
+ stroke={color}
253
+ strokeWidth="2"
254
+ points={values.map((v: number, i: number) =>
255
+ `${(i / (values.length - 1)) * 60},${20 - ((v - min) / range) * 20}`
256
+ ).join(' ')}
257
+ />
258
+ </svg>
259
+ )
260
+ }
261
+
262
+ // Loading Skeleton bileşeni
263
+ const ChartSkeleton = ({ height }: { height: number }) => (
264
+ <div className="w-full" style={{ height }}>
265
+ <div className="flex items-end justify-center h-full gap-2 px-8">
266
+ {[...Array(8)].map((_, i) => (
267
+ <motion.div
268
+ key={i}
269
+ initial={{ height: 0 }}
270
+ animate={{ height: `${Math.random() * 80 + 20}%` }}
271
+ transition={{
272
+ duration: 1.5,
273
+ delay: i * 0.1,
274
+ repeat: Infinity,
275
+ repeatType: "reverse"
276
+ }}
277
+ className="w-full bg-gradient-to-t from-primary/20 to-primary/5 rounded-t-lg"
278
+ />
279
+ ))}
280
+ </div>
281
+ </div>
282
+ )
100
283
 
101
284
  export function AdvancedChart({
102
285
  data,
@@ -126,9 +309,34 @@ export function AdvancedChart({
126
309
  error = null,
127
310
  animated = true,
128
311
  responsive = true,
312
+ showCrosshair = false,
313
+ enableZoom = false,
314
+ enablePan = false,
315
+ showMiniMap = false,
316
+ darkMode = false,
317
+ gradientColors = true,
318
+ interactiveLegend = true,
319
+ showDataLabels = false,
320
+ sparklineMode = false,
321
+ animationDuration = 1500,
322
+ theme = 'default',
129
323
  }: AdvancedChartProps) {
130
324
  const [isFullscreen, setIsFullscreen] = React.useState(false)
325
+ const [showSettings, setShowSettings] = React.useState(false)
326
+ const [zoomLevel, setZoomLevel] = React.useState(100)
327
+ const [selectedTheme, setSelectedTheme] = React.useState(theme)
328
+ const [hoveredDataPoint, setHoveredDataPoint] = React.useState<any>(null)
329
+ const [hiddenSeries, setHiddenSeries] = React.useState<Set<string>>(new Set())
131
330
  const chartRef = React.useRef<HTMLDivElement>(null)
331
+ const containerRef = React.useRef<HTMLDivElement>(null)
332
+
333
+ // Tema renklerini al
334
+ const themeColors = React.useMemo(() => {
335
+ return COLOR_THEMES[selectedTheme] || DEFAULT_COLORS
336
+ }, [selectedTheme])
337
+
338
+ // Gradient tanımlamaları için benzersiz ID oluştur
339
+ const gradientId = React.useId()
132
340
 
133
341
  // Calculate trend for the first series
134
342
  const trend = React.useMemo(() => {
@@ -149,48 +357,159 @@ export function AdvancedChart({
149
357
  }
150
358
  }, [data, series])
151
359
 
152
- const handleExport = (format: 'png' | 'svg' | 'pdf') => {
360
+ // Seri görünürlüğünü toggle et
361
+ const toggleSeriesVisibility = (dataKey: string) => {
362
+ setHiddenSeries(prev => {
363
+ const newSet = new Set(prev)
364
+ if (newSet.has(dataKey)) {
365
+ newSet.delete(dataKey)
366
+ } else {
367
+ newSet.add(dataKey)
368
+ }
369
+ return newSet
370
+ })
371
+ }
372
+
373
+ // Legend item click handler
374
+ const handleLegendItemClick = (entry: any) => {
375
+ if (interactiveLegend) {
376
+ toggleSeriesVisibility(entry.dataKey)
377
+ }
378
+ }
379
+
380
+ const handleExport = (format: 'png' | 'svg' | 'pdf' | 'json' | 'csv') => {
153
381
  if (onExport) {
154
382
  onExport(format)
155
383
  }
156
384
  }
385
+
386
+ // Zoom kontrolü
387
+ const handleZoom = (direction: 'in' | 'out') => {
388
+ setZoomLevel(prev => {
389
+ if (direction === 'in') return Math.min(prev + 10, 200)
390
+ return Math.max(prev - 10, 50)
391
+ })
392
+ }
157
393
 
394
+ // Gradient tanımlamaları oluştur
395
+ const renderGradientDefs = () => (
396
+ <defs>
397
+ {series.map((s, index) => {
398
+ const color = s.color || themeColors[index % themeColors.length]
399
+ return (
400
+ <linearGradient
401
+ key={`gradient-${s.dataKey}`}
402
+ id={`gradient-${gradientId}-${s.dataKey}`}
403
+ x1="0" y1="0" x2="0" y2="1"
404
+ >
405
+ <stop offset="0%" stopColor={color} stopOpacity={0.8} />
406
+ <stop offset="100%" stopColor={color} stopOpacity={0.1} />
407
+ </linearGradient>
408
+ )
409
+ })}
410
+ </defs>
411
+ )
412
+
158
413
  const renderChart = () => {
159
414
  const commonProps = {
160
415
  data,
161
416
  width: typeof width === 'string' ? undefined : width,
162
- height,
163
- margin: { top: 20, right: 30, left: 20, bottom: 20 },
164
- // onClick: onDataPointClick,
417
+ height: sparklineMode ? 60 : height,
418
+ margin: sparklineMode
419
+ ? { top: 5, right: 5, left: 5, bottom: 5 }
420
+ : { top: 20, right: 30, left: 20, bottom: showBrush ? 60 : 20 },
165
421
  }
166
422
 
167
- const visibleSeries = series.filter(s => !s.hide)
423
+ const visibleSeries = series.filter(s => !s.hide && !hiddenSeries.has(s.dataKey))
424
+
425
+ // Axis stil özellikleri
426
+ const axisStyle = {
427
+ fontSize: 12,
428
+ fill: darkMode ? '#9ca3af' : '#6b7280',
429
+ }
430
+
431
+ // Grid stil özellikleri
432
+ const gridStyle = {
433
+ stroke: darkMode ? '#374151' : '#e5e7eb',
434
+ strokeDasharray: '3 3',
435
+ opacity: 0.5
436
+ }
168
437
 
169
438
  switch (type) {
170
439
  case 'line':
171
440
  return (
172
441
  <LineChart {...commonProps}>
173
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
174
- <XAxis dataKey={xAxisKey} />
175
- <YAxis />
176
- {showTooltip && <Tooltip />}
177
- {showLegend && <Legend />}
178
- {visibleSeries.map((s, index) => (
179
- <Line
180
- key={s.dataKey}
181
- type={s.type || 'monotone'}
182
- dataKey={s.dataKey}
183
- stroke={s.color || colors[index % colors.length]}
184
- strokeWidth={s.strokeWidth || 2}
185
- name={s.name}
186
- animationDuration={animated ? 1000 : 0}
442
+ {gradientColors && renderGradientDefs()}
443
+ {showGrid && !sparklineMode && (
444
+ <CartesianGrid {...gridStyle} />
445
+ )}
446
+ {!sparklineMode && (
447
+ <>
448
+ <XAxis
449
+ dataKey={xAxisKey}
450
+ {...axisStyle}
451
+ tick={{ fontSize: 11 }}
452
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
453
+ />
454
+ <YAxis
455
+ {...axisStyle}
456
+ tick={{ fontSize: 11 }}
457
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
458
+ />
459
+ </>
460
+ )}
461
+ {showTooltip && !sparklineMode && (
462
+ <Tooltip
463
+ cursor={showCrosshair ? {
464
+ stroke: darkMode ? '#6b7280' : '#9ca3af',
465
+ strokeWidth: 1,
466
+ strokeDasharray: '5 5'
467
+ } : false}
187
468
  />
188
- ))}
469
+ )}
470
+ {showLegend && !sparklineMode && (
471
+ <Legend
472
+ content={(props) => {
473
+ const CustomLegendComponent = customLegend
474
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
475
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
476
+ }}
477
+ />
478
+ )}
479
+ {visibleSeries.map((s, index) => {
480
+ const color = s.color || themeColors[index % themeColors.length]
481
+ return (
482
+ <Line
483
+ key={s.dataKey}
484
+ type={s.type || 'monotone'}
485
+ dataKey={s.dataKey}
486
+ stroke={color}
487
+ strokeWidth={sparklineMode ? 1.5 : (s.strokeWidth || 2)}
488
+ strokeDasharray={s.strokeDasharray}
489
+ name={s.name}
490
+ dot={sparklineMode ? false : (s.dot !== undefined ? s.dot : {
491
+ r: 3,
492
+ strokeWidth: 2,
493
+ fill: darkMode ? '#1f2937' : '#ffffff'
494
+ })}
495
+ activeDot={sparklineMode ? false : (s.activeDot !== undefined ? s.activeDot : {
496
+ r: 5,
497
+ strokeWidth: 2,
498
+ fill: color,
499
+ stroke: darkMode ? '#1f2937' : '#ffffff',
500
+ className: 'animate-pulse'
501
+ })}
502
+ animationDuration={animated ? animationDuration : 0}
503
+ animationBegin={index * 100}
504
+ label={showDataLabels ? s.label : false}
505
+ />
506
+ )
507
+ })}
189
508
  {showReference && referenceLines.map((ref, index) => (
190
509
  <ReferenceLine
191
510
  key={index}
192
511
  y={ref.value}
193
- stroke={ref.color || '#666'}
512
+ stroke={ref.color || (darkMode ? '#6b7280' : '#9ca3af')}
194
513
  strokeDasharray="5 5"
195
514
  label={ref.label}
196
515
  />
@@ -200,95 +519,210 @@ export function AdvancedChart({
200
519
  key={index}
201
520
  x1={ref.x1}
202
521
  x2={ref.x2}
203
- fill={ref.color || '#f0f0f0'}
522
+ fill={ref.color || (darkMode ? '#374151' : '#f3f4f6')}
204
523
  fillOpacity={0.3}
205
524
  label={ref.label}
206
525
  />
207
526
  ))}
208
- {showBrush && <Brush />}
527
+ {showBrush && !sparklineMode && (
528
+ <Brush
529
+ height={30}
530
+ fill={darkMode ? '#1f2937' : '#f9fafb'}
531
+ stroke={darkMode ? '#374151' : '#e5e7eb'}
532
+ />
533
+ )}
209
534
  </LineChart>
210
535
  )
211
536
 
212
537
  case 'bar':
213
538
  return (
214
539
  <BarChart {...commonProps}>
215
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
216
- <XAxis dataKey={xAxisKey} />
217
- <YAxis />
218
- {showTooltip && <Tooltip />}
219
- {showLegend && <Legend />}
220
- {visibleSeries.map((s, index) => (
221
- <Bar
222
- key={s.dataKey}
223
- dataKey={s.dataKey}
224
- fill={s.color || colors[index % colors.length]}
225
- name={s.name}
226
- animationDuration={animated ? 1000 : 0}
540
+ {gradientColors && renderGradientDefs()}
541
+ {showGrid && <CartesianGrid {...gridStyle} />}
542
+ <XAxis
543
+ dataKey={xAxisKey}
544
+ {...axisStyle}
545
+ tick={{ fontSize: 11 }}
546
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
547
+ />
548
+ <YAxis
549
+ {...axisStyle}
550
+ tick={{ fontSize: 11 }}
551
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
552
+ />
553
+ {showTooltip && (
554
+ <Tooltip
555
+ cursor={{ fill: darkMode ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)' }}
227
556
  />
228
- ))}
557
+ )}
558
+ {showLegend && (
559
+ <Legend
560
+ content={(props) => {
561
+ const CustomLegendComponent = customLegend
562
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
563
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
564
+ }}
565
+ />
566
+ )}
567
+ {visibleSeries.map((s, index) => {
568
+ const color = s.color || themeColors[index % themeColors.length]
569
+ return (
570
+ <Bar
571
+ key={s.dataKey}
572
+ dataKey={s.dataKey}
573
+ fill={gradientColors ? `url(#gradient-${gradientId}-${s.dataKey})` : color}
574
+ name={s.name}
575
+ animationDuration={animated ? animationDuration : 0}
576
+ animationBegin={index * 100}
577
+ radius={[4, 4, 0, 0]}
578
+ label={showDataLabels ? s.label : false}
579
+ stackId={s.stackId}
580
+ onMouseEnter={() => setHoveredDataPoint(s.dataKey)}
581
+ onMouseLeave={() => setHoveredDataPoint(null)}
582
+ className="hover:opacity-80 transition-opacity cursor-pointer"
583
+ />
584
+ )
585
+ })}
229
586
  {showReference && referenceLines.map((ref, index) => (
230
587
  <ReferenceLine
231
588
  key={index}
232
589
  y={ref.value}
233
- stroke={ref.color || '#666'}
590
+ stroke={ref.color || (darkMode ? '#6b7280' : '#9ca3af')}
234
591
  strokeDasharray="5 5"
235
592
  label={ref.label}
236
593
  />
237
594
  ))}
238
- {showBrush && <Brush />}
595
+ {showBrush && (
596
+ <Brush
597
+ height={30}
598
+ fill={darkMode ? '#1f2937' : '#f9fafb'}
599
+ stroke={darkMode ? '#374151' : '#e5e7eb'}
600
+ />
601
+ )}
239
602
  </BarChart>
240
603
  )
241
604
 
242
605
  case 'area':
243
606
  return (
244
607
  <AreaChart {...commonProps}>
245
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
246
- <XAxis dataKey={xAxisKey} />
247
- <YAxis />
608
+ {gradientColors && renderGradientDefs()}
609
+ {showGrid && <CartesianGrid {...gridStyle} />}
610
+ <XAxis
611
+ dataKey={xAxisKey}
612
+ {...axisStyle}
613
+ tick={{ fontSize: 11 }}
614
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
615
+ />
616
+ <YAxis
617
+ {...axisStyle}
618
+ tick={{ fontSize: 11 }}
619
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
620
+ />
248
621
  {showTooltip && <Tooltip />}
249
- {showLegend && <Legend />}
250
- {visibleSeries.map((s, index) => (
251
- <Area
252
- key={s.dataKey}
253
- type={s.type || 'monotone'}
254
- dataKey={s.dataKey}
255
- stroke={s.color || colors[index % colors.length]}
256
- fill={s.color || colors[index % colors.length]}
257
- fillOpacity={s.fillOpacity || 0.3}
258
- name={s.name}
259
- animationDuration={animated ? 1000 : 0}
622
+ {showLegend && (
623
+ <Legend
624
+ content={(props) => {
625
+ const CustomLegendComponent = customLegend
626
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
627
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
628
+ }}
260
629
  />
261
- ))}
630
+ )}
631
+ {visibleSeries.map((s, index) => {
632
+ const color = s.color || themeColors[index % themeColors.length]
633
+ return (
634
+ <Area
635
+ key={s.dataKey}
636
+ type={s.type || 'monotone'}
637
+ dataKey={s.dataKey}
638
+ stroke={color}
639
+ strokeWidth={2}
640
+ fill={gradientColors ? `url(#gradient-${gradientId}-${s.dataKey})` : color}
641
+ fillOpacity={s.fillOpacity || 0.6}
642
+ name={s.name}
643
+ animationDuration={animated ? animationDuration : 0}
644
+ animationBegin={index * 100}
645
+ dot={s.dot !== undefined ? s.dot : false}
646
+ activeDot={s.activeDot !== undefined ? s.activeDot : {
647
+ r: 4,
648
+ strokeWidth: 2,
649
+ fill: color,
650
+ stroke: darkMode ? '#1f2937' : '#ffffff',
651
+ className: 'animate-pulse'
652
+ }}
653
+ label={showDataLabels ? s.label : false}
654
+ stackId={s.stackId}
655
+ />
656
+ )
657
+ })}
262
658
  {showReference && referenceLines.map((ref, index) => (
263
659
  <ReferenceLine
264
660
  key={index}
265
661
  y={ref.value}
266
- stroke={ref.color || '#666'}
662
+ stroke={ref.color || (darkMode ? '#6b7280' : '#9ca3af')}
267
663
  strokeDasharray="5 5"
268
664
  label={ref.label}
269
665
  />
270
666
  ))}
271
- {showBrush && <Brush />}
667
+ {showBrush && (
668
+ <Brush
669
+ height={30}
670
+ fill={darkMode ? '#1f2937' : '#f9fafb'}
671
+ stroke={darkMode ? '#374151' : '#e5e7eb'}
672
+ />
673
+ )}
272
674
  </AreaChart>
273
675
  )
274
676
 
275
677
  case 'pie':
276
678
  return (
277
679
  <PieChart {...commonProps}>
278
- {showTooltip && <Tooltip />}
279
- {showLegend && <Legend />}
680
+ {showTooltip && (
681
+ <Tooltip
682
+ /* @ts-ignore */
683
+ content={customTooltip ? customTooltip : CustomTooltip}
684
+ />
685
+ )}
686
+ {showLegend && (
687
+ <Legend
688
+ content={(props) => {
689
+ const CustomLegendComponent = customLegend
690
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
691
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
692
+ }}
693
+ />
694
+ )}
280
695
  <Pie
281
696
  data={data}
282
697
  dataKey={series[0]?.dataKey || 'value'}
283
698
  nameKey={xAxisKey}
284
699
  cx="50%"
285
700
  cy="50%"
701
+ innerRadius={type === 'pie' ? 0 : '40%'}
286
702
  outerRadius={Math.min(height, typeof width === 'number' ? width : 400) / 3}
287
- animationDuration={animated ? 1000 : 0}
703
+ animationDuration={animated ? animationDuration : 0}
704
+ animationBegin={0}
705
+ label={showDataLabels ? {
706
+ fill: darkMode ? '#e5e7eb' : '#374151',
707
+ fontSize: 12
708
+ } : false}
709
+ labelLine={showDataLabels ? {
710
+ stroke: darkMode ? '#6b7280' : '#9ca3af',
711
+ strokeWidth: 1
712
+ } : false}
288
713
  >
289
- {data.map((entry, index) => (
290
- <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
291
- ))}
714
+ {data.map((entry, index) => {
715
+ const color = themeColors[index % themeColors.length]
716
+ return (
717
+ <Cell
718
+ key={`cell-${index}`}
719
+ fill={color}
720
+ className="hover:opacity-80 transition-opacity cursor-pointer"
721
+ onMouseEnter={() => setHoveredDataPoint(entry)}
722
+ onMouseLeave={() => setHoveredDataPoint(null)}
723
+ />
724
+ )
725
+ })}
292
726
  </Pie>
293
727
  </PieChart>
294
728
  )
@@ -296,23 +730,142 @@ export function AdvancedChart({
296
730
  case 'scatter':
297
731
  return (
298
732
  <ScatterChart {...commonProps}>
299
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
300
- <XAxis dataKey={xAxisKey} />
301
- <YAxis dataKey={yAxisKey} />
733
+ {showGrid && <CartesianGrid {...gridStyle} />}
734
+ <XAxis
735
+ dataKey={xAxisKey}
736
+ {...axisStyle}
737
+ tick={{ fontSize: 11 }}
738
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
739
+ />
740
+ <YAxis
741
+ dataKey={yAxisKey}
742
+ {...axisStyle}
743
+ tick={{ fontSize: 11 }}
744
+ axisLine={{ stroke: darkMode ? '#4b5563' : '#d1d5db' }}
745
+ />
302
746
  {showTooltip && <Tooltip />}
303
- {showLegend && <Legend />}
304
- {visibleSeries.map((s, index) => (
305
- <Scatter
306
- key={s.dataKey}
307
- dataKey={s.dataKey}
308
- fill={s.color || colors[index % colors.length]}
309
- name={s.name}
310
- animationDuration={animated ? 1000 : 0}
747
+ {showLegend && (
748
+ <Legend
749
+ content={(props) => {
750
+ const CustomLegendComponent = customLegend
751
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
752
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
753
+ }}
311
754
  />
312
- ))}
755
+ )}
756
+ {visibleSeries.map((s, index) => {
757
+ const color = s.color || themeColors[index % themeColors.length]
758
+ return (
759
+ <Scatter
760
+ key={s.dataKey}
761
+ data={data}
762
+ fill={color}
763
+ name={s.name}
764
+ animationDuration={animated ? animationDuration : 0}
765
+ animationBegin={index * 100}
766
+ shape={(props: any) => {
767
+ const isHovered = hoveredDataPoint === props.payload
768
+ return (
769
+ <circle
770
+ {...props}
771
+ r={isHovered ? 6 : 4}
772
+ className="transition-all duration-200 hover:r-6"
773
+ onMouseEnter={() => setHoveredDataPoint(props.payload)}
774
+ onMouseLeave={() => setHoveredDataPoint(null)}
775
+ />
776
+ )
777
+ }}
778
+ />
779
+ )
780
+ })}
313
781
  </ScatterChart>
314
782
  )
315
783
 
784
+ case 'radar':
785
+ return (
786
+ <RadarChart {...commonProps}>
787
+ <PolarGrid
788
+ stroke={darkMode ? '#374151' : '#e5e7eb'}
789
+ strokeDasharray="3 3"
790
+ />
791
+ <PolarAngleAxis
792
+ dataKey={xAxisKey}
793
+ {...axisStyle}
794
+ tick={{ fontSize: 10 }}
795
+ />
796
+ <PolarRadiusAxis
797
+ {...axisStyle}
798
+ tick={{ fontSize: 10 }}
799
+ axisLine={false}
800
+ />
801
+ {showTooltip && (
802
+ <Tooltip
803
+ /* @ts-ignore */
804
+ content={customTooltip ? customTooltip : CustomTooltip}
805
+ />
806
+ )}
807
+ {showLegend && (
808
+ <Legend
809
+ content={(props) => {
810
+ const CustomLegendComponent = customLegend
811
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
812
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
813
+ }}
814
+ />
815
+ )}
816
+ {visibleSeries.map((s, index) => {
817
+ const color = s.color || themeColors[index % themeColors.length]
818
+ return (
819
+ <Radar
820
+ key={s.dataKey}
821
+ dataKey={s.dataKey}
822
+ stroke={color}
823
+ fill={color}
824
+ fillOpacity={0.3}
825
+ name={s.name}
826
+ animationDuration={animated ? animationDuration : 0}
827
+ animationBegin={index * 100}
828
+ />
829
+ )
830
+ })}
831
+ </RadarChart>
832
+ )
833
+
834
+ case 'radialBar':
835
+ return (
836
+ <RadialBarChart
837
+ {...commonProps}
838
+ innerRadius="10%"
839
+ outerRadius="90%"
840
+ >
841
+ {showTooltip && (
842
+ <Tooltip
843
+ /* @ts-ignore */
844
+ content={customTooltip ? customTooltip : CustomTooltip}
845
+ />
846
+ )}
847
+ {showLegend && (
848
+ <Legend
849
+ content={(props) => {
850
+ const CustomLegendComponent = customLegend
851
+ return CustomLegendComponent ? <CustomLegendComponent {...props} /> :
852
+ <CustomLegend {...props} onItemClick={handleLegendItemClick} />
853
+ }}
854
+ />
855
+ )}
856
+ <RadialBar
857
+ dataKey={series[0]?.dataKey || 'value'}
858
+ cornerRadius={10}
859
+ fill={themeColors[0]}
860
+ animationDuration={animated ? animationDuration : 0}
861
+ label={showDataLabels ? {
862
+ fill: darkMode ? '#e5e7eb' : '#374151',
863
+ position: 'center'
864
+ } : false}
865
+ />
866
+ </RadialBarChart>
867
+ )
868
+
316
869
  default:
317
870
  return <div>Unsupported chart type</div>
318
871
  }
@@ -320,12 +873,20 @@ export function AdvancedChart({
320
873
 
321
874
  if (loading) {
322
875
  return (
323
- <Card className={cn("w-full", className)}>
324
- <CardContent className="flex items-center justify-center" style={{ height }}>
325
- <div className="flex items-center space-x-2">
326
- <RefreshCw className="h-4 w-4 animate-spin" />
327
- <span>Loading chart...</span>
876
+ <Card className={cn("w-full overflow-hidden", className)}>
877
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
878
+ <div className="space-y-1">
879
+ <Skeleton className="h-4 w-32" />
880
+ <Skeleton className="h-3 w-48" />
881
+ </div>
882
+ <div className="flex items-center space-x-1">
883
+ <Skeleton className="h-8 w-8 rounded" />
884
+ <Skeleton className="h-8 w-8 rounded" />
885
+ <Skeleton className="h-8 w-8 rounded" />
328
886
  </div>
887
+ </CardHeader>
888
+ <CardContent>
889
+ <ChartSkeleton height={height} />
329
890
  </CardContent>
330
891
  </Card>
331
892
  )
@@ -335,77 +896,336 @@ export function AdvancedChart({
335
896
  return (
336
897
  <Card className={cn("w-full", className)}>
337
898
  <CardContent className="flex items-center justify-center" style={{ height }}>
338
- <div className="text-center">
339
- <p className="text-destructive mb-2">{error}</p>
899
+ <motion.div
900
+ initial={{ opacity: 0, scale: 0.9 }}
901
+ animate={{ opacity: 1, scale: 1 }}
902
+ className="text-center"
903
+ >
904
+ <div className="w-12 h-12 rounded-full bg-destructive/10 dark:bg-destructive/20 flex items-center justify-center mx-auto mb-4">
905
+ <X className="w-6 h-6 text-destructive" />
906
+ </div>
907
+ <p className="text-destructive mb-4 font-medium">{error}</p>
340
908
  {onRefresh && (
341
- <Button variant="outline" onClick={onRefresh}>
342
- <RefreshCw className="mr-2 h-4 w-4" />
909
+ <Button
910
+ variant="outline"
911
+ onClick={onRefresh}
912
+ className="group"
913
+ >
914
+ <RefreshCw className="mr-2 h-4 w-4 group-hover:rotate-180 transition-transform duration-500" />
343
915
  Retry
344
916
  </Button>
345
917
  )}
346
- </div>
918
+ </motion.div>
347
919
  </CardContent>
348
920
  </Card>
349
921
  )
350
922
  }
351
923
 
352
924
  return (
353
- <Card className={cn("w-full", className)} ref={chartRef}>
354
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
355
- <div className="space-y-1">
356
- <CardTitle className="text-base font-medium">
357
- {title}
358
- {trend && (
359
- <span className="ml-2 inline-flex items-center">
360
- {trend.direction === 'up' && <TrendingUp className="h-4 w-4 text-green-500" />}
361
- {trend.direction === 'down' && <TrendingDown className="h-4 w-4 text-red-500" />}
362
- {trend.direction === 'neutral' && <Minus className="h-4 w-4 text-gray-500" />}
363
- <span className={cn(
364
- "ml-1 text-sm",
365
- trend.direction === 'up' && "text-green-500",
366
- trend.direction === 'down' && "text-red-500",
367
- trend.direction === 'neutral' && "text-gray-500"
368
- )}>
369
- {trend.percentage}%
925
+ <motion.div
926
+ ref={containerRef}
927
+ className={cn(
928
+ "relative",
929
+ isFullscreen && "fixed inset-0 z-50 bg-background p-4"
930
+ )}
931
+ initial={{ opacity: 0, y: 20 }}
932
+ animate={{ opacity: 1, y: 0 }}
933
+ transition={{ duration: 0.5 }}
934
+ >
935
+ <Card className={cn(
936
+ "w-full overflow-hidden transition-all duration-300",
937
+ darkMode && "dark",
938
+ isFullscreen && "h-full",
939
+ className
940
+ )} ref={chartRef}>
941
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
942
+ <motion.div
943
+ className="space-y-1"
944
+ initial={{ opacity: 0, x: -20 }}
945
+ animate={{ opacity: 1, x: 0 }}
946
+ transition={{ delay: 0.1 }}
947
+ >
948
+ <CardTitle className="text-base font-medium flex items-center gap-2">
949
+ {title}
950
+ {trend && (
951
+ <motion.span
952
+ className="inline-flex items-center"
953
+ initial={{ scale: 0 }}
954
+ animate={{ scale: 1 }}
955
+ transition={{ type: "spring", delay: 0.3 }}
956
+ >
957
+ {trend.direction === 'up' && (
958
+ <TrendingUp className="h-4 w-4 text-green-500 dark:text-green-400" />
959
+ )}
960
+ {trend.direction === 'down' && (
961
+ <TrendingDown className="h-4 w-4 text-red-500 dark:text-red-400" />
962
+ )}
963
+ {trend.direction === 'neutral' && (
964
+ <Minus className="h-4 w-4 text-gray-500 dark:text-gray-400" />
965
+ )}
966
+ <span className={cn(
967
+ "ml-1 text-sm font-semibold",
968
+ trend.direction === 'up' && "text-green-500 dark:text-green-400",
969
+ trend.direction === 'down' && "text-red-500 dark:text-red-400",
970
+ trend.direction === 'neutral' && "text-gray-500 dark:text-gray-400"
971
+ )}>
972
+ {trend.percentage}%
973
+ </span>
974
+ </motion.span>
975
+ )}
976
+ {sparklineMode && series[0] && (
977
+ <SparklinePreview
978
+ data={data}
979
+ dataKey={series[0].dataKey}
980
+ color={series[0].color || themeColors[0]}
981
+ />
982
+ )}
983
+ </CardTitle>
984
+ {subtitle && (
985
+ <p className="text-xs text-muted-foreground">{subtitle}</p>
986
+ )}
987
+ </motion.div>
988
+
989
+ <motion.div
990
+ className="flex items-center space-x-1"
991
+ initial={{ opacity: 0, x: 20 }}
992
+ animate={{ opacity: 1, x: 0 }}
993
+ transition={{ delay: 0.2 }}
994
+ >
995
+ {/* Zoom controls */}
996
+ {enableZoom && !sparklineMode && (
997
+ <div className="flex items-center border rounded-lg mr-2">
998
+ <Button
999
+ variant="ghost"
1000
+ size="sm"
1001
+ onClick={() => handleZoom('out')}
1002
+ disabled={zoomLevel <= 50}
1003
+ className="h-7 w-7 p-0"
1004
+ >
1005
+ <ZoomOut className="h-3 w-3" />
1006
+ </Button>
1007
+ <span className="text-xs px-2 text-muted-foreground">
1008
+ {zoomLevel}%
370
1009
  </span>
371
- </span>
1010
+ <Button
1011
+ variant="ghost"
1012
+ size="sm"
1013
+ onClick={() => handleZoom('in')}
1014
+ disabled={zoomLevel >= 200}
1015
+ className="h-7 w-7 p-0"
1016
+ >
1017
+ <ZoomIn className="h-3 w-3" />
1018
+ </Button>
1019
+ </div>
372
1020
  )}
373
- </CardTitle>
374
- {subtitle && (
375
- <p className="text-xs text-muted-foreground">{subtitle}</p>
376
- )}
377
- </div>
378
- <div className="flex items-center space-x-1">
379
- {onRefresh && (
380
- <Button variant="ghost" size="sm" onClick={onRefresh}>
381
- <RefreshCw className="h-4 w-4" />
1021
+
1022
+ {/* Other controls */}
1023
+ {onRefresh && (
1024
+ <Button
1025
+ variant="ghost"
1026
+ size="sm"
1027
+ onClick={onRefresh}
1028
+ className="group"
1029
+ >
1030
+ <RefreshCw className="h-4 w-4 group-hover:rotate-180 transition-transform duration-500" />
1031
+ </Button>
1032
+ )}
1033
+
1034
+ {/* Settings popover */}
1035
+ <Popover open={showSettings} onOpenChange={setShowSettings}>
1036
+ <PopoverTrigger asChild>
1037
+ <Button variant="ghost" size="sm">
1038
+ <Settings className="h-4 w-4" />
1039
+ </Button>
1040
+ </PopoverTrigger>
1041
+ <PopoverContent className="w-80" align="end">
1042
+ <div className="space-y-4">
1043
+ <div className="space-y-2">
1044
+ <h4 className="font-medium text-sm">Chart Settings</h4>
1045
+ </div>
1046
+
1047
+ {/* Theme selector */}
1048
+ <div className="space-y-2">
1049
+ <Label className="text-xs">Color Theme</Label>
1050
+ <div className="grid grid-cols-3 gap-2">
1051
+ {Object.entries(COLOR_THEMES).map(([themeName, colors]) => (
1052
+ <button
1053
+ key={themeName}
1054
+ onClick={() => setSelectedTheme(themeName as any)}
1055
+ className={cn(
1056
+ "p-2 rounded-lg border-2 transition-all",
1057
+ selectedTheme === themeName
1058
+ ? "border-primary"
1059
+ : "border-transparent hover:border-muted-foreground/20"
1060
+ )}
1061
+ >
1062
+ <div className="flex gap-1">
1063
+ {colors.slice(0, 3).map((color, i) => (
1064
+ <div
1065
+ key={i}
1066
+ className="w-3 h-3 rounded-full"
1067
+ style={{ backgroundColor: color }}
1068
+ />
1069
+ ))}
1070
+ </div>
1071
+ <span className="text-xs mt-1 block capitalize">
1072
+ {themeName}
1073
+ </span>
1074
+ </button>
1075
+ ))}
1076
+ </div>
1077
+ </div>
1078
+
1079
+ {/* Animation toggle */}
1080
+ <div className="flex items-center justify-between">
1081
+ <Label htmlFor="animation" className="text-xs">
1082
+ Animations
1083
+ </Label>
1084
+ <Switch
1085
+ id="animation"
1086
+ checked={animated}
1087
+ onCheckedChange={(checked) => {
1088
+ // Update animated prop
1089
+ }}
1090
+ />
1091
+ </div>
1092
+
1093
+ {/* Grid toggle */}
1094
+ <div className="flex items-center justify-between">
1095
+ <Label htmlFor="grid" className="text-xs">
1096
+ Show Grid
1097
+ </Label>
1098
+ <Switch
1099
+ id="grid"
1100
+ checked={showGrid}
1101
+ onCheckedChange={(checked) => {
1102
+ // Update showGrid prop
1103
+ }}
1104
+ />
1105
+ </div>
1106
+
1107
+ {/* Crosshair toggle */}
1108
+ <div className="flex items-center justify-between">
1109
+ <Label htmlFor="crosshair" className="text-xs">
1110
+ Crosshair Cursor
1111
+ </Label>
1112
+ <Switch
1113
+ id="crosshair"
1114
+ checked={showCrosshair}
1115
+ onCheckedChange={(checked) => {
1116
+ // Update showCrosshair prop
1117
+ }}
1118
+ />
1119
+ </div>
1120
+ </div>
1121
+ </PopoverContent>
1122
+ </Popover>
1123
+
1124
+ {/* Export menu */}
1125
+ {onExport && (
1126
+ <DropdownMenu>
1127
+ <DropdownMenuTrigger asChild>
1128
+ <Button variant="ghost" size="sm">
1129
+ <Download className="h-4 w-4" />
1130
+ </Button>
1131
+ </DropdownMenuTrigger>
1132
+ <DropdownMenuContent align="end">
1133
+ <DropdownMenuItem onClick={() => handleExport('png')}>
1134
+ <Image className="mr-2 h-4 w-4" />
1135
+ Export as PNG
1136
+ </DropdownMenuItem>
1137
+ <DropdownMenuItem onClick={() => handleExport('svg')}>
1138
+ <FileSpreadsheet className="mr-2 h-4 w-4" />
1139
+ Export as SVG
1140
+ </DropdownMenuItem>
1141
+ <DropdownMenuItem onClick={() => handleExport('pdf')}>
1142
+ <FileSpreadsheet className="mr-2 h-4 w-4" />
1143
+ Export as PDF
1144
+ </DropdownMenuItem>
1145
+ <DropdownMenuSeparator />
1146
+ <DropdownMenuItem onClick={() => handleExport('csv')}>
1147
+ <FileSpreadsheet className="mr-2 h-4 w-4" />
1148
+ Export as CSV
1149
+ </DropdownMenuItem>
1150
+ <DropdownMenuItem onClick={() => handleExport('json')}>
1151
+ <FileJson className="mr-2 h-4 w-4" />
1152
+ Export as JSON
1153
+ </DropdownMenuItem>
1154
+ </DropdownMenuContent>
1155
+ </DropdownMenu>
1156
+ )}
1157
+
1158
+ {/* Fullscreen toggle */}
1159
+ <Button
1160
+ variant="ghost"
1161
+ size="sm"
1162
+ onClick={() => setIsFullscreen(!isFullscreen)}
1163
+ >
1164
+ {isFullscreen ? (
1165
+ <X className="h-4 w-4" />
1166
+ ) : (
1167
+ <Maximize2 className="h-4 w-4" />
1168
+ )}
382
1169
  </Button>
1170
+ </motion.div>
1171
+ </CardHeader>
1172
+
1173
+ <CardContent className="relative">
1174
+ {/* Pan indicator */}
1175
+ {enablePan && (
1176
+ <motion.div
1177
+ initial={{ opacity: 0 }}
1178
+ animate={{ opacity: 1 }}
1179
+ className="absolute top-2 left-2 z-10 flex items-center gap-1 text-xs text-muted-foreground bg-background/80 backdrop-blur-sm px-2 py-1 rounded-md"
1180
+ >
1181
+ <Move className="h-3 w-3" />
1182
+ <span>Drag to pan</span>
1183
+ </motion.div>
383
1184
  )}
384
- <Button variant="ghost" size="sm">
385
- <Settings className="h-4 w-4" />
386
- </Button>
387
- {onExport && (
388
- <Button variant="ghost" size="sm" onClick={() => handleExport('png')}>
389
- <Download className="h-4 w-4" />
390
- </Button>
1185
+
1186
+ {/* Chart container */}
1187
+ <motion.div
1188
+ animate={{ scale: zoomLevel / 100 }}
1189
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
1190
+ style={{ transformOrigin: "center" }}
1191
+ >
1192
+ {responsive ? (
1193
+ <ResponsiveContainer width="100%" height={sparklineMode ? 60 : height}>
1194
+ {renderChart()}
1195
+ </ResponsiveContainer>
1196
+ ) : (
1197
+ <div style={{ width: '100%', height: sparklineMode ? '60px' : `${height}px` }}>
1198
+ {renderChart()}
1199
+ </div>
1200
+ )}
1201
+ </motion.div>
1202
+
1203
+ {/* Mini map */}
1204
+ {showMiniMap && !sparklineMode && (
1205
+ <motion.div
1206
+ initial={{ opacity: 0, y: 20 }}
1207
+ animate={{ opacity: 1, y: 0 }}
1208
+ className="absolute bottom-2 right-2 w-32 h-20 bg-background/80 backdrop-blur-sm border rounded-lg p-1"
1209
+ >
1210
+ <ResponsiveContainer width="100%" height="100%">
1211
+ <LineChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
1212
+ {series.filter(s => !s.hide && !hiddenSeries.has(s.dataKey)).map((s, index) => (
1213
+ <Line
1214
+ key={s.dataKey}
1215
+ type="monotone"
1216
+ dataKey={s.dataKey}
1217
+ stroke={s.color || themeColors[index % themeColors.length]}
1218
+ strokeWidth={1}
1219
+ dot={false}
1220
+ />
1221
+ ))}
1222
+ </LineChart>
1223
+ </ResponsiveContainer>
1224
+ </motion.div>
391
1225
  )}
392
- <Button variant="ghost" size="sm" onClick={() => setIsFullscreen(!isFullscreen)}>
393
- <Maximize2 className="h-4 w-4" />
394
- </Button>
395
- </div>
396
- </CardHeader>
397
- <CardContent>
398
- {responsive ? (
399
- <ResponsiveContainer width="100%" height={height}>
400
- {renderChart()}
401
- </ResponsiveContainer>
402
- ) : (
403
- <div style={{ width: '100%', height: `${height}px` }}>
404
- {renderChart()}
405
- </div>
406
- )}
407
- </CardContent>
408
- </Card>
1226
+ </CardContent>
1227
+ </Card>
1228
+ </motion.div>
409
1229
  )
410
1230
  }
411
1231