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 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 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 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 frame.push_str(&format!("┌{}┐\n", "─".repeat(frame_width - 2)));
185
186 frame.push_str(&format!("│{:^width$}│\n", title, width = frame_width - 2));
188 frame.push_str(&format!("├{}┤\n", "─".repeat(frame_width - 2)));
189
190 for line in lines {
192 frame.push_str(&format!("│ {:<width$} │\n", line, width = frame_width - 4));
193 }
194
195 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}