1#![doc = "Layout algorithms for visualizing code structures"]
2
3use crate::geometry::{Point, Rect, Size};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct LayoutConfig {
10 pub node_width: f64,
12 pub node_height: f64,
14 pub horizontal_spacing: f64,
16 pub vertical_spacing: f64,
18 pub margin: f64,
20 pub padding: f64,
22}
23
24impl Default for LayoutConfig {
25 fn default() -> Self {
26 Self { node_width: 120.0, node_height: 60.0, horizontal_spacing: 40.0, vertical_spacing: 30.0, margin: 20.0, padding: 10.0 }
27 }
28}
29
30#[derive(Debug, Clone)]
32pub struct PositionedNode {
33 pub id: String,
35 pub label: String,
37 pub rect: Rect,
39 pub node_type: NodeType,
41}
42
43#[derive(Debug, Clone)]
45pub struct Layout {
46 pub bounds: Rect,
48 pub nodes: HashMap<String, PositionedNode>,
50 pub edges: Vec<Edge>,
52}
53
54impl Layout {
55 pub fn new() -> Self {
57 Self { bounds: Rect::default(), nodes: HashMap::new(), edges: Vec::new() }
58 }
59
60 pub fn add_node(&mut self, id: String, rect: Rect) {
62 let label = id.clone();
63 self.nodes.insert(id.clone(), PositionedNode { id, label, rect, node_type: NodeType::Default });
64 self.update_bounds()
65 }
66
67 pub fn add_node_with_metadata(&mut self, id: String, label: String, rect: Rect, node_type: NodeType) {
69 self.nodes.insert(id.clone(), PositionedNode { id, label, rect, node_type });
70 self.update_bounds()
71 }
72
73 pub fn add_edge(&mut self, edge: Edge) {
75 self.edges.push(edge)
76 }
77
78 fn update_bounds(&mut self) {
79 if self.nodes.is_empty() {
80 self.bounds = Rect::default();
81 return;
82 }
83
84 let mut min_x = f64::INFINITY;
85 let mut min_y = f64::INFINITY;
86 let mut max_x = f64::NEG_INFINITY;
87 let mut max_y = f64::NEG_INFINITY;
88
89 for node in self.nodes.values() {
90 let rect = &node.rect;
91 min_x = min_x.min(rect.min_x());
92 min_y = min_y.min(rect.min_y());
93 max_x = max_x.max(rect.max_x());
94 max_y = max_y.max(rect.max_y());
95 }
96
97 self.bounds = Rect::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y);
98 }
99}
100
101impl Default for Layout {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct Edge {
110 pub from: String,
112 pub to: String,
114 pub points: Vec<Point>,
116 pub label: Option<String>,
118}
119
120impl Edge {
121 pub fn new(from: String, to: String) -> Self {
123 Self { from, to, points: Vec::new(), label: None }
124 }
125
126 pub fn with_label(mut self, label: String) -> Self {
128 self.label = Some(label);
129 self
130 }
131
132 pub fn with_points(mut self, points: Vec<Point>) -> Self {
134 self.points = points;
135 self
136 }
137}
138
139pub trait LayoutEngine {
141 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout>;
143}
144
145#[derive(Debug, Clone)]
147pub struct LayoutNode {
148 pub id: String,
150 pub size: Size,
152 pub label: String,
154 pub node_type: NodeType,
156}
157
158impl LayoutNode {
159 pub fn new(id: String, label: String) -> Self {
161 Self { id, label, size: Size::default(), node_type: NodeType::Default }
162 }
163
164 pub fn with_size(mut self, size: Size) -> Self {
166 self.size = size;
167 self
168 }
169
170 pub fn with_type(mut self, node_type: NodeType) -> Self {
172 self.node_type = node_type;
173 self
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct LayoutEdge {
180 pub from: String,
182 pub to: String,
184 pub label: Option<String>,
186 pub edge_type: EdgeType,
188}
189
190impl LayoutEdge {
191 pub fn new(from: String, to: String) -> Self {
193 Self { from, to, label: None, edge_type: EdgeType::Default }
194 }
195
196 pub fn with_label(mut self, label: String) -> Self {
198 self.label = Some(label);
199 self
200 }
201
202 pub fn with_type(mut self, edge_type: EdgeType) -> Self {
204 self.edge_type = edge_type;
205 self
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum NodeType {
212 Default,
214 Function,
216 Struct,
218 Enum,
220 Variable,
222 Constant,
224 Module,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum EdgeType {
231 Default,
233 Dependency,
235 Inheritance,
237 Association,
239 Composition,
241 Call,
243}
244
245pub struct HierarchicalLayout {
247 direction: LayoutDirection,
248}
249
250impl HierarchicalLayout {
251 pub fn new(direction: LayoutDirection) -> Self {
253 Self { direction }
254 }
255}
256
257impl LayoutEngine for HierarchicalLayout {
258 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout> {
260 let mut layout = Layout::new();
261
262 if nodes.is_empty() {
263 return Ok(layout);
264 }
265
266 let hierarchy = build_hierarchy(nodes, edges)?;
268
269 let positioned_nodes = match self.direction {
271 LayoutDirection::TopDown => layout_top_down(&hierarchy, config),
272 LayoutDirection::LeftRight => layout_left_right(&hierarchy, config),
273 LayoutDirection::BottomUp => layout_bottom_up(&hierarchy, config),
274 LayoutDirection::RightLeft => layout_right_left(&hierarchy, config),
275 };
276
277 for (id, rect) in positioned_nodes {
279 layout.add_node(id, rect);
280 }
281
282 for edge in edges {
284 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
285 let routed_edge = route_edge(&from_node.rect, &to_node.rect, &edge.from, &edge.to, edge.label.clone());
286 layout.add_edge(routed_edge);
287 }
288 }
289
290 Ok(layout)
291 }
292}
293
294pub struct ForceDirectedLayout {
296 iterations: usize,
297 spring_strength: f64,
298 repulsion_strength: f64,
299 damping: f64,
300}
301
302impl ForceDirectedLayout {
303 pub fn new() -> Self {
305 Self { iterations: 100, spring_strength: 0.1, repulsion_strength: 1000.0, damping: 0.9 }
306 }
307
308 pub fn with_iterations(mut self, iterations: usize) -> Self {
310 self.iterations = iterations;
311 self
312 }
313
314 pub fn with_spring_strength(mut self, strength: f64) -> Self {
316 self.spring_strength = strength;
317 self
318 }
319
320 pub fn with_repulsion_strength(mut self, strength: f64) -> Self {
322 self.repulsion_strength = strength;
323 self
324 }
325}
326
327impl Default for ForceDirectedLayout {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333impl LayoutEngine for ForceDirectedLayout {
334 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout> {
336 let mut layout = Layout::new();
337
338 if nodes.is_empty() {
339 return Ok(layout);
340 }
341
342 let mut positions: HashMap<String, Point> = HashMap::new();
344 let mut velocities: HashMap<String, Point> = HashMap::new();
345
346 for (i, node) in nodes.iter().enumerate() {
347 let angle = 2.0 * std::f64::consts::PI * i as f64 / nodes.len() as f64;
348 let radius = 100.0;
349 positions.insert(node.id.clone(), Point::new(radius * angle.cos(), radius * angle.sin()));
350 velocities.insert(node.id.clone(), Point::origin());
351 }
352
353 for _ in 0..self.iterations {
355 let mut forces: HashMap<String, Point> = HashMap::new();
356
357 for node in nodes {
359 forces.insert(node.id.clone(), Point::origin());
360 }
361
362 for i in 0..nodes.len() {
364 for j in (i + 1)..nodes.len() {
365 let node1 = &nodes[i];
366 let node2 = &nodes[j];
367
368 if let (Some(pos1), Some(pos2)) = (positions.get(&node1.id), positions.get(&node2.id)) {
369 let diff = *pos1 - *pos2;
370 let distance = pos1.distance_to(pos2).max(1.0);
371 let force_magnitude = self.repulsion_strength / (distance * distance);
372 let force_direction = Point::new(diff.x / distance, diff.y / distance);
373 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
374
375 *forces.get_mut(&node1.id).unwrap() = *forces.get(&node1.id).unwrap() + force;
376 *forces.get_mut(&node2.id).unwrap() = *forces.get(&node2.id).unwrap() - force
377 }
378 }
379 }
380
381 for edge in edges {
383 if let (Some(pos1), Some(pos2)) = (positions.get(&edge.from), positions.get(&edge.to)) {
384 let diff = *pos2 - *pos1;
385 let distance = pos1.distance_to(pos2);
386 let ideal_length = config.horizontal_spacing;
387 let force_magnitude = self.spring_strength * (distance - ideal_length);
388 let force_direction = Point::new(diff.x / distance, diff.y / distance);
389 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
390
391 *forces.get_mut(&edge.from).unwrap() = *forces.get(&edge.from).unwrap() + force;
392 *forces.get_mut(&edge.to).unwrap() = *forces.get(&edge.to).unwrap() - force
393 }
394 }
395
396 for node in nodes {
398 if let (Some(force), Some(velocity), Some(position)) = (forces.get(&node.id), velocities.get_mut(&node.id), positions.get_mut(&node.id)) {
399 *velocity = Point::new(velocity.x * self.damping + force.x, velocity.y * self.damping + force.y);
400 *position = *position + *velocity
401 }
402 }
403 }
404
405 for node in nodes {
407 if let Some(position) = positions.get(&node.id) {
408 let rect = Rect::new(Point::new(position.x - node.size.width / 2.0, position.y - node.size.height / 2.0), node.size);
409 layout.add_node(node.id.clone(), rect)
410 }
411 }
412
413 for edge in edges {
415 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
416 let routed_edge = route_edge(&from_node.rect, &to_node.rect, &edge.from, &edge.to, edge.label.clone());
417 layout.add_edge(routed_edge)
418 }
419 }
420
421 Ok(layout)
422 }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum LayoutDirection {
428 TopDown,
430 BottomUp,
432 LeftRight,
434 RightLeft,
436}
437
438#[derive(Debug, Clone)]
440struct HierarchyNode {
441 id: String,
442 children: Vec<HierarchyNode>,
443 size: Size,
444}
445
446fn build_hierarchy(nodes: &[LayoutNode], edges: &[LayoutEdge]) -> crate::Result<Vec<HierarchyNode>> {
448 let mut children_map: HashMap<String, Vec<String>> = HashMap::new();
450 let mut has_parent: std::collections::HashSet<String> = std::collections::HashSet::new();
451
452 for edge in edges {
453 children_map.entry(edge.from.clone()).or_default().push(edge.to.clone());
454 has_parent.insert(edge.to.clone());
455 }
456
457 let mut roots = Vec::new();
458 for node in nodes {
459 if !has_parent.contains(&node.id) {
460 roots.push(build_hierarchy_node(&node.id, nodes, &children_map))
461 }
462 }
463
464 Ok(roots)
465}
466
467fn build_hierarchy_node(id: &str, nodes: &[LayoutNode], children_map: &HashMap<String, Vec<String>>) -> HierarchyNode {
468 let node = nodes.iter().find(|n| n.id == id).unwrap();
469 let children = children_map.get(id).map(|child_ids| child_ids.iter().map(|child_id| build_hierarchy_node(child_id, nodes, children_map)).collect()).unwrap_or_default();
470
471 HierarchyNode { id: id.to_string(), children, size: node.size }
472}
473
474fn layout_top_down(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
475 let mut positions = HashMap::new();
476 let mut current_y = config.margin;
477
478 for root in hierarchy {
479 layout_node_top_down(root, config.margin, &mut current_y, config, &mut positions);
480 current_y += config.vertical_spacing
481 }
482
483 positions
484}
485
486fn layout_node_top_down(node: &HierarchyNode, x: f64, y: &mut f64, config: &LayoutConfig, positions: &mut HashMap<String, Rect>) {
487 let rect = Rect::new(Point::new(x, *y), node.size);
488 positions.insert(node.id.clone(), rect);
489
490 *y += node.size.height + config.vertical_spacing;
491
492 let mut child_x = x + config.horizontal_spacing;
493 for child in &node.children {
494 layout_node_top_down(child, child_x, y, config, positions);
495 child_x += child.size.width + config.horizontal_spacing
496 }
497}
498
499fn layout_left_right(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
500 let mut positions = HashMap::new();
501 let mut current_x = config.margin;
502
503 for root in hierarchy {
504 layout_node_left_right(root, &mut current_x, config.margin, config, &mut positions);
505 current_x += config.horizontal_spacing
506 }
507
508 positions
509}
510
511fn layout_node_left_right(node: &HierarchyNode, x: &mut f64, y: f64, config: &LayoutConfig, positions: &mut HashMap<String, Rect>) {
512 let rect = Rect::new(Point::new(*x, y), node.size);
513 positions.insert(node.id.clone(), rect);
514
515 *x += node.size.width + config.horizontal_spacing;
516
517 let mut child_y = y + config.vertical_spacing;
518 for child in &node.children {
519 layout_node_left_right(child, x, child_y, config, positions);
520 child_y += child.size.height + config.vertical_spacing
521 }
522}
523
524fn layout_bottom_up(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
525 layout_top_down(hierarchy, config)
527}
528
529fn layout_right_left(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
530 layout_left_right(hierarchy, config)
532}
533
534fn route_edge(from_rect: &Rect, to_rect: &Rect, from_id: &str, to_id: &str, label: Option<String>) -> Edge {
535 let from_center = from_rect.center();
537 let to_center = to_rect.center();
538
539 Edge { from: from_id.to_string(), to: to_id.to_string(), points: vec![from_center, to_center], label }
540}