sorting_race/lib/
visualization.rs

1//! Visualization components for the sorting race
2
3use crate::models::traits::{Sorter, Telemetry};
4
5/// Visualization renderer for the sorting race
6#[derive(Debug)]
7pub struct RaceVisualizer {
8    width: usize,
9    height: usize,
10    show_comparisons: bool,
11    show_metrics: bool,
12    color_enabled: bool,
13}
14
15impl RaceVisualizer {
16    /// Create a new race visualizer
17    pub fn new(width: usize, height: usize) -> Self {
18        Self {
19            width,
20            height,
21            show_comparisons: true,
22            show_metrics: true,
23            color_enabled: true,
24        }
25    }
26
27    /// Set whether to show comparison highlights
28    pub fn set_show_comparisons(&mut self, show: bool) {
29        self.show_comparisons = show;
30    }
31
32    /// Set whether to show metrics
33    pub fn set_show_metrics(&mut self, show: bool) {
34        self.show_metrics = show;
35    }
36
37    /// Set whether colors are enabled
38    pub fn set_color_enabled(&mut self, enabled: bool) {
39        self.color_enabled = enabled;
40    }
41
42    /// Render the current state of all algorithms
43    pub fn render(&self, algorithms: &[Box<dyn Sorter>]) -> String {
44        let mut output = String::new();
45        
46        // Header
47        output.push_str(&format!("┌{}┐\n", "─".repeat(self.width - 2)));
48        output.push_str(&format!("│{:^width$}│\n", "Sorting Algorithm Race", width = self.width - 2));
49        output.push_str(&format!("├{}┤\n", "─".repeat(self.width - 2)));
50
51        // Render each algorithm
52        for (i, algorithm) in algorithms.iter().enumerate() {
53            let telemetry = algorithm.get_telemetry();
54            
55            // Algorithm name and status
56            let status = if algorithm.is_complete() {
57                "COMPLETE"
58            } else {
59                "RUNNING"
60            };
61            
62            output.push_str(&format!(
63                "│ {:<20} │ {:>8} │ C:{:>6} M:{:>6} │\n",
64                algorithm.name(),
65                status,
66                telemetry.total_comparisons,
67                telemetry.total_moves
68            ));
69            
70            // Array visualization
71            output.push_str(&self.render_array(algorithm.get_array(), &telemetry));
72            
73            if i < algorithms.len() - 1 {
74                output.push_str(&format!("├{}┤\n", "─".repeat(self.width - 2)));
75            }
76        }
77
78        // Footer
79        output.push_str(&format!("└{}┘\n", "─".repeat(self.width - 2)));
80
81        output
82    }
83
84    /// Render a single array with highlights
85    fn render_array(&self, array: &[i32], telemetry: &Telemetry) -> String {
86        let mut output = String::new();
87        
88        // Calculate scaling for visualization
89        let max_val = array.iter().max().copied().unwrap_or(1);
90        let bar_height = (self.height / 8).max(1); // Reserve space for multiple algorithms
91        
92        // Render array as bar chart
93        for row in (0..bar_height).rev() {
94            output.push_str("│ ");
95            
96            for (i, &value) in array.iter().enumerate() {
97                let normalized_height = (value as f32 / max_val as f32 * bar_height as f32) as usize;
98                let char = if normalized_height > row {
99                    if telemetry.highlights.contains(&i) && self.show_comparisons {
100                        '*' // Highlight character
101                    } else {
102                        '█' // Normal bar character
103                    }
104                } else {
105                    ' '
106                };
107                output.push(char);
108            }
109            
110            output.push_str(" │\n");
111        }
112
113        // Status text
114        if !telemetry.status_text.is_empty() {
115            output.push_str(&format!(
116                "│ Status: {:<width$} │\n",
117                telemetry.status_text,
118                width = self.width - 12
119            ));
120        }
121
122        output
123    }
124
125    /// Render metrics summary
126    pub fn render_metrics(&self, algorithms: &[Box<dyn Sorter>]) -> String {
127        if !self.show_metrics {
128            return String::new();
129        }
130
131        let mut output = String::new();
132        output.push_str("Performance Metrics:\n");
133        output.push_str("┌────────────────────┬──────────┬──────────┬──────────┐\n");
134        output.push_str("│ Algorithm          │ Comps    │ Moves    │ Memory   │\n");
135        output.push_str("├────────────────────┼──────────┼──────────┼──────────┤\n");
136
137        for algorithm in algorithms {
138            let telemetry = algorithm.get_telemetry();
139            output.push_str(&format!(
140                "│ {:<18} │ {:>8} │ {:>8} │ {:>6}B │\n",
141                algorithm.name(),
142                telemetry.total_comparisons,
143                telemetry.total_moves,
144                telemetry.memory_current
145            ));
146        }
147
148        output.push_str("└────────────────────┴──────────┴──────────┴──────────┘\n");
149        output
150    }
151
152    /// Render progress bars for all algorithms
153    pub fn render_progress(&self, algorithms: &[Box<dyn Sorter>]) -> String {
154        let mut output = String::new();
155        
156        for algorithm in algorithms {
157            let telemetry = algorithm.get_telemetry();
158            let progress = telemetry.progress_hint.clamp(0.0, 1.0);
159            let progress_width = 30;
160            let filled = (progress * progress_width as f32) as usize;
161            
162            let bar = "█".repeat(filled) + &"░".repeat(progress_width - filled);
163            
164            output.push_str(&format!(
165                "{:<15} [{}] {:>5.1}%\n",
166                algorithm.name(),
167                bar,
168                progress * 100.0
169            ));
170        }
171
172        output
173    }
174
175    /// Create a simple text-based visualization frame
176    pub fn create_frame(&self, title: &str, content: &str) -> String {
177        let lines: Vec<&str> = content.lines().collect();
178        let content_width = lines.iter().map(|line| line.len()).max().unwrap_or(0);
179        let frame_width = content_width.max(title.len()) + 4;
180        
181        let mut frame = String::new();
182        
183        // Top border
184        frame.push_str(&format!("┌{}┐\n", "─".repeat(frame_width - 2)));
185        
186        // Title
187        frame.push_str(&format!("│{:^width$}│\n", title, width = frame_width - 2));
188        frame.push_str(&format!("├{}┤\n", "─".repeat(frame_width - 2)));
189        
190        // Content
191        for line in lines {
192            frame.push_str(&format!("│ {:<width$} │\n", line, width = frame_width - 4));
193        }
194        
195        // Bottom border
196        frame.push_str(&format!("└{}┘\n", "─".repeat(frame_width - 2)));
197        
198        frame
199    }
200}
201
202impl Default for RaceVisualizer {
203    fn default() -> Self {
204        Self::new(80, 24)
205    }
206}