Skip to content

Commit 0bdde0e

Browse files
committed
A
1 parent 1858060 commit 0bdde0e

4 files changed

Lines changed: 89 additions & 77 deletions

File tree

src/graph.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashSet;
22

3-
use egui::Pos2;
3+
use egui::{Pos2, Rect};
44
use petgraph::stable_graph::DefaultIx;
55
use petgraph::Directed;
66

@@ -44,6 +44,8 @@ pub struct Graph<
4444
selected_nodes: Vec<NodeIndex<Ix>>,
4545
selected_edges: Vec<EdgeIndex<Ix>>,
4646
dragged_node: Option<NodeIndex<Ix>>,
47+
48+
bounds: Rect,
4749
}
4850

4951
impl<N, E, Ty, Ix, Dn, De> From<&StableGraph<N, E, Ty, Ix>> for Graph<N, E, Ty, Ix, Dn, De>
@@ -75,6 +77,7 @@ where
7577
selected_nodes: Vec::default(),
7678
selected_edges: Vec::default(),
7779
dragged_node: Option::default(),
80+
bounds: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO),
7881
}
7982
}
8083

@@ -400,4 +403,12 @@ where
400403
pub fn node_count(&self) -> usize {
401404
self.g.node_count()
402405
}
406+
407+
pub fn set_bounds(&mut self, bounds: Rect) {
408+
self.bounds = bounds;
409+
}
410+
411+
pub fn bounds(&self) -> Rect {
412+
self.bounds
413+
}
403414
}

src/graph_view.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ where
239239
self.g.set_selected_nodes(selected_nodes);
240240
self.g.set_selected_edges(selected_edges);
241241
self.g.set_dragged_node(dragged);
242+
self.g.set_bounds(meta.graph_bounds());
242243
}
243244

244245
/// Fits the graph to the screen if it is the first frame or
Lines changed: 71 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
use egui::{Pos2, Vec2};
1+
use egui::Vec2;
2+
use petgraph::{csr::IndexType, EdgeType};
23
use serde::{Deserialize, Serialize};
34

4-
use crate::layouts::{Layout, LayoutState};
5+
use crate::{
6+
layouts::{Layout, LayoutState},
7+
DisplayEdge, DisplayNode, Graph,
8+
};
59

6-
const FORCE_CENTER_REPEL: f32 = 25.0;
7-
const FORCE_NEIGHBOR_ATTR: f32 = 0.01;
8-
const DT: f32 = 0.5;
10+
const DT: f32 = 0.05;
11+
const GRAVITY: f32 = 3.0;
12+
const EPSILON: f32 = 0.001;
913

1014
#[derive(Debug, Clone, Serialize, Deserialize)]
1115
pub struct State {
@@ -30,87 +34,83 @@ impl Layout<State> for ForceDirected {
3034
ForceDirected { state }
3135
}
3236

33-
fn next<N, E, Ty, Ix, Dn, De>(&mut self, g: &mut crate::Graph<N, E, Ty, Ix, Dn, De>)
37+
fn next<N, E, Ty, Ix, Dn, De>(&mut self, g: &mut Graph<N, E, Ty, Ix, Dn, De>)
3438
where
3539
N: Clone,
3640
E: Clone,
37-
Ty: petgraph::EdgeType,
38-
Ix: petgraph::csr::IndexType,
39-
Dn: crate::DisplayNode<N, E, Ty, Ix>,
40-
De: crate::DisplayEdge<N, E, Ty, Ix, Dn>,
41+
Ty: EdgeType,
42+
Ix: IndexType,
43+
Dn: DisplayNode<N, E, Ty, Ix>,
44+
De: DisplayEdge<N, E, Ty, Ix, Dn>,
4145
{
42-
if !self.state.is_running {
46+
if !self.state.is_running || g.node_count() == 0 {
4347
return;
4448
}
4549

46-
let center = find_center(g);
47-
let indices: Vec<_> = g.g_mut().node_indices().collect();
48-
for idx in indices {
49-
let loc = g.g_mut().node_weight(idx).unwrap().location();
50-
let vc = center - loc;
51-
let vc_len_sq = vc.length().powi(2);
52-
let dx_center = if vc_len_sq > 0.0 {
53-
-vc * (FORCE_CENTER_REPEL / vc_len_sq) * DT
54-
} else {
55-
Vec2::ZERO
56-
};
57-
58-
let dx_neighbor = g
59-
.g()
60-
.neighbors_undirected(idx)
61-
.map(|nbr_idx| {
62-
let vn = g.g().node_weight(nbr_idx).unwrap().location() - loc;
63-
if vn.length() > 0.0 {
64-
vn * FORCE_NEIGHBOR_ATTR * DT
65-
} else {
66-
Vec2::ZERO
67-
}
68-
})
69-
.fold(Vec2::ZERO, |acc, v| acc + v);
70-
71-
let new_loc = loc + dx_center + dx_neighbor;
50+
/* ----------------------------------------------------------------- */
51+
/* pre-computed values */
52+
/* ----------------------------------------------------------------- */
53+
let n = g.node_count() as f32;
54+
let area = g.bounds().area().max(1.0);
55+
let k = (area / n).sqrt(); // ideal edge length
56+
let centre = g.bounds().center();
57+
58+
let indices: Vec<_> = g.g().node_indices().collect();
59+
let mut disp: Vec<Vec2> = vec![Vec2::ZERO; indices.len()];
60+
61+
/* ----------------------------------------------------------------- */
62+
/* PASS 1 — node-to-node repulsion (O(|V|²)) */
63+
/* ----------------------------------------------------------------- */
64+
for i in 0..indices.len() {
65+
for j in (i + 1)..indices.len() {
66+
let idx_i = indices[i];
67+
let idx_j = indices[j];
68+
69+
let delta = g.g().node_weight(idx_i).unwrap().location()
70+
- g.g().node_weight(idx_j).unwrap().location();
71+
let dist = delta.length().max(EPSILON); // no division by 0
72+
73+
let force = (k * k) / dist;
74+
let dir = delta / dist; // unit vector
75+
76+
disp[i] += dir * force; // push i
77+
disp[j] -= dir * force; // equal & opposite push j
78+
}
79+
}
80+
81+
/* ----------------------------------------------------------------- */
82+
/* PASS 2 — edge attraction + centre gravity */
83+
/* ----------------------------------------------------------------- */
84+
for (idx_pos, &idx) in indices.iter().enumerate() {
85+
let loc = g.g().node_weight(idx).unwrap().location();
86+
87+
// attract towards every neighbour
88+
for nbr in g.g().neighbors_undirected(idx) {
89+
let delta = g.g().node_weight(nbr).unwrap().location() - loc;
90+
let dist = delta.length().max(EPSILON);
91+
92+
let force = (dist * dist) / k;
93+
disp[idx_pos] += (delta / dist) * force;
94+
}
95+
96+
// gentle gravity to centre
97+
disp[idx_pos] += (centre - loc) * GRAVITY;
98+
}
99+
100+
/* ----------------------------------------------------------------- */
101+
/* integrate & write back */
102+
/* ----------------------------------------------------------------- */
103+
for (idx_pos, &idx) in indices.iter().enumerate() {
104+
let loc = g.g().node_weight(idx).unwrap().location();
105+
let new_loc = loc + disp[idx_pos] * DT;
72106

73107
g.g_mut()
74108
.node_weight_mut(idx)
75109
.unwrap()
76110
.set_location(new_loc);
77111
}
78112
}
79-
80113
fn state(&self) -> State {
81114
self.state.clone()
82115
}
83116
}
84-
85-
fn find_center<N, E, Ty, Ix, Dn, De>(g: &crate::Graph<N, E, Ty, Ix, Dn, De>) -> Pos2
86-
where
87-
N: Clone,
88-
E: Clone,
89-
Ty: petgraph::EdgeType,
90-
Ix: petgraph::csr::IndexType,
91-
Dn: crate::DisplayNode<N, E, Ty, Ix>,
92-
De: crate::DisplayEdge<N, E, Ty, Ix, Dn>,
93-
{
94-
let mut min_x = f32::MAX;
95-
let mut max_x = f32::MIN;
96-
let mut min_y = f32::MAX;
97-
let mut max_y = f32::MIN;
98-
99-
for node in g.g().node_weights() {
100-
let loc = node.location();
101-
if loc.x < min_x {
102-
min_x = loc.x;
103-
}
104-
if loc.x > max_x {
105-
max_x = loc.x;
106-
}
107-
if loc.y < min_y {
108-
min_y = loc.y;
109-
}
110-
if loc.y > max_y {
111-
max_y = loc.y;
112-
}
113-
}
114-
115-
Pos2::new((min_x + max_x) / 2.0, (min_y + max_y) / 2.0)
116-
}

src/metadata.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ const KEY: &str = "egui_graphs_metadata";
88

99
#[derive(Clone, Debug, Serialize, Deserialize)]
1010
struct Bounds {
11-
min: Vec2,
12-
max: Vec2,
11+
min: Pos2,
12+
max: Pos2,
1313
}
1414

1515
impl Default for Bounds {
1616
fn default() -> Self {
1717
Self {
18-
min: Vec2::new(f32::MAX, f32::MAX),
19-
max: Vec2::new(f32::MIN, f32::MIN),
18+
min: Pos2::new(f32::MAX, f32::MAX),
19+
max: Pos2::new(f32::MIN, f32::MIN),
2020
}
2121
}
2222
}
@@ -117,7 +117,7 @@ impl Metadata {
117117

118118
/// Returns bounding rect of the graph.
119119
pub fn graph_bounds(&self) -> Rect {
120-
Rect::from_min_max(self.bounds.min.to_pos2(), self.bounds.max.to_pos2())
120+
Rect::from_min_max(self.bounds.min, self.bounds.max)
121121
}
122122

123123
/// Resets the bounds iterator.

0 commit comments

Comments
 (0)