1use std::collections::HashMap;
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Position {
12 pub x: f32,
14 pub y: f32,
16}
17
18impl Position {
19 #[allow(dead_code)]
21 pub fn new(x: f32, y: f32) -> Self {
22 Self { x, y }
23 }
24
25 #[allow(dead_code)]
27 pub fn distance(&self, other: &Position) -> f32 {
28 let dx = self.x - other.x;
29 let dy = self.y - other.y;
30 (dx * dx + dy * dy).sqrt()
31 }
32}
33
34#[allow(dead_code)]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct LayoutNodeId(pub usize);
38
39#[allow(dead_code)]
41#[derive(Debug, Clone, Copy)]
42pub struct LayoutEdge {
43 pub from: LayoutNodeId,
45 pub to: LayoutNodeId,
47}
48
49#[allow(dead_code)]
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub enum LayoutAlgorithm {
53 ForceDirected {
55 iterations: usize,
57 ideal_length: f32,
59 },
60 Hierarchical {
62 layer_spacing: f32,
64 node_spacing: f32,
66 },
67 Grid {
69 columns: usize,
71 cell_width: f32,
73 cell_height: f32,
75 },
76}
77
78impl Default for LayoutAlgorithm {
79 fn default() -> Self {
80 LayoutAlgorithm::Grid {
81 columns: 4,
82 cell_width: 150.0,
83 cell_height: 100.0,
84 }
85 }
86}
87
88#[allow(dead_code)]
90pub struct GraphLayout {
91 algorithm: LayoutAlgorithm,
92}
93
94impl GraphLayout {
95 #[allow(dead_code)]
97 pub fn new(algorithm: LayoutAlgorithm) -> Self {
98 Self { algorithm }
99 }
100
101 #[allow(dead_code)]
103 pub fn compute(
104 &self,
105 node_ids: &[LayoutNodeId],
106 edges: &[LayoutEdge],
107 ) -> HashMap<LayoutNodeId, Position> {
108 match self.algorithm {
109 LayoutAlgorithm::Grid {
110 columns,
111 cell_width,
112 cell_height,
113 } => self.grid_layout(node_ids, columns, cell_width, cell_height),
114 LayoutAlgorithm::Hierarchical {
115 layer_spacing,
116 node_spacing,
117 } => self.hierarchical_layout(node_ids, edges, layer_spacing, node_spacing),
118 LayoutAlgorithm::ForceDirected {
119 iterations,
120 ideal_length,
121 } => self.force_directed_layout(node_ids, edges, iterations, ideal_length),
122 }
123 }
124
125 #[allow(dead_code)]
127 fn grid_layout(
128 &self,
129 node_ids: &[LayoutNodeId],
130 columns: usize,
131 cell_width: f32,
132 cell_height: f32,
133 ) -> HashMap<LayoutNodeId, Position> {
134 let cols = columns.max(1);
135 let mut positions = HashMap::new();
136 for (i, &node_id) in node_ids.iter().enumerate() {
137 let col = i % cols;
138 let row = i / cols;
139 positions.insert(
140 node_id,
141 Position::new(col as f32 * cell_width, row as f32 * cell_height),
142 );
143 }
144 positions
145 }
146
147 #[allow(dead_code)]
149 fn hierarchical_layout(
150 &self,
151 node_ids: &[LayoutNodeId],
152 edges: &[LayoutEdge],
153 layer_spacing: f32,
154 node_spacing: f32,
155 ) -> HashMap<LayoutNodeId, Position> {
156 let mut in_degree: HashMap<LayoutNodeId, usize> = HashMap::new();
158 for &id in node_ids {
159 in_degree.insert(id, 0);
160 }
161 for edge in edges {
162 *in_degree.entry(edge.to).or_insert(0) += 1;
163 }
164
165 let mut layers: HashMap<usize, Vec<LayoutNodeId>> = HashMap::new();
167 for (&node, °) in &in_degree {
168 layers.entry(deg).or_default().push(node);
169 }
170
171 let mut positions = HashMap::new();
172 for (layer_idx, (_, layer_nodes)) in layers.iter().enumerate() {
173 for (node_idx, &node_id) in layer_nodes.iter().enumerate() {
174 positions.insert(
175 node_id,
176 Position::new(
177 node_idx as f32 * node_spacing,
178 layer_idx as f32 * layer_spacing,
179 ),
180 );
181 }
182 }
183 positions
184 }
185
186 #[allow(dead_code)]
188 fn force_directed_layout(
189 &self,
190 node_ids: &[LayoutNodeId],
191 edges: &[LayoutEdge],
192 iterations: usize,
193 ideal_length: f32,
194 ) -> HashMap<LayoutNodeId, Position> {
195 if node_ids.is_empty() {
196 return HashMap::new();
197 }
198
199 let n = node_ids.len();
201 let mut positions: HashMap<LayoutNodeId, Position> = HashMap::new();
202 for (i, &id) in node_ids.iter().enumerate() {
203 let angle = 2.0 * std::f32::consts::PI * i as f32 / n as f32;
204 positions.insert(
205 id,
206 Position::new(angle.cos() * ideal_length, angle.sin() * ideal_length),
207 );
208 }
209
210 let k = ideal_length;
211 let k2 = k * k;
212
213 for _ in 0..iterations {
214 let mut displacements: HashMap<LayoutNodeId, (f32, f32)> = HashMap::new();
215 for &id in node_ids {
216 displacements.insert(id, (0.0, 0.0));
217 }
218
219 for i in 0..n {
221 for j in (i + 1)..n {
222 let u = node_ids[i];
223 let v = node_ids[j];
224 let pu = match positions.get(&u) {
226 Some(p) => *p,
227 None => continue,
228 };
229 let pv = match positions.get(&v) {
230 Some(p) => *p,
231 None => continue,
232 };
233 let dx = pu.x - pv.x;
234 let dy = pu.y - pv.y;
235 let dist2 = (dx * dx + dy * dy).max(0.001);
236 let dist = dist2.sqrt();
237 let force = k2 / dist;
238 let fx = (dx / dist) * force;
239 let fy = (dy / dist) * force;
240 if let Some(d) = displacements.get_mut(&u) {
241 d.0 += fx;
242 d.1 += fy;
243 }
244 if let Some(d) = displacements.get_mut(&v) {
245 d.0 -= fx;
246 d.1 -= fy;
247 }
248 }
249 }
250
251 for edge in edges {
253 let pu = match positions.get(&edge.from) {
254 Some(p) => *p,
255 None => continue,
256 };
257 let pv = match positions.get(&edge.to) {
258 Some(p) => *p,
259 None => continue,
260 };
261 let dx = pu.x - pv.x;
262 let dy = pu.y - pv.y;
263 let dist = (dx * dx + dy * dy).sqrt().max(0.001);
264 let force = dist * dist / k;
265 let fx = (dx / dist) * force;
266 let fy = (dy / dist) * force;
267 if let Some(d) = displacements.get_mut(&edge.from) {
268 d.0 -= fx;
269 d.1 -= fy;
270 }
271 if let Some(d) = displacements.get_mut(&edge.to) {
272 d.0 += fx;
273 d.1 += fy;
274 }
275 }
276
277 let temp = k / 10.0;
279 for &id in node_ids {
280 let (dx, dy) = match displacements.get(&id) {
282 Some(&d) => d,
283 None => continue,
284 };
285 let disp_len = (dx * dx + dy * dy).sqrt().max(0.001);
286 let clamped = disp_len.min(temp);
287 if let Some(pos) = positions.get_mut(&id) {
288 pos.x += (dx / disp_len) * clamped;
289 pos.y += (dy / disp_len) * clamped;
290 }
291 }
292 }
293
294 positions
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 fn node_ids(n: usize) -> Vec<LayoutNodeId> {
303 (0..n).map(LayoutNodeId).collect()
304 }
305
306 #[test]
307 fn test_position_creation() {
308 let p = Position::new(1.0, 2.0);
309 assert_eq!(p.x, 1.0);
310 assert_eq!(p.y, 2.0);
311 }
312
313 #[test]
314 fn test_position_distance_zero() {
315 let p = Position::new(3.0, 4.0);
316 assert_eq!(p.distance(&p), 0.0);
317 }
318
319 #[test]
320 fn test_position_distance_nonzero() {
321 let a = Position::new(0.0, 0.0);
322 let b = Position::new(3.0, 4.0);
323 assert!((a.distance(&b) - 5.0).abs() < 1e-5);
324 }
325
326 #[test]
327 fn test_grid_layout_empty() {
328 let layout = GraphLayout::new(LayoutAlgorithm::Grid {
329 columns: 4,
330 cell_width: 100.0,
331 cell_height: 100.0,
332 });
333 let positions = layout.compute(&[], &[]);
334 assert!(positions.is_empty());
335 }
336
337 #[test]
338 fn test_grid_layout_count() {
339 let layout = GraphLayout::new(LayoutAlgorithm::Grid {
340 columns: 3,
341 cell_width: 100.0,
342 cell_height: 100.0,
343 });
344 let ids = node_ids(6);
345 let positions = layout.compute(&ids, &[]);
346 assert_eq!(positions.len(), 6);
347 }
348
349 #[test]
350 fn test_grid_layout_first_node_origin() {
351 let layout = GraphLayout::new(LayoutAlgorithm::Grid {
352 columns: 4,
353 cell_width: 100.0,
354 cell_height: 100.0,
355 });
356 let ids = node_ids(4);
357 let positions = layout.compute(&ids, &[]);
358 let p = positions[&LayoutNodeId(0)];
359 assert_eq!(p.x, 0.0);
360 assert_eq!(p.y, 0.0);
361 }
362
363 #[test]
364 fn test_grid_layout_second_row() {
365 let layout = GraphLayout::new(LayoutAlgorithm::Grid {
366 columns: 2,
367 cell_width: 100.0,
368 cell_height: 80.0,
369 });
370 let ids = node_ids(4);
371 let positions = layout.compute(&ids, &[]);
372 let p = positions[&LayoutNodeId(2)]; assert_eq!(p.x, 0.0);
374 assert_eq!(p.y, 80.0);
375 }
376
377 #[test]
378 fn test_hierarchical_layout_count() {
379 let layout = GraphLayout::new(LayoutAlgorithm::Hierarchical {
380 layer_spacing: 80.0,
381 node_spacing: 120.0,
382 });
383 let ids = node_ids(4);
384 let edges = vec![LayoutEdge {
385 from: LayoutNodeId(0),
386 to: LayoutNodeId(1),
387 }];
388 let positions = layout.compute(&ids, &edges);
389 assert_eq!(positions.len(), 4);
390 }
391
392 #[test]
393 fn test_hierarchical_positions_finite() {
394 let layout = GraphLayout::new(LayoutAlgorithm::Hierarchical {
395 layer_spacing: 80.0,
396 node_spacing: 120.0,
397 });
398 let ids = node_ids(5);
399 let edges = vec![
400 LayoutEdge {
401 from: LayoutNodeId(0),
402 to: LayoutNodeId(2),
403 },
404 LayoutEdge {
405 from: LayoutNodeId(1),
406 to: LayoutNodeId(3),
407 },
408 ];
409 let positions = layout.compute(&ids, &edges);
410 for (_, pos) in &positions {
411 assert!(pos.x.is_finite());
412 assert!(pos.y.is_finite());
413 }
414 }
415
416 #[test]
417 fn test_force_directed_empty() {
418 let layout = GraphLayout::new(LayoutAlgorithm::ForceDirected {
419 iterations: 10,
420 ideal_length: 100.0,
421 });
422 let positions = layout.compute(&[], &[]);
423 assert!(positions.is_empty());
424 }
425
426 #[test]
427 fn test_force_directed_count() {
428 let layout = GraphLayout::new(LayoutAlgorithm::ForceDirected {
429 iterations: 5,
430 ideal_length: 100.0,
431 });
432 let ids = node_ids(4);
433 let edges = vec![LayoutEdge {
434 from: LayoutNodeId(0),
435 to: LayoutNodeId(1),
436 }];
437 let positions = layout.compute(&ids, &edges);
438 assert_eq!(positions.len(), 4);
439 }
440
441 #[test]
442 fn test_force_directed_positions_finite() {
443 let layout = GraphLayout::new(LayoutAlgorithm::ForceDirected {
444 iterations: 20,
445 ideal_length: 80.0,
446 });
447 let ids = node_ids(6);
448 let edges = vec![
449 LayoutEdge {
450 from: LayoutNodeId(0),
451 to: LayoutNodeId(1),
452 },
453 LayoutEdge {
454 from: LayoutNodeId(1),
455 to: LayoutNodeId(2),
456 },
457 LayoutEdge {
458 from: LayoutNodeId(2),
459 to: LayoutNodeId(3),
460 },
461 ];
462 let positions = layout.compute(&ids, &edges);
463 for (_, pos) in &positions {
464 assert!(pos.x.is_finite());
465 assert!(pos.y.is_finite());
466 }
467 }
468
469 #[test]
470 fn test_default_algorithm_is_grid() {
471 let algo = LayoutAlgorithm::default();
472 assert!(matches!(algo, LayoutAlgorithm::Grid { .. }));
473 }
474}