Skip to main content

sklears_inspection/visualization/
interactive.rs

1//! Interactive visualization features
2//!
3//! This module provides real-time plot updates, interactive visualizations,
4//! and user interaction handling capabilities.
5
6use crate::{Float, SklResult};
7// ✅ SciRS2 Policy Compliant Import
8use scirs2_core::ndarray::{Array2, ArrayView2};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12use super::core::PlotConfig;
13
14/// Type alias for interactive update callback
15type UpdateCallback = Option<Box<dyn Fn(&ArrayView2<Float>) -> SklResult<()> + Send + Sync>>;
16
17/// Interactive visualizer with real-time capabilities
18pub struct InteractiveVisualizer {
19    /// Plot configuration
20    config: PlotConfig,
21    /// Update callback function
22    update_callback: UpdateCallback,
23}
24
25impl std::fmt::Debug for InteractiveVisualizer {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_struct("InteractiveVisualizer")
28            .field("config", &self.config)
29            .field("update_callback", &self.update_callback.is_some())
30            .finish()
31    }
32}
33
34impl InteractiveVisualizer {
35    /// Create new interactive visualizer
36    pub fn new(config: PlotConfig) -> Self {
37        Self {
38            config,
39            update_callback: None,
40        }
41    }
42
43    /// Set update callback function
44    pub fn with_update_callback<F>(mut self, callback: F) -> Self
45    where
46        F: Fn(&ArrayView2<Float>) -> SklResult<()> + Send + Sync + 'static,
47    {
48        self.update_callback = Some(Box::new(callback));
49        self
50    }
51
52    /// Update visualization with new data
53    pub fn update_data(&self, data: &ArrayView2<Float>) -> SklResult<()> {
54        if let Some(ref callback) = self.update_callback {
55            callback(data)?;
56        }
57        Ok(())
58    }
59
60    /// Enable real-time updates
61    pub fn enable_real_time(&mut self, enabled: bool) {
62        self.config.interactive = enabled;
63    }
64
65    /// Get current configuration
66    pub fn config(&self) -> &PlotConfig {
67        &self.config
68    }
69}
70
71/// Real-time plot updater
72pub struct RealTimePlotUpdater {
73    /// Current data buffer
74    data_buffer: HashMap<String, Array2<Float>>,
75    /// Update frequency in Hz
76    update_frequency: Float,
77    /// Maximum buffer size
78    max_buffer_size: usize,
79    /// Is actively updating
80    is_active: bool,
81    /// Plot configuration
82    #[allow(dead_code)] // stored for plot rendering configuration
83    config: PlotConfig,
84}
85
86impl RealTimePlotUpdater {
87    /// Create new real-time updater
88    pub fn new(config: PlotConfig) -> Self {
89        Self {
90            data_buffer: HashMap::new(),
91            update_frequency: 30.0, // 30 Hz default
92            max_buffer_size: 1000,
93            is_active: false,
94            config,
95        }
96    }
97
98    /// Set update frequency in Hz
99    pub fn with_frequency(mut self, frequency: Float) -> Self {
100        self.update_frequency = frequency;
101        self
102    }
103
104    /// Set maximum buffer size
105    pub fn with_buffer_size(mut self, size: usize) -> Self {
106        self.max_buffer_size = size;
107        self
108    }
109
110    /// Add data to buffer
111    pub fn add_data(&mut self, series_name: String, data: Array2<Float>) -> SklResult<()> {
112        if data.nrows() > self.max_buffer_size {
113            return Err(crate::SklearsError::InvalidInput(
114                "Data size exceeds maximum buffer size".to_string(),
115            ));
116        }
117
118        self.data_buffer.insert(series_name, data);
119        Ok(())
120    }
121
122    /// Start real-time updates
123    pub fn start(&mut self) {
124        self.is_active = true;
125    }
126
127    /// Stop real-time updates
128    pub fn stop(&mut self) {
129        self.is_active = false;
130    }
131
132    /// Check if actively updating
133    pub fn is_active(&self) -> bool {
134        self.is_active
135    }
136
137    /// Get current buffer data
138    pub fn get_buffer_data(&self) -> &HashMap<String, Array2<Float>> {
139        &self.data_buffer
140    }
141
142    /// Clear buffer
143    pub fn clear_buffer(&mut self) {
144        self.data_buffer.clear();
145    }
146
147    /// Get update frequency
148    pub fn update_frequency(&self) -> Float {
149        self.update_frequency
150    }
151}
152
153/// Plot interaction handler for user interactions
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct PlotInteractionHandler {
156    /// Zoom enabled
157    pub zoom_enabled: bool,
158    /// Pan enabled
159    pub pan_enabled: bool,
160    /// Selection enabled
161    pub selection_enabled: bool,
162    /// Hover tooltips enabled
163    pub hover_enabled: bool,
164    /// Click callbacks enabled
165    pub click_enabled: bool,
166    /// Brush selection enabled
167    pub brush_enabled: bool,
168}
169
170impl Default for PlotInteractionHandler {
171    fn default() -> Self {
172        Self {
173            zoom_enabled: true,
174            pan_enabled: true,
175            selection_enabled: true,
176            hover_enabled: true,
177            click_enabled: true,
178            brush_enabled: false,
179        }
180    }
181}
182
183impl PlotInteractionHandler {
184    /// Create new interaction handler
185    pub fn new() -> Self {
186        Self::default()
187    }
188
189    /// Enable/disable zoom functionality
190    pub fn with_zoom(mut self, enabled: bool) -> Self {
191        self.zoom_enabled = enabled;
192        self
193    }
194
195    /// Enable/disable pan functionality
196    pub fn with_pan(mut self, enabled: bool) -> Self {
197        self.pan_enabled = enabled;
198        self
199    }
200
201    /// Enable/disable selection
202    pub fn with_selection(mut self, enabled: bool) -> Self {
203        self.selection_enabled = enabled;
204        self
205    }
206
207    /// Enable/disable hover tooltips
208    pub fn with_hover(mut self, enabled: bool) -> Self {
209        self.hover_enabled = enabled;
210        self
211    }
212
213    /// Enable/disable click callbacks
214    pub fn with_click(mut self, enabled: bool) -> Self {
215        self.click_enabled = enabled;
216        self
217    }
218
219    /// Enable/disable brush selection
220    pub fn with_brush(mut self, enabled: bool) -> Self {
221        self.brush_enabled = enabled;
222        self
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    // ✅ SciRS2 Policy Compliant Import
230    use scirs2_core::ndarray::array;
231
232    #[test]
233    fn test_interactive_visualizer_creation() {
234        let config = PlotConfig::default();
235        let visualizer = InteractiveVisualizer::new(config);
236
237        assert!(visualizer.update_callback.is_none());
238        assert!(visualizer.config().interactive);
239    }
240
241    #[test]
242    fn test_real_time_plot_updater() {
243        let config = PlotConfig::default();
244        let mut updater = RealTimePlotUpdater::new(config);
245
246        assert!(!updater.is_active());
247        assert_eq!(updater.update_frequency(), 30.0);
248
249        updater.start();
250        assert!(updater.is_active());
251
252        updater.stop();
253        assert!(!updater.is_active());
254    }
255
256    #[test]
257    fn test_plot_interaction_handler() {
258        let handler = PlotInteractionHandler::new()
259            .with_zoom(false)
260            .with_pan(true)
261            .with_selection(false);
262
263        assert!(!handler.zoom_enabled);
264        assert!(handler.pan_enabled);
265        assert!(!handler.selection_enabled);
266        assert!(handler.hover_enabled); // Default remains true
267    }
268
269    #[test]
270    fn test_real_time_updater_data_buffer() {
271        let config = PlotConfig::default();
272        let mut updater = RealTimePlotUpdater::new(config);
273
274        let data = array![[1.0, 2.0], [3.0, 4.0]];
275        updater
276            .add_data("test_series".to_string(), data)
277            .expect("operation should succeed");
278
279        assert_eq!(updater.get_buffer_data().len(), 1);
280        assert!(updater.get_buffer_data().contains_key("test_series"));
281
282        updater.clear_buffer();
283        assert_eq!(updater.get_buffer_data().len(), 0);
284    }
285}