1use 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#[derive(Debug)]
19pub struct AdvancedNativePlotter {
20 pub(crate) config: NativePlotConfig,
22 pub(crate) svg_canvas: SvgCanvas,
24 pub(crate) animation_engine: AnimationEngine,
26 pub(crate) interactive_controller: InteractiveController,
28}
29
30impl AdvancedNativePlotter {
31 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 pub fn create_comprehensive_plot(
43 &mut self,
44 data: &ArrayView2<f64>,
45 result: &AdvancedClusteringResult,
46 ) -> Result<NativeVisualizationOutput> {
47 self.svg_canvas.clear();
49
50 let cluster_plot = self.create_native_cluster_plot(data, result)?;
52
53 let dendrogram = if result.selected_algorithm.contains("hierarchical") {
55 Some(self.create_native_dendrogram(data, result)?)
56 } else {
57 None
58 };
59
60 let plot_3d = if data.ncols() > 2 {
62 Some(self.create_native_3d_plot(data, result)?)
63 } else {
64 None
65 };
66
67 let quantum_animation = if self.config.enable_animations {
69 Some(self.create_quantum_coherence_animation(result)?)
70 } else {
71 None
72 };
73
74 let neuromorphic_plot = self.create_neuromorphic_activity_plot(result)?;
76
77 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 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 let plot_data = if n_features > 2 {
103 self.apply_native_pca(data, 2)?
104 } else {
105 data.to_owned()
106 };
107
108 let (x_min, x_max, y_min, y_max) = self.calculate_plot_bounds(&plot_data);
110
111 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 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 let quantum_factor = self.calculate_point_quantum_enhancement(i, cluster_id, result);
130 quantum_enhancements.push(quantum_factor);
131
132 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; 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 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 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 for element in &point_elements {
186 self.svg_canvas.add_element(element.clone());
187 }
188 for element in ¢roid_elements {
189 self.svg_canvas.add_element(element.clone());
190 }
191
192 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 fn create_native_dendrogram(
207 &mut self,
208 data: &ArrayView2<f64>,
209 result: &AdvancedClusteringResult,
210 ) -> Result<NativeDendrogramPlot> {
211 let tree = self.build_dendrogram_tree(data, result)?;
213
214 let node_positions = self.calculate_dendrogram_layout(&tree)?;
216
217 let branch_lengths = self.calculate_quantum_branch_lengths(&tree, result)?;
219
220 let quantum_enhancements = self.calculate_dendrogram_quantum_enhancements(&tree, result)?;
222
223 let interactive_features = vec![
225 InteractiveFeature::ZoomPan,
226 InteractiveFeature::NodeSelection,
227 InteractiveFeature::Tooltip,
228 InteractiveFeature::RealTimeFilter,
229 ];
230
231 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 fn create_native_3d_plot(
250 &mut self,
251 data: &ArrayView2<f64>,
252 result: &AdvancedClusteringResult,
253 ) -> Result<Native3DClusterPlot> {
254 let points_3d = if data.ncols() > 3 {
256 self.apply_native_pca(data, 3)?
257 } else if data.ncols() == 2 {
258 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 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; }
271 points_3d
272 } else {
273 data.to_owned()
274 };
275
276 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 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 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 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], attenuation: 0.1,
324 }],
325 };
326
327 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 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; 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 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 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 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 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 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 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 fn create_interactive_performance_dashboard(
418 &mut self,
419 result: &AdvancedClusteringResult,
420 ) -> Result<InteractivePerformanceDashboard> {
421 let metrics = &result.performance;
422
423 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 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 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 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 fn calculate_point_quantum_enhancement(
481 &self,
482 point_idx: usize,
483 cluster_id: usize,
484 result: &AdvancedClusteringResult,
485 ) -> f64 {
486 let base_quantum = result.quantum_advantage / 10.0;
488 let coherence_factor = result.performance.quantum_coherence;
489 let confidence_factor = result.confidence;
490
491 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; 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 if base_color.starts_with("hsl") {
572 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 }
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 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 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 let mean = data.mean_axis(Axis(0)).expect("Operation failed");
660 let centered = data - &mean.insert_axis(Axis(0));
661
662 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 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 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 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 let _ = (x_min, x_max, y_min, ymax);
740
741 Ok(())
742 }
743
744 fn generate_interactive_script(&self) -> String {
745 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 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 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 let mut merge_height = 0.0_f64;
856 while nodes.len() > 1 {
857 let n = nodes.len();
858 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 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 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 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 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; 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[allow(unused_imports)]
1268use crate::advanced_clustering::AdvancedPerformanceMetrics as _AdvancedPerformanceMetrics;