Skip to main content

scirs2_cluster/native_plotting/
plotter.rs

1//! AdvancedNativePlotter — the main plotting engine and all its methods.
2
3use super::types::{
4    AnimationEngine, Camera3D, DendrogramNode, DendrogramTree, DirectionalLight, ExecutionSummary,
5    InteractiveController, InteractiveFeature, InteractivePerformanceDashboard, Lighting3D,
6    MetricTimelinePoint, Native3DClusterPlot, NativeClusterPlot, NativeDendrogramPlot,
7    NativePlotConfig, NativeVisualizationOutput, NeuromorphicActivityPlot, PlotColorScheme,
8    PointLight, QuantumCoherenceAnimation, QuantumCoherenceFrame, QuantumField3D, SvgCanvas,
9    SvgElement,
10};
11use crate::advanced_clustering::{AdvancedClusteringResult, AdvancedPerformanceMetrics};
12use crate::error::{ClusteringError, Result};
13use scirs2_core::ndarray::{Array1, Array2, ArrayView2, Axis};
14use std::collections::HashMap;
15use std::f64::consts::PI;
16
17/// Native plotting engine for Advanced clustering
18#[derive(Debug)]
19pub struct AdvancedNativePlotter {
20    /// Plot configuration
21    pub(crate) config: NativePlotConfig,
22    /// SVG canvas for rendering
23    pub(crate) svg_canvas: SvgCanvas,
24    /// Animation engine
25    pub(crate) animation_engine: AnimationEngine,
26    /// Interactive controller
27    pub(crate) interactive_controller: InteractiveController,
28}
29
30impl AdvancedNativePlotter {
31    /// Create a new native plotter
32    pub fn new(config: NativePlotConfig) -> Self {
33        Self {
34            svg_canvas: SvgCanvas::new(config.width, config.height),
35            animation_engine: AnimationEngine::new(config.animation_fps),
36            interactive_controller: InteractiveController::new(),
37            config,
38        }
39    }
40
41    /// Create comprehensive native visualization
42    pub fn create_comprehensive_plot(
43        &mut self,
44        data: &ArrayView2<f64>,
45        result: &AdvancedClusteringResult,
46    ) -> Result<NativeVisualizationOutput> {
47        // Clear canvas
48        self.svg_canvas.clear();
49
50        // Create main cluster plot
51        let cluster_plot = self.create_native_cluster_plot(data, result)?;
52
53        // Create dendrogram if hierarchical clustering was used
54        let dendrogram = if result.selected_algorithm.contains("hierarchical") {
55            Some(self.create_native_dendrogram(data, result)?)
56        } else {
57            None
58        };
59
60        // Create 3D visualization for high-dimensional data
61        let plot_3d = if data.ncols() > 2 {
62            Some(self.create_native_3d_plot(data, result)?)
63        } else {
64            None
65        };
66
67        // Create quantum coherence animation
68        let quantum_animation = if self.config.enable_animations {
69            Some(self.create_quantum_coherence_animation(result)?)
70        } else {
71            None
72        };
73
74        // Create neuromorphic activity visualization
75        let neuromorphic_plot = self.create_neuromorphic_activity_plot(result)?;
76
77        // Create interactive performance dashboard
78        let performance_dashboard = self.create_interactive_performance_dashboard(result)?;
79
80        Ok(NativeVisualizationOutput {
81            cluster_plot,
82            dendrogram,
83            plot_3d,
84            quantum_animation,
85            neuromorphic_plot,
86            performance_dashboard,
87            svg_content: self.svg_canvas.to_svg(),
88            interactive_script: self.generate_interactive_script(),
89        })
90    }
91
92    /// Create native cluster plot with quantum enhancement
93    fn create_native_cluster_plot(
94        &mut self,
95        data: &ArrayView2<f64>,
96        result: &AdvancedClusteringResult,
97    ) -> Result<NativeClusterPlot> {
98        let n_samples = data.nrows();
99        let n_features = data.ncols();
100
101        // Apply dimensionality reduction if needed
102        let plot_data = if n_features > 2 {
103            self.apply_native_pca(data, 2)?
104        } else {
105            data.to_owned()
106        };
107
108        // Calculate plot bounds
109        let (x_min, x_max, y_min, y_max) = self.calculate_plot_bounds(&plot_data);
110
111        // Create coordinate transformation
112        let margin = 50.0;
113        let plot_width = self.config.width as f64 - 2.0 * margin;
114        let plot_height = self.config.height as f64 - 2.0 * margin;
115
116        let x_scale = plot_width / (x_max - x_min);
117        let y_scale = plot_height / (y_max - y_min);
118
119        // Plot data points with quantum enhancement
120        let mut point_elements = Vec::new();
121        let mut quantum_enhancements = Vec::new();
122
123        for i in 0..n_samples {
124            let x = margin + (plot_data[[i, 0]] - x_min) * x_scale;
125            let y = margin + (plot_data[[i, 1]] - y_min) * y_scale;
126            let cluster_id = result.clusters[i];
127
128            // Calculate quantum enhancement for this point
129            let quantum_factor = self.calculate_point_quantum_enhancement(i, cluster_id, result);
130            quantum_enhancements.push(quantum_factor);
131
132            // Determine point color and size based on quantum properties
133            let base_color = self.get_cluster_color(cluster_id);
134            let enhanced_color = self.apply_quantum_color_enhancement(base_color, quantum_factor);
135            let point_radius = 3.0 + quantum_factor * 2.0; // Quantum-enhanced size
136
137            let circle = SvgElement::Circle {
138                cx: x,
139                cy: y,
140                r: point_radius,
141                fill: enhanced_color.clone(),
142                stroke: "#000000".to_string(),
143                stroke_width: 0.5,
144                opacity: 0.8 + quantum_factor * 0.2,
145            };
146
147            point_elements.push(circle);
148        }
149
150        // Plot centroids with special quantum aura
151        let mut centroid_elements = Vec::new();
152        for (cluster_id, centroid) in result.centroids.outer_iter().enumerate() {
153            if centroid.len() >= 2 {
154                let x = margin + (centroid[0] - x_min) * x_scale;
155                let y = margin + (centroid[1] - y_min) * y_scale;
156
157                // Create quantum aura around centroid
158                let aura_radius = 15.0;
159                let aura = SvgElement::Circle {
160                    cx: x,
161                    cy: y,
162                    r: aura_radius,
163                    fill: "none".to_string(),
164                    stroke: self.get_cluster_color(cluster_id),
165                    stroke_width: 2.0,
166                    opacity: 0.3,
167                };
168
169                let centroid_circle = SvgElement::Circle {
170                    cx: x,
171                    cy: y,
172                    r: 6.0,
173                    fill: self.get_cluster_color(cluster_id),
174                    stroke: "#FFFFFF".to_string(),
175                    stroke_width: 2.0,
176                    opacity: 1.0,
177                };
178
179                centroid_elements.push(aura);
180                centroid_elements.push(centroid_circle);
181            }
182        }
183
184        // Add all elements to canvas
185        for element in &point_elements {
186            self.svg_canvas.add_element(element.clone());
187        }
188        for element in &centroid_elements {
189            self.svg_canvas.add_element(element.clone());
190        }
191
192        // Add axes and labels
193        self.add_plot_axes_and_labels(x_min, x_max, y_min, y_max, margin)?;
194
195        Ok(NativeClusterPlot {
196            data: plot_data,
197            point_elements,
198            centroid_elements,
199            quantum_enhancements,
200            bounds: (x_min, x_max, y_min, y_max),
201            scale: (x_scale, y_scale),
202        })
203    }
204
205    /// Create native dendrogram visualization
206    fn create_native_dendrogram(
207        &mut self,
208        data: &ArrayView2<f64>,
209        result: &AdvancedClusteringResult,
210    ) -> Result<NativeDendrogramPlot> {
211        // Create hierarchical tree structure
212        let tree = self.build_dendrogram_tree(data, result)?;
213
214        // Calculate node positions using optimal layout
215        let node_positions = self.calculate_dendrogram_layout(&tree)?;
216
217        // Calculate branch lengths based on quantum distances
218        let branch_lengths = self.calculate_quantum_branch_lengths(&tree, result)?;
219
220        // Add quantum enhancement data
221        let quantum_enhancements = self.calculate_dendrogram_quantum_enhancements(&tree, result)?;
222
223        // Create interactive features
224        let interactive_features = vec![
225            InteractiveFeature::ZoomPan,
226            InteractiveFeature::NodeSelection,
227            InteractiveFeature::Tooltip,
228            InteractiveFeature::RealTimeFilter,
229        ];
230
231        // Render dendrogram to SVG
232        self.render_dendrogram_to_svg(
233            &tree,
234            &node_positions,
235            &branch_lengths,
236            &quantum_enhancements,
237        )?;
238
239        Ok(NativeDendrogramPlot {
240            tree,
241            node_positions,
242            branch_lengths,
243            quantum_enhancements,
244            interactive_features,
245        })
246    }
247
248    /// Create native 3D cluster plot
249    fn create_native_3d_plot(
250        &mut self,
251        data: &ArrayView2<f64>,
252        result: &AdvancedClusteringResult,
253    ) -> Result<Native3DClusterPlot> {
254        // Reduce to 3D if needed
255        let points_3d = if data.ncols() > 3 {
256            self.apply_native_pca(data, 3)?
257        } else if data.ncols() == 2 {
258            // Add a third dimension with quantum enhancement
259            let mut points_3d = Array2::zeros((data.nrows(), 3));
260            points_3d
261                .slice_mut(scirs2_core::ndarray::s![.., 0..2])
262                .assign(data);
263
264            // Calculate third dimension based on quantum properties
265            for i in 0..data.nrows() {
266                let cluster_id = result.clusters[i];
267                let quantum_factor =
268                    self.calculate_point_quantum_enhancement(i, cluster_id, result);
269                points_3d[[i, 2]] = quantum_factor * 5.0; // Scale for visibility
270            }
271            points_3d
272        } else {
273            data.to_owned()
274        };
275
276        // Generate point colors
277        let mut point_colors = Vec::new();
278        for i in 0..points_3d.nrows() {
279            let cluster_id = result.clusters[i];
280            let base_color = self.get_cluster_color_rgb(cluster_id);
281            let quantum_factor = self.calculate_point_quantum_enhancement(i, cluster_id, result);
282            let enhanced_color =
283                self.apply_quantum_color_enhancement_rgb(base_color, quantum_factor);
284            point_colors.push(enhanced_color);
285        }
286
287        // Calculate 3D centroids
288        let centroids_3d = if result.centroids.ncols() >= 3 {
289            result
290                .centroids
291                .slice(scirs2_core::ndarray::s![.., 0..3])
292                .to_owned()
293        } else {
294            let mut centroids_3d = Array2::zeros((result.centroids.nrows(), 3));
295            centroids_3d
296                .slice_mut(scirs2_core::ndarray::s![.., 0..result.centroids.ncols()])
297                .assign(&result.centroids);
298            centroids_3d
299        };
300
301        // Setup camera
302        let camera = Camera3D {
303            position: [10.0, 10.0, 10.0],
304            target: [0.0, 0.0, 0.0],
305            up: [0.0, 1.0, 0.0],
306            fov: 45.0,
307            near: 0.1,
308            far: 100.0,
309        };
310
311        // Setup lighting
312        let lighting = Lighting3D {
313            ambient: 0.3,
314            directional_lights: vec![DirectionalLight {
315                direction: [-1.0, -1.0, -1.0],
316                intensity: 0.7,
317                color: [1.0, 1.0, 1.0],
318            }],
319            point_lights: vec![PointLight {
320                position: [5.0, 5.0, 5.0],
321                intensity: 0.5,
322                color: [0.0, 1.0, 1.0], // Quantum cyan
323                attenuation: 0.1,
324            }],
325        };
326
327        // Create quantum field visualization
328        let quantum_field = self.create_quantum_field_3d(&points_3d, result)?;
329
330        Ok(Native3DClusterPlot {
331            points_3d,
332            point_colors,
333            centroids_3d,
334            camera,
335            lighting,
336            quantum_field,
337        })
338    }
339
340    /// Create quantum coherence animation
341    fn create_quantum_coherence_animation(
342        &mut self,
343        result: &AdvancedClusteringResult,
344    ) -> Result<QuantumCoherenceAnimation> {
345        let num_frames = (self.config.animation_fps * 5.0) as usize; // 5 second animation
346        let mut frames = Vec::new();
347
348        for frame_idx in 0..num_frames {
349            let time = frame_idx as f64 / self.config.animation_fps;
350
351            // Create quantum coherence visualization for this frame
352            let coherence_frame = self.create_quantum_coherence_frame(result, time)?;
353
354            frames.push(coherence_frame);
355        }
356
357        Ok(QuantumCoherenceAnimation {
358            frames,
359            duration: 5.0,
360            fps: self.config.animation_fps,
361        })
362    }
363
364    /// Create neuromorphic activity plot
365    fn create_neuromorphic_activity_plot(
366        &mut self,
367        result: &AdvancedClusteringResult,
368    ) -> Result<NeuromorphicActivityPlot> {
369        let n_neurons = result.centroids.nrows();
370        let time_steps = 100;
371
372        // Simulate neuromorphic activity based on clustering performance
373        let mut activity_matrix = Array2::zeros((time_steps, n_neurons));
374        let mut spike_trains = Array2::zeros((time_steps, n_neurons));
375
376        for t in 0..time_steps {
377            let time = t as f64 / time_steps as f64;
378
379            for neuron in 0..n_neurons {
380                // Base activity influenced by quantum coherence
381                let base_activity = result.performance.neural_adaptation_rate;
382                let quantum_modulation =
383                    result.performance.quantum_coherence * (2.0 * PI * time * 3.0).sin();
384                let noise = 0.1 * (time * 47.0 + neuron as f64 * 13.0).sin();
385
386                let activity = base_activity + 0.2 * quantum_modulation + noise;
387                activity_matrix[[t, neuron]] = activity.max(0.0).min(1.0);
388
389                // Generate spikes based on activity
390                let spike_threshold = 0.7;
391                let spike_prob = if activity > spike_threshold { 1.0 } else { 0.0 };
392                spike_trains[[t, neuron]] = spike_prob;
393            }
394        }
395
396        // Create plasticity visualization
397        let mut plasticity_changes = Array2::zeros((n_neurons, n_neurons));
398        for i in 0..n_neurons {
399            for j in 0..n_neurons {
400                if i != j {
401                    let distance = ((i as f64 - j as f64).abs() / n_neurons as f64).min(1.0);
402                    let plasticity = result.performance.neural_adaptation_rate * (1.0 - distance);
403                    plasticity_changes[[i, j]] = plasticity;
404                }
405            }
406        }
407
408        Ok(NeuromorphicActivityPlot {
409            activity_matrix,
410            spike_trains,
411            plasticity_changes,
412            time_resolution: 1.0 / time_steps as f64,
413        })
414    }
415
416    /// Create interactive performance dashboard
417    fn create_interactive_performance_dashboard(
418        &mut self,
419        result: &AdvancedClusteringResult,
420    ) -> Result<InteractivePerformanceDashboard> {
421        let metrics = &result.performance;
422
423        // Create performance metrics visualization
424        let mut performance_metrics = HashMap::new();
425        performance_metrics.insert("Silhouette Score".to_string(), metrics.silhouette_score);
426        performance_metrics.insert("Quantum Coherence".to_string(), metrics.quantum_coherence);
427        performance_metrics.insert(
428            "Neural Adaptation".to_string(),
429            metrics.neural_adaptation_rate,
430        );
431        performance_metrics.insert("Energy Efficiency".to_string(), metrics.energy_efficiency);
432
433        // Create improvement comparisons
434        let mut improvements = HashMap::new();
435        improvements.insert("AI Speedup".to_string(), result.ai_speedup);
436        improvements.insert("Quantum Advantage".to_string(), result.quantum_advantage);
437        improvements.insert(
438            "Neuromorphic Benefit".to_string(),
439            result.neuromorphic_benefit,
440        );
441        improvements.insert(
442            "Meta-learning Improvement".to_string(),
443            result.meta_learning_improvement,
444        );
445
446        // Create real-time metrics timeline
447        let mut metrics_timeline = Vec::new();
448        for i in 0..metrics.ai_iterations {
449            let progress = i as f64 / metrics.ai_iterations as f64;
450            let timestamp = progress * metrics.execution_time;
451
452            // Simulate metric evolution during optimization
453            let coherence = metrics.quantum_coherence * (1.0 - 0.3 * (-progress * 5.0).exp());
454            let adaptation = metrics.neural_adaptation_rate * (1.0 + 0.5 * progress);
455
456            metrics_timeline.push(MetricTimelinePoint {
457                timestamp,
458                quantum_coherence: coherence,
459                neural_adaptation: adaptation,
460                ai_confidence: result.confidence * (1.0 - (-progress * 3.0).exp()),
461            });
462        }
463
464        Ok(InteractivePerformanceDashboard {
465            performance_metrics,
466            improvements,
467            metrics_timeline,
468            execution_summary: ExecutionSummary {
469                total_time: metrics.execution_time,
470                memory_usage: metrics.memory_usage,
471                iterations: metrics.ai_iterations,
472                algorithm: result.selected_algorithm.clone(),
473                confidence: result.confidence,
474            },
475        })
476    }
477
478    // Helper methods for calculations and rendering
479
480    fn calculate_point_quantum_enhancement(
481        &self,
482        point_idx: usize,
483        cluster_id: usize,
484        result: &AdvancedClusteringResult,
485    ) -> f64 {
486        // Calculate quantum enhancement based on clustering properties
487        let base_quantum = result.quantum_advantage / 10.0;
488        let coherence_factor = result.performance.quantum_coherence;
489        let confidence_factor = result.confidence;
490
491        // Add point-specific quantum noise
492        let quantum_phase = 2.0 * PI * (point_idx as f64 + cluster_id as f64) / 100.0;
493        let phase_modulation = quantum_phase.cos() * 0.2;
494
495        (base_quantum + coherence_factor * 0.3 + confidence_factor * 0.2 + phase_modulation)
496            .max(0.0)
497            .min(1.0)
498    }
499
500    fn get_cluster_color(&self, cluster_id: usize) -> String {
501        match self.config.color_scheme {
502            PlotColorScheme::Quantum => {
503                let hue = (cluster_id as f64 * 137.5) % 360.0; // Golden angle
504                format!("hsl({}, 70%, 60%)", hue)
505            }
506            PlotColorScheme::Neuromorphic => {
507                let colors = ["#00FF00", "#FFD700", "#FF4500", "#FF1493", "#00CED1"];
508                colors[cluster_id % colors.len()].to_string()
509            }
510            PlotColorScheme::AI => {
511                let colors = ["#FFD700", "#FF8C00", "#FF4500", "#DC143C", "#B22222"];
512                colors[cluster_id % colors.len()].to_string()
513            }
514            PlotColorScheme::Scientific => {
515                let intensity = 128 + (cluster_id * 32) % 128;
516                format!("rgb({}, {}, {})", intensity, intensity, intensity)
517            }
518            PlotColorScheme::Custom(ref colors) => {
519                if colors.is_empty() {
520                    "#0088FF".to_string()
521                } else {
522                    let color = colors[cluster_id % colors.len()];
523                    format!("rgb({}, {}, {})", color[0], color[1], color[2])
524                }
525            }
526        }
527    }
528
529    fn get_cluster_color_rgb(&self, cluster_id: usize) -> [u8; 3] {
530        match self.config.color_scheme {
531            PlotColorScheme::Quantum => {
532                let hue = (cluster_id as f64 * 137.5) % 360.0;
533                self.hsl_to_rgb(hue, 0.7, 0.6)
534            }
535            PlotColorScheme::Neuromorphic => {
536                let colors = [
537                    [0, 255, 0],
538                    [255, 215, 0],
539                    [255, 69, 0],
540                    [255, 20, 147],
541                    [0, 206, 209],
542                ];
543                colors[cluster_id % colors.len()]
544            }
545            PlotColorScheme::AI => {
546                let colors = [
547                    [255, 215, 0],
548                    [255, 140, 0],
549                    [255, 69, 0],
550                    [220, 20, 60],
551                    [178, 34, 34],
552                ];
553                colors[cluster_id % colors.len()]
554            }
555            PlotColorScheme::Scientific => {
556                let intensity = 128 + (cluster_id * 32) % 128;
557                [intensity as u8, intensity as u8, intensity as u8]
558            }
559            PlotColorScheme::Custom(ref colors) => {
560                if colors.is_empty() {
561                    [0, 136, 255]
562                } else {
563                    colors[cluster_id % colors.len()]
564                }
565            }
566        }
567    }
568
569    fn apply_quantum_color_enhancement(&self, base_color: String, quantum_factor: f64) -> String {
570        // Apply quantum shimmer effect to color
571        if base_color.starts_with("hsl") {
572            // Extract hue, saturation, lightness
573            if let Some(hsl_part) = base_color
574                .strip_prefix("hsl(")
575                .and_then(|s| s.strip_suffix(")"))
576            {
577                let parts: Vec<&str> = hsl_part.split(", ").collect();
578                if parts.len() == 3 {
579                    if let (Ok(h), Ok(s), Ok(l)) = (
580                        parts[0].parse::<f64>(),
581                        parts[1].strip_suffix("%").unwrap_or("0").parse::<f64>(),
582                        parts[2].strip_suffix("%").unwrap_or("0").parse::<f64>(),
583                    ) {
584                        let enhanced_s = (s + quantum_factor * 20.0).min(100.0);
585                        let enhanced_l = (l + quantum_factor * 10.0).min(90.0);
586                        return format!("hsl({}, {}%, {}%)", h, enhanced_s, enhanced_l);
587                    }
588                }
589            }
590        }
591        base_color // Return original if parsing fails
592    }
593
594    fn apply_quantum_color_enhancement_rgb(
595        &self,
596        base_color: [u8; 3],
597        quantum_factor: f64,
598    ) -> [u8; 3] {
599        let enhancement = (quantum_factor * 50.0) as u8;
600        [
601            (base_color[0] as u16 + enhancement as u16).min(255) as u8,
602            base_color[1],
603            (base_color[2] as u16 + enhancement as u16).min(255) as u8,
604        ]
605    }
606
607    fn hsl_to_rgb(&self, h: f64, s: f64, l: f64) -> [u8; 3] {
608        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
609        let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
610        let m = l - c / 2.0;
611
612        let (r_prime, g_prime, b_prime) = match h as u32 {
613            0..=59 => (c, x, 0.0),
614            60..=119 => (x, c, 0.0),
615            120..=179 => (0.0, c, x),
616            180..=239 => (0.0, x, c),
617            240..=299 => (x, 0.0, c),
618            _ => (c, 0.0, x),
619        };
620
621        [
622            ((r_prime + m) * 255.0) as u8,
623            ((g_prime + m) * 255.0) as u8,
624            ((b_prime + m) * 255.0) as u8,
625        ]
626    }
627
628    fn calculate_plot_bounds(&self, data: &Array2<f64>) -> (f64, f64, f64, f64) {
629        let x_values = data.column(0);
630        let y_values = data.column(1);
631
632        let x_min = x_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
633        let x_max = x_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
634        let y_min = y_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
635        let y_max = y_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
636
637        // Add some padding
638        let x_padding = (x_max - x_min) * 0.1;
639        let y_padding = (y_max - y_min) * 0.1;
640
641        (
642            x_min - x_padding,
643            x_max + x_padding,
644            y_min - y_padding,
645            y_max + y_padding,
646        )
647    }
648
649    fn apply_native_pca(&self, data: &ArrayView2<f64>, target_dims: usize) -> Result<Array2<f64>> {
650        // Simplified PCA implementation for native plotting
651        let n_samples = data.nrows();
652        let n_features = data.ncols();
653
654        if target_dims >= n_features {
655            return Ok(data.to_owned());
656        }
657
658        // Center the data
659        let mean = data.mean_axis(Axis(0)).expect("Operation failed");
660        let centered = data - &mean.insert_axis(Axis(0));
661
662        // For simplicity, just take the first few dimensions with some processing
663        let mut reduced = Array2::zeros((n_samples, target_dims));
664
665        for i in 0..n_samples {
666            for j in 0..target_dims {
667                let mut component = 0.0;
668                for k in 0..n_features {
669                    let weight = (k as f64 * PI / n_features as f64
670                        + j as f64 * PI / target_dims as f64)
671                        .cos();
672                    component += centered[[i, k]] * weight;
673                }
674                reduced[[i, j]] = component / (n_features as f64).sqrt();
675            }
676        }
677
678        Ok(reduced)
679    }
680
681    fn add_plot_axes_and_labels(
682        &mut self,
683        x_min: f64,
684        x_max: f64,
685        y_min: f64,
686        ymax: f64,
687        margin: f64,
688    ) -> Result<()> {
689        let plot_width = self.config.width as f64 - 2.0 * margin;
690        let plot_height = self.config.height as f64 - 2.0 * margin;
691
692        // X-axis
693        let x_axis = SvgElement::Line {
694            x1: margin,
695            y1: margin + plot_height,
696            x2: margin + plot_width,
697            y2: margin + plot_height,
698            stroke: "#333333".to_string(),
699            stroke_width: 2.0,
700            opacity: 1.0,
701        };
702
703        // Y-axis
704        let y_axis = SvgElement::Line {
705            x1: margin,
706            y1: margin,
707            x2: margin,
708            y2: margin + plot_height,
709            stroke: "#333333".to_string(),
710            stroke_width: 2.0,
711            opacity: 1.0,
712        };
713
714        // Axis labels
715        let x_label = SvgElement::Text {
716            x: margin + plot_width / 2.0,
717            y: margin + plot_height + 30.0,
718            content: "Principal Component 1".to_string(),
719            font_size: 14.0,
720            fill: "#333333".to_string(),
721            text_anchor: "middle".to_string(),
722        };
723
724        let y_label = SvgElement::Text {
725            x: margin - 30.0,
726            y: margin + plot_height / 2.0,
727            content: "Principal Component 2".to_string(),
728            font_size: 14.0,
729            fill: "#333333".to_string(),
730            text_anchor: "middle".to_string(),
731        };
732
733        self.svg_canvas.add_element(x_axis);
734        self.svg_canvas.add_element(y_axis);
735        self.svg_canvas.add_element(x_label);
736        self.svg_canvas.add_element(y_label);
737
738        // Suppress unused variable warnings for bounds that are used for label generation
739        let _ = (x_min, x_max, y_min, ymax);
740
741        Ok(())
742    }
743
744    fn generate_interactive_script(&self) -> String {
745        // Generate JavaScript for interactivity
746        r#"
747        // Advanced Native Plotting Interactive Script
748        (function() {
749            let zoom = 1.0;
750            let panX = 0, panY = 0;
751            let selectedElements = [];
752
753            // Initialize interactive features
754            function initInteractivity() {
755                const svg = document.querySelector('svg');
756                if (!svg) return;
757
758                // Zoom and pan
759                svg.addEventListener('wheel', handleZoom);
760                svg.addEventListener('mousedown', handlePanStart);
761                svg.addEventListener('mousemove', handlePanMove);
762                svg.addEventListener('mouseup', handlePanEnd);
763
764                // Element selection
765                svg.addEventListener('click', handleElementClick);
766                svg.addEventListener('mouseover', handleElementHover);
767                svg.addEventListener('mouseout', handleElementOut);
768            }
769
770            function handleZoom(event) {
771                event.preventDefault();
772                const delta = event.deltaY > 0 ? 0.9 : 1.1;
773                zoom *= delta;
774                updateTransform();
775            }
776
777            function handleElementClick(event) {
778                const target = event.target;
779                if (target.tagName === 'circle' || target.tagName === 'path') {
780                    toggleSelection(target);
781                }
782            }
783
784            function toggleSelection(element) {
785                const index = selectedElements.indexOf(element);
786                if (index > -1) {
787                    selectedElements.splice(index, 1);
788                    element.classList.remove('selected');
789                } else {
790                    selectedElements.push(element);
791                    element.classList.add('selected');
792                }
793            }
794
795            function updateTransform() {
796                const svg = document.querySelector('svg');
797                const mainGroup = svg.querySelector('g.main-group');
798                if (mainGroup) {
799                    mainGroup.setAttribute('transform',
800                        `translate(${panX}, ${panY}) scale(${zoom})`);
801                }
802            }
803
804            // Initialize when DOM is ready
805            if (document.readyState === 'loading') {
806                document.addEventListener('DOMContentLoaded', initInteractivity);
807            } else {
808                initInteractivity();
809            }
810        })();
811        "#
812        .to_string()
813    }
814
815    /// Build a hierarchical dendrogram tree from cluster results.
816    ///
817    /// Constructs a binary tree by greedily merging the two nearest cluster
818    /// centroids at each step, mirroring single-linkage agglomeration.
819    pub(crate) fn build_dendrogram_tree(
820        &self,
821        data: &ArrayView2<f64>,
822        result: &AdvancedClusteringResult,
823    ) -> Result<DendrogramTree> {
824        let n_samples = data.nrows();
825        if n_samples == 0 {
826            return Err(ClusteringError::InvalidInput(
827                "Cannot build dendrogram from empty data".into(),
828            ));
829        }
830
831        // Create leaf nodes — one per data point.
832        let mut nodes: Vec<DendrogramNode> = (0..n_samples)
833            .map(|i| {
834                let cluster_id = if i < result.clusters.len() {
835                    result.clusters[i]
836                } else {
837                    0
838                };
839                let quantum_coherence = result.performance.quantum_coherence
840                    * ((i as f64 * PI / n_samples as f64).cos().abs());
841                DendrogramNode {
842                    id: format!("leaf_{i}"),
843                    height: 0.0,
844                    children: Vec::new(),
845                    data_indices: vec![i],
846                    quantum_coherence,
847                    neuromorphic_activity: result.performance.neural_adaptation_rate
848                        * (1.0 - (cluster_id as f64 / (result.centroids.nrows().max(1) as f64))),
849                }
850            })
851            .collect();
852
853        // Agglomerate: repeatedly merge the two nodes with the smallest
854        // centroid-distance until a single root remains.
855        let mut merge_height = 0.0_f64;
856        while nodes.len() > 1 {
857            let n = nodes.len();
858            // Find the pair (i, j) that minimises the L2 centroid distance.
859            // Centroid approximated as mean of represented data points.
860            let centroid = |node: &DendrogramNode| -> Array1<f64> {
861                let cols = data.ncols();
862                let mut sum: Array1<f64> = Array1::zeros(cols);
863                for &idx in &node.data_indices {
864                    if idx < data.nrows() {
865                        for c in 0..cols {
866                            sum[c] += data[[idx, c]];
867                        }
868                    }
869                }
870                let cnt = node.data_indices.len().max(1) as f64;
871                sum.mapv(|v: f64| v / cnt)
872            };
873
874            let mut best_dist = f64::INFINITY;
875            let mut best_i = 0;
876            let mut best_j = 1;
877            for i in 0..n {
878                let ci = centroid(&nodes[i]);
879                for j in (i + 1)..n {
880                    let cj = centroid(&nodes[j]);
881                    let dist: f64 = ci
882                        .iter()
883                        .zip(cj.iter())
884                        .map(|(a, b)| (a - b) * (a - b))
885                        .sum::<f64>()
886                        .sqrt();
887                    if dist < best_dist {
888                        best_dist = dist;
889                        best_i = i;
890                        best_j = j;
891                    }
892                }
893            }
894
895            merge_height += best_dist;
896            // Merge nodes[best_j] into nodes[best_i].
897            let node_j = nodes.remove(best_j);
898            let node_i = nodes.remove(best_i);
899            let mut merged_indices = node_i.data_indices.clone();
900            merged_indices.extend_from_slice(&node_j.data_indices);
901            let avg_coherence = (node_i.quantum_coherence + node_j.quantum_coherence) / 2.0;
902            let avg_neuro = (node_i.neuromorphic_activity + node_j.neuromorphic_activity) / 2.0;
903            let merged = DendrogramNode {
904                id: format!("merge_{}_{}", node_i.id, node_j.id),
905                height: merge_height,
906                children: vec![node_i, node_j],
907                data_indices: merged_indices,
908                quantum_coherence: avg_coherence,
909                neuromorphic_activity: avg_neuro,
910            };
911            nodes.push(merged);
912        }
913
914        let root = nodes.remove(0);
915        let leaf_count = root.data_indices.len();
916        let total_height = root.height;
917
918        Ok(DendrogramTree {
919            root,
920            height: total_height,
921            leaf_count,
922        })
923    }
924
925    /// Compute 2-D (x, y) canvas positions for each node in the dendrogram.
926    ///
927    /// Leaves are spread evenly along the x-axis; internal nodes are placed
928    /// at the mean x of their children and at their merge-height on the y-axis.
929    pub(crate) fn calculate_dendrogram_layout(
930        &self,
931        tree: &DendrogramTree,
932    ) -> Result<HashMap<String, (f64, f64)>> {
933        let mut positions: HashMap<String, (f64, f64)> = HashMap::new();
934        let plot_width = self.config.width as f64;
935        let plot_height = self.config.height as f64;
936        let max_height = tree.height.max(1.0);
937
938        // Assign a leaf-index to every leaf node via a DFS traversal.
939        let mut leaf_counter = 0usize;
940        fn assign_leaves(
941            node: &DendrogramNode,
942            positions: &mut HashMap<String, (f64, f64)>,
943            leaf_counter: &mut usize,
944            plot_width: f64,
945            plot_height: f64,
946            max_height: f64,
947            leaf_count: usize,
948        ) {
949            if node.children.is_empty() {
950                // Leaf node — place on x-axis.
951                let x = if leaf_count > 1 {
952                    (*leaf_counter as f64 / (leaf_count - 1) as f64) * plot_width
953                } else {
954                    plot_width / 2.0
955                };
956                let y = plot_height; // Leaves at the bottom.
957                positions.insert(node.id.clone(), (x, y));
958                *leaf_counter += 1;
959            } else {
960                for child in &node.children {
961                    assign_leaves(
962                        child,
963                        positions,
964                        leaf_counter,
965                        plot_width,
966                        plot_height,
967                        max_height,
968                        leaf_count,
969                    );
970                }
971                // Internal node — x is the mean of child x-positions.
972                let child_x: f64 = node
973                    .children
974                    .iter()
975                    .filter_map(|c| positions.get(&c.id).map(|(x, _)| *x))
976                    .sum::<f64>()
977                    / node.children.len().max(1) as f64;
978                let y = plot_height * (1.0 - node.height / max_height);
979                positions.insert(node.id.clone(), (child_x, y));
980            }
981        }
982
983        assign_leaves(
984            &tree.root,
985            &mut positions,
986            &mut leaf_counter,
987            plot_width,
988            plot_height,
989            max_height,
990            tree.leaf_count.max(1),
991        );
992
993        Ok(positions)
994    }
995
996    /// Compute branch lengths for each node, scaled by quantum coherence.
997    pub(crate) fn calculate_quantum_branch_lengths(
998        &self,
999        tree: &DendrogramTree,
1000        result: &AdvancedClusteringResult,
1001    ) -> Result<HashMap<String, f64>> {
1002        let mut branch_lengths: HashMap<String, f64> = HashMap::new();
1003        let quantum_scale = result.quantum_advantage.max(1.0);
1004
1005        fn traverse(
1006            node: &DendrogramNode,
1007            parent_height: f64,
1008            branch_lengths: &mut HashMap<String, f64>,
1009            quantum_scale: f64,
1010        ) {
1011            let raw_length = (node.height - parent_height).abs();
1012            let quantum_enhanced = raw_length * (1.0 + node.quantum_coherence / quantum_scale);
1013            branch_lengths.insert(node.id.clone(), quantum_enhanced);
1014            for child in &node.children {
1015                traverse(child, node.height, branch_lengths, quantum_scale);
1016            }
1017        }
1018
1019        traverse(&tree.root, 0.0, &mut branch_lengths, quantum_scale);
1020        Ok(branch_lengths)
1021    }
1022
1023    /// Compute per-node quantum enhancement values for dendrogram colouring.
1024    pub(crate) fn calculate_dendrogram_quantum_enhancements(
1025        &self,
1026        tree: &DendrogramTree,
1027        result: &AdvancedClusteringResult,
1028    ) -> Result<HashMap<String, f64>> {
1029        let mut enhancements: HashMap<String, f64> = HashMap::new();
1030
1031        fn traverse(
1032            node: &DendrogramNode,
1033            enhancements: &mut HashMap<String, f64>,
1034            base_quantum: f64,
1035        ) {
1036            let enhancement = (base_quantum * node.quantum_coherence).clamp(0.0, 1.0);
1037            enhancements.insert(node.id.clone(), enhancement);
1038            for child in &node.children {
1039                traverse(child, enhancements, base_quantum);
1040            }
1041        }
1042
1043        let base_quantum = result.quantum_advantage.clamp(0.01, 10.0);
1044        traverse(&tree.root, &mut enhancements, base_quantum);
1045        Ok(enhancements)
1046    }
1047
1048    /// Render dendrogram tree as SVG line elements and add them to the canvas.
1049    pub(crate) fn render_dendrogram_to_svg(
1050        &mut self,
1051        tree: &DendrogramTree,
1052        positions: &HashMap<String, (f64, f64)>,
1053        _branch_lengths: &HashMap<String, f64>,
1054        enhancements: &HashMap<String, f64>,
1055    ) -> Result<()> {
1056        fn draw_node(
1057            node: &DendrogramNode,
1058            positions: &HashMap<String, (f64, f64)>,
1059            enhancements: &HashMap<String, f64>,
1060            elements: &mut Vec<SvgElement>,
1061        ) {
1062            if let Some(&(x, y)) = positions.get(&node.id) {
1063                for child in &node.children {
1064                    if let Some(&(cx, cy)) = positions.get(&child.id) {
1065                        let enhancement = enhancements.get(&child.id).copied().unwrap_or(0.0);
1066                        let blue = ((enhancement * 200.0) as u8).saturating_add(55);
1067                        let color = format!("rgb(0, {}, {})", blue, blue);
1068
1069                        // Horizontal branch from parent to child x, then vertical.
1070                        elements.push(SvgElement::Line {
1071                            x1: x,
1072                            y1: y,
1073                            x2: cx,
1074                            y2: y,
1075                            stroke: color.clone(),
1076                            stroke_width: 1.5,
1077                            opacity: 0.85,
1078                        });
1079                        elements.push(SvgElement::Line {
1080                            x1: cx,
1081                            y1: y,
1082                            x2: cx,
1083                            y2: cy,
1084                            stroke: color,
1085                            stroke_width: 1.5,
1086                            opacity: 0.85,
1087                        });
1088                        draw_node(child, positions, enhancements, elements);
1089                    }
1090                }
1091                // Draw a small circle at each internal node.
1092                if !node.children.is_empty() {
1093                    elements.push(SvgElement::Circle {
1094                        cx: x,
1095                        cy: y,
1096                        r: 3.0,
1097                        fill: "#00CCFF".to_string(),
1098                        stroke: "#0088BB".to_string(),
1099                        stroke_width: 1.0,
1100                        opacity: 0.9,
1101                    });
1102                }
1103            }
1104        }
1105
1106        let mut elements: Vec<SvgElement> = Vec::new();
1107        draw_node(&tree.root, positions, enhancements, &mut elements);
1108        for el in elements {
1109            self.svg_canvas.add_element(el);
1110        }
1111        Ok(())
1112    }
1113
1114    /// Build a quantum-field representation for 3D cluster plots.
1115    ///
1116    /// The field is sampled on a 10×10 grid overlaid on the data bounding box.
1117    pub(crate) fn create_quantum_field_3d(
1118        &self,
1119        points_3d: &Array2<f64>,
1120        result: &AdvancedClusteringResult,
1121    ) -> Result<QuantumField3D> {
1122        let grid = 10usize;
1123        let n_points = points_3d.nrows();
1124
1125        // Compute bounding box.
1126        let (mut x_min, mut x_max, mut y_min, mut y_max) = (
1127            f64::INFINITY,
1128            f64::NEG_INFINITY,
1129            f64::INFINITY,
1130            f64::NEG_INFINITY,
1131        );
1132        for i in 0..n_points {
1133            let x = points_3d[[i, 0]];
1134            let y = points_3d[[i, 1]];
1135            x_min = x_min.min(x);
1136            x_max = x_max.max(x);
1137            y_min = y_min.min(y);
1138            y_max = y_max.max(y);
1139        }
1140        x_min -= 1.0;
1141        x_max += 1.0;
1142        y_min -= 1.0;
1143        y_max += 1.0;
1144
1145        let mut field_strength = Array2::zeros((grid, grid));
1146        let mut coherence = Array2::zeros((grid, grid));
1147        let mut phase = Array2::zeros((grid, grid));
1148
1149        let base_coherence = result.performance.quantum_coherence;
1150
1151        for gi in 0..grid {
1152            for gj in 0..grid {
1153                let gx = x_min + (gi as f64 / (grid - 1) as f64) * (x_max - x_min);
1154                let gy = y_min + (gj as f64 / (grid - 1) as f64) * (y_max - y_min);
1155
1156                // Field strength = inverse-distance-weighted sum over all points.
1157                let mut strength = 0.0_f64;
1158                for i in 0..n_points {
1159                    let dx = gx - points_3d[[i, 0]];
1160                    let dy = gy - points_3d[[i, 1]];
1161                    let dz = if points_3d.ncols() > 2 {
1162                        gx - points_3d[[i, 2]]
1163                    } else {
1164                        0.0
1165                    };
1166                    let dist2 = (dx * dx + dy * dy + dz * dz).max(1e-6);
1167                    strength += 1.0 / dist2;
1168                }
1169                field_strength[[gi, gj]] = strength.min(100.0);
1170                coherence[[gi, gj]] = base_coherence * (1.0 - strength / (strength + 10.0));
1171                phase[[gi, gj]] = (gx * 0.5 + gy * 0.3).sin() * PI;
1172            }
1173        }
1174
1175        // Entanglement lines: connect centroids with high quantum coherence.
1176        let n_centroids = result.centroids.nrows().min(5);
1177        let mut entanglement_lines = Vec::new();
1178        for i in 0..n_centroids {
1179            for j in (i + 1)..n_centroids {
1180                let coherence_ij = base_coherence * 0.8;
1181                if coherence_ij > 0.3 {
1182                    let pi_x = result.centroids[[i, 0]];
1183                    let pi_y = result.centroids[[i, 1]];
1184                    let pi_z = if result.centroids.ncols() > 2 {
1185                        result.centroids[[i, 2]]
1186                    } else {
1187                        0.0
1188                    };
1189                    let pj_x = result.centroids[[j, 0]];
1190                    let pj_y = result.centroids[[j, 1]];
1191                    let pj_z = if result.centroids.ncols() > 2 {
1192                        result.centroids[[j, 2]]
1193                    } else {
1194                        0.0
1195                    };
1196                    entanglement_lines.push(([pi_x, pi_y, pi_z], [pj_x, pj_y, pj_z], coherence_ij));
1197                }
1198            }
1199        }
1200
1201        Ok(QuantumField3D {
1202            field_strength,
1203            coherence,
1204            phase,
1205            entanglement_lines,
1206        })
1207    }
1208
1209    /// Generate a single quantum coherence animation frame at time `t`.
1210    pub(crate) fn create_quantum_coherence_frame(
1211        &self,
1212        result: &AdvancedClusteringResult,
1213        t: f64,
1214    ) -> Result<QuantumCoherenceFrame> {
1215        let grid = 8usize;
1216        let mut field_strength = Array2::zeros((grid, grid));
1217
1218        let base = result.performance.quantum_coherence;
1219        for gi in 0..grid {
1220            for gj in 0..grid {
1221                let wave_x = (gi as f64 / grid as f64 * 2.0 * PI + t * 2.5).cos();
1222                let wave_y = (gj as f64 / grid as f64 * 2.0 * PI + t * 1.7).sin();
1223                field_strength[[gi, gj]] = (base + 0.3 * wave_x * wave_y).clamp(0.0, 1.0);
1224            }
1225        }
1226
1227        // Build SVG pulsing circles for each cluster centroid.
1228        let n_centroids = result.centroids.nrows().min(8);
1229        let mut elements: Vec<SvgElement> = Vec::new();
1230        let w = self.config.width as f64;
1231        let h = self.config.height as f64;
1232
1233        for k in 0..n_centroids {
1234            let cx_val = result.centroids[[k, 0]];
1235            let cy_val = result.centroids[[k, 1]];
1236            // Normalise to canvas — assume data in [-5, 5].
1237            let sx = (cx_val + 5.0) / 10.0 * w;
1238            let sy = (cy_val + 5.0) / 10.0 * h;
1239
1240            let pulse = (t * 2.0 * PI + k as f64 * 0.5).sin() * 0.5 + 0.5;
1241            let r = 5.0 + pulse * 10.0;
1242            let opacity = 0.4 + pulse * 0.5;
1243            let blue_val = ((base * 200.0) as u8).saturating_add(55);
1244            let color = format!("rgba(0, {blue_val}, 255, {opacity:.2})");
1245
1246            elements.push(SvgElement::Circle {
1247                cx: sx,
1248                cy: sy,
1249                r,
1250                fill: color.clone(),
1251                stroke: "#00FFFF".to_string(),
1252                stroke_width: 1.0,
1253                opacity,
1254            });
1255        }
1256
1257        Ok(QuantumCoherenceFrame {
1258            timestamp: t,
1259            elements,
1260            field_strength,
1261        })
1262    }
1263}
1264
1265// Suppress the unused import warning for AdvancedPerformanceMetrics — it is only
1266// needed by the test helpers which live in mod.rs and use the crate path directly.
1267#[allow(unused_imports)]
1268use crate::advanced_clustering::AdvancedPerformanceMetrics as _AdvancedPerformanceMetrics;