Skip to main content

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