sorting_race/lib/
visualization.rs1use crate::models::traits::{Sorter, Telemetry};
4
5#[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 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 pub fn set_show_comparisons(&mut self, show: bool) {
29 self.show_comparisons = show;
30 }
31
32 pub fn set_show_metrics(&mut self, show: bool) {
34 self.show_metrics = show;
35 }
36
37 pub fn set_color_enabled(&mut self, enabled: bool) {
39 self.color_enabled = enabled;
40 }
41
42 pub fn render(&self, algorithms: &[Box<dyn Sorter>]) -> String {
44 let mut output = String::new();
45
46 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 for (i, algorithm) in algorithms.iter().enumerate() {
53 let telemetry = algorithm.get_telemetry();
54
55 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 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 output.push_str(&format!("└{}┘\n", "─".repeat(self.width - 2)));
80
81 output
82 }
83
84 fn render_array(&self, array: &[i32], telemetry: &Telemetry) -> String {
86 let mut output = String::new();
87
88 let max_val = array.iter().max().copied().unwrap_or(1);
90 let bar_height = (self.height / 8).max(1); 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 '*' } else {
102 '█' }
104 } else {
105 ' '
106 };
107 output.push(char);
108 }
109
110 output.push_str(" │\n");
111 }
112
113 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 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 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 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 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 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 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 frame.push_str(&format!("┌{}┐\n", "─".repeat(frame_width - 2)));
216
217 frame.push_str(&format!("│{:^width$}│\n", title, width = frame_width - 2));
219 frame.push_str(&format!("├{}┤\n", "─".repeat(frame_width - 2)));
220
221 for line in lines {
223 frame.push_str(&format!("│ {:<width$} │\n", line, width = frame_width - 4));
224 }
225
226 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}