1#![doc = "Graph layout algorithms for dependency and relationship visualization"]
2
3use crate::{
4 geometry::{Point, Rect, Size},
5 layout::{Edge, Layout},
6};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, Serialize, 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, Serialize, Deserialize)]
60pub struct GraphEdge {
61 pub from: String,
63 pub to: String,
65 pub label: Option<String>,
67 pub edge_type: String,
69 pub weight: f64,
71 pub directed: bool,
73 pub attributes: HashMap<String, String>,
75}
76
77impl GraphEdge {
78 pub fn new(from: String, to: String) -> Self {
80 Self { from, to, label: None, edge_type: "default".to_string(), weight: 1.0, directed: true, attributes: HashMap::new() }
81 }
82
83 pub fn with_label(mut self, label: String) -> Self {
85 self.label = Some(label);
86 self
87 }
88
89 pub fn with_type(mut self, edge_type: String) -> Self {
91 self.edge_type = edge_type;
92 self
93 }
94
95 pub fn with_weight(mut self, weight: f64) -> Self {
97 self.weight = weight;
98 self
99 }
100
101 pub fn undirected(mut self) -> Self {
103 self.directed = false;
104 self
105 }
106
107 pub fn with_attribute(mut self, key: String, value: String) -> Self {
109 self.attributes.insert(key, value);
110 self
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct Graph {
117 pub nodes: HashMap<String, GraphNode>,
119 pub edges: Vec<GraphEdge>,
121 pub directed: bool,
123}
124
125impl Graph {
126 pub fn new(directed: bool) -> Self {
128 Self { nodes: HashMap::new(), edges: Vec::new(), directed }
129 }
130
131 pub fn add_node(&mut self, node: GraphNode) {
133 self.nodes.insert(node.id.clone(), node);
134 }
135
136 pub fn add_edge(&mut self, edge: GraphEdge) {
138 self.edges.push(edge);
139 }
140
141 pub fn get_neighbors(&self, node_id: &str) -> Vec<&str> {
143 let mut neighbors = Vec::new();
144
145 for edge in &self.edges {
146 if edge.from == node_id {
147 neighbors.push(edge.to.as_str());
148 }
149 if !self.directed && edge.to == node_id {
150 neighbors.push(edge.from.as_str());
151 }
152 }
153
154 neighbors
155 }
156
157 pub fn get_degree(&self, node_id: &str) -> usize {
159 self.get_neighbors(node_id).len()
160 }
161
162 pub fn is_connected(&self) -> bool {
164 if self.nodes.is_empty() {
165 return true;
166 }
167
168 let start_node = self.nodes.keys().next().unwrap();
169 let mut visited = HashSet::new();
170 let mut stack = vec![start_node.as_str()];
171
172 while let Some(node) = stack.pop() {
173 if visited.contains(node) {
174 continue;
175 }
176
177 visited.insert(node);
178
179 for neighbor in self.get_neighbors(node) {
180 if !visited.contains(neighbor) {
181 stack.push(neighbor);
182 }
183 }
184 }
185
186 visited.len() == self.nodes.len()
187 }
188
189 pub fn find_cycles(&self) -> Vec<Vec<String>> {
191 let mut cycles = Vec::new();
192 let mut visited = HashSet::new();
193 let mut rec_stack = HashSet::new();
194 let mut path = Vec::new();
195
196 for node_id in self.nodes.keys() {
197 if !visited.contains(node_id) {
198 self.dfs_cycles(node_id, &mut visited, &mut rec_stack, &mut path, &mut cycles);
199 }
200 }
201
202 cycles
203 }
204
205 fn dfs_cycles(&self, node: &str, visited: &mut HashSet<String>, rec_stack: &mut HashSet<String>, path: &mut Vec<String>, cycles: &mut Vec<Vec<String>>) {
206 visited.insert(node.to_string());
207 rec_stack.insert(node.to_string());
208 path.push(node.to_string());
209
210 for neighbor in self.get_neighbors(node) {
211 if !visited.contains(neighbor) {
212 self.dfs_cycles(neighbor, visited, rec_stack, path, cycles);
213 }
214 else if rec_stack.contains(neighbor) {
215 if let Some(cycle_start) = path.iter().position(|n| n == neighbor) {
217 cycles.push(path[cycle_start..].to_vec());
218 }
219 }
220 }
221
222 rec_stack.remove(node);
223 path.pop();
224 }
225}
226
227impl crate::Visualize for Graph {
228 fn visualize(&self) -> crate::Result<String> {
229 GraphLayout::new().visualize(self)
230 }
231}
232
233pub struct GraphLayout {
235 algorithm: GraphLayoutAlgorithm,
236 config: GraphLayoutConfig,
237}
238
239impl Default for GraphLayout {
240 fn default() -> Self {
241 Self { algorithm: GraphLayoutAlgorithm::ForceDirected, config: GraphLayoutConfig::default() }
242 }
243}
244
245impl GraphLayout {
246 pub fn new() -> Self {
248 Self::default()
249 }
250
251 pub fn force_directed() -> Self {
253 Self::new().with_algorithm(GraphLayoutAlgorithm::ForceDirected)
254 }
255
256 pub fn circular() -> Self {
258 Self::new().with_algorithm(GraphLayoutAlgorithm::Circular)
259 }
260
261 pub fn with_algorithm(mut self, algorithm: GraphLayoutAlgorithm) -> Self {
263 self.algorithm = algorithm;
264 self
265 }
266
267 pub fn with_config(mut self, config: GraphLayoutConfig) -> Self {
269 self.config = config;
270 self
271 }
272
273 pub fn with_repulsion(mut self, repulsion: f64) -> Self {
275 self.config.repulsion_strength = repulsion;
276 self
277 }
278
279 pub fn with_attraction(mut self, attraction: f64) -> Self {
281 self.config.spring_strength = attraction;
282 self
283 }
284
285 pub fn with_iterations(mut self, iterations: usize) -> Self {
287 self.config.iterations = iterations;
288 self
289 }
290
291 pub fn visualize(&self, graph: &Graph) -> crate::Result<String> {
293 let layout = self.layout_graph(graph)?;
294 crate::render::SvgRenderer::new().render_layout(&layout)
295 }
296
297 pub fn layout_graph(&self, graph: &Graph) -> crate::Result<Layout> {
299 match self.algorithm {
300 GraphLayoutAlgorithm::ForceDirected => self.force_directed_layout(graph),
301 GraphLayoutAlgorithm::Circular => self.circular_layout(graph),
302 GraphLayoutAlgorithm::Hierarchical => self.hierarchical_layout(graph),
303 GraphLayoutAlgorithm::Grid => self.grid_layout(graph),
304 GraphLayoutAlgorithm::Organic => self.organic_layout(graph),
305 }
306 }
307
308 fn force_directed_layout(&self, graph: &Graph) -> crate::Result<Layout> {
310 let mut layout = Layout::new();
311
312 if graph.nodes.is_empty() {
313 return Ok(layout);
314 }
315
316 let mut positions: HashMap<String, Point> = HashMap::new();
317 let mut velocities: HashMap<String, Point> = HashMap::new();
318
319 for (i, node_id) in graph.nodes.keys().enumerate() {
321 let angle = 2.0 * std::f64::consts::PI * i as f64 / graph.nodes.len() as f64;
322 let radius = 100.0;
323 positions.insert(node_id.clone(), Point::new(radius * angle.cos(), radius * angle.sin()));
324 velocities.insert(node_id.clone(), Point::origin());
325 }
326
327 for _ in 0..self.config.iterations {
329 let mut forces: HashMap<String, Point> = HashMap::new();
330
331 for node_id in graph.nodes.keys() {
333 forces.insert(node_id.clone(), Point::origin());
334 }
335
336 let node_ids: Vec<_> = graph.nodes.keys().collect();
338 for i in 0..node_ids.len() {
339 for j in (i + 1)..node_ids.len() {
340 let node1_id = node_ids[i];
341 let node2_id = node_ids[j];
342
343 if let (Some(pos1), Some(pos2)) = (positions.get(node1_id), positions.get(node2_id)) {
344 let diff = *pos1 - *pos2;
345 let distance = pos1.distance_to(pos2).max(1.0);
346 let force_magnitude = self.config.repulsion_strength / (distance * distance);
347 let force_direction = Point::new(diff.x / distance, diff.y / distance);
348 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
349
350 *forces.get_mut(node1_id).unwrap() = *forces.get(node1_id).unwrap() + force;
351 *forces.get_mut(node2_id).unwrap() = *forces.get(node2_id).unwrap() - force;
352 }
353 }
354 }
355
356 for edge in &graph.edges {
358 if let (Some(pos1), Some(pos2)) = (positions.get(&edge.from), positions.get(&edge.to)) {
359 let diff = *pos2 - *pos1;
360 let distance = pos1.distance_to(pos2);
361 let ideal_length = self.config.ideal_edge_length;
362 let force_magnitude = self.config.spring_strength * (distance - ideal_length) * edge.weight;
363
364 if distance > 0.0 {
365 let force_direction = Point::new(diff.x / distance, diff.y / distance);
366 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
367
368 *forces.get_mut(&edge.from).unwrap() = *forces.get(&edge.from).unwrap() + force;
369 *forces.get_mut(&edge.to).unwrap() = *forces.get(&edge.to).unwrap() - force;
370 }
371 }
372 }
373
374 for node_id in graph.nodes.keys() {
376 if let (Some(force), Some(velocity), Some(position)) = (forces.get(node_id), velocities.get_mut(node_id), positions.get_mut(node_id)) {
377 *velocity = Point::new(velocity.x * self.config.damping + force.x, velocity.y * self.config.damping + force.y);
378
379 let speed = (velocity.x * velocity.x + velocity.y * velocity.y).sqrt();
381 if speed > self.config.max_velocity {
382 let scale = self.config.max_velocity / speed;
383 velocity.x *= scale;
384 velocity.y *= scale;
385 }
386
387 *position = *position + *velocity;
388 }
389 }
390 }
391
392 for (node_id, node) in &graph.nodes {
394 if let Some(position) = positions.get(node_id) {
395 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
396 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
397 let nt = match node.node_type.as_str() {
398 "function" => crate::layout::NodeType::Function,
399 "struct" => crate::layout::NodeType::Struct,
400 "module" => crate::layout::NodeType::Module,
401 _ => crate::layout::NodeType::Default,
402 };
403 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
404 }
405 }
406
407 for edge in &graph.edges {
409 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
410 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
411 layout.add_edge(layout_edge);
412 }
413 }
414
415 Ok(layout)
416 }
417
418 fn circular_layout(&self, graph: &Graph) -> crate::Result<Layout> {
420 let mut layout = Layout::new();
421
422 if graph.nodes.is_empty() {
423 return Ok(layout);
424 }
425
426 let node_count = graph.nodes.len();
427 let radius = self.config.circle_radius;
428 let angle_step = 2.0 * std::f64::consts::PI / node_count as f64;
429
430 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
431 let angle = i as f64 * angle_step;
432 let position = Point::new(radius * angle.cos(), radius * angle.sin());
433 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
434 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
435 let nt = match node.node_type.as_str() {
436 "function" => crate::layout::NodeType::Function,
437 "struct" => crate::layout::NodeType::Struct,
438 "module" => crate::layout::NodeType::Module,
439 _ => crate::layout::NodeType::Default,
440 };
441 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
442 }
443
444 for edge in &graph.edges {
446 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
447 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
448 layout.add_edge(layout_edge);
449 }
450 }
451
452 Ok(layout)
453 }
454
455 fn hierarchical_layout(&self, graph: &Graph) -> crate::Result<Layout> {
457 let mut layout = Layout::new();
458
459 if graph.nodes.is_empty() {
460 return Ok(layout);
461 }
462
463 let layers = self.topological_layers(graph)?;
465
466 for (layer_index, layer) in layers.iter().enumerate() {
468 let y = layer_index as f64 * self.config.layer_distance;
469 let layer_width = layer.len() as f64 * (self.config.node_width + self.config.node_spacing);
470 let start_x = -layer_width / 2.0;
471
472 for (node_index, node_id) in layer.iter().enumerate() {
473 let x = start_x + node_index as f64 * (self.config.node_width + self.config.node_spacing);
474 let node = &graph.nodes[node_id];
475 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
476 let rect = Rect::new(Point::new(x, y), size);
477 let nt = match node.node_type.as_str() {
478 "function" => crate::layout::NodeType::Function,
479 "struct" => crate::layout::NodeType::Struct,
480 "module" => crate::layout::NodeType::Module,
481 _ => crate::layout::NodeType::Default,
482 };
483 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
484 }
485 }
486
487 for edge in &graph.edges {
489 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
490 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
491 layout.add_edge(layout_edge);
492 }
493 }
494
495 Ok(layout)
496 }
497
498 fn grid_layout(&self, graph: &Graph) -> crate::Result<Layout> {
500 let mut layout = Layout::new();
501
502 if graph.nodes.is_empty() {
503 return Ok(layout);
504 }
505
506 let node_count = graph.nodes.len();
507 let cols = (node_count as f64).sqrt().ceil() as usize;
508 let _rows = (node_count + cols - 1) / cols;
509
510 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
511 let row = i / cols;
512 let col = i % cols;
513 let x = col as f64 * (self.config.node_width + self.config.node_spacing);
514 let y = row as f64 * (self.config.node_height + self.config.node_spacing);
515 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
516 let rect = Rect::new(Point::new(x, y), size);
517 let nt = match node.node_type.as_str() {
518 "function" => crate::layout::NodeType::Function,
519 "struct" => crate::layout::NodeType::Struct,
520 "module" => crate::layout::NodeType::Module,
521 _ => crate::layout::NodeType::Default,
522 };
523 layout.add_node_with_metadata(node_id.clone(), node.label.clone(), rect, nt);
524 }
525
526 for edge in &graph.edges {
528 if let (Some(from_node), Some(to_node)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
529 let layout_edge = Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_node.rect.center(), to_node.rect.center()]);
530 layout.add_edge(layout_edge);
531 }
532 }
533
534 Ok(layout)
535 }
536
537 fn organic_layout(&self, graph: &Graph) -> crate::Result<Layout> {
539 let mut organic_config = self.config.clone();
541 organic_config.spring_strength *= 0.5;
542 organic_config.repulsion_strength *= 1.5;
543 organic_config.damping = 0.95;
544
545 let organic_layout = GraphLayout::new().with_algorithm(GraphLayoutAlgorithm::ForceDirected).with_config(organic_config);
546
547 organic_layout.force_directed_layout(graph)
548 }
549
550 fn topological_layers(&self, graph: &Graph) -> crate::Result<Vec<Vec<String>>> {
552 let mut in_degree: HashMap<String, usize> = HashMap::new();
553 let mut layers = Vec::new();
554
555 for node_id in graph.nodes.keys() {
557 in_degree.insert(node_id.clone(), 0);
558 }
559
560 for edge in &graph.edges {
562 *in_degree.get_mut(&edge.to).unwrap() += 1;
563 }
564
565 while !in_degree.is_empty() {
567 let mut current_layer = Vec::new();
568
569 let zero_in_degree: Vec<_> = in_degree.iter().filter(|&(_, °ree)| degree == 0).map(|(id, _)| id.clone()).collect();
571
572 if zero_in_degree.is_empty() {
573 if let Some((min_node, _)) = in_degree.iter().min_by_key(|&(_, °ree)| degree) {
575 current_layer.push(min_node.clone());
576 }
577 }
578 else {
579 current_layer = zero_in_degree;
580 }
581
582 for node_id in ¤t_layer {
584 in_degree.remove(node_id);
585
586 for edge in &graph.edges {
588 if edge.from == *node_id {
589 if let Some(degree) = in_degree.get_mut(&edge.to) {
590 *degree = degree.saturating_sub(1);
591 }
592 }
593 }
594 }
595
596 layers.push(current_layer);
597 }
598
599 Ok(layers)
600 }
601}
602
603#[derive(Debug, Clone)]
605pub struct GraphLayoutConfig {
606 pub node_width: f64,
608 pub node_height: f64,
610 pub node_spacing: f64,
612 pub layer_distance: f64,
614 pub circle_radius: f64,
616 pub iterations: usize,
618 pub spring_strength: f64,
620 pub repulsion_strength: f64,
622 pub damping: f64,
624 pub max_velocity: f64,
626 pub ideal_edge_length: f64,
628}
629
630impl Default for GraphLayoutConfig {
631 fn default() -> Self {
632 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 }
633 }
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub enum GraphLayoutAlgorithm {
639 ForceDirected,
641 Circular,
643 Hierarchical,
645 Grid,
647 Organic,
649}