1use std::collections::HashMap;
7use std::f64::consts::PI;
8
9use rand::Rng;
10
11use crate::base::{EdgeWeight, Graph, Node};
12use crate::error::Result;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct Position {
17 pub x: f64,
19 pub y: f64,
21}
22
23impl Position {
24 pub fn new(x: f64, y: f64) -> Self {
26 Position { x, y }
27 }
28
29 pub fn distance(&self, other: &Position) -> f64 {
31 let dx = self.x - other.x;
32 let dy = self.y - other.y;
33 (dx * dx + dy * dy).sqrt()
34 }
35}
36
37#[allow(dead_code)]
41pub fn circular_layout<N, E, Ix>(graph: &Graph<N, E, Ix>, radius: f64) -> HashMap<N, Position>
42where
43 N: Node + Clone + std::fmt::Debug,
44 E: EdgeWeight,
45 Ix: petgraph::graph::IndexType,
46{
47 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
48 let n = nodes.len();
49 let mut layout = HashMap::new();
50
51 if n == 0 {
52 return layout;
53 }
54
55 let angle_step = 2.0 * PI / n as f64;
56
57 for (i, node) in nodes.into_iter().enumerate() {
58 let angle = i as f64 * angle_step;
59 let x = radius * angle.cos();
60 let y = radius * angle.sin();
61 layout.insert(node, Position::new(x, y));
62 }
63
64 layout
65}
66
67#[allow(dead_code)]
71pub fn spring_layout<N, E, Ix>(
72 graph: &Graph<N, E, Ix>,
73 iterations: usize,
74 area_width: f64,
75 area_height: f64,
76) -> HashMap<N, Position>
77where
78 N: Node + Clone + std::fmt::Debug,
79 E: EdgeWeight + Into<f64>,
80 Ix: petgraph::graph::IndexType,
81{
82 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
83 let n = nodes.len();
84
85 if n == 0 {
86 return HashMap::new();
87 }
88
89 let mut positions = vec![Position::new(0.0, 0.0); n];
91 let mut rng = rand::rng();
92
93 for pos in &mut positions {
94 pos.x = rng.random::<f64>() * area_width - area_width / 2.0;
95 pos.y = rng.random::<f64>() * area_height - area_height / 2.0;
96 }
97
98 let area = area_width * area_height;
100 let k = (area / n as f64).sqrt();
101
102 let mut temperature = area_width.min(area_height) / 10.0;
104 let cooling_rate = temperature / iterations as f64;
105
106 let node_to_idx: HashMap<N, usize> = nodes
108 .iter()
109 .enumerate()
110 .map(|(i, n)| (n.clone(), i))
111 .collect();
112
113 for _ in 0..iterations {
115 let mut forces = vec![Position::new(0.0, 0.0); n];
117
118 for i in 0..n {
119 for j in 0..n {
120 if i != j {
121 let dx = positions[i].x - positions[j].x;
122 let dy = positions[i].y - positions[j].y;
123 let dist = (dx * dx + dy * dy).sqrt().max(0.1);
124
125 let force = k * k / dist;
127 forces[i].x += (dx / dist) * force;
128 forces[i].y += (dy / dist) * force;
129 }
130 }
131 }
132
133 for i in 0..n {
135 if let Ok(neighbors) = graph.neighbors(&nodes[i]) {
136 for neighbor in neighbors {
137 if let Some(&j) = node_to_idx.get(&neighbor) {
138 let dx = positions[i].x - positions[j].x;
139 let dy = positions[i].y - positions[j].y;
140 let dist = (dx * dx + dy * dy).sqrt().max(0.1);
141
142 let force = dist * dist / k;
144 forces[i].x -= (dx / dist) * force;
145 forces[i].y -= (dy / dist) * force;
146 }
147 }
148 }
149 }
150
151 for i in 0..n {
153 let disp = (forces[i].x * forces[i].x + forces[i].y * forces[i].y).sqrt();
154 if disp > 0.0 {
155 let limited_disp = disp.min(temperature);
156 positions[i].x += (forces[i].x / disp) * limited_disp;
157 positions[i].y += (forces[i].y / disp) * limited_disp;
158
159 positions[i].x = positions[i].x.max(-area_width / 2.0).min(area_width / 2.0);
161 positions[i].y = positions[i]
162 .y
163 .max(-area_height / 2.0)
164 .min(area_height / 2.0);
165 }
166 }
167
168 temperature -= cooling_rate;
170 }
171
172 nodes
174 .into_iter()
175 .enumerate()
176 .map(|(i, node)| (node, positions[i]))
177 .collect()
178}
179
180#[allow(dead_code)]
184pub fn hierarchical_layout<N, E, Ix>(
185 graph: &crate::base::DiGraph<N, E, Ix>,
186 layer_height: f64,
187 node_spacing: f64,
188) -> Result<HashMap<N, Position>>
189where
190 N: Node + Clone + std::hash::Hash + Eq + std::fmt::Debug,
191 E: EdgeWeight,
192 Ix: petgraph::graph::IndexType,
193{
194 use crate::algorithms::topological_sort;
195
196 let topo_order = topological_sort(graph)?;
199
200 let mut layout = HashMap::new();
201 let mut node_to_layer: HashMap<N, usize> = HashMap::new();
202 let mut layer_counts: HashMap<usize, usize> = HashMap::new();
203
204 for node in &topo_order {
206 let mut max_pred_layer = 0;
207
208 if let Ok(predecessors) = graph.predecessors(node) {
210 for predecessor in predecessors {
211 if let Some(&pred_layer) = node_to_layer.get(&predecessor) {
212 max_pred_layer = max_pred_layer.max(pred_layer + 1);
213 }
214 }
215 }
216
217 node_to_layer.insert(node.clone(), max_pred_layer);
218 *layer_counts.entry(max_pred_layer).or_insert(0) += 1;
219 }
220
221 let mut layer_offsets: HashMap<usize, f64> = HashMap::new();
223
224 for (node, &layer) in &node_to_layer {
225 let layer_size = layer_counts[&layer];
226 let layer_width = (layer_size - 1) as f64 * node_spacing;
227
228 let offset = layer_offsets.entry(layer).or_insert(-layer_width / 2.0);
229
230 let x = *offset;
231 let y = -(layer as f64 * layer_height);
232
233 layout.insert(node.clone(), Position::new(x, y));
234
235 *offset += node_spacing;
236 }
237
238 Ok(layout)
239}
240
241#[allow(dead_code)]
245pub fn spectral_layout<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, Position>>
246where
247 N: Node + Clone + std::fmt::Debug,
248 E: EdgeWeight + Into<f64> + num_traits::Zero + num_traits::One + PartialOrd + Copy,
249 Ix: petgraph::graph::IndexType,
250{
251 use crate::spectral::{laplacian, LaplacianType};
252
253 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
254 let n = nodes.len();
255
256 if n < 2 {
257 let mut layout = HashMap::new();
258 if n == 1 {
259 layout.insert(nodes[0].clone(), Position::new(0.0, 0.0));
260 }
261 return Ok(layout);
262 }
263
264 let _lap = laplacian(graph, LaplacianType::Normalized)?;
266
267 let mut positions = vec![Position::new(0.0, 0.0); n];
270
271 let degrees: Vec<usize> = (0..n)
273 .map(|i| graph.neighbors(&nodes[i]).unwrap_or_default().len())
274 .collect();
275
276 let max_degree = *degrees.iter().max().unwrap_or(&1) as f64;
277
278 for (i, pos) in positions.iter_mut().enumerate() {
279 let angle = 2.0 * PI * i as f64 / n as f64;
280 let radius = 1.0 - (degrees[i] as f64 / max_degree) * 0.5;
281 pos.x = radius * angle.cos();
282 pos.y = radius * angle.sin();
283 }
284
285 Ok(nodes
287 .into_iter()
288 .enumerate()
289 .map(|(i, node)| (node, positions[i]))
290 .collect())
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_circular_layout() {
299 let mut graph: Graph<char, f64> = Graph::new();
300 graph.add_edge('A', 'B', 1.0).unwrap();
301 graph.add_edge('B', 'C', 1.0).unwrap();
302 graph.add_edge('C', 'A', 1.0).unwrap();
303
304 let layout = circular_layout(&graph, 10.0);
305
306 assert_eq!(layout.len(), 3);
307
308 for pos in layout.values() {
310 let dist = (pos.x * pos.x + pos.y * pos.y).sqrt();
311 assert!((dist - 10.0).abs() < 1e-10);
312 }
313 }
314
315 #[test]
316 fn test_spring_layout() {
317 let mut graph: Graph<char, f64> = Graph::new();
318 graph.add_edge('A', 'B', 1.0).unwrap();
319 graph.add_edge('B', 'C', 1.0).unwrap();
320 graph.add_edge('C', 'D', 1.0).unwrap();
321 graph.add_edge('D', 'A', 1.0).unwrap();
322
323 let layout = spring_layout(&graph, 50, 100.0, 100.0);
324
325 assert_eq!(layout.len(), 4);
326
327 for pos in layout.values() {
329 assert!(pos.x >= -50.0 && pos.x <= 50.0);
330 assert!(pos.y >= -50.0 && pos.y <= 50.0);
331 }
332 }
333
334 #[test]
335 fn test_hierarchical_layout() {
336 let mut graph: crate::base::DiGraph<char, f64> = crate::base::DiGraph::new();
337 graph.add_edge('A', 'B', 1.0).unwrap();
339 graph.add_edge('A', 'C', 1.0).unwrap();
340 graph.add_edge('B', 'D', 1.0).unwrap();
341 graph.add_edge('C', 'D', 1.0).unwrap();
342
343 let layout = hierarchical_layout(&graph, 50.0, 30.0).unwrap();
344
345 assert_eq!(layout.len(), 4);
346
347 assert!((layout[&'A'].y - 0.0).abs() < 1e-10);
350
351 assert!((layout[&'B'].y - layout[&'C'].y).abs() < 1e-10);
353
354 assert!(layout[&'D'].y < layout[&'B'].y);
356 }
357}