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 {
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/// Layout result containing positioned elements
32#[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/// Edge connecting two nodes
82#[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
106/// Layout engine trait
107pub trait LayoutEngine {
108    fn layout(&self, nodes: &[LayoutNode], edges: &[LayoutEdge], config: &LayoutConfig) -> crate::Result<Layout>;
109}
110
111/// Node to be laid out
112#[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/// Edge to be laid out
137#[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/// Node type for styling
162#[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/// Edge type for styling
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum EdgeType {
176    Default,
177    Dependency,
178    Inheritance,
179    Association,
180    Composition,
181    Call,
182}
183
184/// Hierarchical layout engine (tree-like structures)
185pub 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        // Build hierarchy from edges
204        let hierarchy = build_hierarchy(nodes, edges)?;
205
206        // Layout nodes hierarchically
207        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        // Add nodes to layout
215        for (id, rect) in positioned_nodes {
216            layout.add_node(id, rect);
217        }
218
219        // Add edges with routing
220        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
231/// Force-directed layout engine
232pub 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        // Initialize random positions
275        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        // Run force-directed simulation
286        for _ in 0..self.iterations {
287            let mut forces: HashMap<String, Point> = HashMap::new();
288
289            // Initialize forces
290            for node in nodes {
291                forces.insert(node.id.clone(), Point::origin());
292            }
293
294            // Repulsion forces
295            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            // Attraction forces (springs)
314            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            // Update positions
329            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        // Convert positions to rectangles
340        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        // Add edges
349        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/// Layout direction
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum LayoutDirection {
363    TopDown,
364    BottomUp,
365    LeftRight,
366    RightLeft,
367}
368
369/// Hierarchy node for tree layouts
370#[derive(Debug, Clone)]
371struct HierarchyNode {
372    id: String,
373    children: Vec<HierarchyNode>,
374    size: Size,
375}
376
377// Helper functions
378fn build_hierarchy(nodes: &[LayoutNode], edges: &[LayoutEdge]) -> crate::Result<Vec<HierarchyNode>> {
379    // Simple implementation - find roots and build tree
380    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    // Similar to top_down but reversed
472    layout_top_down(hierarchy, config)
473}
474
475fn layout_right_left(hierarchy: &[HierarchyNode], config: &LayoutConfig) -> HashMap<String, Rect> {
476    // Similar to left_right but reversed
477    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    // Simple straight line routing
482    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}