1#![doc = "Graph layout algorithms for dependency and relationship visualization"]
2
3use crate::{
4 geometry::{Point, Rect, Size},
5 layout::{Edge, Layout},
6};
7use std::collections::{HashMap, HashSet};
8
9#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct GraphNode {
13 pub id: String,
15 pub label: String,
17 pub node_type: String,
19 pub size: Option<Size>,
21 pub attributes: HashMap<String, String>,
23 pub weight: f64,
25}
26
27impl GraphNode {
28 pub fn new(id: String, label: String) -> Self {
30 Self { id, label, node_type: "default".to_string(), size: None, attributes: HashMap::new(), weight: 1.0 }
31 }
32
33 pub fn with_type(mut self, node_type: String) -> Self {
35 self.node_type = node_type;
36 self
37 }
38
39 pub fn with_size(mut self, size: Size) -> Self {
41 self.size = Some(size);
42 self
43 }
44
45 pub fn with_attribute(mut self, key: String, value: String) -> Self {
47 self.attributes.insert(key, value);
48 self
49 }
50
51 pub fn with_weight(mut self, weight: f64) -> Self {
53 self.weight = weight;
54 self
55 }
56}
57
58#[derive(Debug, Clone)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub struct GraphEdge {
62 pub from: String,
64 pub to: String,
66 pub label: Option<String>,
68 pub edge_type: String,
70 pub weight: f64,
72 pub directed: bool,
74 pub attributes: HashMap<String, String>,
76}
77
78impl GraphEdge {
79 pub fn new(from: String, to: String) -> Self {
81 Self { from, to, label: None, edge_type: "default".to_string(), weight: 1.0, directed: true, attributes: HashMap::new() }
82 }
83
84 pub fn with_label(mut self, label: String) -> Self {
86 self.label = Some(label);
87 self
88 }
89
90 pub fn with_type(mut self, edge_type: String) -> Self {
92 self.edge_type = edge_type;
93 self
94 }
95
96 pub fn with_weight(mut self, weight: f64) -> Self {
98 self.weight = weight;
99 self
100 }
101
102 pub fn undirected(mut self) -> Self {
104 self.directed = false;
105 self
106 }
107
108 pub fn with_attribute(mut self, key: String, value: String) -> Self {
110 self.attributes.insert(key, value);
111 self
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct Graph {
118 pub nodes: HashMap<String, GraphNode>,
120 pub edges: Vec<GraphEdge>,
122 pub directed: bool,
124}
125
126impl Graph {
127 pub fn new(directed: bool) -> Self {
129 Self { nodes: HashMap::new(), edges: Vec::new(), directed }
130 }
131
132 pub fn add_node(&mut self, node: GraphNode) {
134 self.nodes.insert(node.id.clone(), node);
135 }
136
137 pub fn add_edge(&mut self, edge: GraphEdge) {
139 self.edges.push(edge);
140 }
141
142 pub fn get_neighbors(&self, node_id: &str) -> Vec<&str> {
144 let mut neighbors = Vec::new();
145
146 for edge in &self.edges {
147 if edge.from == node_id {
148 neighbors.push(edge.to.as_str());
149 }
150 if !self.directed && edge.to == node_id {
151 neighbors.push(edge.from.as_str());
152 }
153 }
154
155 neighbors
156 }
157
158 pub fn get_degree(&self, node_id: &str) -> usize {
160 self.get_neighbors(node_id).len()
161 }
162
163 pub fn is_connected(&self) -> bool {
165 if self.nodes.is_empty() {
166 return true;
167 }
168
169 let start_node = self.nodes.keys().next().unwrap();
170 let mut visited = HashSet::new();
171 let mut stack = vec![start_node.as_str()];
172
173 while let Some(node) = stack.pop() {
174 if visited.contains(node) {
175 continue;
176 }
177
178 visited.insert(node);
179
180 for neighbor in self.get_neighbors(node) {
181 if !visited.contains(neighbor) {
182 stack.push(neighbor);
183 }
184 }
185 }
186
187 visited.len() == self.nodes.len()
188 }
189
190 pub fn find_cycles(&self) -> Vec<Vec<String>> {
192 let mut cycles = Vec::new();
193 let mut visited = HashSet::new();
194 let mut rec_stack = HashSet::new();
195 let mut path = Vec::new();
196
197 for node_id in self.nodes.keys() {
198 if !visited.contains(node_id) {
199 self.dfs_cycles(node_id, &mut visited, &mut rec_stack, &mut path, &mut cycles);
200 }
201 }
202
203 cycles
204 }
205
206 fn dfs_cycles(&self, node: &str, visited: &mut HashSet<String>, rec_stack: &mut HashSet<String>, path: &mut Vec<String>, cycles: &mut Vec<Vec<String>>) {
207 visited.insert(node.to_string());
208 rec_stack.insert(node.to_string());
209 path.push(node.to_string());
210
211 for neighbor in self.get_neighbors(node) {
212 if !visited.contains(neighbor) {
213 self.dfs_cycles(neighbor, visited, rec_stack, path, cycles);
214 }
215 else if rec_stack.contains(neighbor) {
216 if let Some(cycle_start) = path.iter().position(|n| n == neighbor) {
218 cycles.push(path[cycle_start..].to_vec());
219 }
220 }
221 }
222
223 rec_stack.remove(node);
224 path.pop();
225 }
226}
227
228impl crate::Visualize for Graph {
229 fn visualize(&self) -> crate::Result<String> {
230 GraphLayout::new().visualize(self)
231 }
232}
233
234pub struct GraphLayout {
236 algorithm: GraphLayoutAlgorithm,
237 config: GraphLayoutConfig,
238}
239
240impl Default for GraphLayout {
241 fn default() -> Self {
242 Self { algorithm: GraphLayoutAlgorithm::ForceDirected, config: GraphLayoutConfig::default() }
243 }
244}
245
246impl GraphLayout {
247 pub fn new() -> Self {
249 Self::default()
250 }
251
252 pub fn force_directed() -> Self {
254 Self::new().with_algorithm(GraphLayoutAlgorithm::ForceDirected)
255 }
256
257 pub fn circular() -> Self {
259 Self::new().with_algorithm(GraphLayoutAlgorithm::Circular)
260 }
261
262 pub fn with_algorithm(mut self, algorithm: GraphLayoutAlgorithm) -> Self {
264 self.algorithm = algorithm;
265 self
266 }
267
268 pub fn with_config(mut self, config: GraphLayoutConfig) -> Self {
270 self.config = config;
271 self
272 }
273
274 pub fn with_repulsion(mut self, repulsion: f64) -> Self {
276 self.config.repulsion_strength = repulsion;
277 self
278 }
279
280 pub fn with_attraction(mut self, attraction: f64) -> Self {
282 self.config.spring_strength = attraction;
283 self
284 }
285
286 pub fn with_iterations(mut self, iterations: usize) -> Self {
288 self.config.iterations = iterations;
289 self
290 }
291
292 pub fn visualize(&self, graph: &Graph) -> crate::Result<String> {
294 let layout = self.layout_graph(graph)?;
295 crate::render::SvgRenderer::new().render_layout(&layout)
296 }
297
298 pub fn layout_graph(&self, graph: &Graph) -> crate::Result<Layout> {
300 match self.algorithm {
301 GraphLayoutAlgorithm::ForceDirected => self.force_directed_layout(graph),
302 GraphLayoutAlgorithm::Circular => self.circular_layout(graph),
303 GraphLayoutAlgorithm::Hierarchical => self.hierarchical_layout(graph),
304 GraphLayoutAlgorithm::Grid => self.grid_layout(graph),
305 GraphLayoutAlgorithm::Organic => self.organic_layout(graph),
306 }
307 }
308
309 fn force_directed_layout(&self, graph: &Graph) -> crate::Result<Layout> {
311 let mut layout = Layout::new();
312
313 if graph.nodes.is_empty() {
314 return Ok(layout);
315 }
316
317 let mut positions: HashMap<String, Point> = HashMap::new();
318 let mut velocities: HashMap<String, Point> = HashMap::new();
319
320 for (i, node_id) in graph.nodes.keys().enumerate() {
322 let angle = 2.0 * std::f64::consts::PI * i as f64 / graph.nodes.len() as f64;
323 let radius = 100.0;
324 positions.insert(node_id.clone(), Point::new(radius * angle.cos(), radius * angle.sin()));
325 velocities.insert(node_id.clone(), Point::origin());
326 }
327
328 for _ in 0..self.config.iterations {
330 let mut forces: HashMap<String, Point> = HashMap::new();
331
332 for node_id in graph.nodes.keys() {
334 forces.insert(node_id.clone(), Point::origin());
335 }
336
337 let node_ids: Vec<_> = graph.nodes.keys().collect();
339 for i in 0..node_ids.len() {
340 for j in (i + 1)..node_ids.len() {
341 let node1_id = node_ids[i];
342 let node2_id = node_ids[j];
343
344 if let (Some(pos1), Some(pos2)) = (positions.get(node1_id), positions.get(node2_id)) {
345 let diff = *pos1 - *pos2;
346 let distance = pos1.distance_to(pos2).max(1.0);
347 let force_magnitude = self.config.repulsion_strength / (distance * distance);
348 let force_direction = Point::new(diff.x / distance, diff.y / distance);
349 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
350
351 *forces.get_mut(node1_id).unwrap() = *forces.get(node1_id).unwrap() + force;
352 *forces.get_mut(node2_id).unwrap() = *forces.get(node2_id).unwrap() - force;
353 }
354 }
355 }
356
357 for edge in &graph.edges {
359 if let (Some(pos1), Some(pos2)) = (positions.get(&edge.from), positions.get(&edge.to)) {
360 let diff = *pos2 - *pos1;
361 let distance = pos1.distance_to(pos2);
362 let ideal_length = self.config.ideal_edge_length;
363 let force_magnitude = self.config.spring_strength * (distance - ideal_length) * edge.weight;
364
365 if distance > 0.0 {
366 let force_direction = Point::new(diff.x / distance, diff.y / distance);
367 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
368
369 *forces.get_mut(&edge.from).unwrap() = *forces.get(&edge.from).unwrap() + force;
370 *forces.get_mut(&edge.to).unwrap() = *forces.get(&edge.to).unwrap() - force;
371 }
372 }
373 }
374
375 for node_id in graph.nodes.keys() {
377 if let (Some(force), Some(velocity), Some(position)) = (forces.get(node_id), velocities.get_mut(node_id), positions.get_mut(node_id)) {
378 *velocity = Point::new(velocity.x * self.config.damping + force.x, velocity.y * self.config.damping + force.y);
379
380 let speed = (velocity.x * velocity.x + velocity.y * velocity.y).sqrt();
382 if speed > self.config.max_velocity {
383 let scale = self.config.max_velocity / speed;
384 velocity.x *= scale;
385 velocity.y *= scale;
386 }
387
388 *position = *position + *velocity;
389 }
390 }
391 }
392
393 for (node_id, node) in &graph.nodes {
395 if let Some(position) = positions.get(node_id) {
396 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
397 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
398 let nt = match node.node_type.as_str() {
399 "function" => crate::layout::NodeType::Function,
400 "struct" => crate::layout::NodeType::Struct,
401 "module" => crate::layout::NodeType::Module,
402 _ => crate::layout::NodeType::Default,
403 };
404 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
405 }
406 }
407
408 for edge in &graph.edges {
410 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
411 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
412 layout.add_edge(layout_edge);
413 }
414 }
415
416 Ok(layout)
417 }
418
419 fn circular_layout(&self, graph: &Graph) -> crate::Result<Layout> {
421 let mut layout = Layout::new();
422
423 if graph.nodes.is_empty() {
424 return Ok(layout);
425 }
426
427 let node_count = graph.nodes.len();
428 let radius = self.config.circle_radius;
429 let angle_step = 2.0 * std::f64::consts::PI / node_count as f64;
430
431 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
432 let angle = i as f64 * angle_step;
433 let position = Point::new(radius * angle.cos(), radius * angle.sin());
434 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
435 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
436 let nt = match node.node_type.as_str() {
437 "function" => crate::layout::NodeType::Function,
438 "struct" => crate::layout::NodeType::Struct,
439 "module" => crate::layout::NodeType::Module,
440 _ => crate::layout::NodeType::Default,
441 };
442 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
443 }
444
445 for edge in &graph.edges {
447 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
448 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
449 layout.add_edge(layout_edge);
450 }
451 }
452
453 Ok(layout)
454 }
455
456 fn hierarchical_layout(&self, graph: &Graph) -> crate::Result<Layout> {
458 let mut layout = Layout::new();
459
460 if graph.nodes.is_empty() {
461 return Ok(layout);
462 }
463
464 let layers = self.topological_layers(graph)?;
466
467 for (layer_index, layer) in layers.iter().enumerate() {
469 let y = layer_index as f64 * self.config.layer_distance;
470 let layer_width = layer.len() as f64 * (self.config.node_width + self.config.node_spacing);
471 let start_x = -layer_width / 2.0;
472
473 for (node_index, node_id) in layer.iter().enumerate() {
474 let x = start_x + node_index as f64 * (self.config.node_width + self.config.node_spacing);
475 let node = &graph.nodes[node_id];
476 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
477 let rect = Rect::new(Point::new(x, y), size);
478 let nt = match node.node_type.as_str() {
479 "function" => crate::layout::NodeType::Function,
480 "struct" => crate::layout::NodeType::Struct,
481 "module" => crate::layout::NodeType::Module,
482 _ => crate::layout::NodeType::Default,
483 };
484 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
485 }
486 }
487
488 for edge in &graph.edges {
490 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
491 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
492 layout.add_edge(layout_edge);
493 }
494 }
495
496 Ok(layout)
497 }
498
499 fn grid_layout(&self, graph: &Graph) -> crate::Result<Layout> {
501 let mut layout = Layout::new();
502
503 if graph.nodes.is_empty() {
504 return Ok(layout);
505 }
506
507 let node_count = graph.nodes.len();
508 let cols = (node_count as f64).sqrt().ceil() as usize;
509 let _rows = (node_count + cols - 1) / cols;
510
511 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
512 let row = i / cols;
513 let col = i % cols;
514 let x = col as f64 * (self.config.node_width + self.config.node_spacing);
515 let y = row as f64 * (self.config.node_height + self.config.node_spacing);
516 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
517 let rect = Rect::new(Point::new(x, y), size);
518 let nt = match node.node_type.as_str() {
519 "function" => crate::layout::NodeType::Function,
520 "struct" => crate::layout::NodeType::Struct,
521 "module" => crate::layout::NodeType::Module,
522 _ => crate::layout::NodeType::Default,
523 };
524 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
525 }
526
527 for edge in &graph.edges {
529 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
530 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
531 layout.add_edge(layout_edge);
532 }
533 }
534
535 Ok(layout)
536 }
537
538 fn organic_layout(&self, graph: &Graph) -> crate::Result<Layout> {
540 let mut organic_config = self.config.clone();
542 organic_config.spring_strength *= 0.5;
543 organic_config.repulsion_strength *= 1.5;
544 organic_config.damping = 0.95;
545
546 let organic_layout = GraphLayout::new().with_algorithm(GraphLayoutAlgorithm::ForceDirected).with_config(organic_config);
547
548 organic_layout.force_directed_layout(graph)
549 }
550
551 fn topological_layers(&self, graph: &Graph) -> crate::Result<Vec<Vec<String>>> {
553 let mut in_degree: HashMap<String, usize> = HashMap::new();
554 let mut layers = Vec::new();
555
556 for node_id in graph.nodes.keys() {
558 in_degree.insert(node_id.clone(), 0);
559 }
560
561 for edge in &graph.edges {
563 *in_degree.get_mut(&edge.to).unwrap() += 1;
564 }
565
566 while !in_degree.is_empty() {
568 let mut current_layer = Vec::new();
569
570 let zero_in_degree: Vec<_> = in_degree.iter().filter(|&(_, °ree)| degree == 0).map(|(id, _)| id.clone()).collect();
572
573 if zero_in_degree.is_empty() {
574 if let Some((min_node, _)) = in_degree.iter().min_by_key(|&(_, °ree)| degree) {
576 current_layer.push(min_node.clone());
577 }
578 }
579 else {
580 current_layer = zero_in_degree;
581 }
582
583 for node_id in ¤t_layer {
585 in_degree.remove(node_id);
586
587 for edge in &graph.edges {
589 if edge.from == *node_id {
590 if let Some(degree) = in_degree.get_mut(&edge.to) {
591 *degree = degree.saturating_sub(1);
592 }
593 }
594 }
595 }
596
597 layers.push(current_layer);
598 }
599
600 Ok(layers)
601 }
602}
603
604#[derive(Debug, Clone)]
606pub struct GraphLayoutConfig {
607 pub node_width: f64,
609 pub node_height: f64,
611 pub node_spacing: f64,
613 pub layer_distance: f64,
615 pub circle_radius: f64,
617 pub iterations: usize,
619 pub spring_strength: f64,
621 pub repulsion_strength: f64,
623 pub damping: f64,
625 pub max_velocity: f64,
627 pub ideal_edge_length: f64,
629}
630
631impl Default for GraphLayoutConfig {
632 fn default() -> Self {
633 Self { node_width: 80.0, node_height: 40.0, node_spacing: 20.0, layer_distance: 100.0, circle_radius: 200.0, iterations: 100, spring_strength: 0.1, repulsion_strength: 1000.0, damping: 0.9, max_velocity: 10.0, ideal_edge_length: 100.0 }
634 }
635}
636
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
639pub enum GraphLayoutAlgorithm {
640 ForceDirected,
642 Circular,
644 Hierarchical,
646 Grid,
648 Organic,
650}