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 std::collections::HashMap;
5
6/// Layout configuration
7#[derive(Debug, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct LayoutConfig {
10    /// Default width for nodes.
11    pub node_width: f64,
12    /// Default height for nodes.
13    pub node_height: f64,
14    /// Horizontal spacing between nodes.
15    pub horizontal_spacing: f64,
16    /// Vertical spacing between nodes.
17    pub vertical_spacing: f64,
18    /// Margin around the layout.
19    pub margin: f64,
20    /// Padding inside the layout.
21    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/// Positioned node in a layout
31#[derive(Debug, Clone)]
32pub struct PositionedNode {
33    /// Unique identifier for the node.
34    pub id: String,
35    /// Display label for the node.
36    pub label: String,
37    /// Rectangle representing the node's position and size.
38    pub rect: Rect,
39    /// Type of the node for styling.
40    pub node_type: NodeType,
41}
42
43/// Layout result containing positioned elements
44#[derive(Debug, Clone)]
45pub struct Layout {
46    /// Bounding box of all elements in the layout.
47    pub bounds: Rect,
48    /// Map of node IDs to their positioned nodes.
49    pub nodes: HashMap<String, PositionedNode>,
50    /// List of edges connecting the nodes.
51    pub edges: Vec<Edge>,
52}
53
54impl Layout {
55    /// Creates a new empty layout.
56    pub fn new() -> Self {
57        Self { bounds: Rect::default(), nodes: HashMap::new(), edges: Vec::new() }
58    }
59
60    /// Adds a node to the layout at the specified rectangle.
61    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    /// Adds a node with explicit label and type.
68    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    /// Adds an edge to the layout.
74    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/// Edge connecting two nodes
108#[derive(Debug, Clone)]
109pub struct Edge {
110    /// ID of the source node.
111    pub from: String,
112    /// ID of the target node.
113    pub to: String,
114    /// Control points for the edge path.
115    pub points: Vec<Point>,
116    /// Optional label for the edge.
117    pub label: Option<String>,
118}
119
120impl Edge {
121    /// Creates a new edge between two nodes.
122    pub fn new(from: String, to: String) -> Self {
123        Self { from, to, points: Vec::new(), label: None }
124    }
125
126    /// Sets the label for the edge.
127    pub fn with_label(mut self, label: String) -> Self {
128        self.label = Some(label);
129        self
130    }
131
132    /// Sets the control points for the edge.
133    pub fn with_points(mut self, points: Vec<Point>) -> Self {
134        self.points = points;
135        self
136    }
137}
138
139/// Layout engine trait
140pub trait LayoutEngine {
141    /// Computes the layout for a set of nodes and edges.
142    fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout>;
143}
144
145/// Node to be laid out
146#[derive(Debug, Clone)]
147pub struct LayoutNode {
148    /// Unique identifier for the node.
149    pub id: String,
150    /// Preferred size of the node.
151    pub size: Size,
152    /// Display label for the node.
153    pub label: String,
154    /// Type of the node for styling.
155    pub node_type: NodeType,
156}
157
158impl LayoutNode {
159    /// Creates a new layout node with ID and label.
160    pub fn new(id: String, label: String) -> Self {
161        Self { id, label, size: Size::default(), node_type: NodeType::Default }
162    }
163
164    /// Sets the preferred size of the node.
165    pub fn with_size(mut self, size: Size) -> Self {
166        self.size = size;
167        self
168    }
169
170    /// Sets the type of the node.
171    pub fn with_type(mut self, node_type: NodeType) -> Self {
172        self.node_type = node_type;
173        self
174    }
175}
176
177/// Edge to be laid out
178#[derive(Debug, Clone)]
179pub struct LayoutEdge {
180    /// ID of the source node.
181    pub from: String,
182    /// ID of the target node.
183    pub to: String,
184    /// Optional label for the edge.
185    pub label: Option<String>,
186    /// Type of the edge for styling.
187    pub edge_type: EdgeType,
188}
189
190impl LayoutEdge {
191    /// Creates a new layout edge between two nodes.
192    pub fn new(from: String, to: String) -> Self {
193        Self { from, to, label: None, edge_type: EdgeType::Default }
194    }
195
196    /// Sets the label for the edge.
197    pub fn with_label(mut self, label: String) -> Self {
198        self.label = Some(label);
199        self
200    }
201
202    /// Sets the type of the edge.
203    pub fn with_type(mut self, edge_type: EdgeType) -> Self {
204        self.edge_type = edge_type;
205        self
206    }
207}
208
209/// Node type for styling
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum NodeType {
212    /// Default node type.
213    Default,
214    /// Function or method.
215    Function,
216    /// Struct or class.
217    Struct,
218    /// Enum definition.
219    Enum,
220    /// Variable or field.
221    Variable,
222    /// Constant value.
223    Constant,
224    /// Module or namespace.
225    Module,
226}
227
228/// Edge type for styling
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum EdgeType {
231    /// Default edge type.
232    Default,
233    /// General dependency.
234    Dependency,
235    /// Inheritance or implementation.
236    Inheritance,
237    /// Association between elements.
238    Association,
239    /// Composition relationship.
240    Composition,
241    /// Function or method call.
242    Call,
243}
244
245/// Hierarchical layout engine (tree-like structures)
246pub struct HierarchicalLayout {
247    direction: LayoutDirection,
248}
249
250impl HierarchicalLayout {
251    /// Creates a new hierarchical layout engine with the specified direction.
252    pub fn new(direction: LayoutDirection) -> Self {
253        Self { direction }
254    }
255}
256
257impl LayoutEngine for HierarchicalLayout {
258    /// Lays out nodes hierarchically in the specified direction.
259    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        // Build hierarchy from edges
267        let hierarchy = build_hierarchy(nodes, edges)?;
268
269        // Layout nodes hierarchically
270        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        // Add nodes to layout
278        for (id, rect) in positioned_nodes {
279            layout.add_node(id, rect);
280        }
281
282        // Add edges with routing
283        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
294/// Force-directed layout engine
295pub struct ForceDirectedLayout {
296    iterations: usize,
297    spring_strength: f64,
298    repulsion_strength: f64,
299    damping: f64,
300}
301
302impl ForceDirectedLayout {
303    /// Creates a new force-directed layout engine with default parameters.
304    pub fn new() -> Self {
305        Self { iterations: 100, spring_strength: 0.1, repulsion_strength: 1000.0, damping: 0.9 }
306    }
307
308    /// Sets the number of simulation iterations.
309    pub fn with_iterations(mut self, iterations: usize) -> Self {
310        self.iterations = iterations;
311        self
312    }
313
314    /// Sets the strength of the attractive spring forces.
315    pub fn with_spring_strength(mut self, strength: f64) -> Self {
316        self.spring_strength = strength;
317        self
318    }
319
320    /// Sets the strength of the repulsive forces between nodes.
321    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    /// Lays out nodes using a force-directed simulation.
335    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        // Initialize random positions
343        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        // Run force-directed simulation
354        for _ in 0..self.iterations {
355            let mut forces: HashMap<String, Point> = HashMap::new();
356
357            // Initialize forces
358            for node in nodes {
359                forces.insert(node.id.clone(), Point::origin());
360            }
361
362            // Repulsion forces
363            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            // Attraction forces (springs)
382            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            // Update positions
397            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        // Convert positions to rectangles
406        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        // Add edges
414        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/// Layout direction
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum LayoutDirection {
428    /// Top to bottom.
429    TopDown,
430    /// Bottom to top.
431    BottomUp,
432    /// Left to right.
433    LeftRight,
434    /// Right to left.
435    RightLeft,
436}
437
438/// Hierarchy node for tree layouts
439#[derive(Debug, Clone)]
440struct HierarchyNode {
441    id: String,
442    children: Vec<HierarchyNode>,
443    size: Size,
444}
445
446// Helper functions
447fn build_hierarchy(nodes: &[LayoutNode], edges: &[LayoutEdge]) -> crate::Result<Vec<HierarchyNode>> {
448    // Simple implementation - find roots and build tree
449    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    // Similar to top_down but reversed
526    layout_top_down(hierarchy, config)
527}
528
529fn layout_right_left(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
530    // Similar to left_right but reversed
531    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    // Simple straight line routing
536    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}