1#![doc = "Tree layout algorithms for AST visualization"]
2
3use crate::{
4 geometry::{Point, Rect, Size},
5 layout::{Edge, Layout},
6};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct TreeNode {
13 pub id: String,
14 pub label: String,
15 pub node_type: String,
16 pub children: Vec<TreeNode>,
17 pub attributes: HashMap<String, String>,
18 pub size: Option<Size>,
19}
20
21impl TreeNode {
22 pub fn new(id: String, label: String, node_type: String) -> Self {
23 Self { id, label, node_type, children: Vec::new(), attributes: HashMap::new(), size: None }
24 }
25
26 pub fn with_child(mut self, child: TreeNode) -> Self {
27 self.children.push(child);
28 self
29 }
30
31 pub fn with_children(mut self, children: Vec<TreeNode>) -> Self {
32 self.children = children;
33 self
34 }
35
36 pub fn with_attribute(mut self, key: String, value: String) -> Self {
37 self.attributes.insert(key, value);
38 self
39 }
40
41 pub fn with_size(mut self, size: Size) -> Self {
42 self.size = Some(size);
43 self
44 }
45
46 pub fn is_leaf(&self) -> bool {
47 self.children.is_empty()
48 }
49
50 pub fn depth(&self) -> usize {
51 if self.children.is_empty() { 1 } else { 1 + self.children.iter().map(|child| child.depth()).max().unwrap_or(0) }
52 }
53
54 pub fn node_count(&self) -> usize {
55 1 + self.children.iter().map(|child| child.node_count()).sum::<usize>()
56 }
57
58 pub fn leaf_count(&self) -> usize {
59 if self.children.is_empty() { 1 } else { self.children.iter().map(|child| child.leaf_count()).sum() }
60 }
61}
62
63pub struct TreeLayout {
65 algorithm: TreeLayoutAlgorithm,
66 config: TreeLayoutConfig,
67}
68
69impl TreeLayout {
70 pub fn new(algorithm: TreeLayoutAlgorithm) -> Self {
71 Self { algorithm, config: TreeLayoutConfig::default() }
72 }
73
74 pub fn with_config(mut self, config: TreeLayoutConfig) -> Self {
75 self.config = config;
76 self
77 }
78
79 pub fn layout_tree(&self, tree: &TreeNode) -> crate::Result<Layout> {
80 match self.algorithm {
81 TreeLayoutAlgorithm::Layered => self.layered_layout(tree),
82 TreeLayoutAlgorithm::Radial => self.radial_layout(tree),
83 TreeLayoutAlgorithm::Compact => self.compact_layout(tree),
84 TreeLayoutAlgorithm::Balloon => self.balloon_layout(tree),
85 }
86 }
87
88 fn layered_layout(&self, tree: &TreeNode) -> crate::Result<Layout> {
90 let mut layout = Layout::new();
91 let mut node_positions = HashMap::new();
92 let mut level_widths = HashMap::new();
93
94 self.calculate_level_widths(tree, 0, &mut level_widths);
96
97 let mut current_positions = HashMap::new();
99 self.position_layered_node(tree, 0, 0.0, &level_widths, &mut current_positions, &mut node_positions);
100
101 for (id, (position, size)) in node_positions {
103 let rect = Rect::new(position, size);
104 layout.add_node(id, rect);
105 }
106
107 self.add_tree_edges(tree, &mut layout);
109
110 Ok(layout)
111 }
112
113 fn radial_layout(&self, tree: &TreeNode) -> crate::Result<Layout> {
115 let mut layout = Layout::new();
116 let mut node_positions = HashMap::new();
117
118 let root_size = self.get_node_size(tree);
120 let root_pos = Point::new(0.0, 0.0);
121 node_positions.insert(tree.id.clone(), (root_pos, root_size));
122
123 if !tree.children.is_empty() {
125 let radius = self.config.level_distance;
126 self.position_radial_children(
127 &tree.children,
128 root_pos,
129 radius,
130 0.0,
131 2.0 * std::f64::consts::PI,
132 1,
133 &mut node_positions,
134 );
135 }
136
137 for (id, (position, size)) in node_positions {
139 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
140 layout.add_node(id, rect);
141 }
142
143 self.add_tree_edges(tree, &mut layout);
145
146 Ok(layout)
147 }
148
149 fn compact_layout(&self, tree: &TreeNode) -> crate::Result<Layout> {
151 let mut layout = Layout::new();
152 let mut node_positions = HashMap::new();
153
154 let positioned_tree = self.position_compact_node(tree, 0.0, 0.0, 0);
156 self.extract_positions(&positioned_tree, &mut node_positions);
157
158 for (id, (position, size)) in node_positions {
160 let rect = Rect::new(position, size);
161 layout.add_node(id, rect);
162 }
163
164 self.add_tree_edges(tree, &mut layout);
166
167 Ok(layout)
168 }
169
170 fn balloon_layout(&self, tree: &TreeNode) -> crate::Result<Layout> {
172 let mut layout = Layout::new();
173 let mut node_positions = HashMap::new();
174
175 let root_size = self.get_node_size(tree);
177 let root_pos = Point::new(0.0, 0.0);
178 node_positions.insert(tree.id.clone(), (root_pos, root_size));
179
180 if !tree.children.is_empty() {
182 let cluster_radius = self.config.level_distance;
183 self.position_balloon_children(&tree.children, root_pos, cluster_radius, &mut node_positions);
184 }
185
186 for (id, (position, size)) in node_positions {
188 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
189 layout.add_node(id, rect);
190 }
191
192 self.add_tree_edges(tree, &mut layout);
194
195 Ok(layout)
196 }
197
198 fn get_node_size(&self, node: &TreeNode) -> Size {
200 node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height))
201 }
202
203 fn calculate_level_widths(&self, node: &TreeNode, level: usize, level_widths: &mut HashMap<usize, f64>) {
204 let node_size = self.get_node_size(node);
205 let current_width = level_widths.get(&level).unwrap_or(&0.0);
206 level_widths.insert(level, current_width + node_size.width + self.config.sibling_distance);
207
208 for child in &node.children {
209 self.calculate_level_widths(child, level + 1, level_widths);
210 }
211 }
212
213 fn position_layered_node(
214 &self,
215 node: &TreeNode,
216 level: usize,
217 _parent_x: f64,
218 level_widths: &HashMap<usize, f64>,
219 current_positions: &mut HashMap<usize, f64>,
220 node_positions: &mut HashMap<String, (Point, Size)>,
221 ) {
222 let node_size = self.get_node_size(node);
223 let level_width = level_widths.get(&level).unwrap_or(&0.0);
224 let default_x = -level_width / 2.0;
225 let current_x = current_positions.get(&level).unwrap_or(&default_x);
226
227 let x = if level == 0 {
228 0.0 }
230 else {
231 *current_x + node_size.width / 2.0
232 };
233
234 let y = level as f64 * self.config.level_distance;
235
236 node_positions.insert(node.id.clone(), (Point::new(x, y), node_size));
237 current_positions.insert(level, current_x + node_size.width + self.config.sibling_distance);
238
239 for child in &node.children {
241 self.position_layered_node(child, level + 1, x, level_widths, current_positions, node_positions);
242 }
243 }
244
245 fn position_radial_children(
246 &self,
247 children: &[TreeNode],
248 center: Point,
249 radius: f64,
250 start_angle: f64,
251 angle_span: f64,
252 level: usize,
253 node_positions: &mut HashMap<String, (Point, Size)>,
254 ) {
255 if children.is_empty() {
256 return;
257 }
258
259 let angle_step = angle_span / children.len() as f64;
260
261 for (i, child) in children.iter().enumerate() {
262 let angle = start_angle + i as f64 * angle_step + angle_step / 2.0;
263 let child_pos = Point::new(center.x + radius * angle.cos(), center.y + radius * angle.sin());
264
265 let child_size = self.get_node_size(child);
266 node_positions.insert(child.id.clone(), (child_pos, child_size));
267
268 if !child.children.is_empty() {
270 let child_radius = radius + self.config.level_distance;
271 let child_angle_span = angle_step * 0.8; self.position_radial_children(
273 &child.children,
274 child_pos,
275 child_radius,
276 angle - child_angle_span / 2.0,
277 child_angle_span,
278 level + 1,
279 node_positions,
280 );
281 }
282 }
283 }
284
285 fn position_compact_node(&self, node: &TreeNode, x: f64, y: f64, level: usize) -> PositionedTreeNode {
286 let size = self.get_node_size(node);
287 let mut positioned_children = Vec::new();
288 let mut child_x = x;
289
290 for child in &node.children {
291 let positioned_child = self.position_compact_node(child, child_x, y + self.config.level_distance, level + 1);
292 child_x += positioned_child.subtree_width + self.config.sibling_distance;
293 positioned_children.push(positioned_child);
294 }
295
296 let subtree_width = if positioned_children.is_empty() {
297 size.width
298 }
299 else {
300 positioned_children.iter().map(|c| c.subtree_width).sum::<f64>()
301 + (positioned_children.len() - 1) as f64 * self.config.sibling_distance
302 };
303
304 let node_x = if positioned_children.is_empty() { x } else { x + subtree_width / 2.0 - size.width / 2.0 };
306
307 PositionedTreeNode {
308 id: node.id.clone(),
309 position: Point::new(node_x, y),
310 size,
311 subtree_width,
312 children: positioned_children,
313 }
314 }
315
316 fn position_balloon_children(
317 &self,
318 children: &[TreeNode],
319 center: Point,
320 radius: f64,
321 node_positions: &mut HashMap<String, (Point, Size)>,
322 ) {
323 if children.is_empty() {
324 return;
325 }
326
327 let angle_step = 2.0 * std::f64::consts::PI / children.len() as f64;
329
330 for (i, child) in children.iter().enumerate() {
331 let angle = i as f64 * angle_step;
332 let child_pos = Point::new(center.x + radius * angle.cos(), center.y + radius * angle.sin());
333
334 let child_size = self.get_node_size(child);
335 node_positions.insert(child.id.clone(), (child_pos, child_size));
336
337 if !child.children.is_empty() {
339 let child_radius = radius * 0.6; self.position_balloon_children(&child.children, child_pos, child_radius, node_positions);
341 }
342 }
343 }
344
345 fn extract_positions(&self, positioned_node: &PositionedTreeNode, positions: &mut HashMap<String, (Point, Size)>) {
346 positions.insert(positioned_node.id.clone(), (positioned_node.position, positioned_node.size));
347
348 for child in &positioned_node.children {
349 self.extract_positions(child, positions);
350 }
351 }
352
353 fn add_tree_edges(&self, node: &TreeNode, layout: &mut Layout) {
354 for child in &node.children {
355 let edge = Edge::new(node.id.clone(), child.id.clone());
356 layout.add_edge(edge);
357
358 self.add_tree_edges(child, layout);
360 }
361 }
362}
363
364#[derive(Debug, Clone)]
366pub struct TreeLayoutConfig {
367 pub node_width: f64,
368 pub node_height: f64,
369 pub level_distance: f64,
370 pub sibling_distance: f64,
371 pub subtree_distance: f64,
372}
373
374impl Default for TreeLayoutConfig {
375 fn default() -> Self {
376 Self { node_width: 100.0, node_height: 40.0, level_distance: 80.0, sibling_distance: 20.0, subtree_distance: 40.0 }
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub enum TreeLayoutAlgorithm {
383 Layered, Radial, Compact, Balloon, }
388
389#[derive(Debug, Clone)]
391struct PositionedTreeNode {
392 id: String,
393 position: Point,
394 size: Size,
395 subtree_width: f64,
396 children: Vec<PositionedTreeNode>,
397}
398
399pub struct TreeRenderer {
401 config: TreeRenderConfig,
402}
403
404impl TreeRenderer {
405 pub fn new() -> Self {
406 Self { config: TreeRenderConfig::default() }
407 }
408
409 pub fn with_config(mut self, config: TreeRenderConfig) -> Self {
410 self.config = config;
411 self
412 }
413
414 fn get_node_label(&self, tree: &TreeNode, node_id: &str) -> String {
476 self.find_node_label(tree, node_id).unwrap_or_else(|| node_id.to_string())
477 }
478
479 fn find_node_label(&self, node: &TreeNode, target_id: &str) -> Option<String> {
480 if node.id == target_id {
481 return Some(node.label.clone());
482 }
483
484 for child in &node.children {
485 if let Some(label) = self.find_node_label(child, target_id) {
486 return Some(label);
487 }
488 }
489
490 None
491 }
492}
493
494impl Default for TreeRenderer {
495 fn default() -> Self {
496 Self::new()
497 }
498}
499
500#[derive(Debug, Clone)]
502pub struct TreeRenderConfig {
503 pub node_fill_color: String,
504 pub node_stroke_color: String,
505 pub node_stroke_width: f64,
506 pub edge_color: String,
507 pub edge_width: f64,
508 pub text_color: String,
509 pub font_family: String,
510 pub font_size: f64,
511}
512
513impl Default for TreeRenderConfig {
514 fn default() -> Self {
515 Self {
516 node_fill_color: "#ffffff".to_string(),
517 node_stroke_color: "#000000".to_string(),
518 node_stroke_width: 1.0,
519 edge_color: "#666666".to_string(),
520 edge_width: 1.0,
521 text_color: "#000000".to_string(),
522 font_family: "Arial, sans-serif".to_string(),
523 font_size: 12.0,
524 }
525 }
526}