Skip to main content

sklears_inspection/visualization/
interactive_framework.rs

1//! Interactive Visualization Framework
2//!
3//! This module provides the core interactive visualization framework that enables
4//! real-time updates, callback handling, and dynamic plot management.
5//!
6//! ## Key Features
7//!
8//! - **Real-time Updates**: Live data streaming and plot updates
9//! - **Callback System**: Flexible event handling and custom interactions
10//! - **Multi-plot Management**: Centralized configuration and control
11//! - **Thread-safe Operations**: Safe concurrent access and updates
12
13use super::config_types::PlotConfig;
14use crate::Float;
15use scirs2_core::ndarray::Array2;
16use std::collections::HashMap;
17
18/// Type alias for update callback boxed trait objects
19type UpdateCallbackFn = Box<dyn Fn(&str, &Array2<Float>) + Send + Sync>;
20
21/// Interactive visualization framework
22pub struct InteractiveVisualizer {
23    /// Current plot configurations
24    pub plot_configs: HashMap<String, PlotConfig>,
25    /// Update callbacks
26    pub update_callbacks: Vec<UpdateCallbackFn>,
27    /// Real-time update enabled
28    pub real_time_updates: bool,
29}
30
31impl std::fmt::Debug for InteractiveVisualizer {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("InteractiveVisualizer")
34            .field("plot_configs", &self.plot_configs)
35            .field("callback_count", &self.update_callbacks.len())
36            .field("real_time_updates", &self.real_time_updates)
37            .finish()
38    }
39}
40
41impl InteractiveVisualizer {
42    /// Create a new interactive visualizer
43    ///
44    /// # Examples
45    ///
46    /// ```rust
47    /// use sklears_inspection::visualization::InteractiveVisualizer;
48    ///
49    /// let visualizer = InteractiveVisualizer::new();
50    /// assert!(!visualizer.real_time_updates);
51    /// ```
52    pub fn new() -> Self {
53        Self {
54            plot_configs: HashMap::new(),
55            update_callbacks: Vec::new(),
56            real_time_updates: false,
57        }
58    }
59
60    /// Enable real-time updates
61    pub fn enable_real_time_updates(&mut self) {
62        self.real_time_updates = true;
63    }
64
65    /// Disable real-time updates
66    pub fn disable_real_time_updates(&mut self) {
67        self.real_time_updates = false;
68    }
69
70    /// Check if real-time updates are enabled
71    pub fn is_real_time_enabled(&self) -> bool {
72        self.real_time_updates
73    }
74
75    /// Add an update callback
76    pub fn add_update_callback<F>(&mut self, callback: F)
77    where
78        F: Fn(&str, &Array2<Float>) + Send + Sync + 'static,
79    {
80        self.update_callbacks.push(Box::new(callback));
81    }
82
83    /// Clear all update callbacks
84    pub fn clear_callbacks(&mut self) {
85        self.update_callbacks.clear();
86    }
87
88    /// Get number of registered callbacks
89    pub fn callback_count(&self) -> usize {
90        self.update_callbacks.len()
91    }
92
93    /// Update plot data
94    pub fn update_plot_data(&self, plot_id: &str, data: &Array2<Float>) {
95        if self.real_time_updates {
96            for callback in &self.update_callbacks {
97                callback(plot_id, data);
98            }
99        }
100    }
101
102    /// Add or update a plot configuration
103    pub fn set_plot_config(&mut self, plot_id: String, config: PlotConfig) {
104        self.plot_configs.insert(plot_id, config);
105    }
106
107    /// Get plot configuration by ID
108    pub fn get_plot_config(&self, plot_id: &str) -> Option<&PlotConfig> {
109        self.plot_configs.get(plot_id)
110    }
111
112    /// Remove a plot configuration
113    pub fn remove_plot(&mut self, plot_id: &str) -> Option<PlotConfig> {
114        self.plot_configs.remove(plot_id)
115    }
116
117    /// Get all plot IDs
118    pub fn plot_ids(&self) -> Vec<&String> {
119        self.plot_configs.keys().collect()
120    }
121
122    /// Get number of configured plots
123    pub fn plot_count(&self) -> usize {
124        self.plot_configs.len()
125    }
126
127    /// Clear all plot configurations
128    pub fn clear_plots(&mut self) {
129        self.plot_configs.clear();
130    }
131}
132
133impl Default for InteractiveVisualizer {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::super::config_types::PlotConfig;
142    use super::*;
143    use scirs2_core::ndarray::array;
144
145    #[test]
146    fn test_interactive_visualizer_creation() {
147        let visualizer = InteractiveVisualizer::new();
148        assert!(!visualizer.real_time_updates);
149        assert_eq!(visualizer.callback_count(), 0);
150        assert_eq!(visualizer.plot_count(), 0);
151    }
152
153    #[test]
154    fn test_real_time_updates() {
155        let mut visualizer = InteractiveVisualizer::new();
156        assert!(!visualizer.is_real_time_enabled());
157
158        visualizer.enable_real_time_updates();
159        assert!(visualizer.is_real_time_enabled());
160
161        visualizer.disable_real_time_updates();
162        assert!(!visualizer.is_real_time_enabled());
163    }
164
165    #[test]
166    fn test_callbacks() {
167        let mut visualizer = InteractiveVisualizer::new();
168
169        // Add callback
170        visualizer.add_update_callback(|_id, _data| {
171            // Test callback
172        });
173        assert_eq!(visualizer.callback_count(), 1);
174
175        // Clear callbacks
176        visualizer.clear_callbacks();
177        assert_eq!(visualizer.callback_count(), 0);
178    }
179
180    #[test]
181    fn test_plot_management() {
182        let mut visualizer = InteractiveVisualizer::new();
183        let config = PlotConfig::default();
184        let plot_id = "test_plot".to_string();
185
186        // Add plot
187        visualizer.set_plot_config(plot_id.clone(), config.clone());
188        assert_eq!(visualizer.plot_count(), 1);
189        assert!(visualizer.get_plot_config("test_plot").is_some());
190
191        // Update plot
192        let mut updated_config = config.clone();
193        updated_config.title = "Updated Title".to_string();
194        visualizer.set_plot_config(plot_id.clone(), updated_config);
195        assert_eq!(visualizer.plot_count(), 1);
196        assert_eq!(
197            visualizer
198                .get_plot_config("test_plot")
199                .expect("operation should succeed")
200                .title,
201            "Updated Title"
202        );
203
204        // Remove plot
205        let removed = visualizer.remove_plot("test_plot");
206        assert!(removed.is_some());
207        assert_eq!(visualizer.plot_count(), 0);
208    }
209
210    #[test]
211    fn test_plot_data_update() {
212        let mut visualizer = InteractiveVisualizer::new();
213        let data = array![[1.0, 2.0], [3.0, 4.0]];
214
215        // Should not trigger callbacks when real-time is disabled
216        visualizer.update_plot_data("test", &data);
217
218        // Enable real-time updates
219        visualizer.enable_real_time_updates();
220
221        // Add a callback that sets a flag
222        use std::sync::atomic::{AtomicBool, Ordering};
223        use std::sync::Arc;
224
225        let callback_triggered = Arc::new(AtomicBool::new(false));
226        let callback_triggered_clone = callback_triggered.clone();
227
228        visualizer.add_update_callback(move |_id, _data| {
229            callback_triggered_clone.store(true, Ordering::Relaxed);
230        });
231
232        // Update should trigger callback
233        visualizer.update_plot_data("test", &data);
234        assert!(callback_triggered.load(Ordering::Relaxed));
235    }
236
237    #[test]
238    fn test_default_implementation() {
239        let visualizer = InteractiveVisualizer::default();
240        assert!(!visualizer.real_time_updates);
241        assert_eq!(visualizer.plot_count(), 0);
242    }
243}