Skip to main content

torsh_benches/
visualization.rs

1//! Advanced Visualization Tools for ToRSh Benchmarks
2//!
3//! This module provides comprehensive visualization capabilities for benchmark data,
4//! including interactive charts, performance trend analysis, heatmaps, and statistical plots.
5
6use crate::{
7    performance_dashboards::{PerformancePoint, RegressionSeverity},
8    regression_detection::AdvancedRegressionResult,
9    BenchResult,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// Visualization configuration
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct VisualizationConfig {
17    /// Chart width in pixels
18    pub width: u32,
19    /// Chart height in pixels
20    pub height: u32,
21    /// Chart theme
22    pub theme: ChartTheme,
23    /// Enable interactive features
24    pub interactive: bool,
25    /// Animation duration in milliseconds
26    pub animation_duration: u32,
27    /// Color palette
28    pub color_palette: Vec<String>,
29    /// Font family
30    pub font_family: String,
31    /// Font size
32    pub font_size: u32,
33    /// Export format
34    pub export_format: ExportFormat,
35}
36
37impl Default for VisualizationConfig {
38    fn default() -> Self {
39        Self {
40            width: 800,
41            height: 600,
42            theme: ChartTheme::Light,
43            interactive: true,
44            animation_duration: 750,
45            color_palette: vec![
46                "#3498db".to_string(),
47                "#e74c3c".to_string(),
48                "#2ecc71".to_string(),
49                "#f39c12".to_string(),
50                "#9b59b6".to_string(),
51                "#1abc9c".to_string(),
52                "#34495e".to_string(),
53                "#e67e22".to_string(),
54            ],
55            font_family: "Arial, sans-serif".to_string(),
56            font_size: 12,
57            export_format: ExportFormat::Html,
58        }
59    }
60}
61
62/// Chart theme options
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum ChartTheme {
65    Light,
66    Dark,
67    Minimal,
68    Professional,
69    Colorful,
70}
71
72/// Export format options
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub enum ExportFormat {
75    Html,
76    Svg,
77    Png,
78    Pdf,
79    Json,
80}
81
82/// Chart type enumeration
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84pub enum ChartType {
85    Line,
86    Bar,
87    Scatter,
88    Heatmap,
89    Box,
90    Violin,
91    Histogram,
92    Radar,
93    Treemap,
94    Sunburst,
95}
96
97/// Visualization data point
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct VisualizationPoint {
100    /// X-axis value
101    pub x: f64,
102    /// Y-axis value
103    pub y: f64,
104    /// Label for the point
105    pub label: String,
106    /// Additional metadata
107    pub metadata: HashMap<String, String>,
108    /// Color override
109    pub color: Option<String>,
110    /// Size override
111    pub size: Option<f64>,
112}
113
114/// Chart series data
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ChartSeries {
117    /// Series name
118    pub name: String,
119    /// Data points
120    pub data: Vec<VisualizationPoint>,
121    /// Chart type for this series
122    pub chart_type: ChartType,
123    /// Color override
124    pub color: Option<String>,
125    /// Line width or bar width
126    pub width: Option<f64>,
127    /// Fill opacity
128    pub fill_opacity: Option<f64>,
129    /// Dash pattern for lines
130    pub dash_pattern: Option<Vec<f64>>,
131}
132
133/// Chart configuration
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct Chart {
136    /// Chart title
137    pub title: String,
138    /// Chart subtitle
139    pub subtitle: Option<String>,
140    /// X-axis configuration
141    pub x_axis: AxisConfig,
142    /// Y-axis configuration
143    pub y_axis: AxisConfig,
144    /// Chart series
145    pub series: Vec<ChartSeries>,
146    /// Chart type (default for all series)
147    pub chart_type: ChartType,
148    /// Legend configuration
149    pub legend: LegendConfig,
150    /// Tooltip configuration
151    pub tooltip: TooltipConfig,
152    /// Annotations
153    pub annotations: Vec<Annotation>,
154}
155
156/// Axis configuration
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AxisConfig {
159    /// Axis title
160    pub title: String,
161    /// Axis type
162    pub axis_type: AxisType,
163    /// Minimum value
164    pub min: Option<f64>,
165    /// Maximum value
166    pub max: Option<f64>,
167    /// Tick interval
168    pub tick_interval: Option<f64>,
169    /// Logarithmic scale
170    pub log_scale: bool,
171    /// Grid lines
172    pub grid: bool,
173    /// Axis label format
174    pub label_format: String,
175}
176
177/// Axis type
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179pub enum AxisType {
180    Linear,
181    Logarithmic,
182    DateTime,
183    Category,
184}
185
186/// Legend configuration
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct LegendConfig {
189    /// Show legend
190    pub show: bool,
191    /// Legend position
192    pub position: LegendPosition,
193    /// Legend orientation
194    pub orientation: LegendOrientation,
195}
196
197/// Legend position
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub enum LegendPosition {
200    Top,
201    Bottom,
202    Left,
203    Right,
204    TopLeft,
205    TopRight,
206    BottomLeft,
207    BottomRight,
208}
209
210/// Legend orientation
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
212pub enum LegendOrientation {
213    Horizontal,
214    Vertical,
215}
216
217/// Tooltip configuration
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct TooltipConfig {
220    /// Show tooltips
221    pub show: bool,
222    /// Tooltip trigger
223    pub trigger: TooltipTrigger,
224    /// Custom tooltip format
225    pub format: Option<String>,
226}
227
228/// Tooltip trigger
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230pub enum TooltipTrigger {
231    Hover,
232    Click,
233    Focus,
234}
235
236/// Chart annotation
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct Annotation {
239    /// Annotation type
240    pub annotation_type: AnnotationType,
241    /// X coordinate
242    pub x: f64,
243    /// Y coordinate
244    pub y: f64,
245    /// Text content
246    pub text: String,
247    /// Color
248    pub color: Option<String>,
249    /// Font size
250    pub font_size: Option<u32>,
251}
252
253/// Annotation type
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
255pub enum AnnotationType {
256    Text,
257    Arrow,
258    Line,
259    Rectangle,
260    Circle,
261}
262
263/// Advanced visualization generator
264pub struct VisualizationGenerator {
265    /// Configuration
266    config: VisualizationConfig,
267    /// Chart templates
268    templates: HashMap<String, Chart>,
269}
270
271impl VisualizationGenerator {
272    /// Create a new visualization generator
273    pub fn new(config: VisualizationConfig) -> Self {
274        Self {
275            config,
276            templates: HashMap::new(),
277        }
278    }
279
280    /// Create with default configuration
281    pub fn default() -> Self {
282        Self::new(VisualizationConfig::default())
283    }
284
285    /// Generate performance trend chart
286    pub fn generate_performance_trend(&self, points: &[PerformancePoint]) -> Chart {
287        let mut data = Vec::new();
288
289        for point in points {
290            data.push(VisualizationPoint {
291                x: point.timestamp.timestamp_millis() as f64,
292                y: point.mean_time_ns / 1_000_000.0, // Convert to milliseconds
293                label: format!("{} ({})", point.benchmark_name, point.size),
294                metadata: {
295                    let mut meta = HashMap::new();
296                    meta.insert("benchmark".to_string(), point.benchmark_name.clone());
297                    meta.insert("size".to_string(), point.size.to_string());
298                    meta.insert("dtype".to_string(), point.dtype.clone());
299                    meta.insert(
300                        "throughput".to_string(),
301                        point
302                            .throughput
303                            .map(|t| t.to_string())
304                            .unwrap_or("N/A".to_string()),
305                    );
306                    meta
307                },
308                color: None,
309                size: None,
310            });
311        }
312
313        Chart {
314            title: "Performance Trends Over Time".to_string(),
315            subtitle: Some("Execution time trends for benchmark operations".to_string()),
316            x_axis: AxisConfig {
317                title: "Time".to_string(),
318                axis_type: AxisType::DateTime,
319                min: None,
320                max: None,
321                tick_interval: None,
322                log_scale: false,
323                grid: true,
324                label_format: "%Y-%m-%d %H:%M".to_string(),
325            },
326            y_axis: AxisConfig {
327                title: "Execution Time (ms)".to_string(),
328                axis_type: AxisType::Linear,
329                min: Some(0.0),
330                max: None,
331                tick_interval: None,
332                log_scale: false,
333                grid: true,
334                label_format: "{:.2f}".to_string(),
335            },
336            series: vec![ChartSeries {
337                name: "Performance".to_string(),
338                data,
339                chart_type: ChartType::Line,
340                color: Some(self.config.color_palette[0].clone()),
341                width: Some(2.0),
342                fill_opacity: None,
343                dash_pattern: None,
344            }],
345            chart_type: ChartType::Line,
346            legend: LegendConfig {
347                show: true,
348                position: LegendPosition::TopRight,
349                orientation: LegendOrientation::Vertical,
350            },
351            tooltip: TooltipConfig {
352                show: true,
353                trigger: TooltipTrigger::Hover,
354                format: None,
355            },
356            annotations: Vec::new(),
357        }
358    }
359
360    /// Generate throughput comparison chart
361    pub fn generate_throughput_comparison(&self, results: &[BenchResult]) -> Chart {
362        let mut series_map: HashMap<String, Vec<VisualizationPoint>> = HashMap::new();
363
364        for result in results {
365            if let Some(throughput) = result.throughput {
366                let series_name = format!("{:?}", result.dtype);
367                let entry = series_map.entry(series_name).or_insert_with(Vec::new);
368
369                entry.push(VisualizationPoint {
370                    x: result.size as f64,
371                    y: throughput,
372                    label: format!("{} ({})", result.name, result.size),
373                    metadata: {
374                        let mut meta = HashMap::new();
375                        meta.insert("benchmark".to_string(), result.name.clone());
376                        meta.insert("size".to_string(), result.size.to_string());
377                        meta.insert(
378                            "mean_time".to_string(),
379                            (result.mean_time_ns / 1_000_000.0).to_string(),
380                        );
381                        meta
382                    },
383                    color: None,
384                    size: None,
385                });
386            }
387        }
388
389        let mut series = Vec::new();
390        for (i, (name, data)) in series_map.into_iter().enumerate() {
391            series.push(ChartSeries {
392                name,
393                data,
394                chart_type: ChartType::Bar,
395                color: Some(self.config.color_palette[i % self.config.color_palette.len()].clone()),
396                width: Some(0.8),
397                fill_opacity: Some(0.8),
398                dash_pattern: None,
399            });
400        }
401
402        Chart {
403            title: "Throughput Comparison".to_string(),
404            subtitle: Some("Operations per second across different data types".to_string()),
405            x_axis: AxisConfig {
406                title: "Input Size".to_string(),
407                axis_type: AxisType::Linear,
408                min: None,
409                max: None,
410                tick_interval: None,
411                log_scale: false,
412                grid: true,
413                label_format: "{:.0}".to_string(),
414            },
415            y_axis: AxisConfig {
416                title: "Throughput (ops/sec)".to_string(),
417                axis_type: AxisType::Linear,
418                min: Some(0.0),
419                max: None,
420                tick_interval: None,
421                log_scale: false,
422                grid: true,
423                label_format: "{:.0}".to_string(),
424            },
425            series,
426            chart_type: ChartType::Bar,
427            legend: LegendConfig {
428                show: true,
429                position: LegendPosition::TopRight,
430                orientation: LegendOrientation::Vertical,
431            },
432            tooltip: TooltipConfig {
433                show: true,
434                trigger: TooltipTrigger::Hover,
435                format: None,
436            },
437            annotations: Vec::new(),
438        }
439    }
440
441    /// Generate performance heatmap
442    pub fn generate_performance_heatmap(&self, results: &[BenchResult]) -> Chart {
443        let mut data = Vec::new();
444        let mut benchmarks = Vec::new();
445        let mut sizes = Vec::new();
446
447        // Collect unique benchmarks and sizes
448        for result in results {
449            if !benchmarks.contains(&result.name) {
450                benchmarks.push(result.name.clone());
451            }
452            if !sizes.contains(&result.size) {
453                sizes.push(result.size);
454            }
455        }
456
457        benchmarks.sort();
458        sizes.sort();
459
460        // Create heatmap data
461        for (i, benchmark) in benchmarks.iter().enumerate() {
462            for (j, &size) in sizes.iter().enumerate() {
463                if let Some(result) = results
464                    .iter()
465                    .find(|r| r.name == *benchmark && r.size == size)
466                {
467                    data.push(VisualizationPoint {
468                        x: j as f64,
469                        y: i as f64,
470                        label: format!("{} ({})", benchmark, size),
471                        metadata: {
472                            let mut meta = HashMap::new();
473                            meta.insert("benchmark".to_string(), benchmark.clone());
474                            meta.insert("size".to_string(), size.to_string());
475                            meta.insert(
476                                "value".to_string(),
477                                (result.mean_time_ns / 1_000_000.0).to_string(),
478                            );
479                            meta
480                        },
481                        color: None,
482                        size: Some(result.mean_time_ns / 1_000_000.0),
483                    });
484                }
485            }
486        }
487
488        Chart {
489            title: "Performance Heatmap".to_string(),
490            subtitle: Some("Execution time across benchmarks and input sizes".to_string()),
491            x_axis: AxisConfig {
492                title: "Input Size".to_string(),
493                axis_type: AxisType::Category,
494                min: None,
495                max: None,
496                tick_interval: None,
497                log_scale: false,
498                grid: false,
499                label_format: "{:.0}".to_string(),
500            },
501            y_axis: AxisConfig {
502                title: "Benchmark".to_string(),
503                axis_type: AxisType::Category,
504                min: None,
505                max: None,
506                tick_interval: None,
507                log_scale: false,
508                grid: false,
509                label_format: "{}".to_string(),
510            },
511            series: vec![ChartSeries {
512                name: "Execution Time".to_string(),
513                data,
514                chart_type: ChartType::Heatmap,
515                color: None,
516                width: None,
517                fill_opacity: Some(0.8),
518                dash_pattern: None,
519            }],
520            chart_type: ChartType::Heatmap,
521            legend: LegendConfig {
522                show: true,
523                position: LegendPosition::Right,
524                orientation: LegendOrientation::Vertical,
525            },
526            tooltip: TooltipConfig {
527                show: true,
528                trigger: TooltipTrigger::Hover,
529                format: Some("Value: {:.2f} ms".to_string()),
530            },
531            annotations: Vec::new(),
532        }
533    }
534
535    /// Generate regression analysis chart
536    pub fn generate_regression_analysis(&self, regressions: &[AdvancedRegressionResult]) -> Chart {
537        let mut data = Vec::new();
538
539        for (i, regression) in regressions.iter().enumerate() {
540            data.push(VisualizationPoint {
541                x: i as f64,
542                y: regression.effect_size * 100.0,
543                label: regression.benchmark_id.clone(),
544                metadata: {
545                    let mut meta = HashMap::new();
546                    meta.insert("benchmark".to_string(), regression.benchmark_id.clone());
547                    meta.insert("p_value".to_string(), regression.p_value.to_string());
548                    meta.insert(
549                        "effect_size".to_string(),
550                        regression.effect_size.to_string(),
551                    );
552                    meta.insert("severity".to_string(), format!("{:?}", regression.severity));
553                    meta
554                },
555                color: Some(match regression.severity {
556                    RegressionSeverity::Minor => "#f39c12".to_string(),
557                    RegressionSeverity::Moderate => "#e67e22".to_string(),
558                    RegressionSeverity::Major => "#d35400".to_string(),
559                    RegressionSeverity::Critical => "#c0392b".to_string(),
560                }),
561                size: Some(regression.p_value / 10.0),
562            });
563        }
564
565        Chart {
566            title: "Performance Regression Analysis".to_string(),
567            subtitle: Some("Performance changes with severity indicators".to_string()),
568            x_axis: AxisConfig {
569                title: "Benchmark Index".to_string(),
570                axis_type: AxisType::Linear,
571                min: None,
572                max: None,
573                tick_interval: None,
574                log_scale: false,
575                grid: true,
576                label_format: "{:.0}".to_string(),
577            },
578            y_axis: AxisConfig {
579                title: "Performance Change (%)".to_string(),
580                axis_type: AxisType::Linear,
581                min: None,
582                max: None,
583                tick_interval: None,
584                log_scale: false,
585                grid: true,
586                label_format: "{:.1f}%".to_string(),
587            },
588            series: vec![ChartSeries {
589                name: "Regressions".to_string(),
590                data,
591                chart_type: ChartType::Scatter,
592                color: None,
593                width: None,
594                fill_opacity: Some(0.7),
595                dash_pattern: None,
596            }],
597            chart_type: ChartType::Scatter,
598            legend: LegendConfig {
599                show: false,
600                position: LegendPosition::TopRight,
601                orientation: LegendOrientation::Vertical,
602            },
603            tooltip: TooltipConfig {
604                show: true,
605                trigger: TooltipTrigger::Hover,
606                format: Some("Change: {:.1f}%, Confidence: {:.1f}%".to_string()),
607            },
608            annotations: vec![
609                Annotation {
610                    annotation_type: AnnotationType::Line,
611                    x: 0.0,
612                    y: 0.0,
613                    text: "No Change".to_string(),
614                    color: Some("#7f8c8d".to_string()),
615                    font_size: None,
616                },
617                Annotation {
618                    annotation_type: AnnotationType::Line,
619                    x: 0.0,
620                    y: 10.0,
621                    text: "10% Regression Threshold".to_string(),
622                    color: Some("#e74c3c".to_string()),
623                    font_size: None,
624                },
625            ],
626        }
627    }
628
629    /// Generate memory usage analysis chart
630    pub fn generate_memory_analysis(&self, results: &[BenchResult]) -> Chart {
631        let mut execution_data = Vec::new();
632        let mut memory_data = Vec::new();
633
634        for result in results {
635            if let Some(memory) = result.memory_usage {
636                execution_data.push(VisualizationPoint {
637                    x: result.size as f64,
638                    y: result.mean_time_ns / 1_000_000.0,
639                    label: format!("{} ({})", result.name, result.size),
640                    metadata: HashMap::new(),
641                    color: None,
642                    size: None,
643                });
644
645                memory_data.push(VisualizationPoint {
646                    x: result.size as f64,
647                    y: memory as f64 / 1_048_576.0, // Convert to MB
648                    label: format!("{} ({})", result.name, result.size),
649                    metadata: HashMap::new(),
650                    color: None,
651                    size: None,
652                });
653            }
654        }
655
656        Chart {
657            title: "Memory Usage vs Execution Time".to_string(),
658            subtitle: Some("Correlation between memory usage and performance".to_string()),
659            x_axis: AxisConfig {
660                title: "Input Size".to_string(),
661                axis_type: AxisType::Linear,
662                min: None,
663                max: None,
664                tick_interval: None,
665                log_scale: false,
666                grid: true,
667                label_format: "{:.0}".to_string(),
668            },
669            y_axis: AxisConfig {
670                title: "Value".to_string(),
671                axis_type: AxisType::Linear,
672                min: Some(0.0),
673                max: None,
674                tick_interval: None,
675                log_scale: false,
676                grid: true,
677                label_format: "{:.2f}".to_string(),
678            },
679            series: vec![
680                ChartSeries {
681                    name: "Execution Time (ms)".to_string(),
682                    data: execution_data,
683                    chart_type: ChartType::Line,
684                    color: Some(self.config.color_palette[0].clone()),
685                    width: Some(2.0),
686                    fill_opacity: None,
687                    dash_pattern: None,
688                },
689                ChartSeries {
690                    name: "Memory Usage (MB)".to_string(),
691                    data: memory_data,
692                    chart_type: ChartType::Bar,
693                    color: Some(self.config.color_palette[1].clone()),
694                    width: Some(0.6),
695                    fill_opacity: Some(0.7),
696                    dash_pattern: None,
697                },
698            ],
699            chart_type: ChartType::Line,
700            legend: LegendConfig {
701                show: true,
702                position: LegendPosition::TopLeft,
703                orientation: LegendOrientation::Vertical,
704            },
705            tooltip: TooltipConfig {
706                show: true,
707                trigger: TooltipTrigger::Hover,
708                format: None,
709            },
710            annotations: Vec::new(),
711        }
712    }
713
714    /// Generate statistical distribution chart
715    pub fn generate_distribution_chart(&self, results: &[BenchResult]) -> Chart {
716        let mut data = Vec::new();
717        let mut benchmarks = Vec::new();
718
719        // Group results by benchmark name
720        let mut benchmark_groups: HashMap<String, Vec<&BenchResult>> = HashMap::new();
721        for result in results {
722            benchmark_groups
723                .entry(result.name.clone())
724                .or_insert_with(Vec::new)
725                .push(result);
726        }
727
728        for (i, (benchmark, group)) in benchmark_groups.iter().enumerate() {
729            benchmarks.push(benchmark.clone());
730
731            // Calculate statistics for box plot
732            let mut times: Vec<f64> = group.iter().map(|r| r.mean_time_ns / 1_000_000.0).collect();
733            times.sort_by(|a, b| a.partial_cmp(b).expect("NaN values in benchmark times"));
734
735            let min = times[0];
736            let max = times[times.len() - 1];
737            let median = times[times.len() / 2];
738            let q1 = times[times.len() / 4];
739            let q3 = times[3 * times.len() / 4];
740
741            data.push(VisualizationPoint {
742                x: i as f64,
743                y: median,
744                label: benchmark.clone(),
745                metadata: {
746                    let mut meta = HashMap::new();
747                    meta.insert("min".to_string(), min.to_string());
748                    meta.insert("q1".to_string(), q1.to_string());
749                    meta.insert("median".to_string(), median.to_string());
750                    meta.insert("q3".to_string(), q3.to_string());
751                    meta.insert("max".to_string(), max.to_string());
752                    meta
753                },
754                color: None,
755                size: None,
756            });
757        }
758
759        Chart {
760            title: "Performance Distribution".to_string(),
761            subtitle: Some("Statistical distribution of benchmark execution times".to_string()),
762            x_axis: AxisConfig {
763                title: "Benchmark".to_string(),
764                axis_type: AxisType::Category,
765                min: None,
766                max: None,
767                tick_interval: None,
768                log_scale: false,
769                grid: false,
770                label_format: "{}".to_string(),
771            },
772            y_axis: AxisConfig {
773                title: "Execution Time (ms)".to_string(),
774                axis_type: AxisType::Linear,
775                min: Some(0.0),
776                max: None,
777                tick_interval: None,
778                log_scale: false,
779                grid: true,
780                label_format: "{:.2f}".to_string(),
781            },
782            series: vec![ChartSeries {
783                name: "Distribution".to_string(),
784                data,
785                chart_type: ChartType::Box,
786                color: Some(self.config.color_palette[0].clone()),
787                width: Some(0.8),
788                fill_opacity: Some(0.7),
789                dash_pattern: None,
790            }],
791            chart_type: ChartType::Box,
792            legend: LegendConfig {
793                show: false,
794                position: LegendPosition::TopRight,
795                orientation: LegendOrientation::Vertical,
796            },
797            tooltip: TooltipConfig {
798                show: true,
799                trigger: TooltipTrigger::Hover,
800                format: Some("Median: {:.2f} ms".to_string()),
801            },
802            annotations: Vec::new(),
803        }
804    }
805
806    /// Export chart to HTML
807    pub fn export_to_html(&self, chart: &Chart, output_path: &str) -> std::io::Result<()> {
808        let html = self.generate_html_chart(chart);
809        std::fs::write(output_path, html)?;
810        Ok(())
811    }
812
813    /// Generate HTML representation of chart
814    fn generate_html_chart(&self, chart: &Chart) -> String {
815        let chart_json = serde_json::to_string_pretty(chart).unwrap_or_default();
816        let theme_css = self.get_theme_css();
817
818        format!(
819            r#"
820<!DOCTYPE html>
821<html lang="en">
822<head>
823    <meta charset="UTF-8">
824    <meta name="viewport" content="width=device-width, initial-scale=1.0">
825    <title>{}</title>
826    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
827    <style>
828        body {{
829            font-family: {};
830            margin: 20px;
831            background: {};
832        }}
833        .chart-container {{
834            width: {}px;
835            height: {}px;
836            margin: 20px auto;
837            background: white;
838            border-radius: 8px;
839            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
840            padding: 20px;
841        }}
842        .chart-title {{
843            text-align: center;
844            font-size: 24px;
845            font-weight: bold;
846            margin-bottom: 10px;
847            color: #2c3e50;
848        }}
849        .chart-subtitle {{
850            text-align: center;
851            font-size: 16px;
852            color: #7f8c8d;
853            margin-bottom: 20px;
854        }}
855        {}
856    </style>
857</head>
858<body>
859    <div class="chart-container">
860        <div class="chart-title">{}</div>
861        {}
862        <div id="chart"></div>
863    </div>
864    
865    <script>
866        const chartData = {};
867        
868        // Convert chart data to Plotly format
869        const plotlyData = chartData.series.map(series => ({{
870            x: series.data.map(point => point.x),
871            y: series.data.map(point => point.y),
872            type: getPlotlyType(series.chart_type),
873            name: series.name,
874            marker: {{ color: series.color }},
875            mode: 'lines+markers'
876        }}));
877        
878        const layout = {{
879            title: chartData.title,
880            xaxis: {{ title: chartData.x_axis.title }},
881            yaxis: {{ title: chartData.y_axis.title }},
882            showlegend: chartData.legend.show,
883            font: {{ family: '{}', size: {} }},
884            plot_bgcolor: 'rgba(0,0,0,0)',
885            paper_bgcolor: 'rgba(0,0,0,0)',
886            autosize: true,
887            responsive: true
888        }};
889        
890        const config = {{
891            responsive: true,
892            displayModeBar: {},
893            modeBarButtonsToRemove: ['lasso2d', 'select2d']
894        }};
895        
896        Plotly.newPlot('chart', plotlyData, layout, config);
897        
898        function getPlotlyType(chartType) {{
899            switch (chartType) {{
900                case 'Line': return 'scatter';
901                case 'Bar': return 'bar';
902                case 'Scatter': return 'scatter';
903                case 'Heatmap': return 'heatmap';
904                case 'Box': return 'box';
905                case 'Histogram': return 'histogram';
906                default: return 'scatter';
907            }}
908        }}
909    </script>
910</body>
911</html>
912        "#,
913            chart.title,
914            self.config.font_family,
915            self.get_background_color(),
916            self.config.width,
917            self.config.height,
918            theme_css,
919            chart.title,
920            chart
921                .subtitle
922                .as_ref()
923                .map(|s| format!("<div class=\"chart-subtitle\">{}</div>", s))
924                .unwrap_or_default(),
925            chart_json,
926            self.config.font_family,
927            self.config.font_size,
928            self.config.interactive.to_string()
929        )
930    }
931
932    /// Get theme-specific CSS
933    fn get_theme_css(&self) -> String {
934        match self.config.theme {
935            ChartTheme::Dark => r#"
936                body { background: #2c3e50; color: white; }
937                .chart-container { background: #34495e; }
938                .chart-title { color: #ecf0f1; }
939                .chart-subtitle { color: #bdc3c7; }
940            "#
941            .to_string(),
942            ChartTheme::Minimal => r#"
943                body { background: #fafafa; }
944                .chart-container { box-shadow: none; border: 1px solid #e0e0e0; }
945            "#
946            .to_string(),
947            ChartTheme::Professional => r#"
948                body { background: #f8f9fa; }
949                .chart-container { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
950                .chart-title { color: #343a40; }
951            "#
952            .to_string(),
953            _ => String::new(),
954        }
955    }
956
957    /// Get background color for theme
958    fn get_background_color(&self) -> &'static str {
959        match self.config.theme {
960            ChartTheme::Dark => "#2c3e50",
961            ChartTheme::Minimal => "#fafafa",
962            ChartTheme::Professional => "#f8f9fa",
963            _ => "#f5f5f5",
964        }
965    }
966
967    /// Save chart template
968    pub fn save_template(&mut self, name: &str, chart: Chart) {
969        self.templates.insert(name.to_string(), chart);
970    }
971
972    /// Load chart template
973    pub fn load_template(&self, name: &str) -> Option<&Chart> {
974        self.templates.get(name)
975    }
976
977    /// Generate comprehensive dashboard
978    pub fn generate_dashboard(
979        &self,
980        results: &[BenchResult],
981        points: &[PerformancePoint],
982        regressions: &[AdvancedRegressionResult],
983        output_dir: &str,
984    ) -> std::io::Result<()> {
985        std::fs::create_dir_all(output_dir)?;
986
987        // Generate individual charts
988        let trend_chart = self.generate_performance_trend(points);
989        self.export_to_html(&trend_chart, &format!("{}/trend.html", output_dir))?;
990
991        let throughput_chart = self.generate_throughput_comparison(results);
992        self.export_to_html(
993            &throughput_chart,
994            &format!("{}/throughput.html", output_dir),
995        )?;
996
997        let heatmap_chart = self.generate_performance_heatmap(results);
998        self.export_to_html(&heatmap_chart, &format!("{}/heatmap.html", output_dir))?;
999
1000        let regression_chart = self.generate_regression_analysis(regressions);
1001        self.export_to_html(
1002            &regression_chart,
1003            &format!("{}/regressions.html", output_dir),
1004        )?;
1005
1006        let memory_chart = self.generate_memory_analysis(results);
1007        self.export_to_html(&memory_chart, &format!("{}/memory.html", output_dir))?;
1008
1009        let distribution_chart = self.generate_distribution_chart(results);
1010        self.export_to_html(
1011            &distribution_chart,
1012            &format!("{}/distribution.html", output_dir),
1013        )?;
1014
1015        // Generate main dashboard index
1016        let dashboard_html = self.generate_dashboard_index();
1017        std::fs::write(format!("{}/index.html", output_dir), dashboard_html)?;
1018
1019        Ok(())
1020    }
1021
1022    /// Generate dashboard index page
1023    fn generate_dashboard_index(&self) -> String {
1024        r#"
1025<!DOCTYPE html>
1026<html lang="en">
1027<head>
1028    <meta charset="UTF-8">
1029    <meta name="viewport" content="width=device-width, initial-scale=1.0">
1030    <title>ToRSh Benchmark Visualization Dashboard</title>
1031    <style>
1032        body {
1033            font-family: Arial, sans-serif;
1034            margin: 0;
1035            padding: 20px;
1036            background: #f5f5f5;
1037        }
1038        .header {
1039            text-align: center;
1040            background: #2c3e50;
1041            color: white;
1042            padding: 30px;
1043            border-radius: 8px;
1044            margin-bottom: 30px;
1045        }
1046        .grid {
1047            display: grid;
1048            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1049            gap: 20px;
1050            margin-bottom: 30px;
1051        }
1052        .card {
1053            background: white;
1054            padding: 20px;
1055            border-radius: 8px;
1056            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1057            text-align: center;
1058        }
1059        .card h3 {
1060            color: #2c3e50;
1061            margin-top: 0;
1062        }
1063        .card p {
1064            color: #7f8c8d;
1065            margin-bottom: 20px;
1066        }
1067        .btn {
1068            display: inline-block;
1069            padding: 10px 20px;
1070            background: #3498db;
1071            color: white;
1072            text-decoration: none;
1073            border-radius: 5px;
1074            transition: background 0.3s;
1075        }
1076        .btn:hover {
1077            background: #2980b9;
1078        }
1079        .footer {
1080            text-align: center;
1081            color: #7f8c8d;
1082            margin-top: 30px;
1083        }
1084    </style>
1085</head>
1086<body>
1087    <div class="header">
1088        <h1>🚀 ToRSh Benchmark Visualization Dashboard</h1>
1089        <p>Comprehensive performance analysis and visualization tools</p>
1090    </div>
1091
1092    <div class="grid">
1093        <div class="card">
1094            <h3>📈 Performance Trends</h3>
1095            <p>Track performance changes over time with interactive trend analysis</p>
1096            <a href="trend.html" class="btn">View Trends</a>
1097        </div>
1098        
1099        <div class="card">
1100            <h3>🏃 Throughput Comparison</h3>
1101            <p>Compare throughput across different data types and operations</p>
1102            <a href="throughput.html" class="btn">View Throughput</a>
1103        </div>
1104        
1105        <div class="card">
1106            <h3>🔥 Performance Heatmap</h3>
1107            <p>Visualize performance patterns across benchmarks and sizes</p>
1108            <a href="heatmap.html" class="btn">View Heatmap</a>
1109        </div>
1110        
1111        <div class="card">
1112            <h3>⚠️ Regression Analysis</h3>
1113            <p>Identify and analyze performance regressions with severity levels</p>
1114            <a href="regressions.html" class="btn">View Regressions</a>
1115        </div>
1116        
1117        <div class="card">
1118            <h3>💾 Memory Analysis</h3>
1119            <p>Analyze memory usage patterns and correlations with performance</p>
1120            <a href="memory.html" class="btn">View Memory</a>
1121        </div>
1122        
1123        <div class="card">
1124            <h3>📊 Statistical Distribution</h3>
1125            <p>Explore statistical distributions of benchmark results</p>
1126            <a href="distribution.html" class="btn">View Distribution</a>
1127        </div>
1128    </div>
1129
1130    <div class="footer">
1131        <p>Generated by ToRSh Benchmark Visualization System</p>
1132    </div>
1133</body>
1134</html>
1135        "#
1136        .to_string()
1137    }
1138}
1139
1140/// Visualization builder for easy chart creation
1141pub struct VisualizationBuilder {
1142    config: VisualizationConfig,
1143}
1144
1145impl VisualizationBuilder {
1146    /// Create a new builder
1147    pub fn new() -> Self {
1148        Self {
1149            config: VisualizationConfig::default(),
1150        }
1151    }
1152
1153    /// Set chart dimensions
1154    pub fn dimensions(mut self, width: u32, height: u32) -> Self {
1155        self.config.width = width;
1156        self.config.height = height;
1157        self
1158    }
1159
1160    /// Set chart theme
1161    pub fn theme(mut self, theme: ChartTheme) -> Self {
1162        self.config.theme = theme;
1163        self
1164    }
1165
1166    /// Set interactive mode
1167    pub fn interactive(mut self, interactive: bool) -> Self {
1168        self.config.interactive = interactive;
1169        self
1170    }
1171
1172    /// Set color palette
1173    pub fn colors(mut self, colors: Vec<String>) -> Self {
1174        self.config.color_palette = colors;
1175        self
1176    }
1177
1178    /// Build the visualization generator
1179    pub fn build(self) -> VisualizationGenerator {
1180        VisualizationGenerator::new(self.config)
1181    }
1182}
1183
1184impl Default for VisualizationBuilder {
1185    fn default() -> Self {
1186        Self::new()
1187    }
1188}
1189
1190#[cfg(test)]
1191mod tests {
1192    use super::*;
1193    use chrono::Utc;
1194
1195    #[test]
1196    fn test_visualization_config() {
1197        let config = VisualizationConfig::default();
1198        assert_eq!(config.width, 800);
1199        assert_eq!(config.height, 600);
1200        assert_eq!(config.theme, ChartTheme::Light);
1201        assert!(config.interactive);
1202    }
1203
1204    #[test]
1205    fn test_visualization_builder() {
1206        let generator = VisualizationBuilder::new()
1207            .dimensions(1024, 768)
1208            .theme(ChartTheme::Dark)
1209            .interactive(false)
1210            .build();
1211
1212        assert_eq!(generator.config.width, 1024);
1213        assert_eq!(generator.config.height, 768);
1214        assert_eq!(generator.config.theme, ChartTheme::Dark);
1215        assert!(!generator.config.interactive);
1216    }
1217
1218    #[test]
1219    fn test_throughput_chart_generation() {
1220        let generator = VisualizationGenerator::default();
1221        let results = vec![BenchResult {
1222            name: "test_benchmark".to_string(),
1223            size: 1024,
1224            dtype: torsh_core::dtype::DType::F32,
1225            mean_time_ns: 1000.0,
1226            std_dev_ns: 100.0,
1227            throughput: Some(1000.0),
1228            memory_usage: Some(1024),
1229            peak_memory: Some(2048),
1230            metrics: HashMap::new(),
1231        }];
1232
1233        let chart = generator.generate_throughput_comparison(&results);
1234        assert_eq!(chart.title, "Throughput Comparison");
1235        assert!(!chart.series.is_empty());
1236        assert_eq!(chart.chart_type, ChartType::Bar);
1237    }
1238
1239    #[test]
1240    fn test_performance_trend_generation() {
1241        let generator = VisualizationGenerator::default();
1242        let points = vec![PerformancePoint {
1243            timestamp: Utc::now(),
1244            benchmark_name: "test".to_string(),
1245            size: 1024,
1246            dtype: "F32".to_string(),
1247            mean_time_ns: 1000.0,
1248            std_dev_ns: 100.0,
1249            throughput: Some(1000.0),
1250            memory_usage: Some(1024),
1251            peak_memory: Some(2048),
1252            git_commit: None,
1253            build_config: "release".to_string(),
1254            metadata: HashMap::new(),
1255        }];
1256
1257        let chart = generator.generate_performance_trend(&points);
1258        assert_eq!(chart.title, "Performance Trends Over Time");
1259        assert!(!chart.series.is_empty());
1260        assert_eq!(chart.chart_type, ChartType::Line);
1261    }
1262}