trustformers_debug/visualization/
modern_plotting.rs

1//! Modern Plotting Engine
2//!
3//! Advanced visualization engine with support for modern plotting libraries,
4//! interactive dashboards, and real-time updates.
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11
12use super::types::*;
13
14/// Configuration for modern plotting engine
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ModernPlottingConfig {
17    /// Enable interactive plots
18    pub enable_interactive: bool,
19    /// Enable real-time updates
20    pub enable_realtime: bool,
21    /// Enable web dashboard
22    pub enable_web_dashboard: bool,
23    /// Plotting backend to use
24    pub backend: PlottingBackend,
25    /// Output directory for plots
26    pub output_directory: String,
27    /// Dashboard port
28    pub dashboard_port: u16,
29    /// Maximum number of data points per plot
30    pub max_data_points: usize,
31    /// Auto-refresh interval for real-time plots (milliseconds)
32    pub refresh_interval_ms: u64,
33    /// Enable plot animations
34    pub enable_animations: bool,
35    /// Animation frame rate
36    pub animation_fps: u32,
37}
38
39impl Default for ModernPlottingConfig {
40    fn default() -> Self {
41        Self {
42            enable_interactive: true,
43            enable_realtime: true,
44            enable_web_dashboard: true,
45            backend: PlottingBackend::PlotlyJS,
46            output_directory: "./modern_debug_plots".to_string(),
47            dashboard_port: 8888,
48            max_data_points: 10000,
49            refresh_interval_ms: 1000,
50            enable_animations: true,
51            animation_fps: 30,
52        }
53    }
54}
55
56/// Modern plotting backends
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum PlottingBackend {
59    /// Plotly.js for interactive web-based plots
60    PlotlyJS,
61    /// D3.js for custom interactive visualizations
62    D3JS,
63    /// Chart.js for responsive charts
64    ChartJS,
65    /// Three.js for 3D visualizations
66    ThreeJS,
67    /// Matplotlib backend (Python integration)
68    Matplotlib,
69    /// Bokeh backend (Python integration)
70    Bokeh,
71    /// Custom WebGL backend for high-performance visualizations
72    WebGL,
73}
74
75/// Interactive plot types
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub enum InteractivePlotType {
78    /// Interactive line plot with zoom, pan, hover
79    InteractiveLinePlot,
80    /// Interactive scatter plot with selection
81    InteractiveScatterPlot,
82    /// Interactive heatmap with drill-down
83    InteractiveHeatmap,
84    /// Interactive 3D surface with rotation
85    Interactive3DSurface,
86    /// Real-time streaming plot
87    RealtimeStreamingPlot,
88    /// Animated training visualization
89    AnimatedTrainingPlot,
90    /// Interactive network diagram
91    InteractiveNetworkDiagram,
92    /// Dashboard with multiple plots
93    MultiPlotDashboard,
94    /// Interactive histogram with brushing
95    InteractiveHistogram,
96    /// Parallel coordinates plot
97    ParallelCoordinatesPlot,
98    /// Interactive correlation matrix
99    InteractiveCorrelationMatrix,
100    /// Time series with range selector
101    TimeSeriesWithRangeSelector,
102}
103
104/// Modern plot data with interactive features
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct InteractivePlotData {
107    /// Basic plot data
108    pub plot_data: PlotData,
109    /// Interactive features configuration
110    pub interactive_config: InteractiveConfig,
111    /// Custom styling
112    pub styling: PlotStyling,
113    /// Animation configuration
114    pub animation_config: Option<AnimationConfig>,
115    /// Real-time update configuration
116    pub realtime_config: Option<RealtimeConfig>,
117}
118
119/// Configuration for interactive features
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct InteractiveConfig {
122    /// Enable zoom functionality
123    pub enable_zoom: bool,
124    /// Enable pan functionality
125    pub enable_pan: bool,
126    /// Enable hover tooltips
127    pub enable_hover: bool,
128    /// Enable selection
129    pub enable_selection: bool,
130    /// Enable brush selection
131    pub enable_brush: bool,
132    /// Enable crossfilter
133    pub enable_crossfilter: bool,
134    /// Custom event handlers
135    pub event_handlers: HashMap<String, String>,
136}
137
138impl Default for InteractiveConfig {
139    fn default() -> Self {
140        Self {
141            enable_zoom: true,
142            enable_pan: true,
143            enable_hover: true,
144            enable_selection: true,
145            enable_brush: false,
146            enable_crossfilter: false,
147            event_handlers: HashMap::new(),
148        }
149    }
150}
151
152/// Custom plot styling
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct PlotStyling {
155    /// Color palette
156    pub color_palette: Vec<String>,
157    /// Font configuration
158    pub font_config: FontConfig,
159    /// Line styles
160    pub line_styles: Vec<LineStyle>,
161    /// Marker styles
162    pub marker_styles: Vec<MarkerStyle>,
163    /// Background color
164    pub background_color: String,
165    /// Grid configuration
166    pub grid_config: GridConfig,
167    /// Legend configuration
168    pub legend_config: LegendConfig,
169    /// Custom CSS styles
170    pub custom_css: Option<String>,
171}
172
173impl Default for PlotStyling {
174    fn default() -> Self {
175        Self {
176            color_palette: vec![
177                "#1f77b4".to_string(),
178                "#ff7f0e".to_string(),
179                "#2ca02c".to_string(),
180                "#d62728".to_string(),
181                "#9467bd".to_string(),
182                "#8c564b".to_string(),
183                "#e377c2".to_string(),
184                "#7f7f7f".to_string(),
185                "#bcbd22".to_string(),
186                "#17becf".to_string(),
187            ],
188            font_config: FontConfig::default(),
189            line_styles: vec![LineStyle::Solid, LineStyle::Dashed, LineStyle::Dotted],
190            marker_styles: vec![
191                MarkerStyle::Circle,
192                MarkerStyle::Square,
193                MarkerStyle::Triangle,
194            ],
195            background_color: "#ffffff".to_string(),
196            grid_config: GridConfig::default(),
197            legend_config: LegendConfig::default(),
198            custom_css: None,
199        }
200    }
201}
202
203/// Font configuration
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct FontConfig {
206    pub family: String,
207    pub size: u32,
208    pub weight: FontWeight,
209    pub color: String,
210}
211
212impl Default for FontConfig {
213    fn default() -> Self {
214        Self {
215            family: "Arial, sans-serif".to_string(),
216            size: 12,
217            weight: FontWeight::Normal,
218            color: "#000000".to_string(),
219        }
220    }
221}
222
223/// Font weight options
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub enum FontWeight {
226    Normal,
227    Bold,
228    Light,
229    ExtraBold,
230}
231
232/// Line style options
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub enum LineStyle {
235    Solid,
236    Dashed,
237    Dotted,
238    DashDot,
239    None,
240}
241
242/// Marker style options
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub enum MarkerStyle {
245    Circle,
246    Square,
247    Triangle,
248    Diamond,
249    Cross,
250    Plus,
251    Star,
252    None,
253}
254
255/// Grid configuration
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct GridConfig {
258    pub show_x_grid: bool,
259    pub show_y_grid: bool,
260    pub grid_color: String,
261    pub grid_alpha: f64,
262    pub grid_width: f64,
263}
264
265impl Default for GridConfig {
266    fn default() -> Self {
267        Self {
268            show_x_grid: true,
269            show_y_grid: true,
270            grid_color: "#cccccc".to_string(),
271            grid_alpha: 0.5,
272            grid_width: 1.0,
273        }
274    }
275}
276
277/// Legend configuration
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct LegendConfig {
280    pub show_legend: bool,
281    pub position: LegendPosition,
282    pub background_color: String,
283    pub border_color: String,
284    pub border_width: f64,
285}
286
287impl Default for LegendConfig {
288    fn default() -> Self {
289        Self {
290            show_legend: true,
291            position: LegendPosition::TopRight,
292            background_color: "rgba(255, 255, 255, 0.8)".to_string(),
293            border_color: "#cccccc".to_string(),
294            border_width: 1.0,
295        }
296    }
297}
298
299/// Legend position options
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub enum LegendPosition {
302    TopLeft,
303    TopRight,
304    BottomLeft,
305    BottomRight,
306    Top,
307    Bottom,
308    Left,
309    Right,
310}
311
312/// Animation configuration
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct AnimationConfig {
315    /// Animation type
316    pub animation_type: AnimationType,
317    /// Duration in milliseconds
318    pub duration_ms: u64,
319    /// Easing function
320    pub easing: EasingFunction,
321    /// Number of frames
322    pub frames: u32,
323    /// Loop animation
324    pub loop_animation: bool,
325    /// Auto-start animation
326    pub auto_start: bool,
327}
328
329/// Animation types
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub enum AnimationType {
332    /// Fade in animation
333    FadeIn,
334    /// Slide in animation
335    SlideIn,
336    /// Grow animation
337    Grow,
338    /// Training progress animation
339    TrainingProgress,
340    /// Gradient flow animation
341    GradientFlow,
342    /// Loss landscape flythrough
343    LossLandscapeFlythrough,
344    /// Custom animation
345    Custom(String),
346}
347
348/// Easing functions for animations
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub enum EasingFunction {
351    Linear,
352    EaseIn,
353    EaseOut,
354    EaseInOut,
355    Bounce,
356    Elastic,
357    Back,
358}
359
360/// Real-time plot configuration
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct RealtimeConfig {
363    /// Maximum number of points to keep in buffer
364    pub buffer_size: usize,
365    /// Update frequency in milliseconds
366    pub update_frequency_ms: u64,
367    /// Enable streaming mode
368    pub streaming_mode: bool,
369    /// Data source configuration
370    pub data_source: DataSource,
371    /// Auto-scroll behavior
372    pub auto_scroll: bool,
373    /// Time window for display (in seconds)
374    pub time_window_seconds: f64,
375}
376
377/// Data source for real-time plots
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub enum DataSource {
380    /// WebSocket connection
381    WebSocket { url: String },
382    /// HTTP polling
383    HttpPolling { url: String, interval_ms: u64 },
384    /// File watching
385    FileWatching { path: String },
386    /// Memory buffer
387    MemoryBuffer { buffer_id: String },
388    /// Custom function
389    CustomFunction { function_name: String },
390}
391
392/// Modern plotting engine
393#[derive(Debug)]
394pub struct ModernPlottingEngine {
395    config: ModernPlottingConfig,
396    active_plots: HashMap<String, PlotInstance>,
397    dashboard_server: Option<DashboardServer>,
398    #[allow(dead_code)]
399    plot_cache: HashMap<String, CachedPlot>,
400}
401
402/// Plot instance tracking
403#[derive(Debug, Clone)]
404pub struct PlotInstance {
405    pub id: String,
406    pub plot_type: InteractivePlotType,
407    pub data: InteractivePlotData,
408    pub creation_time: DateTime<Utc>,
409    pub last_update: DateTime<Utc>,
410    pub file_path: Option<PathBuf>,
411    pub is_realtime: bool,
412    pub update_count: u64,
413}
414
415/// Dashboard server for web interface
416#[derive(Debug)]
417pub struct DashboardServer {
418    port: u16,
419    plots: HashMap<String, String>, // plot_id -> HTML content
420    is_running: bool,
421}
422
423/// Cached plot for performance optimization
424#[derive(Debug, Clone)]
425pub struct CachedPlot {
426    pub content: String,
427    pub hash: u64,
428    pub creation_time: DateTime<Utc>,
429    pub access_count: u64,
430}
431
432impl ModernPlottingEngine {
433    /// Create a new modern plotting engine
434    pub fn new(config: ModernPlottingConfig) -> Self {
435        std::fs::create_dir_all(&config.output_directory).ok();
436
437        Self {
438            config,
439            active_plots: HashMap::new(),
440            dashboard_server: None,
441            plot_cache: HashMap::new(),
442        }
443    }
444
445    /// Create an interactive line plot
446    pub async fn create_interactive_line_plot(
447        &mut self,
448        plot_data: InteractivePlotData,
449        plot_id: Option<String>,
450    ) -> Result<String> {
451        let id = plot_id.unwrap_or_else(|| format!("line_plot_{}", Utc::now().timestamp()));
452
453        let html_content = self.generate_plotly_line_plot(&plot_data)?;
454        let file_path = self.save_plot_to_file(&id, &html_content).await?;
455
456        let instance = PlotInstance {
457            id: id.clone(),
458            plot_type: InteractivePlotType::InteractiveLinePlot,
459            data: plot_data,
460            creation_time: Utc::now(),
461            last_update: Utc::now(),
462            file_path: Some(file_path),
463            is_realtime: false,
464            update_count: 0,
465        };
466
467        self.active_plots.insert(id.clone(), instance);
468
469        if self.config.enable_web_dashboard {
470            self.add_plot_to_dashboard(&id, &html_content).await?;
471        }
472
473        Ok(id)
474    }
475
476    /// Create an interactive scatter plot
477    pub async fn create_interactive_scatter_plot(
478        &mut self,
479        x_values: &[f64],
480        y_values: &[f64],
481        labels: Option<&[String]>,
482        title: &str,
483        plot_id: Option<String>,
484    ) -> Result<String> {
485        let id = plot_id.unwrap_or_else(|| format!("scatter_plot_{}", Utc::now().timestamp()));
486
487        let plot_data = InteractivePlotData {
488            plot_data: PlotData {
489                x_values: x_values.to_vec(),
490                y_values: y_values.to_vec(),
491                labels: labels.map(|l| l.to_vec()).unwrap_or_else(|| vec!["Series 1".to_string()]),
492                title: title.to_string(),
493                x_label: "X".to_string(),
494                y_label: "Y".to_string(),
495            },
496            interactive_config: InteractiveConfig::default(),
497            styling: PlotStyling::default(),
498            animation_config: None,
499            realtime_config: None,
500        };
501
502        let html_content = self.generate_plotly_scatter_plot(&plot_data)?;
503        let file_path = self.save_plot_to_file(&id, &html_content).await?;
504
505        let instance = PlotInstance {
506            id: id.clone(),
507            plot_type: InteractivePlotType::InteractiveScatterPlot,
508            data: plot_data,
509            creation_time: Utc::now(),
510            last_update: Utc::now(),
511            file_path: Some(file_path),
512            is_realtime: false,
513            update_count: 0,
514        };
515
516        self.active_plots.insert(id.clone(), instance);
517
518        if self.config.enable_web_dashboard {
519            self.add_plot_to_dashboard(&id, &html_content).await?;
520        }
521
522        Ok(id)
523    }
524
525    /// Create an interactive heatmap
526    pub async fn create_interactive_heatmap(
527        &mut self,
528        values: &[Vec<f64>],
529        x_labels: Option<&[String]>,
530        y_labels: Option<&[String]>,
531        title: &str,
532        plot_id: Option<String>,
533    ) -> Result<String> {
534        let id = plot_id.unwrap_or_else(|| format!("heatmap_{}", Utc::now().timestamp()));
535
536        let default_x_labels: Vec<String> = (0..values.first().map_or(0, |row| row.len()))
537            .map(|i| format!("Col_{}", i))
538            .collect();
539        let default_y_labels: Vec<String> =
540            (0..values.len()).map(|i| format!("Row_{}", i)).collect();
541
542        let heatmap_data = HeatmapData {
543            values: values.to_vec(),
544            x_labels: x_labels.map(|l| l.to_vec()).unwrap_or(default_x_labels),
545            y_labels: y_labels.map(|l| l.to_vec()).unwrap_or(default_y_labels),
546            title: title.to_string(),
547            color_bar_label: "Value".to_string(),
548        };
549
550        let html_content = self.generate_plotly_heatmap(&heatmap_data)?;
551        let file_path = self.save_plot_to_file(&id, &html_content).await?;
552
553        let plot_data = InteractivePlotData {
554            plot_data: PlotData {
555                x_values: vec![],
556                y_values: vec![],
557                labels: vec![],
558                title: title.to_string(),
559                x_label: "X".to_string(),
560                y_label: "Y".to_string(),
561            },
562            interactive_config: InteractiveConfig::default(),
563            styling: PlotStyling::default(),
564            animation_config: None,
565            realtime_config: None,
566        };
567
568        let instance = PlotInstance {
569            id: id.clone(),
570            plot_type: InteractivePlotType::InteractiveHeatmap,
571            data: plot_data,
572            creation_time: Utc::now(),
573            last_update: Utc::now(),
574            file_path: Some(file_path),
575            is_realtime: false,
576            update_count: 0,
577        };
578
579        self.active_plots.insert(id.clone(), instance);
580
581        if self.config.enable_web_dashboard {
582            self.add_plot_to_dashboard(&id, &html_content).await?;
583        }
584
585        Ok(id)
586    }
587
588    /// Create a real-time streaming plot
589    pub async fn create_realtime_plot(
590        &mut self,
591        title: &str,
592        plot_id: Option<String>,
593        realtime_config: RealtimeConfig,
594    ) -> Result<String> {
595        let id = plot_id.unwrap_or_else(|| format!("realtime_plot_{}", Utc::now().timestamp()));
596
597        let plot_data = InteractivePlotData {
598            plot_data: PlotData {
599                x_values: vec![],
600                y_values: vec![],
601                labels: vec!["Real-time Data".to_string()],
602                title: title.to_string(),
603                x_label: "Time".to_string(),
604                y_label: "Value".to_string(),
605            },
606            interactive_config: InteractiveConfig::default(),
607            styling: PlotStyling::default(),
608            animation_config: None,
609            realtime_config: Some(realtime_config),
610        };
611
612        let html_content = self.generate_realtime_plot(&plot_data)?;
613        let file_path = self.save_plot_to_file(&id, &html_content).await?;
614
615        let instance = PlotInstance {
616            id: id.clone(),
617            plot_type: InteractivePlotType::RealtimeStreamingPlot,
618            data: plot_data,
619            creation_time: Utc::now(),
620            last_update: Utc::now(),
621            file_path: Some(file_path),
622            is_realtime: true,
623            update_count: 0,
624        };
625
626        self.active_plots.insert(id.clone(), instance);
627
628        if self.config.enable_web_dashboard {
629            self.add_plot_to_dashboard(&id, &html_content).await?;
630        }
631
632        Ok(id)
633    }
634
635    /// Create an animated training visualization
636    pub async fn create_animated_training_plot(
637        &mut self,
638        training_data: &[f64],
639        validation_data: &[f64],
640        epochs: &[u32],
641        title: &str,
642        plot_id: Option<String>,
643    ) -> Result<String> {
644        let id = plot_id.unwrap_or_else(|| format!("animated_training_{}", Utc::now().timestamp()));
645
646        let animation_config = AnimationConfig {
647            animation_type: AnimationType::TrainingProgress,
648            duration_ms: 5000,
649            easing: EasingFunction::EaseInOut,
650            frames: epochs.len() as u32,
651            loop_animation: false,
652            auto_start: true,
653        };
654
655        let plot_data = InteractivePlotData {
656            plot_data: PlotData {
657                x_values: epochs.iter().map(|&e| e as f64).collect(),
658                y_values: training_data.to_vec(),
659                labels: vec!["Training Loss".to_string(), "Validation Loss".to_string()],
660                title: title.to_string(),
661                x_label: "Epoch".to_string(),
662                y_label: "Loss".to_string(),
663            },
664            interactive_config: InteractiveConfig::default(),
665            styling: PlotStyling::default(),
666            animation_config: Some(animation_config),
667            realtime_config: None,
668        };
669
670        let html_content = self.generate_animated_training_plot(&plot_data, validation_data)?;
671        let file_path = self.save_plot_to_file(&id, &html_content).await?;
672
673        let instance = PlotInstance {
674            id: id.clone(),
675            plot_type: InteractivePlotType::AnimatedTrainingPlot,
676            data: plot_data,
677            creation_time: Utc::now(),
678            last_update: Utc::now(),
679            file_path: Some(file_path),
680            is_realtime: false,
681            update_count: 0,
682        };
683
684        self.active_plots.insert(id.clone(), instance);
685
686        if self.config.enable_web_dashboard {
687            self.add_plot_to_dashboard(&id, &html_content).await?;
688        }
689
690        Ok(id)
691    }
692
693    /// Create a comprehensive dashboard with multiple plots
694    pub async fn create_dashboard(&mut self, plot_ids: &[String], title: &str) -> Result<String> {
695        let dashboard_id = format!("dashboard_{}", Utc::now().timestamp());
696
697        let mut dashboard_html = self.generate_dashboard_template(title)?;
698
699        for plot_id in plot_ids {
700            if let Some(plot_instance) = self.active_plots.get(plot_id) {
701                let plot_html = self.get_plot_html_content(plot_instance)?;
702                dashboard_html =
703                    self.embed_plot_in_dashboard(&dashboard_html, plot_id, &plot_html)?;
704            }
705        }
706
707        dashboard_html = self.finalize_dashboard_html(&dashboard_html)?;
708
709        let dashboard_path =
710            Path::new(&self.config.output_directory).join(format!("{}.html", dashboard_id));
711        tokio::fs::write(&dashboard_path, &dashboard_html).await?;
712
713        if self.config.enable_web_dashboard {
714            self.start_dashboard_server().await?;
715        }
716
717        Ok(dashboard_path.to_string_lossy().to_string())
718    }
719
720    /// Update real-time plot with new data
721    pub async fn update_realtime_plot(
722        &mut self,
723        plot_id: &str,
724        new_x: f64,
725        new_y: f64,
726    ) -> Result<()> {
727        let should_update_dashboard = self.config.enable_web_dashboard;
728        let mut plot_data_for_dashboard = None;
729
730        if let Some(plot_instance) = self.active_plots.get_mut(plot_id) {
731            if plot_instance.is_realtime {
732                // Add new data point
733                plot_instance.data.plot_data.x_values.push(new_x);
734                plot_instance.data.plot_data.y_values.push(new_y);
735
736                // Maintain buffer size
737                if let Some(ref realtime_config) = plot_instance.data.realtime_config {
738                    let buffer_size = realtime_config.buffer_size;
739                    if plot_instance.data.plot_data.x_values.len() > buffer_size {
740                        plot_instance.data.plot_data.x_values.remove(0);
741                        plot_instance.data.plot_data.y_values.remove(0);
742                    }
743                }
744
745                plot_instance.last_update = Utc::now();
746                plot_instance.update_count += 1;
747
748                // Store data for dashboard update
749                if should_update_dashboard {
750                    plot_data_for_dashboard = Some(plot_instance.data.clone());
751                }
752            }
753        }
754
755        // Update dashboard if needed
756        if let Some(data) = plot_data_for_dashboard {
757            self.update_plot_in_dashboard(plot_id, &data).await?;
758        }
759
760        Ok(())
761    }
762
763    /// Get plot statistics
764    pub fn get_plot_statistics(&self, plot_id: &str) -> Option<PlotStatistics> {
765        self.active_plots.get(plot_id).map(|instance| PlotStatistics {
766            plot_id: plot_id.to_string(),
767            plot_type: instance.plot_type.clone(),
768            creation_time: instance.creation_time,
769            last_update: instance.last_update,
770            update_count: instance.update_count,
771            data_points: instance.data.plot_data.x_values.len(),
772            is_realtime: instance.is_realtime,
773            file_size_bytes: instance
774                .file_path
775                .as_ref()
776                .and_then(|path| std::fs::metadata(path).ok())
777                .map(|metadata| metadata.len())
778                .unwrap_or(0),
779        })
780    }
781
782    /// List all active plots
783    pub fn list_active_plots(&self) -> Vec<String> {
784        self.active_plots.keys().cloned().collect()
785    }
786
787    /// Remove a plot
788    pub async fn remove_plot(&mut self, plot_id: &str) -> Result<()> {
789        if let Some(instance) = self.active_plots.remove(plot_id) {
790            // Remove file if it exists
791            if let Some(file_path) = instance.file_path {
792                tokio::fs::remove_file(file_path).await.ok();
793            }
794
795            // Remove from dashboard
796            if self.config.enable_web_dashboard {
797                self.remove_plot_from_dashboard(plot_id).await?;
798            }
799        }
800
801        Ok(())
802    }
803
804    // Private helper methods
805
806    fn generate_plotly_line_plot(&self, data: &InteractivePlotData) -> Result<String> {
807        let plot_data = &data.plot_data;
808        let styling = &data.styling;
809
810        let mut html = String::from(
811            r#"
812<!DOCTYPE html>
813<html>
814<head>
815    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
816    <title>Interactive Line Plot</title>
817</head>
818<body>
819    <div id="plotDiv" style="width:100%;height:600px;"></div>
820    <script>
821        var trace = {
822            x: ["#,
823        );
824
825        // Add x values
826        html.push_str(
827            &plot_data.x_values.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "),
828        );
829
830        html.push_str(
831            r#"],
832            y: ["#,
833        );
834
835        // Add y values
836        html.push_str(
837            &plot_data.y_values.iter().map(|y| y.to_string()).collect::<Vec<_>>().join(", "),
838        );
839
840        html.push_str(&format!(
841            r#"],
842            type: 'scatter',
843            mode: 'lines+markers',
844            name: '{}',
845            line: {{
846                color: '{}',
847                width: 2
848            }},
849            marker: {{
850                size: 6,
851                color: '{}'
852            }}
853        }};
854
855        var layout = {{
856            title: '{}',
857            xaxis: {{
858                title: '{}',
859                showgrid: {},
860                gridcolor: '{}'
861            }},
862            yaxis: {{
863                title: '{}',
864                showgrid: {},
865                gridcolor: '{}'
866            }},
867            font: {{
868                family: '{}',
869                size: {},
870                color: '{}'
871            }},
872            plot_bgcolor: '{}',
873            paper_bgcolor: '{}'
874        }};
875
876        var config = {{
877            responsive: true,
878            displayModeBar: true,
879            modeBarButtonsToAdd: ['pan2d', 'select2d', 'lasso2d', 'resetScale2d'],
880            toImageButtonOptions: {{
881                format: 'png',
882                filename: 'debug_plot',
883                height: 600,
884                width: 800,
885                scale: 1
886            }}
887        }};
888
889        Plotly.newPlot('plotDiv', [trace], layout, config);
890    </script>
891</body>
892</html>"#,
893            plot_data.labels.first().unwrap_or(&"Series 1".to_string()),
894            styling.color_palette.first().unwrap_or(&"#1f77b4".to_string()),
895            styling.color_palette.first().unwrap_or(&"#1f77b4".to_string()),
896            plot_data.title,
897            plot_data.x_label,
898            styling.grid_config.show_x_grid,
899            styling.grid_config.grid_color,
900            plot_data.y_label,
901            styling.grid_config.show_y_grid,
902            styling.grid_config.grid_color,
903            styling.font_config.family,
904            styling.font_config.size,
905            styling.font_config.color,
906            styling.background_color,
907            styling.background_color
908        ));
909
910        Ok(html)
911    }
912
913    fn generate_plotly_scatter_plot(&self, data: &InteractivePlotData) -> Result<String> {
914        let plot_data = &data.plot_data;
915        let styling = &data.styling;
916
917        let html = format!(
918            r#"
919<!DOCTYPE html>
920<html>
921<head>
922    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
923    <title>Interactive Scatter Plot</title>
924</head>
925<body>
926    <div id="plotDiv" style="width:100%;height:600px;"></div>
927    <script>
928        var trace = {{
929            x: [{}],
930            y: [{}],
931            mode: 'markers',
932            type: 'scatter',
933            name: '{}',
934            marker: {{
935                size: 8,
936                color: '{}',
937                opacity: 0.7,
938                line: {{
939                    color: '{}',
940                    width: 1
941                }}
942            }}
943        }};
944
945        var layout = {{
946            title: '{}',
947            xaxis: {{
948                title: '{}',
949                showgrid: true
950            }},
951            yaxis: {{
952                title: '{}',
953                showgrid: true
954            }},
955            hovermode: 'closest'
956        }};
957
958        var config = {{
959            responsive: true,
960            displayModeBar: true
961        }};
962
963        Plotly.newPlot('plotDiv', [trace], layout, config);
964    </script>
965</body>
966</html>"#,
967            plot_data.x_values.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "),
968            plot_data.y_values.iter().map(|y| y.to_string()).collect::<Vec<_>>().join(", "),
969            plot_data.labels.first().unwrap_or(&"Series 1".to_string()),
970            styling.color_palette.first().unwrap_or(&"#1f77b4".to_string()),
971            styling.color_palette.first().unwrap_or(&"#1f77b4".to_string()),
972            plot_data.title,
973            plot_data.x_label,
974            plot_data.y_label
975        );
976
977        Ok(html)
978    }
979
980    fn generate_plotly_heatmap(&self, data: &HeatmapData) -> Result<String> {
981        let values_json = serde_json::to_string(&data.values)?;
982        let x_labels_json = serde_json::to_string(&data.x_labels)?;
983        let y_labels_json = serde_json::to_string(&data.y_labels)?;
984
985        let html = format!(
986            r#"
987<!DOCTYPE html>
988<html>
989<head>
990    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
991    <title>Interactive Heatmap</title>
992</head>
993<body>
994    <div id="plotDiv" style="width:100%;height:600px;"></div>
995    <script>
996        var data = [{{
997            z: {},
998            x: {},
999            y: {},
1000            type: 'heatmap',
1001            colorscale: 'Viridis',
1002            showscale: true,
1003            colorbar: {{
1004                title: '{}'
1005            }}
1006        }}];
1007
1008        var layout = {{
1009            title: '{}',
1010            xaxis: {{
1011                title: 'Features'
1012            }},
1013            yaxis: {{
1014                title: 'Samples'
1015            }}
1016        }};
1017
1018        var config = {{
1019            responsive: true,
1020            displayModeBar: true
1021        }};
1022
1023        Plotly.newPlot('plotDiv', data, layout, config);
1024    </script>
1025</body>
1026</html>"#,
1027            values_json, x_labels_json, y_labels_json, data.color_bar_label, data.title
1028        );
1029
1030        Ok(html)
1031    }
1032
1033    fn generate_realtime_plot(&self, data: &InteractivePlotData) -> Result<String> {
1034        let realtime_config = data.realtime_config.as_ref().unwrap();
1035
1036        let html = format!(
1037            r#"
1038<!DOCTYPE html>
1039<html>
1040<head>
1041    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
1042    <title>Real-time Plot</title>
1043</head>
1044<body>
1045    <div id="plotDiv" style="width:100%;height:600px;"></div>
1046    <script>
1047        var trace = {{
1048            x: [],
1049            y: [],
1050            mode: 'lines',
1051            type: 'scatter',
1052            name: '{}'
1053        }};
1054
1055        var layout = {{
1056            title: '{}',
1057            xaxis: {{
1058                title: '{}',
1059                range: [0, {}]
1060            }},
1061            yaxis: {{
1062                title: '{}'
1063            }}
1064        }};
1065
1066        var config = {{
1067            responsive: true,
1068            displayModeBar: true
1069        }};
1070
1071        Plotly.newPlot('plotDiv', [trace], layout, config);
1072
1073        // Simulate real-time updates
1074        var cnt = 0;
1075        var interval = setInterval(function() {{
1076            var time = new Date().getTime();
1077            var y = Math.sin(cnt * 0.1) + Math.random() * 0.1;
1078
1079            Plotly.extendTraces('plotDiv', {{
1080                x: [[time]],
1081                y: [[y]]
1082            }}, [0]);
1083
1084            // Keep only last {} points
1085            if (trace.x.length > {}) {{
1086                Plotly.relayout('plotDiv', {{
1087                    'xaxis.range': [trace.x[trace.x.length - {}], trace.x[trace.x.length - 1]]
1088                }});
1089            }}
1090
1091            cnt++;
1092        }}, {});
1093    </script>
1094</body>
1095</html>"#,
1096            data.plot_data.labels.first().unwrap_or(&"Real-time Data".to_string()),
1097            data.plot_data.title,
1098            data.plot_data.x_label,
1099            realtime_config.time_window_seconds,
1100            data.plot_data.y_label,
1101            realtime_config.buffer_size,
1102            realtime_config.buffer_size,
1103            realtime_config.buffer_size,
1104            realtime_config.update_frequency_ms
1105        );
1106
1107        Ok(html)
1108    }
1109
1110    fn generate_animated_training_plot(
1111        &self,
1112        data: &InteractivePlotData,
1113        validation_data: &[f64],
1114    ) -> Result<String> {
1115        let training_json = serde_json::to_string(&data.plot_data.y_values)?;
1116        let validation_json = serde_json::to_string(validation_data)?;
1117        let epochs_json = serde_json::to_string(&data.plot_data.x_values)?;
1118
1119        let html = format!(
1120            r#"
1121<!DOCTYPE html>
1122<html>
1123<head>
1124    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
1125    <title>Animated Training Plot</title>
1126</head>
1127<body>
1128    <div id="plotDiv" style="width:100%;height:600px;"></div>
1129    <div id="controls">
1130        <button onclick="animateTraining()">Start Animation</button>
1131        <button onclick="resetAnimation()">Reset</button>
1132    </div>
1133    <script>
1134        var trainingData = {};
1135        var validationData = {};
1136        var epochs = {};
1137        var currentFrame = 0;
1138
1139        var trace1 = {{
1140            x: [],
1141            y: [],
1142            mode: 'lines+markers',
1143            type: 'scatter',
1144            name: 'Training Loss',
1145            line: {{color: '#1f77b4', width: 3}},
1146            marker: {{size: 6}}
1147        }};
1148
1149        var trace2 = {{
1150            x: [],
1151            y: [],
1152            mode: 'lines+markers',
1153            type: 'scatter',
1154            name: 'Validation Loss',
1155            line: {{color: '#ff7f0e', width: 3}},
1156            marker: {{size: 6}}
1157        }};
1158
1159        var layout = {{
1160            title: '{}',
1161            xaxis: {{title: '{}'}},
1162            yaxis: {{title: '{}'}},
1163            showlegend: true
1164        }};
1165
1166        Plotly.newPlot('plotDiv', [trace1, trace2], layout);
1167
1168        function animateTraining() {{
1169            var interval = setInterval(function() {{
1170                if (currentFrame >= trainingData.length) {{
1171                    clearInterval(interval);
1172                    return;
1173                }}
1174
1175                trace1.x.push(epochs[currentFrame]);
1176                trace1.y.push(trainingData[currentFrame]);
1177                trace2.x.push(epochs[currentFrame]);
1178                trace2.y.push(validationData[currentFrame]);
1179
1180                Plotly.redraw('plotDiv');
1181                currentFrame++;
1182            }}, 200);
1183        }}
1184
1185        function resetAnimation() {{
1186            currentFrame = 0;
1187            trace1.x = [];
1188            trace1.y = [];
1189            trace2.x = [];
1190            trace2.y = [];
1191            Plotly.redraw('plotDiv');
1192        }}
1193    </script>
1194</body>
1195</html>"#,
1196            training_json,
1197            validation_json,
1198            epochs_json,
1199            data.plot_data.title,
1200            data.plot_data.x_label,
1201            data.plot_data.y_label
1202        );
1203
1204        Ok(html)
1205    }
1206
1207    fn generate_dashboard_template(&self, title: &str) -> Result<String> {
1208        let html = format!(
1209            r#"
1210<!DOCTYPE html>
1211<html>
1212<head>
1213    <meta charset="utf-8">
1214    <title>{}</title>
1215    <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
1216    <style>
1217        body {{
1218            font-family: Arial, sans-serif;
1219            margin: 20px;
1220            background-color: #f5f5f5;
1221        }}
1222        .dashboard-header {{
1223            text-align: center;
1224            margin-bottom: 30px;
1225            padding: 20px;
1226            background-color: white;
1227            border-radius: 10px;
1228            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1229        }}
1230        .plot-container {{
1231            display: inline-block;
1232            width: 48%;
1233            margin: 1%;
1234            background-color: white;
1235            border-radius: 10px;
1236            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1237            padding: 10px;
1238        }}
1239        .plot-container.full-width {{
1240            width: 98%;
1241        }}
1242        .controls {{
1243            text-align: center;
1244            margin: 20px 0;
1245        }}
1246        button {{
1247            padding: 10px 20px;
1248            margin: 0 10px;
1249            background-color: #007bff;
1250            color: white;
1251            border: none;
1252            border-radius: 5px;
1253            cursor: pointer;
1254        }}
1255        button:hover {{
1256            background-color: #0056b3;
1257        }}
1258    </style>
1259</head>
1260<body>
1261    <div class="dashboard-header">
1262        <h1>{}</h1>
1263        <p>Real-time debugging dashboard</p>
1264    </div>
1265    <div class="controls">
1266        <button onclick="refreshAll()">Refresh All</button>
1267        <button onclick="exportDashboard()">Export</button>
1268        <button onclick="toggleAutoRefresh()">Toggle Auto-refresh</button>
1269    </div>
1270    <div id="plots-container">"#,
1271            title, title
1272        );
1273
1274        Ok(html)
1275    }
1276
1277    fn embed_plot_in_dashboard(
1278        &self,
1279        dashboard_html: &str,
1280        plot_id: &str,
1281        plot_html: &str,
1282    ) -> Result<String> {
1283        // Extract the plot div and script from the plot HTML
1284        let plot_div = format!(
1285            r#"<div class="plot-container" id="container-{}"></div>"#,
1286            plot_id
1287        );
1288
1289        let mut updated_html = dashboard_html.replace(
1290            r#"<div id="plots-container">"#,
1291            &format!(r#"<div id="plots-container">{}"#, plot_div),
1292        );
1293
1294        // Add the plot script (simplified - would need proper HTML parsing in production)
1295        updated_html.push_str(&format!(
1296            r#"
1297    <script>
1298        // Plot {} initialization would go here
1299        // Extracted from: {}
1300    </script>"#,
1301            plot_id,
1302            plot_html.len()
1303        ));
1304
1305        Ok(updated_html)
1306    }
1307
1308    fn finalize_dashboard_html(&self, html: &str) -> Result<String> {
1309        let finalized = format!(
1310            r#"{}
1311    </div>
1312    <script>
1313        function refreshAll() {{
1314            location.reload();
1315        }}
1316
1317        function exportDashboard() {{
1318            // Export functionality
1319            alert('Export functionality would be implemented here');
1320        }}
1321
1322        var autoRefresh = false;
1323        function toggleAutoRefresh() {{
1324            autoRefresh = !autoRefresh;
1325            if (autoRefresh) {{
1326                setInterval(refreshAll, 30000); // Refresh every 30 seconds
1327            }}
1328        }}
1329    </script>
1330</body>
1331</html>"#,
1332            html
1333        );
1334
1335        Ok(finalized)
1336    }
1337
1338    async fn save_plot_to_file(&self, plot_id: &str, content: &str) -> Result<PathBuf> {
1339        let file_path = Path::new(&self.config.output_directory).join(format!("{}.html", plot_id));
1340        tokio::fs::write(&file_path, content).await?;
1341        Ok(file_path)
1342    }
1343
1344    async fn add_plot_to_dashboard(&mut self, plot_id: &str, content: &str) -> Result<()> {
1345        if self.dashboard_server.is_none() {
1346            self.dashboard_server = Some(DashboardServer {
1347                port: self.config.dashboard_port,
1348                plots: HashMap::new(),
1349                is_running: false,
1350            });
1351        }
1352
1353        if let Some(ref mut server) = self.dashboard_server {
1354            server.plots.insert(plot_id.to_string(), content.to_string());
1355        }
1356
1357        Ok(())
1358    }
1359
1360    async fn start_dashboard_server(&mut self) -> Result<()> {
1361        if let Some(ref mut server) = self.dashboard_server {
1362            if !server.is_running {
1363                // In a real implementation, this would start an actual web server
1364                server.is_running = true;
1365                tracing::info!("Dashboard server started on port {}", server.port);
1366            }
1367        }
1368        Ok(())
1369    }
1370
1371    async fn update_plot_in_dashboard(
1372        &mut self,
1373        plot_id: &str,
1374        data: &InteractivePlotData,
1375    ) -> Result<()> {
1376        // Update the plot in the dashboard (simplified implementation)
1377        let updated_content = self.generate_plotly_line_plot(data)?;
1378
1379        if let Some(ref mut server) = self.dashboard_server {
1380            server.plots.insert(plot_id.to_string(), updated_content);
1381        }
1382        Ok(())
1383    }
1384
1385    async fn remove_plot_from_dashboard(&mut self, plot_id: &str) -> Result<()> {
1386        if let Some(ref mut server) = self.dashboard_server {
1387            server.plots.remove(plot_id);
1388        }
1389        Ok(())
1390    }
1391
1392    fn get_plot_html_content(&self, instance: &PlotInstance) -> Result<String> {
1393        // Return the HTML content for the plot
1394        match instance.plot_type {
1395            InteractivePlotType::InteractiveLinePlot => {
1396                self.generate_plotly_line_plot(&instance.data)
1397            },
1398            InteractivePlotType::InteractiveScatterPlot => {
1399                self.generate_plotly_scatter_plot(&instance.data)
1400            },
1401            InteractivePlotType::RealtimeStreamingPlot => {
1402                self.generate_realtime_plot(&instance.data)
1403            },
1404            _ => Ok("Plot content not available".to_string()),
1405        }
1406    }
1407}
1408
1409/// Statistics for a plot instance
1410#[derive(Debug, Clone, Serialize, Deserialize)]
1411pub struct PlotStatistics {
1412    pub plot_id: String,
1413    pub plot_type: InteractivePlotType,
1414    pub creation_time: DateTime<Utc>,
1415    pub last_update: DateTime<Utc>,
1416    pub update_count: u64,
1417    pub data_points: usize,
1418    pub is_realtime: bool,
1419    pub file_size_bytes: u64,
1420}
1421
1422#[cfg(test)]
1423mod tests {
1424    use super::*;
1425
1426    #[tokio::test]
1427    async fn test_modern_plotting_engine_creation() {
1428        let config = ModernPlottingConfig::default();
1429        let engine = ModernPlottingEngine::new(config);
1430        assert_eq!(engine.active_plots.len(), 0);
1431    }
1432
1433    #[tokio::test]
1434    async fn test_create_interactive_line_plot() {
1435        let config = ModernPlottingConfig::default();
1436        let mut engine = ModernPlottingEngine::new(config);
1437
1438        let plot_data = InteractivePlotData {
1439            plot_data: PlotData {
1440                x_values: vec![1.0, 2.0, 3.0, 4.0, 5.0],
1441                y_values: vec![1.0, 4.0, 2.0, 3.0, 5.0],
1442                labels: vec!["Test Data".to_string()],
1443                title: "Test Plot".to_string(),
1444                x_label: "X Axis".to_string(),
1445                y_label: "Y Axis".to_string(),
1446            },
1447            interactive_config: InteractiveConfig::default(),
1448            styling: PlotStyling::default(),
1449            animation_config: None,
1450            realtime_config: None,
1451        };
1452
1453        let result = engine.create_interactive_line_plot(plot_data, None).await;
1454        assert!(result.is_ok());
1455
1456        let plot_id = result.unwrap();
1457        assert!(engine.active_plots.contains_key(&plot_id));
1458    }
1459
1460    #[tokio::test]
1461    async fn test_create_interactive_scatter_plot() {
1462        let config = ModernPlottingConfig::default();
1463        let mut engine = ModernPlottingEngine::new(config);
1464
1465        let x_values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1466        let y_values = vec![2.0, 3.0, 1.0, 4.0, 5.0];
1467
1468        let result = engine
1469            .create_interactive_scatter_plot(&x_values, &y_values, None, "Test Scatter Plot", None)
1470            .await;
1471
1472        assert!(result.is_ok());
1473
1474        let plot_id = result.unwrap();
1475        assert!(engine.active_plots.contains_key(&plot_id));
1476    }
1477}