Skip to main content

oak_visualize/layout/
mod.rs

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/// Layout configuration
8#[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/// Positioned node in a layout
25#[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/// Layout result containing positioned elements
34#[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/// Edge connecting two nodes
91#[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
115/// Layout engine trait
116pub trait LayoutEngine {
117    fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout>;
118}
119
120/// Node to be laid out
121#[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/// Edge to be laid out
146#[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/// Node type for styling
171#[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/// Edge type for styling
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum EdgeType {
185    Default,
186    Dependency,
187    Inheritance,
188    Association,
189    Composition,
190    Call,
191}
192
193/// Hierarchical layout engine (tree-like structures)
194pub 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        // Build hierarchy from edges
213        let hierarchy = build_hierarchy(nodes, edges)?;
214
215        // Layout nodes hierarchically
216        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        // Add nodes to layout
224        for (id, rect) in positioned_nodes {
225            layout.add_node(id, rect);
226        }
227
228        // Add edges with routing
229        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
240/// Force-directed layout engine
241pub 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        // Initialize random positions
284        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        // Run force-directed simulation
295        for _ in 0..self.iterations {
296            let mut forces: HashMap<String, Point> = HashMap::new();
297
298            // Initialize forces
299            for node in nodes {
300                forces.insert(node.id.clone(), Point::origin());
301            }
302
303            // Repulsion forces
304            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            // Attraction forces (springs)
323            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            // Update positions
338            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        // Convert positions to rectangles
347        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        // Add edges
355        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/// Layout direction
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub enum LayoutDirection {
369    TopDown,
370    BottomUp,
371    LeftRight,
372    RightLeft,
373}
374
375/// Hierarchy node for tree layouts
376#[derive(Debug, Clone)]
377struct HierarchyNode {
378    id: String,
379    children: Vec<HierarchyNode>,
380    size: Size,
381}
382
383// Helper functions
384fn build_hierarchy(nodes: &[LayoutNode], edges: &[LayoutEdge]) -> crate::Result<Vec<HierarchyNode>> {
385    // Simple implementation - find roots and build tree
386    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    // Similar to top_down but reversed
463    layout_top_down(hierarchy, config)
464}
465
466fn layout_right_left(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
467    // Similar to left_right but reversed
468    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    // Simple straight line routing
473    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}