Skip to main content

scirs2_cluster/native_plotting/
mod.rs

1//! Native Plotting for Advanced Clustering - Advanced Visualization Engine
2//!
3//! This module provides comprehensive native plotting capabilities for Advanced clustering,
4//! including interactive dendrogram visualization, 3D cluster plots, real-time animation,
5//! and advanced quantum state visualizations without external dependencies.
6
7mod plotter;
8mod svg;
9mod types;
10
11// Re-export everything publicly
12pub use plotter::AdvancedNativePlotter;
13pub use types::{
14    AnimationEngine, AnimationFrame, Camera3D, DendrogramNode, DendrogramTree, DirectionalLight,
15    ExecutionSummary, ExportQuality, InteractiveController, InteractiveFeature,
16    InteractivePerformanceDashboard, Lighting3D, MetricTimelinePoint, Native3DClusterPlot,
17    NativeClusterPlot, NativeDendrogramPlot, NativePlotConfig, NativeVisualizationOutput,
18    NeuromorphicActivityPlot, PlotColorScheme, PointLight, QuantumCoherenceAnimation,
19    QuantumCoherenceFrame, QuantumField3D, SvgCanvas, SvgElement, Transformation,
20};
21
22use crate::advanced_clustering::AdvancedClusteringResult;
23use crate::error::{ClusteringError, Result};
24use scirs2_core::ndarray::ArrayView2;
25
26/// Convenience function to create native Advanced visualization
27#[allow(dead_code)]
28pub fn create_native_advanced_plot(
29    data: &ArrayView2<f64>,
30    result: &AdvancedClusteringResult,
31    config: Option<NativePlotConfig>,
32) -> Result<NativeVisualizationOutput> {
33    let config = config.unwrap_or_default();
34    let mut plotter = AdvancedNativePlotter::new(config);
35    plotter.create_comprehensive_plot(data, result)
36}
37
38/// Export native visualization to file
39#[allow(dead_code)]
40pub fn export_native_visualization(
41    output: &NativeVisualizationOutput,
42    filename: &str,
43    format: &str,
44) -> Result<()> {
45    match format.to_lowercase().as_str() {
46        "svg" => {
47            use std::fs::File;
48            use std::io::Write;
49
50            let mut file = File::create(format!("{}.svg", filename)).map_err(|e| {
51                ClusteringError::InvalidInput(format!("Failed to create SVG file: {}", e))
52            })?;
53
54            file.write_all(output.svg_content.as_bytes()).map_err(|e| {
55                ClusteringError::InvalidInput(format!("Failed to write SVG file: {}", e))
56            })?;
57
58            println!("Exported native Advanced visualization to {filename}.svg");
59        }
60        "html" => {
61            use std::fs::File;
62            use std::io::Write;
63
64            let html_content = format!(
65                r#"<!DOCTYPE html>
66<html>
67<head>
68    <title>Advanced Native Visualization</title>
69    <style>
70        body {{ margin: 0; padding: 20px; background: #1a1a2e; }}
71        .selected {{ stroke: #FFD700 !important; stroke-width: 3px !important; }}
72    </style>
73</head>
74<body>
75    {}
76    <script>{}</script>
77</body>
78</html>"#,
79                output.svg_content, output.interactive_script
80            );
81
82            let mut file = File::create(format!("{}.html", filename)).map_err(|e| {
83                ClusteringError::InvalidInput(format!("Failed to create HTML file: {}", e))
84            })?;
85
86            file.write_all(html_content.as_bytes()).map_err(|e| {
87                ClusteringError::InvalidInput(format!("Failed to write HTML file: {}", e))
88            })?;
89
90            println!("Exported interactive Advanced visualization to {filename}.html");
91        }
92        _ => {
93            return Err(ClusteringError::InvalidInput(format!(
94                "Unsupported export format: {}",
95                format
96            )));
97        }
98    }
99
100    Ok(())
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::advanced_clustering::{AdvancedClusteringResult, AdvancedPerformanceMetrics};
107    use scirs2_core::ndarray::{array, Array1, Array2};
108
109    /// Build a minimal AdvancedClusteringResult from small 4-point 2-D data.
110    /// Two clusters: points 0,1 in cluster 0; points 2,3 in cluster 1.
111    fn make_result() -> AdvancedClusteringResult {
112        let centroids = array![[0.5, 0.5], [5.5, 5.5]];
113        let clusters = Array1::from_vec(vec![0, 0, 1, 1]);
114        AdvancedClusteringResult {
115            clusters,
116            centroids,
117            ai_speedup: 1.0,
118            quantum_advantage: 1.0,
119            neuromorphic_benefit: 1.0,
120            meta_learning_improvement: 0.0,
121            selected_algorithm: "test".to_string(),
122            confidence: 0.9,
123            performance: AdvancedPerformanceMetrics {
124                silhouette_score: 0.8,
125                execution_time: 0.001,
126                memory_usage: 1.0,
127                quantum_coherence: 0.7,
128                neural_adaptation_rate: 0.5,
129                ai_iterations: 5,
130                energy_efficiency: 0.9,
131            },
132        }
133    }
134
135    /// Small 4-point 2-D data matching make_result().
136    fn make_data() -> Array2<f64> {
137        array![[0.0, 0.0], [1.0, 1.0], [5.0, 5.0], [6.0, 6.0]]
138    }
139
140    // -----------------------------------------------------------------------
141    // build_dendrogram_tree
142    // -----------------------------------------------------------------------
143    #[test]
144    fn test_build_dendrogram_tree_basic() {
145        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
146        let data = make_data();
147        let result = make_result();
148        let tree = plotter
149            .build_dendrogram_tree(&data.view(), &result)
150            .expect("build_dendrogram_tree failed");
151
152        assert_eq!(tree.leaf_count, 4, "leaf_count should equal n_samples");
153        assert!(
154            tree.root.height >= 0.0,
155            "root height must be non-negative, got {}",
156            tree.root.height
157        );
158    }
159
160    // -----------------------------------------------------------------------
161    // calculate_dendrogram_layout
162    // -----------------------------------------------------------------------
163    #[test]
164    fn test_calculate_dendrogram_layout_all_nodes_have_positions() {
165        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
166        let data = make_data();
167        let result = make_result();
168        let tree = plotter
169            .build_dendrogram_tree(&data.view(), &result)
170            .expect("build_dendrogram_tree failed");
171        let layout = plotter
172            .calculate_dendrogram_layout(&tree)
173            .expect("calculate_dendrogram_layout failed");
174
175        // We should have at least one entry (the root).
176        assert!(
177            !layout.is_empty(),
178            "layout must contain at least one position"
179        );
180        for (id, (x, y)) in &layout {
181            assert!(
182                x.is_finite() && y.is_finite(),
183                "position for node '{id}' is not finite: ({x}, {y})"
184            );
185        }
186    }
187
188    // -----------------------------------------------------------------------
189    // calculate_quantum_branch_lengths
190    // -----------------------------------------------------------------------
191    #[test]
192    fn test_calculate_quantum_branch_lengths_all_nodes() {
193        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
194        let data = make_data();
195        let result = make_result();
196        let tree = plotter
197            .build_dendrogram_tree(&data.view(), &result)
198            .expect("build_dendrogram_tree failed");
199        let lengths = plotter
200            .calculate_quantum_branch_lengths(&tree, &result)
201            .expect("calculate_quantum_branch_lengths failed");
202
203        assert!(!lengths.is_empty(), "branch lengths map must not be empty");
204        for (id, length) in &lengths {
205            assert!(
206                length.is_finite(),
207                "branch length for '{id}' is not finite: {length}"
208            );
209        }
210    }
211
212    // -----------------------------------------------------------------------
213    // calculate_dendrogram_quantum_enhancements
214    // -----------------------------------------------------------------------
215    #[test]
216    fn test_calculate_dendrogram_quantum_enhancements_in_range() {
217        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
218        let data = make_data();
219        let result = make_result();
220        let tree = plotter
221            .build_dendrogram_tree(&data.view(), &result)
222            .expect("build_dendrogram_tree failed");
223        let enhancements = plotter
224            .calculate_dendrogram_quantum_enhancements(&tree, &result)
225            .expect("calculate_dendrogram_quantum_enhancements failed");
226
227        assert!(
228            !enhancements.is_empty(),
229            "enhancements map must not be empty"
230        );
231        for (id, enh) in &enhancements {
232            assert!(
233                *enh >= 0.0 && *enh <= 1.0,
234                "enhancement for '{id}' is out of [0, 1]: {enh}"
235            );
236        }
237    }
238
239    // -----------------------------------------------------------------------
240    // render_dendrogram_to_svg
241    // -----------------------------------------------------------------------
242    #[test]
243    fn test_render_dendrogram_to_svg_adds_elements() {
244        let mut plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
245        let data = make_data();
246        let result = make_result();
247        let tree = plotter
248            .build_dendrogram_tree(&data.view(), &result)
249            .expect("build_dendrogram_tree failed");
250        let positions = plotter
251            .calculate_dendrogram_layout(&tree)
252            .expect("calculate_dendrogram_layout failed");
253        let branch_lengths = plotter
254            .calculate_quantum_branch_lengths(&tree, &result)
255            .expect("calculate_quantum_branch_lengths failed");
256        let enhancements = plotter
257            .calculate_dendrogram_quantum_enhancements(&tree, &result)
258            .expect("calculate_dendrogram_quantum_enhancements failed");
259
260        plotter
261            .render_dendrogram_to_svg(&tree, &positions, &branch_lengths, &enhancements)
262            .expect("render_dendrogram_to_svg failed");
263
264        // The SVG canvas should now contain content.
265        let svg = plotter.svg_canvas.to_svg();
266        assert!(
267            !svg.is_empty(),
268            "SVG canvas must be non-empty after rendering"
269        );
270    }
271
272    // -----------------------------------------------------------------------
273    // create_quantum_field_3d
274    // -----------------------------------------------------------------------
275    #[test]
276    fn test_create_quantum_field_3d_grid_dimensions() {
277        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
278        let data = make_data();
279        let result = make_result();
280        // Need 3-D data for create_quantum_field_3d.
281        let data_3d: Array2<f64> = array![
282            [0.0, 0.0, 0.0],
283            [1.0, 1.0, 1.0],
284            [5.0, 5.0, 5.0],
285            [6.0, 6.0, 6.0]
286        ];
287        let field = plotter
288            .create_quantum_field_3d(&data_3d, &result)
289            .expect("create_quantum_field_3d failed");
290
291        // Method uses a 10×10 grid.
292        let shape = field.field_strength.shape();
293        assert_eq!(shape[0], 10, "field grid rows should be 10");
294        assert_eq!(shape[1], 10, "field grid cols should be 10");
295        // All values should be finite and >= 0.
296        for v in field.field_strength.iter() {
297            assert!(
298                v.is_finite() && *v >= 0.0,
299                "field value should be finite and >= 0, got {v}"
300            );
301        }
302        // Unused variable suppression — we built result but only use data_3d.
303        let _ = data;
304    }
305
306    // -----------------------------------------------------------------------
307    // create_quantum_coherence_frame
308    // -----------------------------------------------------------------------
309    #[test]
310    fn test_create_quantum_coherence_frame_shape_and_timestamp() {
311        let plotter = AdvancedNativePlotter::new(NativePlotConfig::default());
312        let result = make_result();
313        let frame = plotter
314            .create_quantum_coherence_frame(&result, 0.0)
315            .expect("create_quantum_coherence_frame failed");
316
317        assert_eq!(
318            frame.timestamp, 0.0,
319            "timestamp should match argument t=0.0"
320        );
321        let shape = frame.field_strength.shape();
322        assert_eq!(shape[0], 8, "field_strength rows should be 8");
323        assert_eq!(shape[1], 8, "field_strength cols should be 8");
324        // The result has 2 centroids, so there should be 2 SVG elements.
325        assert_eq!(
326            frame.elements.len(),
327            2,
328            "should have one element per centroid"
329        );
330    }
331
332    // -----------------------------------------------------------------------
333    // create_comprehensive_plot (exercises multiple paths via create_native_advanced_plot)
334    // -----------------------------------------------------------------------
335    #[test]
336    fn test_create_comprehensive_plot_runs_ok() {
337        let data = make_data();
338        let result = make_result();
339        let output = create_native_advanced_plot(&data.view(), &result, None)
340            .expect("create_native_advanced_plot failed");
341
342        assert!(
343            !output.svg_content.is_empty(),
344            "svg_content must be non-empty"
345        );
346        // For 2-D data, plot_3d should be None (ncols <= 2).
347        assert!(
348            output.plot_3d.is_none(),
349            "plot_3d should be None for 2-D data"
350        );
351    }
352
353    // -----------------------------------------------------------------------
354    // create_comprehensive_plot with hierarchical algorithm triggers dendrogram path
355    // -----------------------------------------------------------------------
356    #[test]
357    fn test_comprehensive_plot_hierarchical_triggers_dendrogram() {
358        let data = make_data();
359        let mut result = make_result();
360        result.selected_algorithm = "hierarchical".to_string();
361
362        let output = create_native_advanced_plot(&data.view(), &result, None)
363            .expect("create_native_advanced_plot (hierarchical) failed");
364
365        assert!(
366            output.dendrogram.is_some(),
367            "dendrogram should be Some when selected_algorithm contains 'hierarchical'"
368        );
369    }
370}