1#![doc = "Layout algorithms for visualizing code structures"]
2
3use crate::geometry::{Point, Rect, Size};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct LayoutConfig {
10 pub node_width: f64,
11 pub node_height: f64,
12 pub horizontal_spacing: f64,
13 pub vertical_spacing: f64,
14 pub margin: f64,
15 pub padding: f64,
16}
17
18impl Default for LayoutConfig {
19 fn default() -> Self {
20 Self {
21 node_width: 120.0,
22 node_height: 60.0,
23 horizontal_spacing: 40.0,
24 vertical_spacing: 30.0,
25 margin: 20.0,
26 padding: 10.0,
27 }
28 }
29}
30
31#[derive(Debug, Clone)]
33pub struct Layout {
34 pub bounds: Rect,
35 pub nodes: HashMap<String, Rect>,
36 pub edges: Vec<Edge>,
37}
38
39impl Layout {
40 pub fn new() -> Self {
41 Self { bounds: Rect::default(), nodes: HashMap::new(), edges: Vec::new() }
42 }
43
44 pub fn add_node(&mut self, id: String, rect: Rect) {
45 self.nodes.insert(id, rect);
46 self.update_bounds();
47 }
48
49 pub fn add_edge(&mut self, edge: Edge) {
50 self.edges.push(edge);
51 }
52
53 fn update_bounds(&mut self) {
54 if self.nodes.is_empty() {
55 self.bounds = Rect::default();
56 return;
57 }
58
59 let mut min_x = f64::INFINITY;
60 let mut min_y = f64::INFINITY;
61 let mut max_x = f64::NEG_INFINITY;
62 let mut max_y = f64::NEG_INFINITY;
63
64 for rect in self.nodes.values() {
65 min_x = min_x.min(rect.min_x());
66 min_y = min_y.min(rect.min_y());
67 max_x = max_x.max(rect.max_x());
68 max_y = max_y.max(rect.max_y());
69 }
70
71 self.bounds = Rect::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y);
72 }
73}
74
75impl Default for Layout {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct Edge {
84 pub from: String,
85 pub to: String,
86 pub points: Vec<Point>,
87 pub label: Option<String>,
88}
89
90impl Edge {
91 pub fn new(from: String, to: String) -> Self {
92 Self { from, to, points: Vec::new(), label: None }
93 }
94
95 pub fn with_label(mut self, label: String) -> Self {
96 self.label = Some(label);
97 self
98 }
99
100 pub fn with_points(mut self, points: Vec<Point>) -> Self {
101 self.points = points;
102 self
103 }
104}
105
106pub trait LayoutEngine {
108 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout>;
109}
110
111#[derive(Debug, Clone)]
113pub struct LayoutNode {
114 pub id: String,
115 pub size: Size,
116 pub label: String,
117 pub node_type: NodeType,
118}
119
120impl LayoutNode {
121 pub fn new(id: String, label: String) -> Self {
122 Self { id, label, size: Size::default(), node_type: NodeType::Default }
123 }
124
125 pub fn with_size(mut self, size: Size) -> Self {
126 self.size = size;
127 self
128 }
129
130 pub fn with_type(mut self, node_type: NodeType) -> Self {
131 self.node_type = node_type;
132 self
133 }
134}
135
136#[derive(Debug, Clone)]
138pub struct LayoutEdge {
139 pub from: String,
140 pub to: String,
141 pub label: Option<String>,
142 pub edge_type: EdgeType,
143}
144
145impl LayoutEdge {
146 pub fn new(from: String, to: String) -> Self {
147 Self { from, to, label: None, edge_type: EdgeType::Default }
148 }
149
150 pub fn with_label(mut self, label: String) -> Self {
151 self.label = Some(label);
152 self
153 }
154
155 pub fn with_type(mut self, edge_type: EdgeType) -> Self {
156 self.edge_type = edge_type;
157 self
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum NodeType {
164 Default,
165 Function,
166 Struct,
167 Enum,
168 Variable,
169 Constant,
170 Module,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum EdgeType {
176 Default,
177 Dependency,
178 Inheritance,
179 Association,
180 Composition,
181 Call,
182}
183
184pub struct HierarchicalLayout {
186 direction: LayoutDirection,
187}
188
189impl HierarchicalLayout {
190 pub fn new(direction: LayoutDirection) -> Self {
191 Self { direction }
192 }
193}
194
195impl LayoutEngine for HierarchicalLayout {
196 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout> {
197 let mut layout = Layout::new();
198
199 if nodes.is_empty() {
200 return Ok(layout);
201 }
202
203 let hierarchy = build_hierarchy(nodes, edges)?;
205
206 let positioned_nodes = match self.direction {
208 LayoutDirection::TopDown => layout_top_down(&hierarchy, config),
209 LayoutDirection::LeftRight => layout_left_right(&hierarchy, config),
210 LayoutDirection::BottomUp => layout_bottom_up(&hierarchy, config),
211 LayoutDirection::RightLeft => layout_right_left(&hierarchy, config),
212 };
213
214 for (id, rect) in positioned_nodes {
216 layout.add_node(id, rect);
217 }
218
219 for edge in edges {
221 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
222 let routed_edge = route_edge(from_rect, to_rect, &edge.from, &edge.to, edge.label.clone());
223 layout.add_edge(routed_edge);
224 }
225 }
226
227 Ok(layout)
228 }
229}
230
231pub struct ForceDirectedLayout {
233 iterations: usize,
234 spring_strength: f64,
235 repulsion_strength: f64,
236 damping: f64,
237}
238
239impl ForceDirectedLayout {
240 pub fn new() -> Self {
241 Self { iterations: 100, spring_strength: 0.1, repulsion_strength: 1000.0, damping: 0.9 }
242 }
243
244 pub fn with_iterations(mut self, iterations: usize) -> Self {
245 self.iterations = iterations;
246 self
247 }
248
249 pub fn with_spring_strength(mut self, strength: f64) -> Self {
250 self.spring_strength = strength;
251 self
252 }
253
254 pub fn with_repulsion_strength(mut self, strength: f64) -> Self {
255 self.repulsion_strength = strength;
256 self
257 }
258}
259
260impl Default for ForceDirectedLayout {
261 fn default() -> Self {
262 Self::new()
263 }
264}
265
266impl LayoutEngine for ForceDirectedLayout {
267 fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout> {
268 let mut layout = Layout::new();
269
270 if nodes.is_empty() {
271 return Ok(layout);
272 }
273
274 let mut positions: HashMap<String, Point> = HashMap::new();
276 let mut velocities: HashMap<String, Point> = HashMap::new();
277
278 for (i, node) in nodes.iter().enumerate() {
279 let angle = 2.0 * std::f64::consts::PI * i as f64 / nodes.len() as f64;
280 let radius = 100.0;
281 positions.insert(node.id.clone(), Point::new(radius * angle.cos(), radius * angle.sin()));
282 velocities.insert(node.id.clone(), Point::origin());
283 }
284
285 for _ in 0..self.iterations {
287 let mut forces: HashMap<String, Point> = HashMap::new();
288
289 for node in nodes {
291 forces.insert(node.id.clone(), Point::origin());
292 }
293
294 for i in 0..nodes.len() {
296 for j in (i + 1)..nodes.len() {
297 let node1 = &nodes[i];
298 let node2 = &nodes[j];
299
300 if let (Some(pos1), Some(pos2)) = (positions.get(&node1.id), positions.get(&node2.id)) {
301 let diff = *pos1 - *pos2;
302 let distance = pos1.distance_to(pos2).max(1.0);
303 let force_magnitude = self.repulsion_strength / (distance * distance);
304 let force_direction = Point::new(diff.x / distance, diff.y / distance);
305 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
306
307 *forces.get_mut(&node1.id).unwrap() = *forces.get(&node1.id).unwrap() + force;
308 *forces.get_mut(&node2.id).unwrap() = *forces.get(&node2.id).unwrap() - force;
309 }
310 }
311 }
312
313 for edge in edges {
315 if let (Some(pos1), Some(pos2)) = (positions.get(&edge.from), positions.get(&edge.to)) {
316 let diff = *pos2 - *pos1;
317 let distance = pos1.distance_to(pos2);
318 let ideal_length = config.horizontal_spacing;
319 let force_magnitude = self.spring_strength * (distance - ideal_length);
320 let force_direction = Point::new(diff.x / distance, diff.y / distance);
321 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
322
323 *forces.get_mut(&edge.from).unwrap() = *forces.get(&edge.from).unwrap() + force;
324 *forces.get_mut(&edge.to).unwrap() = *forces.get(&edge.to).unwrap() - force;
325 }
326 }
327
328 for node in nodes {
330 if let (Some(force), Some(velocity), Some(position)) =
331 (forces.get(&node.id), velocities.get_mut(&node.id), positions.get_mut(&node.id))
332 {
333 *velocity = Point::new(velocity.x * self.damping + force.x, velocity.y * self.damping + force.y);
334 *position = *position + *velocity;
335 }
336 }
337 }
338
339 for node in nodes {
341 if let Some(position) = positions.get(&node.id) {
342 let rect =
343 Rect::new(Point::new(position.x - node.size.width / 2.0, position.y - node.size.height / 2.0), node.size);
344 layout.add_node(node.id.clone(), rect);
345 }
346 }
347
348 for edge in edges {
350 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
351 let routed_edge = route_edge(from_rect, to_rect, &edge.from, &edge.to, edge.label.clone());
352 layout.add_edge(routed_edge);
353 }
354 }
355
356 Ok(layout)
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum LayoutDirection {
363 TopDown,
364 BottomUp,
365 LeftRight,
366 RightLeft,
367}
368
369#[derive(Debug, Clone)]
371struct HierarchyNode {
372 id: String,
373 children: Vec<HierarchyNode>,
374 size: Size,
375}
376
377fn build_hierarchy(nodes: &[LayoutNode], edges: &[LayoutEdge]) -> crate::Result<Vec<HierarchyNode>> {
379 let mut children_map: HashMap<String, Vec<String>> = HashMap::new();
381 let mut has_parent: std::collections::HashSet<String> = std::collections::HashSet::new();
382
383 for edge in edges {
384 children_map.entry(edge.from.clone()).or_default().push(edge.to.clone());
385 has_parent.insert(edge.to.clone());
386 }
387
388 let mut roots = Vec::new();
389 for node in nodes {
390 if !has_parent.contains(&node.id) {
391 roots.push(build_hierarchy_node(&node.id, nodes, &children_map));
392 }
393 }
394
395 Ok(roots)
396}
397
398fn build_hierarchy_node(id: &str, nodes: &[LayoutNode], children_map: &HashMap<String, Vec<String>>) -> HierarchyNode {
399 let node = nodes.iter().find(|n| n.id == id).unwrap();
400 let children = children_map
401 .get(id)
402 .map(|child_ids| child_ids.iter().map(|child_id| build_hierarchy_node(child_id, nodes, children_map)).collect())
403 .unwrap_or_default();
404
405 HierarchyNode { id: id.to_string(), children, size: node.size }
406}
407
408fn layout_top_down(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
409 let mut positions = HashMap::new();
410 let mut current_y = config.margin;
411
412 for root in hierarchy {
413 layout_node_top_down(root, config.margin, &mut current_y, config, &mut positions);
414 current_y += config.vertical_spacing;
415 }
416
417 positions
418}
419
420fn layout_node_top_down(
421 node: &HierarchyNode,
422 x: f64,
423 y: &mut f64,
424 config: &LayoutConfig,
425 positions: &mut HashMap<String, Rect>,
426) {
427 let rect = Rect::new(Point::new(x, *y), node.size);
428 positions.insert(node.id.clone(), rect);
429
430 *y += node.size.height + config.vertical_spacing;
431
432 let mut child_x = x + config.horizontal_spacing;
433 for child in &node.children {
434 layout_node_top_down(child, child_x, y, config, positions);
435 child_x += child.size.width + config.horizontal_spacing;
436 }
437}
438
439fn layout_left_right(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
440 let mut positions = HashMap::new();
441 let mut current_x = config.margin;
442
443 for root in hierarchy {
444 layout_node_left_right(root, &mut current_x, config.margin, config, &mut positions);
445 current_x += config.horizontal_spacing;
446 }
447
448 positions
449}
450
451fn layout_node_left_right(
452 node: &HierarchyNode,
453 x: &mut f64,
454 y: f64,
455 config: &LayoutConfig,
456 positions: &mut HashMap<String, Rect>,
457) {
458 let rect = Rect::new(Point::new(*x, y), node.size);
459 positions.insert(node.id.clone(), rect);
460
461 *x += node.size.width + config.horizontal_spacing;
462
463 let mut child_y = y + config.vertical_spacing;
464 for child in &node.children {
465 layout_node_left_right(child, x, child_y, config, positions);
466 child_y += child.size.height + config.vertical_spacing;
467 }
468}
469
470fn layout_bottom_up(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
471 layout_top_down(hierarchy, config)
473}
474
475fn layout_right_left(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
476 layout_left_right(hierarchy, config)
478}
479
480fn route_edge(from_rect: &Rect, to_rect: &Rect, from_id: &str, to_id: &str, label: Option<String>) -> Edge {
481 let from_center = from_rect.center();
483 let to_center = to_rect.center();
484
485 Edge { from: from_id.to_string(), to: to_id.to_string(), points: vec![from_center, to_center], label }
486}