1use glam::Vec2;
2use std::collections::{HashMap, HashSet, VecDeque};
3use super::graph_core::{Graph, GraphKind, NodeId};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum LayoutAlgorithm {
7 ForceDirected,
8 Hierarchical,
9 Circular,
10 Spectral,
11 Tree,
12 Random,
13 Grid,
14}
15
16#[derive(Debug, Clone)]
17pub struct LayoutConfig {
18 pub iterations: usize,
19 pub spacing: f32,
20 pub bounds: Vec2,
21 pub seed: u64,
22 pub temperature: f32,
23 pub cooling_factor: f32,
24}
25
26impl Default for LayoutConfig {
27 fn default() -> Self {
28 Self {
29 iterations: 300,
30 spacing: 50.0,
31 bounds: Vec2::new(800.0, 600.0),
32 seed: 42,
33 temperature: 100.0,
34 cooling_factor: 0.95,
35 }
36 }
37}
38
39pub fn compute_layout<N, E>(graph: &Graph<N, E>, algorithm: LayoutAlgorithm, config: &LayoutConfig) -> HashMap<NodeId, Vec2> {
40 match algorithm {
41 LayoutAlgorithm::ForceDirected => {
42 let mut fl = ForceDirectedLayout::new(config.clone());
43 fl.compute(graph)
44 }
45 LayoutAlgorithm::Hierarchical => {
46 let mut hl = HierarchicalLayout::new(config.clone());
47 hl.compute(graph)
48 }
49 LayoutAlgorithm::Circular => {
50 CircularLayout::new(config.clone()).compute(graph)
51 }
52 LayoutAlgorithm::Spectral => {
53 SpectralLayout::new(config.clone()).compute(graph)
54 }
55 LayoutAlgorithm::Tree => {
56 TreeLayout::new(config.clone()).compute(graph)
57 }
58 LayoutAlgorithm::Random => {
59 random_layout(graph, config)
60 }
61 LayoutAlgorithm::Grid => {
62 grid_layout(graph, config)
63 }
64 }
65}
66
67fn pseudo_random(seed: u64, i: u64) -> f64 {
68 let mut x = seed.wrapping_mul(6364136223846793005).wrapping_add(i.wrapping_mul(1442695040888963407));
69 x ^= x >> 33;
70 x = x.wrapping_mul(0xff51afd7ed558ccd);
71 x ^= x >> 33;
72 (x as f64) / (u64::MAX as f64)
73}
74
75fn random_layout<N, E>(graph: &Graph<N, E>, config: &LayoutConfig) -> HashMap<NodeId, Vec2> {
76 let mut positions = HashMap::new();
77 for (i, nid) in graph.node_ids().iter().enumerate() {
78 let x = pseudo_random(config.seed, i as u64 * 2) as f32 * config.bounds.x;
79 let y = pseudo_random(config.seed, i as u64 * 2 + 1) as f32 * config.bounds.y;
80 positions.insert(*nid, Vec2::new(x, y));
81 }
82 positions
83}
84
85fn grid_layout<N, E>(graph: &Graph<N, E>, config: &LayoutConfig) -> HashMap<NodeId, Vec2> {
86 let mut positions = HashMap::new();
87 let n = graph.node_count();
88 if n == 0 { return positions; }
89 let cols = (n as f32).sqrt().ceil() as usize;
90 let spacing = config.spacing;
91 let offset = Vec2::new(
92 config.bounds.x / 2.0 - (cols as f32 * spacing) / 2.0,
93 config.bounds.y / 2.0 - ((n / cols + 1) as f32 * spacing) / 2.0,
94 );
95 for (i, nid) in graph.node_ids().iter().enumerate() {
96 let row = i / cols;
97 let col = i % cols;
98 positions.insert(*nid, Vec2::new(
99 offset.x + col as f32 * spacing,
100 offset.y + row as f32 * spacing,
101 ));
102 }
103 positions
104}
105
106pub struct ForceDirectedLayout {
109 config: LayoutConfig,
110}
111
112impl ForceDirectedLayout {
113 pub fn new(config: LayoutConfig) -> Self {
114 Self { config }
115 }
116
117 pub fn compute<N, E>(&mut self, graph: &Graph<N, E>) -> HashMap<NodeId, Vec2> {
118 let node_ids = graph.node_ids();
119 let n = node_ids.len();
120 if n == 0 { return HashMap::new(); }
121
122 let area = self.config.bounds.x * self.config.bounds.y;
123 let k = (area / n as f32).sqrt(); let mut positions: HashMap<NodeId, Vec2> = HashMap::new();
127 for (i, &nid) in node_ids.iter().enumerate() {
128 let x = pseudo_random(self.config.seed, i as u64 * 2) as f32 * self.config.bounds.x;
129 let y = pseudo_random(self.config.seed, i as u64 * 2 + 1) as f32 * self.config.bounds.y;
130 positions.insert(nid, Vec2::new(x, y));
131 }
132
133 let mut temperature = self.config.temperature;
134
135 for _iter in 0..self.config.iterations {
136 let mut displacements: HashMap<NodeId, Vec2> = HashMap::new();
137 for &nid in &node_ids {
138 displacements.insert(nid, Vec2::ZERO);
139 }
140
141 for i in 0..n {
143 for j in (i + 1)..n {
144 let ni = node_ids[i];
145 let nj = node_ids[j];
146 let pi = positions[&ni];
147 let pj = positions[&nj];
148 let delta = pi - pj;
149 let dist = delta.length().max(0.01);
150 let repulsion = k * k / dist;
151 let force = delta / dist * repulsion;
152 *displacements.get_mut(&ni).unwrap() += force;
153 *displacements.get_mut(&nj).unwrap() -= force;
154 }
155 }
156
157 for edge in graph.edges() {
159 let pi = positions[&edge.from];
160 let pj = positions[&edge.to];
161 let delta = pi - pj;
162 let dist = delta.length().max(0.01);
163 let attraction = dist * dist / k;
164 let force = delta / dist * attraction;
165 *displacements.get_mut(&edge.from).unwrap() -= force;
166 *displacements.get_mut(&edge.to).unwrap() += force;
167 }
168
169 for &nid in &node_ids {
171 let disp = displacements[&nid];
172 let len = disp.length().max(0.01);
173 let clamped = disp / len * len.min(temperature);
174 let mut pos = positions[&nid] + clamped;
175 pos.x = pos.x.clamp(0.0, self.config.bounds.x);
177 pos.y = pos.y.clamp(0.0, self.config.bounds.y);
178 positions.insert(nid, pos);
179 }
180
181 temperature *= self.config.cooling_factor;
182 }
183
184 positions
185 }
186}
187
188pub struct HierarchicalLayout {
191 config: LayoutConfig,
192}
193
194impl HierarchicalLayout {
195 pub fn new(config: LayoutConfig) -> Self {
196 Self { config }
197 }
198
199 pub fn compute<N, E>(&mut self, graph: &Graph<N, E>) -> HashMap<NodeId, Vec2> {
200 let node_ids = graph.node_ids();
201 if node_ids.is_empty() { return HashMap::new(); }
202
203 let mut layers: HashMap<NodeId, usize> = HashMap::new();
205 let mut visited = HashSet::new();
206 let mut queue = VecDeque::new();
207
208 let mut has_incoming: HashSet<NodeId> = HashSet::new();
210 if graph.kind == GraphKind::Directed {
211 for edge in graph.edges() {
212 has_incoming.insert(edge.to);
213 }
214 }
215 let roots: Vec<NodeId> = if graph.kind == GraphKind::Directed {
216 node_ids.iter().filter(|n| !has_incoming.contains(n)).copied().collect()
217 } else {
218 vec![node_ids[0]]
219 };
220 let roots = if roots.is_empty() { vec![node_ids[0]] } else { roots };
221
222 for &root in &roots {
223 if visited.insert(root) {
224 queue.push_back(root);
225 layers.insert(root, 0);
226 }
227 }
228 while let Some(nid) = queue.pop_front() {
229 let layer = layers[&nid];
230 for nbr in graph.neighbors(nid) {
231 if visited.insert(nbr) {
232 layers.insert(nbr, layer + 1);
233 queue.push_back(nbr);
234 }
235 }
236 }
237 for &nid in &node_ids {
239 if !layers.contains_key(&nid) {
240 layers.insert(nid, 0);
241 }
242 }
243
244 let max_layer = layers.values().copied().max().unwrap_or(0);
246 let mut layer_nodes: Vec<Vec<NodeId>> = vec![Vec::new(); max_layer + 1];
247 for (&nid, &layer) in &layers {
248 layer_nodes[layer].push(nid);
249 }
250 for layer in &mut layer_nodes {
252 layer.sort();
253 }
254
255 for _pass in 0..5 {
257 for l in 1..=max_layer {
258 let prev_layer = &layer_nodes[l - 1];
259 let prev_pos: HashMap<NodeId, f32> = prev_layer.iter().enumerate()
260 .map(|(i, &nid)| (nid, i as f32))
261 .collect();
262
263 let mut barycenters: Vec<(NodeId, f32)> = Vec::new();
264 for &nid in &layer_nodes[l] {
265 let nbrs = graph.neighbors(nid);
266 let parent_positions: Vec<f32> = nbrs.iter()
267 .filter_map(|n| prev_pos.get(n).copied())
268 .collect();
269 let bc = if parent_positions.is_empty() {
270 0.0
271 } else {
272 parent_positions.iter().sum::<f32>() / parent_positions.len() as f32
273 };
274 barycenters.push((nid, bc));
275 }
276 barycenters.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
277 layer_nodes[l] = barycenters.into_iter().map(|(nid, _)| nid).collect();
278 }
279 }
280
281 let mut positions = HashMap::new();
283 let layer_spacing = self.config.bounds.y / (max_layer + 1) as f32;
284 for (l, nodes) in layer_nodes.iter().enumerate() {
285 let n = nodes.len();
286 let node_spacing = if n > 1 {
287 self.config.bounds.x / n as f32
288 } else {
289 self.config.bounds.x / 2.0
290 };
291 for (i, &nid) in nodes.iter().enumerate() {
292 let x = if n > 1 {
293 node_spacing * (i as f32 + 0.5)
294 } else {
295 self.config.bounds.x / 2.0
296 };
297 let y = layer_spacing * (l as f32 + 0.5);
298 positions.insert(nid, Vec2::new(x, y));
299 }
300 }
301 positions
302 }
303}
304
305pub struct CircularLayout {
308 config: LayoutConfig,
309}
310
311impl CircularLayout {
312 pub fn new(config: LayoutConfig) -> Self {
313 Self { config }
314 }
315
316 pub fn compute<N, E>(&self, graph: &Graph<N, E>) -> HashMap<NodeId, Vec2> {
317 let node_ids = graph.node_ids();
318 let n = node_ids.len();
319 if n == 0 { return HashMap::new(); }
320
321 let center = self.config.bounds * 0.5;
322 let radius = self.config.bounds.x.min(self.config.bounds.y) * 0.4;
323
324 let mut positions = HashMap::new();
325 for (i, &nid) in node_ids.iter().enumerate() {
326 let angle = 2.0 * std::f32::consts::PI * i as f32 / n as f32;
327 let x = center.x + radius * angle.cos();
328 let y = center.y + radius * angle.sin();
329 positions.insert(nid, Vec2::new(x, y));
330 }
331 positions
332 }
333}
334
335pub struct SpectralLayout {
338 config: LayoutConfig,
339}
340
341impl SpectralLayout {
342 pub fn new(config: LayoutConfig) -> Self {
343 Self { config }
344 }
345
346 pub fn compute<N, E>(&self, graph: &Graph<N, E>) -> HashMap<NodeId, Vec2> {
347 let node_ids = graph.node_ids();
348 let n = node_ids.len();
349 if n == 0 { return HashMap::new(); }
350 if n == 1 {
351 let mut m = HashMap::new();
352 m.insert(node_ids[0], self.config.bounds * 0.5);
353 return m;
354 }
355
356 let idx: HashMap<NodeId, usize> = node_ids.iter().enumerate().map(|(i, &nid)| (nid, i)).collect();
357
358 let mut laplacian = vec![vec![0.0f64; n]; n];
360 for edge in graph.edges() {
361 if let (Some(&i), Some(&j)) = (idx.get(&edge.from), idx.get(&edge.to)) {
362 laplacian[i][j] -= 1.0;
363 laplacian[j][i] -= 1.0;
364 laplacian[i][i] += 1.0;
365 laplacian[j][j] += 1.0;
366 }
367 }
368
369 let eigvec2 = self.fiedler_vector(&laplacian, n, 0);
372 let eigvec3 = self.fiedler_vector_orthogonal(&laplacian, n, &eigvec2);
373
374 let center = self.config.bounds * 0.5;
375 let scale = self.config.bounds.x.min(self.config.bounds.y) * 0.4;
376
377 let mut positions = HashMap::new();
378 let max2 = eigvec2.iter().map(|x| x.abs()).fold(0.0f64, f64::max).max(1e-10);
379 let max3 = eigvec3.iter().map(|x| x.abs()).fold(0.0f64, f64::max).max(1e-10);
380
381 for (i, &nid) in node_ids.iter().enumerate() {
382 let x = center.x + (eigvec2[i] / max2) as f32 * scale;
383 let y = center.y + (eigvec3[i] / max3) as f32 * scale;
384 positions.insert(nid, Vec2::new(x, y));
385 }
386 positions
387 }
388
389 fn fiedler_vector(&self, laplacian: &[Vec<f64>], n: usize, seed_offset: u64) -> Vec<f64> {
390 let max_iter = 200;
395 let mut v: Vec<f64> = (0..n).map(|i| pseudo_random(self.config.seed + seed_offset, i as u64) - 0.5).collect();
397
398 let mean: f64 = v.iter().sum::<f64>() / n as f64;
400 for x in v.iter_mut() { *x -= mean; }
401 let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
402 if norm > 1e-12 { for x in v.iter_mut() { *x /= norm; } }
403
404 for _ in 0..max_iter {
405 let mut w = vec![0.0f64; n];
407 for i in 0..n {
408 for j in 0..n {
409 w[i] += laplacian[i][j] * v[j];
410 }
411 }
412 let mean: f64 = w.iter().sum::<f64>() / n as f64;
414 for x in w.iter_mut() { *x -= mean; }
415 let norm: f64 = w.iter().map(|x| x * x).sum::<f64>().sqrt();
416 if norm > 1e-12 { for x in w.iter_mut() { *x /= norm; } }
417 v = w;
418 }
419 v
420 }
421
422 fn fiedler_vector_orthogonal(&self, laplacian: &[Vec<f64>], n: usize, fiedler: &[f64]) -> Vec<f64> {
423 let max_iter = 200;
424 let mut v: Vec<f64> = (0..n).map(|i| pseudo_random(self.config.seed + 9999, i as u64) - 0.5).collect();
425
426 for _ in 0..max_iter {
427 let mut w = vec![0.0f64; n];
428 for i in 0..n {
429 for j in 0..n {
430 w[i] += laplacian[i][j] * v[j];
431 }
432 }
433 let mean: f64 = w.iter().sum::<f64>() / n as f64;
435 for x in w.iter_mut() { *x -= mean; }
436 let dot: f64 = w.iter().zip(fiedler).map(|(a, b)| a * b).sum();
438 for (x, f) in w.iter_mut().zip(fiedler) { *x -= dot * f; }
439 let norm: f64 = w.iter().map(|x| x * x).sum::<f64>().sqrt();
440 if norm > 1e-12 { for x in w.iter_mut() { *x /= norm; } }
441 v = w;
442 }
443 v
444 }
445}
446
447pub struct TreeLayout {
450 config: LayoutConfig,
451}
452
453impl TreeLayout {
454 pub fn new(config: LayoutConfig) -> Self {
455 Self { config }
456 }
457
458 pub fn compute<N, E>(&self, graph: &Graph<N, E>) -> HashMap<NodeId, Vec2> {
459 let node_ids = graph.node_ids();
460 if node_ids.is_empty() { return HashMap::new(); }
461
462 let root = if graph.kind == GraphKind::Directed {
464 let mut has_incoming: HashSet<NodeId> = HashSet::new();
465 for edge in graph.edges() { has_incoming.insert(edge.to); }
466 node_ids.iter().find(|n| !has_incoming.contains(n)).copied().unwrap_or(node_ids[0])
467 } else {
468 node_ids[0]
469 };
470
471 let mut children: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
473 let mut depth: HashMap<NodeId, usize> = HashMap::new();
474 let mut visited = HashSet::new();
475 let mut queue = VecDeque::new();
476 visited.insert(root);
477 depth.insert(root, 0);
478 queue.push_back(root);
479 children.insert(root, Vec::new());
480
481 while let Some(nid) = queue.pop_front() {
482 let mut kids = Vec::new();
483 for nbr in graph.neighbors(nid) {
484 if visited.insert(nbr) {
485 depth.insert(nbr, depth[&nid] + 1);
486 children.insert(nbr, Vec::new());
487 queue.push_back(nbr);
488 kids.push(nbr);
489 }
490 }
491 children.insert(nid, kids);
492 }
493
494 let mut x_pos: HashMap<NodeId, f32> = HashMap::new();
496 let mut next_x = 0.0f32;
497 self.assign_x(root, &children, &mut x_pos, &mut next_x);
498
499 let max_depth = depth.values().copied().max().unwrap_or(0) as f32;
500 let y_spacing = if max_depth > 0.0 {
501 self.config.bounds.y / (max_depth + 1.0)
502 } else {
503 self.config.bounds.y / 2.0
504 };
505
506 let max_x = x_pos.values().copied().fold(0.0f32, f32::max).max(1.0);
507 let x_scale = self.config.bounds.x / (max_x + 1.0);
508
509 let mut positions = HashMap::new();
510 for (&nid, &d) in &depth {
511 let x = x_pos.get(&nid).copied().unwrap_or(0.0) * x_scale + x_scale * 0.5;
512 let y = d as f32 * y_spacing + y_spacing * 0.5;
513 positions.insert(nid, Vec2::new(x, y));
514 }
515 positions
516 }
517
518 fn assign_x(&self, node: NodeId, children: &HashMap<NodeId, Vec<NodeId>>, x_pos: &mut HashMap<NodeId, f32>, next_x: &mut f32) {
519 let kids = children.get(&node).cloned().unwrap_or_default();
520 if kids.is_empty() {
521 x_pos.insert(node, *next_x);
522 *next_x += 1.0;
523 } else {
524 for &child in &kids {
525 self.assign_x(child, children, x_pos, next_x);
526 }
527 let first = x_pos[&kids[0]];
528 let last = x_pos[kids.last().unwrap()];
529 x_pos.insert(node, (first + last) / 2.0);
530 }
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 fn make_triangle() -> Graph<(), ()> {
539 let mut g = Graph::new(GraphKind::Undirected);
540 let a = g.add_node(());
541 let b = g.add_node(());
542 let c = g.add_node(());
543 g.add_edge(a, b, ());
544 g.add_edge(b, c, ());
545 g.add_edge(a, c, ());
546 g
547 }
548
549 fn make_path(n: usize) -> Graph<(), ()> {
550 let mut g = Graph::new(GraphKind::Undirected);
551 let mut prev = g.add_node(());
552 for _ in 1..n {
553 let next = g.add_node(());
554 g.add_edge(prev, next, ());
555 prev = next;
556 }
557 g
558 }
559
560 #[test]
561 fn test_force_directed_produces_positions() {
562 let g = make_triangle();
563 let config = LayoutConfig { iterations: 50, ..Default::default() };
564 let pos = compute_layout(&g, LayoutAlgorithm::ForceDirected, &config);
565 assert_eq!(pos.len(), 3);
566 for p in pos.values() {
567 assert!(p.x >= 0.0 && p.x <= config.bounds.x);
568 assert!(p.y >= 0.0 && p.y <= config.bounds.y);
569 }
570 }
571
572 #[test]
573 fn test_circular_layout() {
574 let g = make_path(6);
575 let config = LayoutConfig::default();
576 let pos = compute_layout(&g, LayoutAlgorithm::Circular, &config);
577 assert_eq!(pos.len(), 6);
578 }
579
580 #[test]
581 fn test_hierarchical_layout() {
582 let mut g = Graph::new(GraphKind::Directed);
583 let a = g.add_node(());
584 let b = g.add_node(());
585 let c = g.add_node(());
586 let d = g.add_node(());
587 g.add_edge(a, b, ());
588 g.add_edge(a, c, ());
589 g.add_edge(b, d, ());
590 let config = LayoutConfig::default();
591 let pos = compute_layout(&g, LayoutAlgorithm::Hierarchical, &config);
592 assert_eq!(pos.len(), 4);
593 assert!(pos[&a].y < pos[&b].y);
595 assert!(pos[&b].y < pos[&d].y);
596 }
597
598 #[test]
599 fn test_tree_layout() {
600 let mut g = Graph::new(GraphKind::Undirected);
601 let a = g.add_node(());
602 let b = g.add_node(());
603 let c = g.add_node(());
604 let d = g.add_node(());
605 let e = g.add_node(());
606 g.add_edge(a, b, ());
607 g.add_edge(a, c, ());
608 g.add_edge(b, d, ());
609 g.add_edge(b, e, ());
610 let config = LayoutConfig::default();
611 let pos = compute_layout(&g, LayoutAlgorithm::Tree, &config);
612 assert_eq!(pos.len(), 5);
613 }
614
615 #[test]
616 fn test_spectral_layout() {
617 let g = make_path(5);
618 let config = LayoutConfig::default();
619 let pos = compute_layout(&g, LayoutAlgorithm::Spectral, &config);
620 assert_eq!(pos.len(), 5);
621 }
622
623 #[test]
624 fn test_random_layout() {
625 let g = make_triangle();
626 let config = LayoutConfig::default();
627 let pos = compute_layout(&g, LayoutAlgorithm::Random, &config);
628 assert_eq!(pos.len(), 3);
629 }
630
631 #[test]
632 fn test_grid_layout() {
633 let g = make_path(9);
634 let config = LayoutConfig::default();
635 let pos = compute_layout(&g, LayoutAlgorithm::Grid, &config);
636 assert_eq!(pos.len(), 9);
637 }
638
639 #[test]
640 fn test_empty_graph() {
641 let g: Graph<(), ()> = Graph::new(GraphKind::Undirected);
642 let config = LayoutConfig::default();
643 for alg in &[LayoutAlgorithm::ForceDirected, LayoutAlgorithm::Circular, LayoutAlgorithm::Tree] {
644 let pos = compute_layout(&g, *alg, &config);
645 assert!(pos.is_empty());
646 }
647 }
648
649 #[test]
650 fn test_single_node() {
651 let mut g: Graph<(), ()> = Graph::new(GraphKind::Undirected);
652 g.add_node(());
653 let config = LayoutConfig::default();
654 let pos = compute_layout(&g, LayoutAlgorithm::Spectral, &config);
655 assert_eq!(pos.len(), 1);
656 }
657
658 #[test]
659 fn test_force_directed_separates_components() {
660 let mut g: Graph<(), ()> = Graph::new(GraphKind::Undirected);
661 let a = g.add_node(());
662 let b = g.add_node(());
663 g.add_edge(a, b, ());
664 let c = g.add_node(());
665 let d = g.add_node(());
666 g.add_edge(c, d, ());
667 let config = LayoutConfig { iterations: 100, ..Default::default() };
668 let pos = compute_layout(&g, LayoutAlgorithm::ForceDirected, &config);
669 assert_eq!(pos.len(), 4);
670 let dist_ab = (pos[&a] - pos[&b]).length();
672 let dist_ac = (pos[&a] - pos[&c]).length();
673 }
675
676 #[test]
677 fn test_circular_layout_radius() {
678 let mut g: Graph<(), ()> = Graph::new(GraphKind::Undirected);
679 let nodes: Vec<NodeId> = (0..8).map(|_| g.add_node(())).collect();
680 for i in 0..7 { g.add_edge(nodes[i], nodes[i + 1], ()); }
681 let config = LayoutConfig::default();
682 let pos = compute_layout(&g, LayoutAlgorithm::Circular, &config);
683 let center = config.bounds * 0.5;
684 let radius = config.bounds.x.min(config.bounds.y) * 0.4;
685 for p in pos.values() {
686 let dist = (*p - center).length();
687 assert!((dist - radius).abs() < 1.0);
688 }
689 }
690}