1use super::graph_core::Graph;
6use super::types::{Node, Position};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum RenderMode {
15 #[default]
17 Unicode,
18 Ascii,
20 Plain,
22}
23
24#[derive(Debug, Clone)]
26pub struct RenderedGraph {
27 pub width: usize,
29 pub height: usize,
31 pub buffer: Vec<Vec<char>>,
33 pub colors: Vec<Vec<Option<&'static str>>>,
35}
36
37impl RenderedGraph {
38 #[must_use]
40 pub fn new(width: usize, height: usize) -> Self {
41 Self {
42 width,
43 height,
44 buffer: vec![vec![' '; width]; height],
45 colors: vec![vec![None; width]; height],
46 }
47 }
48
49 pub fn set(&mut self, x: usize, y: usize, ch: char, color: Option<&'static str>) {
51 if x < self.width && y < self.height {
52 self.buffer[y][x] = ch;
53 self.colors[y][x] = color;
54 }
55 }
56
57 #[must_use]
59 pub fn to_string_colored(&self) -> String {
60 let mut result = String::new();
61 for y in 0..self.height {
62 for x in 0..self.width {
63 if let Some(color) = self.colors[y][x] {
64 result.push_str(color);
65 result.push(self.buffer[y][x]);
66 result.push_str("\x1b[0m");
67 } else {
68 result.push(self.buffer[y][x]);
69 }
70 }
71 result.push('\n');
72 }
73 result
74 }
75
76 #[must_use]
78 pub fn to_string_plain(&self) -> String {
79 self.buffer.iter().map(|row| row.iter().collect::<String>()).collect::<Vec<_>>().join("\n")
80 }
81}
82
83pub struct GraphRenderer {
85 pub mode: RenderMode,
87 pub show_labels: bool,
89 pub show_edges: bool,
91}
92
93impl Default for GraphRenderer {
94 fn default() -> Self {
95 Self { mode: RenderMode::Unicode, show_labels: true, show_edges: true }
96 }
97}
98
99impl GraphRenderer {
100 #[must_use]
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 #[must_use]
108 pub fn with_mode(mut self, mode: RenderMode) -> Self {
109 self.mode = mode;
110 self
111 }
112
113 pub fn render<N, E>(&self, graph: &Graph<N, E>, width: usize, height: usize) -> RenderedGraph {
115 let mut output = RenderedGraph::new(width, height);
116
117 if self.show_edges {
119 for edge in graph.edges() {
120 if let (Some(from), Some(to)) =
121 (graph.get_node(&edge.from), graph.get_node(&edge.to))
122 {
123 self.draw_edge(&mut output, &from.position, &to.position, width, height);
124 }
125 }
126 }
127
128 for node in graph.nodes() {
130 self.draw_node(&mut output, node, width, height);
131 }
132
133 output
134 }
135
136 fn draw_node<N>(
137 &self,
138 output: &mut RenderedGraph,
139 node: &Node<N>,
140 width: usize,
141 height: usize,
142 ) {
143 let x = (node.position.x / 80.0 * width as f32) as usize;
144 let y = (node.position.y / 24.0 * height as f32) as usize;
145
146 if x < width && y < height {
147 let ch = match self.mode {
148 RenderMode::Unicode => node.status.shape().unicode(),
149 RenderMode::Ascii | RenderMode::Plain => node.status.shape().ascii(),
150 };
151
152 let color = match self.mode {
153 RenderMode::Unicode | RenderMode::Ascii => Some(node.status.color_code()),
154 RenderMode::Plain => None,
155 };
156
157 output.set(x, y, ch, color);
158
159 if self.show_labels {
161 if let Some(ref label) = node.label {
162 let label_start = x.saturating_add(2);
163 for (i, c) in label.chars().take(10).enumerate() {
164 if label_start + i < width {
165 output.set(label_start + i, y, c, color);
166 }
167 }
168 }
169 }
170 }
171 }
172
173 fn draw_edge(
174 &self,
175 output: &mut RenderedGraph,
176 from: &Position,
177 to: &Position,
178 width: usize,
179 height: usize,
180 ) {
181 let x1 = (from.x / 80.0 * width as f32) as i32;
182 let y1 = (from.y / 24.0 * height as f32) as i32;
183 let x2 = (to.x / 80.0 * width as f32) as i32;
184 let y2 = (to.y / 24.0 * height as f32) as i32;
185
186 let dx = (x2 - x1).abs();
188 let dy = (y2 - y1).abs();
189 let sx = if x1 < x2 { 1 } else { -1 };
190 let sy = if y1 < y2 { 1 } else { -1 };
191 let mut err = dx - dy;
192
193 let mut x = x1;
194 let mut y = y1;
195
196 let edge_char = match self.mode {
197 RenderMode::Unicode => 'ยท',
198 RenderMode::Ascii | RenderMode::Plain => '.',
199 };
200
201 while x != x2 || y != y2 {
202 if x >= 0 && x < width as i32 && y >= 0 && y < height as i32 {
203 if output.buffer[y as usize][x as usize] == ' ' {
205 output.set(x as usize, y as usize, edge_char, Some("\x1b[90m"));
206 }
207 }
208
209 let e2 = 2 * err;
210 if e2 > -dy {
211 err -= dy;
212 x += sx;
213 }
214 if e2 < dx {
215 err += dx;
216 y += sy;
217 }
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_render_mode_default() {
228 assert_eq!(RenderMode::default(), RenderMode::Unicode);
229 }
230
231 #[test]
232 fn test_render_mode_equality() {
233 assert_eq!(RenderMode::Unicode, RenderMode::Unicode);
234 assert_eq!(RenderMode::Ascii, RenderMode::Ascii);
235 assert_eq!(RenderMode::Plain, RenderMode::Plain);
236 assert_ne!(RenderMode::Unicode, RenderMode::Ascii);
237 }
238
239 #[test]
240 fn test_rendered_graph_new() {
241 let graph = RenderedGraph::new(80, 24);
242 assert_eq!(graph.width, 80);
243 assert_eq!(graph.height, 24);
244 assert_eq!(graph.buffer.len(), 24);
245 assert_eq!(graph.buffer[0].len(), 80);
246 assert_eq!(graph.colors.len(), 24);
247 }
248
249 #[test]
250 fn test_rendered_graph_set() {
251 let mut graph = RenderedGraph::new(10, 10);
252 graph.set(5, 5, 'X', Some("\x1b[32m"));
253 assert_eq!(graph.buffer[5][5], 'X');
254 assert_eq!(graph.colors[5][5], Some("\x1b[32m"));
255 }
256
257 #[test]
258 fn test_rendered_graph_set_out_of_bounds() {
259 let mut graph = RenderedGraph::new(10, 10);
260 graph.set(15, 15, 'X', None);
261 assert_eq!(graph.buffer[0][0], ' ');
263 }
264
265 #[test]
266 fn test_rendered_graph_to_string_plain() {
267 let mut graph = RenderedGraph::new(5, 3);
268 graph.set(2, 1, '*', None);
269 let output = graph.to_string_plain();
270 assert!(output.contains('*'));
271 }
272
273 #[test]
274 fn test_rendered_graph_to_string_colored() {
275 let mut graph = RenderedGraph::new(5, 3);
276 graph.set(2, 1, '*', Some("\x1b[32m"));
277 let output = graph.to_string_colored();
278 assert!(output.contains("\x1b[32m"));
279 assert!(output.contains("\x1b[0m"));
280 }
281
282 #[test]
283 fn test_graph_renderer_default() {
284 let renderer = GraphRenderer::default();
285 assert_eq!(renderer.mode, RenderMode::Unicode);
286 assert!(renderer.show_labels);
287 assert!(renderer.show_edges);
288 }
289
290 #[test]
291 fn test_graph_renderer_new() {
292 let renderer = GraphRenderer::new();
293 assert_eq!(renderer.mode, RenderMode::Unicode);
294 }
295
296 #[test]
297 fn test_graph_renderer_with_mode() {
298 let renderer = GraphRenderer::new().with_mode(RenderMode::Ascii);
299 assert_eq!(renderer.mode, RenderMode::Ascii);
300 }
301
302 #[test]
303 fn test_graph_renderer_render_empty_graph() {
304 let graph: Graph<(), ()> = Graph::new();
305 let renderer = GraphRenderer::new();
306 let output = renderer.render(&graph, 40, 12);
307 assert_eq!(output.width, 40);
308 assert_eq!(output.height, 12);
309 }
310}