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            // Get actual memory usage from the algorithm
140            let actual_memory = algorithm.get_memory_usage();
141            let memory_display = if actual_memory > 0 {
142                Self::format_memory_bytes(actual_memory)
143            } else {
144                // Fall back to telemetry if get_memory_usage returns 0
145                if telemetry.memory_current > 0 {
146                    Self::format_memory_bytes(telemetry.memory_current)
147                } else {
148                    "N/A".to_string()
149                }
150            };
151            
152            output.push_str(&format!(
153                "│ {:<18} │ {:>8} │ {:>8} │ {:>8} │\n",
154                algorithm.name(),
155                telemetry.total_comparisons,
156                telemetry.total_moves,
157                memory_display
158            ));
159        }
160
161        output.push_str("└────────────────────┴──────────┴──────────┴──────────┘\n");
162        output
163    }
164
165    /// Render progress bars for all algorithms
166    pub fn render_progress(&self, algorithms: &[Box<dyn Sorter>]) -> String {
167        let mut output = String::new();
168        
169        for algorithm in algorithms {
170            let telemetry = algorithm.get_telemetry();
171            let progress = telemetry.progress_hint.clamp(0.0, 1.0);
172            let progress_width = 30;
173            let filled = (progress * progress_width as f32) as usize;
174            
175            let bar = "█".repeat(filled) + &"░".repeat(progress_width - filled);
176            
177            output.push_str(&format!(
178                "{:<15} [{}] {:>5.1}%\n",
179                algorithm.name(),
180                bar,
181                progress * 100.0
182            ));
183        }
184
185        output
186    }
187
188    /// Format bytes into human readable string
189    fn format_memory_bytes(bytes: usize) -> String {
190        const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
191        let mut size = bytes as f64;
192        let mut unit_index = 0;
193
194        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
195            size /= 1024.0;
196            unit_index += 1;
197        }
198
199        if unit_index == 0 {
200            format!("{}B", bytes)
201        } else {
202            format!("{:.1}{}", size, UNITS[unit_index])
203        }
204    }
205
206    /// Create a simple text-based visualization frame
207    pub fn create_frame(&self, title: &str, content: &str) -> String {
208        let lines: Vec<&str> = content.lines().collect();
209        let content_width = lines.iter().map(|line| line.len()).max().unwrap_or(0);
210        let frame_width = content_width.max(title.len()) + 4;
211        
212        let mut frame = String::new();
213        
214        // Top border
215        frame.push_str(&format!("┌{}┐\n", "─".repeat(frame_width - 2)));
216        
217        // Title
218        frame.push_str(&format!("│{:^width$}│\n", title, width = frame_width - 2));
219        frame.push_str(&format!("├{}┤\n", "─".repeat(frame_width - 2)));
220        
221        // Content
222        for line in lines {
223            frame.push_str(&format!("│ {:<width$} │\n", line, width = frame_width - 4));
224        }
225        
226        // Bottom border
227        frame.push_str(&format!("└{}┘\n", "─".repeat(frame_width - 2)));
228        
229        frame
230    }
231}
232
233impl Default for RaceVisualizer {
234    fn default() -> Self {
235        Self::new(80, 24)
236    }
237}