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,
14 pub label: String,
15 pub node_type: String,
16 pub size: Option<Size>,
17 pub attributes: HashMap<String, String>,
18 pub weight: f64,
19}
20
21impl GraphNode {
22 pub fn new(id: String, label: String) -> Self {
23 Self { id, label, node_type: "default".to_string(), size: None, attributes: HashMap::new(), weight: 1.0 }
24 }
25
26 pub fn with_type(mut self, node_type: String) -> Self {
27 self.node_type = node_type;
28 self
29 }
30
31 pub fn with_size(mut self, size: Size) -> Self {
32 self.size = Some(size);
33 self
34 }
35
36 pub fn with_attribute(mut self, key: String, value: String) -> Self {
37 self.attributes.insert(key, value);
38 self
39 }
40
41 pub fn with_weight(mut self, weight: f64) -> Self {
42 self.weight = weight;
43 self
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct GraphEdge {
50 pub from: String,
51 pub to: String,
52 pub label: Option<String>,
53 pub edge_type: String,
54 pub weight: f64,
55 pub directed: bool,
56 pub attributes: HashMap<String, String>,
57}
58
59impl GraphEdge {
60 pub fn new(from: String, to: String) -> Self {
61 Self {
62 from,
63 to,
64 label: None,
65 edge_type: "default".to_string(),
66 weight: 1.0,
67 directed: true,
68 attributes: HashMap::new(),
69 }
70 }
71
72 pub fn with_label(mut self, label: String) -> Self {
73 self.label = Some(label);
74 self
75 }
76
77 pub fn with_type(mut self, edge_type: String) -> Self {
78 self.edge_type = edge_type;
79 self
80 }
81
82 pub fn with_weight(mut self, weight: f64) -> Self {
83 self.weight = weight;
84 self
85 }
86
87 pub fn undirected(mut self) -> Self {
88 self.directed = false;
89 self
90 }
91
92 pub fn with_attribute(mut self, key: String, value: String) -> Self {
93 self.attributes.insert(key, value);
94 self
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct Graph {
101 pub nodes: HashMap<String, GraphNode>,
102 pub edges: Vec<GraphEdge>,
103 pub directed: bool,
104}
105
106impl Graph {
107 pub fn new(directed: bool) -> Self {
108 Self { nodes: HashMap::new(), edges: Vec::new(), directed }
109 }
110
111 pub fn add_node(&mut self, node: GraphNode) {
112 self.nodes.insert(node.id.clone(), node);
113 }
114
115 pub fn add_edge(&mut self, edge: GraphEdge) {
116 self.edges.push(edge);
117 }
118
119 pub fn get_neighbors(&self, node_id: &str) -> Vec<&str> {
120 let mut neighbors = Vec::new();
121
122 for edge in &self.edges {
123 if edge.from == node_id {
124 neighbors.push(edge.to.as_str());
125 }
126 if !self.directed && edge.to == node_id {
127 neighbors.push(edge.from.as_str());
128 }
129 }
130
131 neighbors
132 }
133
134 pub fn get_degree(&self, node_id: &str) -> usize {
135 self.get_neighbors(node_id).len()
136 }
137
138 pub fn is_connected(&self) -> bool {
139 if self.nodes.is_empty() {
140 return true;
141 }
142
143 let start_node = self.nodes.keys().next().unwrap();
144 let mut visited = HashSet::new();
145 let mut stack = vec![start_node.as_str()];
146
147 while let Some(node) = stack.pop() {
148 if visited.contains(node) {
149 continue;
150 }
151
152 visited.insert(node);
153
154 for neighbor in self.get_neighbors(node) {
155 if !visited.contains(neighbor) {
156 stack.push(neighbor);
157 }
158 }
159 }
160
161 visited.len() == self.nodes.len()
162 }
163
164 pub fn find_cycles(&self) -> Vec<Vec<String>> {
165 let mut cycles = Vec::new();
166 let mut visited = HashSet::new();
167 let mut rec_stack = HashSet::new();
168 let mut path = Vec::new();
169
170 for node_id in self.nodes.keys() {
171 if !visited.contains(node_id) {
172 self.dfs_cycles(node_id, &mut visited, &mut rec_stack, &mut path, &mut cycles);
173 }
174 }
175
176 cycles
177 }
178
179 fn dfs_cycles(
180 &self,
181 node: &str,
182 visited: &mut HashSet<String>,
183 rec_stack: &mut HashSet<String>,
184 path: &mut Vec<String>,
185 cycles: &mut Vec<Vec<String>>,
186 ) {
187 visited.insert(node.to_string());
188 rec_stack.insert(node.to_string());
189 path.push(node.to_string());
190
191 for neighbor in self.get_neighbors(node) {
192 if !visited.contains(neighbor) {
193 self.dfs_cycles(neighbor, visited, rec_stack, path, cycles);
194 }
195 else if rec_stack.contains(neighbor) {
196 if let Some(cycle_start) = path.iter().position(|n| n == neighbor) {
198 cycles.push(path[cycle_start..].to_vec());
199 }
200 }
201 }
202
203 rec_stack.remove(node);
204 path.pop();
205 }
206}
207
208pub struct GraphLayout {
210 algorithm: GraphLayoutAlgorithm,
211 config: GraphLayoutConfig,
212}
213
214impl GraphLayout {
215 pub fn new(algorithm: GraphLayoutAlgorithm) -> Self {
216 Self { algorithm, config: GraphLayoutConfig::default() }
217 }
218
219 pub fn with_config(mut self, config: GraphLayoutConfig) -> Self {
220 self.config = config;
221 self
222 }
223
224 pub fn layout_graph(&self, graph: &Graph) -> crate::Result<Layout> {
225 match self.algorithm {
226 GraphLayoutAlgorithm::ForceDirected => self.force_directed_layout(graph),
227 GraphLayoutAlgorithm::Circular => self.circular_layout(graph),
228 GraphLayoutAlgorithm::Hierarchical => self.hierarchical_layout(graph),
229 GraphLayoutAlgorithm::Grid => self.grid_layout(graph),
230 GraphLayoutAlgorithm::Organic => self.organic_layout(graph),
231 }
232 }
233
234 fn force_directed_layout(&self, graph: &Graph) -> crate::Result<Layout> {
236 let mut layout = Layout::new();
237
238 if graph.nodes.is_empty() {
239 return Ok(layout);
240 }
241
242 let mut positions: HashMap<String, Point> = HashMap::new();
243 let mut velocities: HashMap<String, Point> = HashMap::new();
244
245 for (i, node_id) in graph.nodes.keys().enumerate() {
247 let angle = 2.0 * std::f64::consts::PI * i as f64 / graph.nodes.len() as f64;
248 let radius = 100.0;
249 positions.insert(node_id.clone(), Point::new(radius * angle.cos(), radius * angle.sin()));
250 velocities.insert(node_id.clone(), Point::origin());
251 }
252
253 for _ in 0..self.config.iterations {
255 let mut forces: HashMap<String, Point> = HashMap::new();
256
257 for node_id in graph.nodes.keys() {
259 forces.insert(node_id.clone(), Point::origin());
260 }
261
262 let node_ids: Vec<_> = graph.nodes.keys().collect();
264 for i in 0..node_ids.len() {
265 for j in (i + 1)..node_ids.len() {
266 let node1_id = node_ids[i];
267 let node2_id = node_ids[j];
268
269 if let (Some(pos1), Some(pos2)) = (positions.get(node1_id), positions.get(node2_id)) {
270 let diff = *pos1 - *pos2;
271 let distance = pos1.distance_to(pos2).max(1.0);
272 let force_magnitude = self.config.repulsion_strength / (distance * distance);
273 let force_direction = Point::new(diff.x / distance, diff.y / distance);
274 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
275
276 *forces.get_mut(node1_id).unwrap() = *forces.get(node1_id).unwrap() + force;
277 *forces.get_mut(node2_id).unwrap() = *forces.get(node2_id).unwrap() - force;
278 }
279 }
280 }
281
282 for edge in &graph.edges {
284 if let (Some(pos1), Some(pos2)) = (positions.get(&edge.from), positions.get(&edge.to)) {
285 let diff = *pos2 - *pos1;
286 let distance = pos1.distance_to(pos2);
287 let ideal_length = self.config.ideal_edge_length;
288 let force_magnitude = self.config.spring_strength * (distance - ideal_length) * edge.weight;
289
290 if distance > 0.0 {
291 let force_direction = Point::new(diff.x / distance, diff.y / distance);
292 let force = Point::new(force_direction.x * force_magnitude, force_direction.y * force_magnitude);
293
294 *forces.get_mut(&edge.from).unwrap() = *forces.get(&edge.from).unwrap() + force;
295 *forces.get_mut(&edge.to).unwrap() = *forces.get(&edge.to).unwrap() - force;
296 }
297 }
298 }
299
300 for node_id in graph.nodes.keys() {
302 if let (Some(force), Some(velocity), Some(position)) =
303 (forces.get(node_id), velocities.get_mut(node_id), positions.get_mut(node_id))
304 {
305 *velocity =
306 Point::new(velocity.x * self.config.damping + force.x, velocity.y * self.config.damping + force.y);
307
308 let speed = (velocity.x * velocity.x + velocity.y * velocity.y).sqrt();
310 if speed > self.config.max_velocity {
311 let scale = self.config.max_velocity / speed;
312 velocity.x *= scale;
313 velocity.y *= scale;
314 }
315
316 *position = *position + *velocity;
317 }
318 }
319 }
320
321 for (node_id, node) in &graph.nodes {
323 if let Some(position) = positions.get(node_id) {
324 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
325 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
326 layout.add_node((*node_id).clone(), rect);
327 }
328 }
329
330 for edge in &graph.edges {
332 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
333 let layout_edge =
334 Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_rect.center(), to_rect.center()]);
335 layout.add_edge(layout_edge);
336 }
337 }
338
339 Ok(layout)
340 }
341
342 fn circular_layout(&self, graph: &Graph) -> crate::Result<Layout> {
344 let mut layout = Layout::new();
345
346 if graph.nodes.is_empty() {
347 return Ok(layout);
348 }
349
350 let node_count = graph.nodes.len();
351 let radius = self.config.circle_radius;
352 let angle_step = 2.0 * std::f64::consts::PI / node_count as f64;
353
354 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
355 let angle = i as f64 * angle_step;
356 let position = Point::new(radius * angle.cos(), radius * angle.sin());
357 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
358 let rect = Rect::new(Point::new(position.x - size.width / 2.0, position.y - size.height / 2.0), size);
359 layout.add_node((*node_id).clone(), rect);
360 }
361
362 for edge in &graph.edges {
364 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
365 let layout_edge =
366 Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_rect.center(), to_rect.center()]);
367 layout.add_edge(layout_edge);
368 }
369 }
370
371 Ok(layout)
372 }
373
374 fn hierarchical_layout(&self, graph: &Graph) -> crate::Result<Layout> {
376 let mut layout = Layout::new();
377
378 if graph.nodes.is_empty() {
379 return Ok(layout);
380 }
381
382 let layers = self.topological_layers(graph)?;
384
385 for (layer_index, layer) in layers.iter().enumerate() {
387 let y = layer_index as f64 * self.config.layer_distance;
388 let layer_width = layer.len() as f64 * (self.config.node_width + self.config.node_spacing);
389 let start_x = -layer_width / 2.0;
390
391 for (node_index, node_id) in layer.iter().enumerate() {
392 let x = start_x + node_index as f64 * (self.config.node_width + self.config.node_spacing);
393 let node = &graph.nodes[node_id];
394 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
395 let rect = Rect::new(Point::new(x, y), size);
396 layout.add_node((*node_id).clone(), rect);
397 }
398 }
399
400 for edge in &graph.edges {
402 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
403 let layout_edge =
404 Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_rect.center(), to_rect.center()]);
405 layout.add_edge(layout_edge);
406 }
407 }
408
409 Ok(layout)
410 }
411
412 fn grid_layout(&self, graph: &Graph) -> crate::Result<Layout> {
414 let mut layout = Layout::new();
415
416 if graph.nodes.is_empty() {
417 return Ok(layout);
418 }
419
420 let node_count = graph.nodes.len();
421 let cols = (node_count as f64).sqrt().ceil() as usize;
422 let _rows = (node_count + cols - 1) / cols;
423
424 for (i, (node_id, node)) in graph.nodes.iter().enumerate() {
425 let row = i / cols;
426 let col = i % cols;
427 let x = col as f64 * (self.config.node_width + self.config.node_spacing);
428 let y = row as f64 * (self.config.node_height + self.config.node_spacing);
429 let size = node.size.unwrap_or(Size::new(self.config.node_width, self.config.node_height));
430 let rect = Rect::new(Point::new(x, y), size);
431 layout.add_node((*node_id).clone(), rect);
432 }
433
434 for edge in &graph.edges {
436 if let (Some(from_rect), Some(to_rect)) = (layout.nodes.get(&edge.from), layout.nodes.get(&edge.to)) {
437 let layout_edge =
438 Edge::new(edge.from.clone(), edge.to.clone()).with_points(vec![from_rect.center(), to_rect.center()]);
439 layout.add_edge(layout_edge);
440 }
441 }
442
443 Ok(layout)
444 }
445
446 fn organic_layout(&self, graph: &Graph) -> crate::Result<Layout> {
448 let mut organic_config = self.config.clone();
450 organic_config.spring_strength *= 0.5;
451 organic_config.repulsion_strength *= 1.5;
452 organic_config.damping = 0.95;
453
454 let organic_layout = GraphLayout::new(GraphLayoutAlgorithm::ForceDirected).with_config(organic_config);
455
456 organic_layout.force_directed_layout(graph)
457 }
458
459 fn topological_layers(&self, graph: &Graph) -> crate::Result<Vec<Vec<String>>> {
461 let mut in_degree: HashMap<String, usize> = HashMap::new();
462 let mut layers = Vec::new();
463
464 for node_id in graph.nodes.keys() {
466 in_degree.insert(node_id.clone(), 0);
467 }
468
469 for edge in &graph.edges {
471 *in_degree.get_mut(&edge.to).unwrap() += 1;
472 }
473
474 while !in_degree.is_empty() {
476 let mut current_layer = Vec::new();
477
478 let zero_in_degree: Vec<_> =
480 in_degree.iter().filter(|&(_, °ree)| degree == 0).map(|(id, _)| id.clone()).collect();
481
482 if zero_in_degree.is_empty() {
483 if let Some((min_node, _)) = in_degree.iter().min_by_key(|&(_, °ree)| degree) {
485 current_layer.push(min_node.clone());
486 }
487 }
488 else {
489 current_layer = zero_in_degree;
490 }
491
492 for node_id in ¤t_layer {
494 in_degree.remove(node_id);
495
496 for edge in &graph.edges {
498 if edge.from == *node_id {
499 if let Some(degree) = in_degree.get_mut(&edge.to) {
500 *degree = degree.saturating_sub(1);
501 }
502 }
503 }
504 }
505
506 layers.push(current_layer);
507 }
508
509 Ok(layers)
510 }
511}
512
513#[derive(Debug, Clone)]
515pub struct GraphLayoutConfig {
516 pub node_width: f64,
517 pub node_height: f64,
518 pub node_spacing: f64,
519 pub layer_distance: f64,
520 pub circle_radius: f64,
521 pub iterations: usize,
522 pub spring_strength: f64,
523 pub repulsion_strength: f64,
524 pub damping: f64,
525 pub max_velocity: f64,
526 pub ideal_edge_length: f64,
527}
528
529impl Default for GraphLayoutConfig {
530 fn default() -> Self {
531 Self {
532 node_width: 80.0,
533 node_height: 40.0,
534 node_spacing: 20.0,
535 layer_distance: 100.0,
536 circle_radius: 200.0,
537 iterations: 100,
538 spring_strength: 0.1,
539 repulsion_strength: 1000.0,
540 damping: 0.9,
541 max_velocity: 10.0,
542 ideal_edge_length: 100.0,
543 }
544 }
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq)]
549pub enum GraphLayoutAlgorithm {
550 ForceDirected, Circular, Hierarchical, Grid, Organic, }