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