Skip to main content

torsh_hub/
visualization.rs

1//! Visualization tools for model analytics and profiling
2//!
3//! This module provides comprehensive visualization capabilities for:
4//! - Model performance metrics and benchmarks
5//! - Usage analytics and trends
6//! - Training progress and fine-tuning metrics
7//! - System resource utilization
8//! - Model architecture diagrams
9
10// Framework infrastructure - components designed for future use
11#![allow(dead_code)]
12use crate::analytics::{ModelPerformanceData, ModelUsageStats, RealTimeMetrics};
13use crate::fine_tuning::TrainingHistory;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::path::PathBuf;
17use std::time::{Duration, SystemTime};
18use torsh_core::error::{Result, TorshError};
19
20/// Visualization engine for generating charts and graphs
21pub struct VisualizationEngine {
22    config: VisualizationConfig,
23    chart_renderer: ChartRenderer,
24    dashboard_generator: DashboardGenerator,
25    export_manager: ExportManager,
26}
27
28impl VisualizationEngine {
29    /// Get chart renderer
30    pub fn chart_renderer(&self) -> &ChartRenderer {
31        &self.chart_renderer
32    }
33
34    /// Get dashboard generator
35    pub fn dashboard_generator(&self) -> &DashboardGenerator {
36        &self.dashboard_generator
37    }
38}
39
40/// Configuration for visualization settings
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct VisualizationConfig {
43    pub theme: VisualizationTheme,
44    pub default_chart_size: ChartSize,
45    pub color_palette: ColorPalette,
46    pub animation_enabled: bool,
47    pub high_dpi_enabled: bool,
48    pub export_formats: Vec<ExportFormat>,
49}
50
51/// Visual themes for charts and dashboards
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub enum VisualizationTheme {
54    Light,
55    Dark,
56    HighContrast,
57    Custom(CustomTheme),
58}
59
60/// Custom theme configuration
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct CustomTheme {
63    pub background_color: String,
64    pub text_color: String,
65    pub primary_color: String,
66    pub secondary_color: String,
67    pub accent_color: String,
68    pub grid_color: String,
69}
70
71/// Chart size configuration
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ChartSize {
74    pub width: u32,
75    pub height: u32,
76}
77
78/// Color palette for charts
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ColorPalette {
81    pub primary_colors: Vec<String>,
82    pub gradient_colors: Vec<String>,
83    pub status_colors: StatusColors,
84}
85
86/// Status-specific colors
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct StatusColors {
89    pub success: String,
90    pub warning: String,
91    pub error: String,
92    pub info: String,
93}
94
95/// Export format options
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub enum ExportFormat {
98    PNG,
99    SVG,
100    PDF,
101    HTML,
102    JSON,
103}
104
105/// Chart renderer for creating various chart types
106pub struct ChartRenderer {
107    config: VisualizationConfig,
108}
109
110impl ChartRenderer {
111    /// Get visualization configuration
112    pub fn config(&self) -> &VisualizationConfig {
113        &self.config
114    }
115}
116
117/// Dashboard generator for creating comprehensive dashboards
118pub struct DashboardGenerator {
119    templates: HashMap<String, DashboardTemplate>,
120}
121
122impl DashboardGenerator {
123    /// Get dashboard templates
124    pub fn templates(&self) -> &HashMap<String, DashboardTemplate> {
125        &self.templates
126    }
127}
128
129/// Export manager for saving visualizations
130pub struct ExportManager {
131    output_directory: PathBuf,
132}
133
134/// Different types of charts available
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub enum ChartType {
137    Line,
138    Bar,
139    Pie,
140    Scatter,
141    Heatmap,
142    Histogram,
143    BoxPlot,
144    Radar,
145    Treemap,
146    Sankey,
147}
148
149/// Chart data structure
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ChartData {
152    pub title: String,
153    pub chart_type: ChartType,
154    pub datasets: Vec<Dataset>,
155    pub x_axis: Axis,
156    pub y_axis: Axis,
157    pub legend: Option<Legend>,
158    pub annotations: Vec<Annotation>,
159}
160
161/// Dataset for chart data
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct Dataset {
164    pub label: String,
165    pub data: Vec<DataPoint>,
166    pub color: Option<String>,
167    pub style: Option<LineStyle>,
168    pub fill: bool,
169}
170
171/// Individual data point
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct DataPoint {
174    pub x: f64,
175    pub y: f64,
176    pub label: Option<String>,
177    pub metadata: HashMap<String, String>,
178}
179
180/// Axis configuration
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct Axis {
183    pub title: String,
184    pub min: Option<f64>,
185    pub max: Option<f64>,
186    pub scale: AxisScale,
187    pub format: Option<String>,
188}
189
190/// Axis scaling options
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub enum AxisScale {
193    Linear,
194    Logarithmic,
195    Time,
196    Category,
197}
198
199/// Line style options
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub enum LineStyle {
202    Solid,
203    Dashed,
204    Dotted,
205    DashDot,
206}
207
208/// Legend configuration
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct Legend {
211    pub position: LegendPosition,
212    pub columns: u32,
213    pub font_size: u32,
214}
215
216/// Legend position options
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub enum LegendPosition {
219    Top,
220    Bottom,
221    Left,
222    Right,
223    TopLeft,
224    TopRight,
225    BottomLeft,
226    BottomRight,
227}
228
229/// Chart annotations
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct Annotation {
232    pub annotation_type: AnnotationType,
233    pub x: f64,
234    pub y: f64,
235    pub text: String,
236    pub color: Option<String>,
237}
238
239/// Annotation types
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub enum AnnotationType {
242    Point,
243    Line,
244    Rectangle,
245    Circle,
246    Arrow,
247    Text,
248}
249
250/// Dashboard template structure
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct DashboardTemplate {
253    pub name: String,
254    pub description: String,
255    pub layout: DashboardLayout,
256    pub widgets: Vec<DashboardWidget>,
257    pub refresh_interval: Option<Duration>,
258}
259
260/// Dashboard layout configuration
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct DashboardLayout {
263    pub columns: u32,
264    pub rows: u32,
265    pub padding: u32,
266    pub margin: u32,
267}
268
269/// Dashboard widget
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct DashboardWidget {
272    pub widget_type: WidgetType,
273    pub title: String,
274    pub position: WidgetPosition,
275    pub size: WidgetSize,
276    pub data_source: DataSource,
277    pub update_interval: Option<Duration>,
278}
279
280/// Widget types
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub enum WidgetType {
283    Chart(ChartType),
284    Metric,
285    Table,
286    Text,
287    Image,
288    Gauge,
289    Progress,
290    StatusIndicator,
291}
292
293/// Widget position
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct WidgetPosition {
296    pub x: u32,
297    pub y: u32,
298}
299
300/// Widget size
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct WidgetSize {
303    pub width: u32,
304    pub height: u32,
305}
306
307/// Data source for widgets
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub enum DataSource {
310    Analytics(String),
311    Metrics(String),
312    Performance(String),
313    Usage(String),
314    Custom(String),
315}
316
317/// Performance visualization data
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct PerformanceVisualization {
320    pub model_id: String,
321    pub inference_time_chart: ChartData,
322    pub throughput_chart: ChartData,
323    pub resource_utilization_chart: ChartData,
324    pub bottleneck_analysis: Vec<BottleneckVisualization>,
325}
326
327/// Bottleneck visualization
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct BottleneckVisualization {
330    pub bottleneck_type: String,
331    pub severity: String,
332    pub impact_chart: ChartData,
333    pub timeline: Vec<TimelineEvent>,
334}
335
336/// Timeline event for bottleneck analysis
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct TimelineEvent {
339    pub timestamp: SystemTime,
340    pub event_type: String,
341    pub description: String,
342    pub severity: String,
343}
344
345/// Usage analytics visualization
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct UsageVisualization {
348    pub usage_trends: ChartData,
349    pub popular_models: ChartData,
350    pub user_patterns: ChartData,
351    pub geographic_distribution: Option<ChartData>,
352    pub time_series_usage: ChartData,
353}
354
355/// Training progress visualization
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct TrainingVisualization {
358    pub loss_curve: ChartData,
359    pub accuracy_curve: ChartData,
360    pub learning_rate_schedule: ChartData,
361    pub gradient_norms: ChartData,
362    pub validation_metrics: ChartData,
363}
364
365impl Default for VisualizationConfig {
366    fn default() -> Self {
367        Self {
368            theme: VisualizationTheme::Light,
369            default_chart_size: ChartSize {
370                width: 800,
371                height: 600,
372            },
373            color_palette: ColorPalette::default(),
374            animation_enabled: true,
375            high_dpi_enabled: true,
376            export_formats: vec![ExportFormat::PNG, ExportFormat::SVG, ExportFormat::HTML],
377        }
378    }
379}
380
381impl VisualizationConfig {
382    /// Create a dark theme configuration preset optimized for dark backgrounds
383    /// with carefully selected colors for good contrast and readability
384    pub fn dark_theme() -> Self {
385        Self {
386            theme: VisualizationTheme::Dark,
387            color_palette: ColorPalette {
388                primary_colors: vec![
389                    "#FF7979".to_string(),
390                    "#74B9FF".to_string(),
391                    "#00B894".to_string(),
392                    "#FDCB6E".to_string(),
393                    "#E17055".to_string(),
394                    "#A29BFE".to_string(),
395                    "#FD79A8".to_string(),
396                    "#81ECEC".to_string(),
397                ],
398                gradient_colors: vec![
399                    "#2C3E50".to_string(),
400                    "#34495E".to_string(),
401                    "#7F8C8D".to_string(),
402                    "#95A5A6".to_string(),
403                ],
404                status_colors: StatusColors {
405                    success: "#00B894".to_string(),
406                    warning: "#FDCB6E".to_string(),
407                    error: "#E74C3C".to_string(),
408                    info: "#74B9FF".to_string(),
409                },
410            },
411            ..Default::default()
412        }
413    }
414
415    /// Create a high contrast configuration preset for accessibility compliance
416    /// using maximum contrast colors suitable for users with visual impairments
417    pub fn high_contrast() -> Self {
418        Self {
419            theme: VisualizationTheme::HighContrast,
420            color_palette: ColorPalette {
421                primary_colors: vec![
422                    "#000000".to_string(),
423                    "#FFFFFF".to_string(),
424                    "#FF0000".to_string(),
425                    "#00FF00".to_string(),
426                    "#0000FF".to_string(),
427                    "#FFFF00".to_string(),
428                    "#FF00FF".to_string(),
429                    "#00FFFF".to_string(),
430                ],
431                gradient_colors: vec!["#000000".to_string(), "#FFFFFF".to_string()],
432                status_colors: StatusColors {
433                    success: "#00FF00".to_string(),
434                    warning: "#FFFF00".to_string(),
435                    error: "#FF0000".to_string(),
436                    info: "#0000FF".to_string(),
437                },
438            },
439            ..Default::default()
440        }
441    }
442
443    /// Create a configuration optimized for print/publication output
444    /// with high DPI settings, static output, and print-friendly colors
445    pub fn print_optimized() -> Self {
446        Self {
447            animation_enabled: false,
448            high_dpi_enabled: true,
449            export_formats: vec![ExportFormat::PDF, ExportFormat::SVG, ExportFormat::PNG],
450            color_palette: ColorPalette {
451                primary_colors: vec![
452                    "#2C3E50".to_string(),
453                    "#E74C3C".to_string(),
454                    "#3498DB".to_string(),
455                    "#2ECC71".to_string(),
456                    "#F39C12".to_string(),
457                    "#9B59B6".to_string(),
458                    "#1ABC9C".to_string(),
459                    "#E67E22".to_string(),
460                ],
461                gradient_colors: vec!["#BDC3C7".to_string(), "#95A5A6".to_string()],
462                status_colors: StatusColors {
463                    success: "#27AE60".to_string(),
464                    warning: "#F39C12".to_string(),
465                    error: "#E74C3C".to_string(),
466                    info: "#3498DB".to_string(),
467                },
468            },
469            ..Default::default()
470        }
471    }
472}
473
474impl Default for ColorPalette {
475    fn default() -> Self {
476        Self {
477            primary_colors: vec![
478                "#FF6B6B".to_string(),
479                "#4ECDC4".to_string(),
480                "#45B7D1".to_string(),
481                "#96CEB4".to_string(),
482                "#FECA57".to_string(),
483                "#FF9FF3".to_string(),
484                "#54A0FF".to_string(),
485                "#5F27CD".to_string(),
486            ],
487            gradient_colors: vec![
488                "#667eea".to_string(),
489                "#764ba2".to_string(),
490                "#f093fb".to_string(),
491                "#f5576c".to_string(),
492            ],
493            status_colors: StatusColors {
494                success: "#28a745".to_string(),
495                warning: "#ffc107".to_string(),
496                error: "#dc3545".to_string(),
497                info: "#17a2b8".to_string(),
498            },
499        }
500    }
501}
502
503impl VisualizationEngine {
504    /// Create a new visualization engine
505    pub fn new(config: VisualizationConfig, output_dir: PathBuf) -> Result<Self> {
506        std::fs::create_dir_all(&output_dir).map_err(|e| TorshError::IoError(e.to_string()))?;
507
508        Ok(Self {
509            chart_renderer: ChartRenderer::new(config.clone()),
510            dashboard_generator: DashboardGenerator::new(),
511            export_manager: ExportManager::new(output_dir),
512            config,
513        })
514    }
515
516    /// Create performance visualization from analytics data
517    pub fn create_performance_visualization(
518        &self,
519        data: &ModelPerformanceData,
520    ) -> Result<PerformanceVisualization> {
521        let inference_time_chart = self.create_inference_time_chart(data)?;
522        let throughput_chart = self.create_throughput_chart(data)?;
523        let resource_utilization_chart = self.create_resource_utilization_chart(data)?;
524        let bottleneck_analysis = self.create_bottleneck_analysis(data)?;
525
526        Ok(PerformanceVisualization {
527            model_id: data.model_id.clone(),
528            inference_time_chart,
529            throughput_chart,
530            resource_utilization_chart,
531            bottleneck_analysis,
532        })
533    }
534
535    /// Create usage analytics visualization
536    pub fn create_usage_visualization(
537        &self,
538        stats: &[ModelUsageStats],
539    ) -> Result<UsageVisualization> {
540        let usage_trends = self.create_usage_trends_chart(stats)?;
541        let popular_models = self.create_popular_models_chart(stats)?;
542        let user_patterns = self.create_user_patterns_chart(stats)?;
543        let time_series_usage = self.create_time_series_usage_chart(stats)?;
544
545        Ok(UsageVisualization {
546            usage_trends,
547            popular_models,
548            user_patterns,
549            geographic_distribution: None, // Would require geographic data
550            time_series_usage,
551        })
552    }
553
554    /// Create training progress visualization
555    pub fn create_training_visualization(
556        &self,
557        history: &TrainingHistory,
558    ) -> Result<TrainingVisualization> {
559        let loss_curve = self.create_loss_curve_chart(history)?;
560        let accuracy_curve = self.create_accuracy_curve_chart(history)?;
561        let learning_rate_schedule = self.create_lr_schedule_chart(history)?;
562        let gradient_norms = self.create_gradient_norms_chart(history)?;
563        let validation_metrics = self.create_validation_metrics_chart(history)?;
564
565        Ok(TrainingVisualization {
566            loss_curve,
567            accuracy_curve,
568            learning_rate_schedule,
569            gradient_norms,
570            validation_metrics,
571        })
572    }
573
574    /// Create real-time dashboard
575    pub fn create_realtime_dashboard(
576        &self,
577        _metrics: &RealTimeMetrics,
578    ) -> Result<DashboardTemplate> {
579        let dashboard = DashboardTemplate {
580            name: "Real-time Model Hub Dashboard".to_string(),
581            description: "Live monitoring of model hub metrics".to_string(),
582            layout: DashboardLayout {
583                columns: 3,
584                rows: 2,
585                padding: 10,
586                margin: 20,
587            },
588            widgets: vec![
589                DashboardWidget {
590                    widget_type: WidgetType::Metric,
591                    title: "Active Models".to_string(),
592                    position: WidgetPosition { x: 0, y: 0 },
593                    size: WidgetSize {
594                        width: 1,
595                        height: 1,
596                    },
597                    data_source: DataSource::Metrics("active_models".to_string()),
598                    update_interval: Some(Duration::from_secs(5)),
599                },
600                DashboardWidget {
601                    widget_type: WidgetType::Chart(ChartType::Line),
602                    title: "Requests per Second".to_string(),
603                    position: WidgetPosition { x: 1, y: 0 },
604                    size: WidgetSize {
605                        width: 2,
606                        height: 1,
607                    },
608                    data_source: DataSource::Metrics("rps".to_string()),
609                    update_interval: Some(Duration::from_secs(1)),
610                },
611                DashboardWidget {
612                    widget_type: WidgetType::Gauge,
613                    title: "CPU Usage".to_string(),
614                    position: WidgetPosition { x: 0, y: 1 },
615                    size: WidgetSize {
616                        width: 1,
617                        height: 1,
618                    },
619                    data_source: DataSource::Metrics("cpu_usage".to_string()),
620                    update_interval: Some(Duration::from_secs(2)),
621                },
622                DashboardWidget {
623                    widget_type: WidgetType::Gauge,
624                    title: "Memory Usage".to_string(),
625                    position: WidgetPosition { x: 1, y: 1 },
626                    size: WidgetSize {
627                        width: 1,
628                        height: 1,
629                    },
630                    data_source: DataSource::Metrics("memory_usage".to_string()),
631                    update_interval: Some(Duration::from_secs(2)),
632                },
633                DashboardWidget {
634                    widget_type: WidgetType::Chart(ChartType::Bar),
635                    title: "Error Rate".to_string(),
636                    position: WidgetPosition { x: 2, y: 1 },
637                    size: WidgetSize {
638                        width: 1,
639                        height: 1,
640                    },
641                    data_source: DataSource::Metrics("error_rate".to_string()),
642                    update_interval: Some(Duration::from_secs(10)),
643                },
644            ],
645            refresh_interval: Some(Duration::from_secs(5)),
646        };
647
648        Ok(dashboard)
649    }
650
651    /// Export visualization to file
652    pub fn export_visualization<T: Serialize>(
653        &self,
654        visualization: &T,
655        filename: &str,
656        format: ExportFormat,
657    ) -> Result<PathBuf> {
658        self.export_manager.export(visualization, filename, format)
659    }
660
661    /// Create inference time chart
662    fn create_inference_time_chart(&self, data: &ModelPerformanceData) -> Result<ChartData> {
663        let dataset = Dataset {
664            label: "Inference Time".to_string(),
665            data: data
666                .inference_times
667                .iter()
668                .enumerate()
669                .map(|(i, duration)| DataPoint {
670                    x: i as f64,
671                    y: duration.as_millis() as f64,
672                    label: None,
673                    metadata: HashMap::new(),
674                })
675                .collect(),
676            color: Some(self.config.color_palette.primary_colors[0].clone()),
677            style: Some(LineStyle::Solid),
678            fill: false,
679        };
680
681        Ok(ChartData {
682            title: "Model Inference Time".to_string(),
683            chart_type: ChartType::Line,
684            datasets: vec![dataset],
685            x_axis: Axis {
686                title: "Request Number".to_string(),
687                min: None,
688                max: None,
689                scale: AxisScale::Linear,
690                format: None,
691            },
692            y_axis: Axis {
693                title: "Time (ms)".to_string(),
694                min: Some(0.0),
695                max: None,
696                scale: AxisScale::Linear,
697                format: Some("%.1f ms".to_string()),
698            },
699            legend: Some(Legend {
700                position: LegendPosition::TopRight,
701                columns: 1,
702                font_size: 12,
703            }),
704            annotations: vec![],
705        })
706    }
707
708    /// Create throughput chart
709    fn create_throughput_chart(&self, data: &ModelPerformanceData) -> Result<ChartData> {
710        let dataset = Dataset {
711            label: "Requests per Second".to_string(),
712            data: data
713                .throughput_data
714                .iter()
715                .enumerate()
716                .map(|(i, measurement)| DataPoint {
717                    x: i as f64,
718                    y: measurement.requests_per_second as f64,
719                    label: None,
720                    metadata: HashMap::new(),
721                })
722                .collect(),
723            color: Some(self.config.color_palette.primary_colors[1].clone()),
724            style: Some(LineStyle::Solid),
725            fill: true,
726        };
727
728        Ok(ChartData {
729            title: "Model Throughput".to_string(),
730            chart_type: ChartType::Line,
731            datasets: vec![dataset],
732            x_axis: Axis {
733                title: "Time".to_string(),
734                min: None,
735                max: None,
736                scale: AxisScale::Time,
737                format: None,
738            },
739            y_axis: Axis {
740                title: "Requests/Second".to_string(),
741                min: Some(0.0),
742                max: None,
743                scale: AxisScale::Linear,
744                format: Some("%.1f RPS".to_string()),
745            },
746            legend: Some(Legend {
747                position: LegendPosition::TopLeft,
748                columns: 1,
749                font_size: 12,
750            }),
751            annotations: vec![],
752        })
753    }
754
755    /// Create resource utilization chart
756    fn create_resource_utilization_chart(&self, data: &ModelPerformanceData) -> Result<ChartData> {
757        let cpu_dataset = Dataset {
758            label: "CPU Usage".to_string(),
759            data: data
760                .resource_utilization
761                .cpu_usage
762                .iter()
763                .enumerate()
764                .map(|(i, usage)| DataPoint {
765                    x: i as f64,
766                    y: *usage as f64,
767                    label: None,
768                    metadata: HashMap::new(),
769                })
770                .collect(),
771            color: Some(self.config.color_palette.primary_colors[2].clone()),
772            style: Some(LineStyle::Solid),
773            fill: false,
774        };
775
776        let memory_dataset = Dataset {
777            label: "Memory Usage".to_string(),
778            data: data
779                .resource_utilization
780                .memory_usage
781                .iter()
782                .enumerate()
783                .map(|(i, usage)| DataPoint {
784                    x: i as f64,
785                    y: (*usage as f64) / (1024.0 * 1024.0), // Convert to MB
786                    label: None,
787                    metadata: HashMap::new(),
788                })
789                .collect(),
790            color: Some(self.config.color_palette.primary_colors[3].clone()),
791            style: Some(LineStyle::Dashed),
792            fill: false,
793        };
794
795        Ok(ChartData {
796            title: "Resource Utilization".to_string(),
797            chart_type: ChartType::Line,
798            datasets: vec![cpu_dataset, memory_dataset],
799            x_axis: Axis {
800                title: "Time".to_string(),
801                min: None,
802                max: None,
803                scale: AxisScale::Linear,
804                format: None,
805            },
806            y_axis: Axis {
807                title: "Usage".to_string(),
808                min: Some(0.0),
809                max: None,
810                scale: AxisScale::Linear,
811                format: None,
812            },
813            legend: Some(Legend {
814                position: LegendPosition::TopRight,
815                columns: 1,
816                font_size: 12,
817            }),
818            annotations: vec![],
819        })
820    }
821
822    /// Create bottleneck analysis visualization
823    fn create_bottleneck_analysis(
824        &self,
825        data: &ModelPerformanceData,
826    ) -> Result<Vec<BottleneckVisualization>> {
827        let mut visualizations = Vec::new();
828
829        for bottleneck in &data.bottlenecks {
830            let impact_chart = ChartData {
831                title: format!("{:?} Bottleneck Impact", bottleneck.bottleneck_type),
832                chart_type: ChartType::Bar,
833                datasets: vec![Dataset {
834                    label: "Impact".to_string(),
835                    data: vec![DataPoint {
836                        x: 0.0,
837                        y: bottleneck.impact_percentage as f64,
838                        label: Some(bottleneck.description.clone()),
839                        metadata: HashMap::new(),
840                    }],
841                    color: Some(match bottleneck.severity {
842                        crate::analytics::BottleneckSeverity::Low => {
843                            self.config.color_palette.status_colors.info.clone()
844                        }
845                        crate::analytics::BottleneckSeverity::Medium => {
846                            self.config.color_palette.status_colors.warning.clone()
847                        }
848                        crate::analytics::BottleneckSeverity::High => {
849                            self.config.color_palette.status_colors.error.clone()
850                        }
851                        crate::analytics::BottleneckSeverity::Critical => "#8B0000".to_string(),
852                    }),
853                    style: None,
854                    fill: true,
855                }],
856                x_axis: Axis {
857                    title: "Bottleneck".to_string(),
858                    min: None,
859                    max: None,
860                    scale: AxisScale::Category,
861                    format: None,
862                },
863                y_axis: Axis {
864                    title: "Impact (%)".to_string(),
865                    min: Some(0.0),
866                    max: Some(100.0),
867                    scale: AxisScale::Linear,
868                    format: Some("%.1f%%".to_string()),
869                },
870                legend: None,
871                annotations: vec![],
872            };
873
874            visualizations.push(BottleneckVisualization {
875                bottleneck_type: format!("{:?}", bottleneck.bottleneck_type),
876                severity: format!("{:?}", bottleneck.severity),
877                impact_chart,
878                timeline: vec![], // Would be populated with actual timeline data
879            });
880        }
881
882        Ok(visualizations)
883    }
884
885    // Additional chart creation methods would be implemented here...
886    fn create_usage_trends_chart(&self, stats: &[ModelUsageStats]) -> Result<ChartData> {
887        // Aggregate daily usage across all models
888        let mut daily_totals: HashMap<String, u64> = HashMap::new();
889
890        for stat in stats {
891            for (date, count) in &stat.daily_usage {
892                *daily_totals.entry(date.clone()).or_insert(0) += count;
893            }
894        }
895
896        // Sort dates chronologically
897        let mut sorted_dates: Vec<_> = daily_totals.keys().cloned().collect();
898        sorted_dates.sort();
899
900        // Create dataset
901        let dataset = Dataset {
902            label: "Daily Usage".to_string(),
903            data: sorted_dates
904                .iter()
905                .enumerate()
906                .map(|(i, date)| {
907                    let count = daily_totals.get(date).copied().unwrap_or(0);
908                    DataPoint {
909                        x: i as f64,
910                        y: count as f64,
911                        label: Some(date.clone()),
912                        metadata: HashMap::new(),
913                    }
914                })
915                .collect(),
916            color: Some("#3b82f6".to_string()), // Blue
917            style: Some(LineStyle::Solid),
918            fill: true,
919        };
920
921        Ok(ChartData {
922            title: "Usage Trends Over Time".to_string(),
923            chart_type: ChartType::Line,
924            datasets: vec![dataset],
925            x_axis: Axis {
926                title: "Date".to_string(),
927                min: None,
928                max: None,
929                scale: AxisScale::Category,
930                format: None,
931            },
932            y_axis: Axis {
933                title: "Usage Count".to_string(),
934                min: Some(0.0),
935                max: None,
936                scale: AxisScale::Linear,
937                format: Some("d".to_string()),
938            },
939            legend: None,
940            annotations: vec![],
941        })
942    }
943
944    fn create_popular_models_chart(&self, stats: &[ModelUsageStats]) -> Result<ChartData> {
945        // Sort models by popularity score (descending) and take top 10
946        let mut sorted_stats: Vec<_> = stats.iter().collect();
947        sorted_stats.sort_by(|a, b| {
948            b.popularity_score
949                .partial_cmp(&a.popularity_score)
950                .expect("popularity_score should be comparable")
951        });
952        let top_models = sorted_stats.iter().take(10);
953
954        let dataset = Dataset {
955            label: "Popularity Score".to_string(),
956            data: top_models
957                .enumerate()
958                .map(|(i, stat)| DataPoint {
959                    x: i as f64,
960                    y: stat.popularity_score as f64,
961                    label: Some(stat.model_id.clone()),
962                    metadata: HashMap::new(),
963                })
964                .collect(),
965            color: Some("#8b5cf6".to_string()), // Purple
966            style: Some(LineStyle::Solid),
967            fill: false,
968        };
969
970        Ok(ChartData {
971            title: "Popular Models".to_string(),
972            chart_type: ChartType::Bar,
973            datasets: vec![dataset],
974            x_axis: Axis {
975                title: "Model".to_string(),
976                min: None,
977                max: None,
978                scale: AxisScale::Linear,
979                format: None,
980            },
981            y_axis: Axis {
982                title: "Popularity Score".to_string(),
983                min: Some(0.0),
984                max: None,
985                scale: AxisScale::Linear,
986                format: Some(".2f".to_string()),
987            },
988            legend: None,
989            annotations: vec![],
990        })
991    }
992
993    fn create_user_patterns_chart(&self, stats: &[ModelUsageStats]) -> Result<ChartData> {
994        // Aggregate usage patterns across all models by hour
995        let mut hourly_totals = [0u64; 24];
996        for stat in stats {
997            for (hour, &count) in stat.hourly_patterns.iter().enumerate() {
998                hourly_totals[hour] += count;
999            }
1000        }
1001
1002        let dataset = Dataset {
1003            label: "Usage Count".to_string(),
1004            data: hourly_totals
1005                .iter()
1006                .enumerate()
1007                .map(|(hour, &count)| DataPoint {
1008                    x: hour as f64,
1009                    y: count as f64,
1010                    label: Some(format!("{:02}:00", hour)),
1011                    metadata: HashMap::new(),
1012                })
1013                .collect(),
1014            color: Some("#f59e0b".to_string()), // Orange
1015            style: Some(LineStyle::Solid),
1016            fill: false,
1017        };
1018
1019        Ok(ChartData {
1020            title: "User Patterns (Hourly Usage)".to_string(),
1021            chart_type: ChartType::Bar,
1022            datasets: vec![dataset],
1023            x_axis: Axis {
1024                title: "Hour of Day".to_string(),
1025                min: Some(0.0),
1026                max: Some(23.0),
1027                scale: AxisScale::Linear,
1028                format: Some("d".to_string()),
1029            },
1030            y_axis: Axis {
1031                title: "Usage Count".to_string(),
1032                min: Some(0.0),
1033                max: None,
1034                scale: AxisScale::Linear,
1035                format: Some("d".to_string()),
1036            },
1037            legend: None,
1038            annotations: vec![],
1039        })
1040    }
1041
1042    fn create_time_series_usage_chart(&self, stats: &[ModelUsageStats]) -> Result<ChartData> {
1043        // Create a multi-line chart showing usage trends for top 5 models
1044        let mut sorted_stats: Vec<_> = stats.iter().collect();
1045        sorted_stats.sort_by(|a, b| b.total_inferences.cmp(&a.total_inferences));
1046        let top_models = sorted_stats.iter().take(5);
1047
1048        let colors = [
1049            "#3b82f6", // Blue
1050            "#ef4444", // Red
1051            "#10b981", // Green
1052            "#f59e0b", // Orange
1053            "#8b5cf6", // Purple
1054        ];
1055
1056        let mut datasets = vec![];
1057
1058        for (idx, stat) in top_models.enumerate() {
1059            // Sort dates for this model
1060            let mut dates: Vec<_> = stat.daily_usage.keys().cloned().collect();
1061            dates.sort();
1062
1063            let data: Vec<DataPoint> = dates
1064                .iter()
1065                .enumerate()
1066                .map(|(i, date)| {
1067                    let count = stat.daily_usage.get(date).copied().unwrap_or(0);
1068                    DataPoint {
1069                        x: i as f64,
1070                        y: count as f64,
1071                        label: Some(date.clone()),
1072                        metadata: HashMap::new(),
1073                    }
1074                })
1075                .collect();
1076
1077            if !data.is_empty() {
1078                datasets.push(Dataset {
1079                    label: stat.model_id.clone(),
1080                    data,
1081                    color: Some(colors[idx % colors.len()].to_string()),
1082                    style: Some(LineStyle::Solid),
1083                    fill: false,
1084                });
1085            }
1086        }
1087
1088        Ok(ChartData {
1089            title: "Time Series Usage by Model".to_string(),
1090            chart_type: ChartType::Line,
1091            datasets,
1092            x_axis: Axis {
1093                title: "Date".to_string(),
1094                min: None,
1095                max: None,
1096                scale: AxisScale::Category,
1097                format: None,
1098            },
1099            y_axis: Axis {
1100                title: "Usage Count".to_string(),
1101                min: Some(0.0),
1102                max: None,
1103                scale: AxisScale::Linear,
1104                format: Some("d".to_string()),
1105            },
1106            legend: Some(Legend {
1107                position: LegendPosition::TopRight,
1108                font_size: 12,
1109                columns: 1,
1110            }),
1111            annotations: vec![],
1112        })
1113    }
1114
1115    fn create_loss_curve_chart(&self, history: &TrainingHistory) -> Result<ChartData> {
1116        let epochs: Vec<f64> = (1..=history.loss.len()).map(|e| e as f64).collect();
1117
1118        let mut datasets = vec![Dataset {
1119            label: "Training Loss".to_string(),
1120            data: history
1121                .loss
1122                .iter()
1123                .enumerate()
1124                .map(|(i, &loss)| DataPoint {
1125                    x: epochs[i],
1126                    y: loss,
1127                    label: None,
1128                    metadata: HashMap::new(),
1129                })
1130                .collect(),
1131            color: Some("#3b82f6".to_string()), // Blue
1132            style: Some(LineStyle::Solid),
1133            fill: false,
1134        }];
1135
1136        // Add validation loss if available
1137        if !history.val_loss.is_empty() {
1138            datasets.push(Dataset {
1139                label: "Validation Loss".to_string(),
1140                data: history
1141                    .val_loss
1142                    .iter()
1143                    .enumerate()
1144                    .map(|(i, &loss)| DataPoint {
1145                        x: (i + 1) as f64,
1146                        y: loss,
1147                        label: None,
1148                        metadata: HashMap::new(),
1149                    })
1150                    .collect(),
1151                color: Some("#ef4444".to_string()), // Red
1152                style: Some(LineStyle::Dashed),
1153                fill: false,
1154            });
1155        }
1156
1157        Ok(ChartData {
1158            title: "Training Loss".to_string(),
1159            chart_type: ChartType::Line,
1160            datasets,
1161            x_axis: Axis {
1162                title: "Epoch".to_string(),
1163                min: Some(1.0),
1164                max: Some(history.loss.len() as f64),
1165                scale: AxisScale::Linear,
1166                format: Some("d".to_string()),
1167            },
1168            y_axis: Axis {
1169                title: "Loss".to_string(),
1170                min: None,
1171                max: None,
1172                scale: AxisScale::Linear,
1173                format: Some(".4f".to_string()),
1174            },
1175            legend: Some(Legend {
1176                position: LegendPosition::TopRight,
1177                font_size: 12,
1178                columns: 1,
1179            }),
1180            annotations: vec![],
1181        })
1182    }
1183
1184    fn create_accuracy_curve_chart(&self, history: &TrainingHistory) -> Result<ChartData> {
1185        // Look for accuracy metrics in the history
1186        let mut datasets = vec![];
1187
1188        if let Some(train_accuracy) = history.metrics.get("accuracy") {
1189            let epochs: Vec<f64> = (1..=train_accuracy.len()).map(|e| e as f64).collect();
1190            datasets.push(Dataset {
1191                label: "Training Accuracy".to_string(),
1192                data: train_accuracy
1193                    .iter()
1194                    .enumerate()
1195                    .map(|(i, &acc)| DataPoint {
1196                        x: epochs[i],
1197                        y: acc,
1198                        label: None,
1199                        metadata: HashMap::new(),
1200                    })
1201                    .collect(),
1202                color: Some("#3b82f6".to_string()), // Blue
1203                style: Some(LineStyle::Solid),
1204                fill: false,
1205            });
1206        }
1207
1208        if let Some(val_accuracy) = history.metrics.get("val_accuracy") {
1209            let epochs: Vec<f64> = (1..=val_accuracy.len()).map(|e| e as f64).collect();
1210            datasets.push(Dataset {
1211                label: "Validation Accuracy".to_string(),
1212                data: val_accuracy
1213                    .iter()
1214                    .enumerate()
1215                    .map(|(i, &acc)| DataPoint {
1216                        x: epochs[i],
1217                        y: acc,
1218                        label: None,
1219                        metadata: HashMap::new(),
1220                    })
1221                    .collect(),
1222                color: Some("#10b981".to_string()), // Green
1223                style: Some(LineStyle::Dashed),
1224                fill: false,
1225            });
1226        }
1227
1228        let num_epochs = datasets.first().map(|d| d.data.len()).unwrap_or(0);
1229
1230        Ok(ChartData {
1231            title: "Training Accuracy".to_string(),
1232            chart_type: ChartType::Line,
1233            datasets,
1234            x_axis: Axis {
1235                title: "Epoch".to_string(),
1236                min: Some(1.0),
1237                max: Some(num_epochs as f64),
1238                scale: AxisScale::Linear,
1239                format: Some("d".to_string()),
1240            },
1241            y_axis: Axis {
1242                title: "Accuracy".to_string(),
1243                min: Some(0.0),
1244                max: Some(1.0),
1245                scale: AxisScale::Linear,
1246                format: Some(".2%".to_string()),
1247            },
1248            legend: Some(Legend {
1249                position: LegendPosition::BottomRight,
1250                font_size: 12,
1251                columns: 1,
1252            }),
1253            annotations: vec![],
1254        })
1255    }
1256
1257    fn create_lr_schedule_chart(&self, history: &TrainingHistory) -> Result<ChartData> {
1258        let epochs: Vec<f64> = (1..=history.learning_rates.len())
1259            .map(|e| e as f64)
1260            .collect();
1261
1262        let dataset = Dataset {
1263            label: "Learning Rate".to_string(),
1264            data: history
1265                .learning_rates
1266                .iter()
1267                .enumerate()
1268                .map(|(i, &lr)| DataPoint {
1269                    x: epochs[i],
1270                    y: lr,
1271                    label: None,
1272                    metadata: HashMap::new(),
1273                })
1274                .collect(),
1275            color: Some("#10b981".to_string()), // Green
1276            style: Some(LineStyle::Solid),
1277            fill: false,
1278        };
1279
1280        Ok(ChartData {
1281            title: "Learning Rate Schedule".to_string(),
1282            chart_type: ChartType::Line,
1283            datasets: vec![dataset],
1284            x_axis: Axis {
1285                title: "Epoch".to_string(),
1286                min: Some(1.0),
1287                max: Some(history.learning_rates.len() as f64),
1288                scale: AxisScale::Linear,
1289                format: Some("d".to_string()),
1290            },
1291            y_axis: Axis {
1292                title: "Learning Rate".to_string(),
1293                min: None,
1294                max: None,
1295                scale: AxisScale::Logarithmic,
1296                format: Some(".2e".to_string()),
1297            },
1298            legend: Some(Legend {
1299                position: LegendPosition::TopRight,
1300                font_size: 12,
1301                columns: 1,
1302            }),
1303            annotations: vec![],
1304        })
1305    }
1306
1307    fn create_gradient_norms_chart(&self, history: &TrainingHistory) -> Result<ChartData> {
1308        let mut datasets = vec![];
1309
1310        // Look for gradient norm metrics (grad_norm, gradient_norm, etc.)
1311        for (metric_name, values) in &history.metrics {
1312            if metric_name.to_lowercase().contains("grad")
1313                && (metric_name.to_lowercase().contains("norm")
1314                    || metric_name.to_lowercase().contains("magnitude"))
1315            {
1316                let epochs: Vec<f64> = (1..=values.len()).map(|e| e as f64).collect();
1317                datasets.push(Dataset {
1318                    label: metric_name.clone(),
1319                    data: values
1320                        .iter()
1321                        .enumerate()
1322                        .map(|(i, &val)| DataPoint {
1323                            x: epochs[i],
1324                            y: val,
1325                            label: None,
1326                            metadata: HashMap::new(),
1327                        })
1328                        .collect(),
1329                    color: None, // Auto-assign colors
1330                    style: Some(LineStyle::Solid),
1331                    fill: false,
1332                });
1333            }
1334        }
1335
1336        // If no gradient norms found, create empty chart with message
1337        let num_epochs = datasets.first().map(|d| d.data.len()).unwrap_or(1);
1338
1339        Ok(ChartData {
1340            title: "Gradient Norms".to_string(),
1341            chart_type: ChartType::Line,
1342            datasets,
1343            x_axis: Axis {
1344                title: "Epoch".to_string(),
1345                min: Some(1.0),
1346                max: Some(num_epochs as f64),
1347                scale: AxisScale::Linear,
1348                format: Some("d".to_string()),
1349            },
1350            y_axis: Axis {
1351                title: "Gradient Norm".to_string(),
1352                min: None,
1353                max: None,
1354                scale: AxisScale::Logarithmic,
1355                format: Some(".2e".to_string()),
1356            },
1357            legend: Some(Legend {
1358                position: LegendPosition::TopRight,
1359                font_size: 12,
1360                columns: 1,
1361            }),
1362            annotations: vec![],
1363        })
1364    }
1365
1366    fn create_validation_metrics_chart(&self, history: &TrainingHistory) -> Result<ChartData> {
1367        let mut datasets = vec![];
1368
1369        // Collect all validation metrics (those starting with "val_")
1370        for (metric_name, values) in &history.metrics {
1371            if metric_name.starts_with("val_") {
1372                let epochs: Vec<f64> = (1..=values.len()).map(|e| e as f64).collect();
1373                datasets.push(Dataset {
1374                    label: metric_name.clone(),
1375                    data: values
1376                        .iter()
1377                        .enumerate()
1378                        .map(|(i, &val)| DataPoint {
1379                            x: epochs[i],
1380                            y: val,
1381                            label: None,
1382                            metadata: HashMap::new(),
1383                        })
1384                        .collect(),
1385                    color: None, // Auto-assign colors
1386                    style: Some(LineStyle::Solid),
1387                    fill: false,
1388                });
1389            }
1390        }
1391
1392        let num_epochs = datasets.first().map(|d| d.data.len()).unwrap_or(0);
1393
1394        Ok(ChartData {
1395            title: "Validation Metrics".to_string(),
1396            chart_type: ChartType::Line,
1397            datasets,
1398            x_axis: Axis {
1399                title: "Epoch".to_string(),
1400                min: Some(1.0),
1401                max: Some(num_epochs as f64),
1402                scale: AxisScale::Linear,
1403                format: Some("d".to_string()),
1404            },
1405            y_axis: Axis {
1406                title: "Metric Value".to_string(),
1407                min: None,
1408                max: None,
1409                scale: AxisScale::Linear,
1410                format: Some(".4f".to_string()),
1411            },
1412            legend: Some(Legend {
1413                position: LegendPosition::TopRight,
1414                font_size: 12,
1415                columns: 1,
1416            }),
1417            annotations: vec![],
1418        })
1419    }
1420
1421    fn create_placeholder_chart(&self, title: &str) -> ChartData {
1422        ChartData {
1423            title: title.to_string(),
1424            chart_type: ChartType::Line,
1425            datasets: vec![],
1426            x_axis: Axis {
1427                title: "X Axis".to_string(),
1428                min: None,
1429                max: None,
1430                scale: AxisScale::Linear,
1431                format: None,
1432            },
1433            y_axis: Axis {
1434                title: "Y Axis".to_string(),
1435                min: None,
1436                max: None,
1437                scale: AxisScale::Linear,
1438                format: None,
1439            },
1440            legend: None,
1441            annotations: vec![],
1442        }
1443    }
1444}
1445
1446impl ChartRenderer {
1447    fn new(config: VisualizationConfig) -> Self {
1448        Self { config }
1449    }
1450
1451    /// Render chart to HTML
1452    pub fn render_to_html(&self, chart: &ChartData) -> Result<String> {
1453        // Implementation would generate HTML with embedded JavaScript for interactive charts
1454        let html = format!(
1455            r#"
1456            <!DOCTYPE html>
1457            <html>
1458            <head>
1459                <title>{}</title>
1460                <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
1461            </head>
1462            <body>
1463                <div id="chart" style="width:100%;height:600px;"></div>
1464                <script>
1465                    // Chart implementation would go here
1466                    Plotly.newPlot('chart', [], {{}});
1467                </script>
1468            </body>
1469            </html>
1470            "#,
1471            chart.title
1472        );
1473        Ok(html)
1474    }
1475
1476    /// Render chart to SVG
1477    pub fn render_to_svg(&self, chart: &ChartData) -> Result<String> {
1478        // Implementation would generate SVG markup
1479        Ok(format!("<svg><!-- {} chart --></svg>", chart.title))
1480    }
1481}
1482
1483impl DashboardGenerator {
1484    fn new() -> Self {
1485        Self {
1486            templates: HashMap::new(),
1487        }
1488    }
1489
1490    /// Generate dashboard HTML
1491    pub fn generate_dashboard_html(&self, template: &DashboardTemplate) -> Result<String> {
1492        // Implementation would generate a complete dashboard HTML page
1493        let html = format!(
1494            r#"
1495            <!DOCTYPE html>
1496            <html>
1497            <head>
1498                <title>{}</title>
1499                <link rel="stylesheet" href="dashboard.css">
1500                <script src="dashboard.js"></script>
1501            </head>
1502            <body>
1503                <div class="dashboard">
1504                    <h1>{}</h1>
1505                    <div class="dashboard-grid">
1506                        <!-- Widgets would be generated here -->
1507                    </div>
1508                </div>
1509            </body>
1510            </html>
1511            "#,
1512            template.name, template.name
1513        );
1514        Ok(html)
1515    }
1516}
1517
1518impl ExportManager {
1519    fn new(output_directory: PathBuf) -> Self {
1520        Self { output_directory }
1521    }
1522
1523    /// Export data to specified format
1524    pub fn export<T: Serialize>(
1525        &self,
1526        data: &T,
1527        filename: &str,
1528        format: ExportFormat,
1529    ) -> Result<PathBuf> {
1530        let output_path = match format {
1531            ExportFormat::JSON => {
1532                let path = self.output_directory.join(format!("{}.json", filename));
1533                let json = serde_json::to_string_pretty(data)
1534                    .map_err(|e| TorshError::SerializationError(e.to_string()))?;
1535                std::fs::write(&path, json).map_err(|e| TorshError::IoError(e.to_string()))?;
1536                path
1537            }
1538            ExportFormat::HTML => {
1539                let path = self.output_directory.join(format!("{}.html", filename));
1540                // Implementation would convert data to HTML
1541                std::fs::write(&path, "<!-- HTML export -->")?;
1542                path
1543            }
1544            ExportFormat::PNG | ExportFormat::SVG | ExportFormat::PDF => {
1545                let extension = match format {
1546                    ExportFormat::PNG => "png",
1547                    ExportFormat::SVG => "svg",
1548                    ExportFormat::PDF => "pdf",
1549                    _ => {
1550                        return Err(TorshError::InvalidArgument(format!(
1551                            "Unsupported binary export format: {:?}",
1552                            format
1553                        )));
1554                    }
1555                };
1556                let path = self
1557                    .output_directory
1558                    .join(format!("{}.{}", filename, extension));
1559                // Implementation would render to binary format
1560                std::fs::write(&path, b"binary data placeholder")?;
1561                path
1562            }
1563        };
1564
1565        Ok(output_path)
1566    }
1567}
1568
1569#[cfg(test)]
1570mod tests {
1571    use super::*;
1572
1573    #[test]
1574    fn test_visualization_config_default() {
1575        let config = VisualizationConfig::default();
1576        assert_eq!(config.default_chart_size.width, 800);
1577        assert_eq!(config.default_chart_size.height, 600);
1578        assert!(config.animation_enabled);
1579        assert!(config.high_dpi_enabled);
1580    }
1581
1582    #[test]
1583    fn test_color_palette_default() {
1584        let palette = ColorPalette::default();
1585        assert!(!palette.primary_colors.is_empty());
1586        assert_eq!(palette.status_colors.success, "#28a745");
1587        assert_eq!(palette.status_colors.error, "#dc3545");
1588    }
1589
1590    #[test]
1591    fn test_chart_data_creation() {
1592        let chart = ChartData {
1593            title: "Test Chart".to_string(),
1594            chart_type: ChartType::Line,
1595            datasets: vec![],
1596            x_axis: Axis {
1597                title: "X".to_string(),
1598                min: None,
1599                max: None,
1600                scale: AxisScale::Linear,
1601                format: None,
1602            },
1603            y_axis: Axis {
1604                title: "Y".to_string(),
1605                min: None,
1606                max: None,
1607                scale: AxisScale::Linear,
1608                format: None,
1609            },
1610            legend: None,
1611            annotations: vec![],
1612        };
1613
1614        assert_eq!(chart.title, "Test Chart");
1615        assert!(matches!(chart.chart_type, ChartType::Line));
1616    }
1617}