1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::data::FA2Data;
use crate::layout::FA2Layout;
use crate::traits::Float;
#[derive(Debug, Clone)]
pub(crate) enum RepulsionMode<F: Float> {
Pairwise,
BarnesHut { theta: F },
}
/// Struct representing a settings of the FA2 layout algorithm.
#[derive(Debug, Clone)]
pub struct FA2Settings<F: Float> {
pub(crate) repulsion_mode: RepulsionMode<F>,
// pub(crate) lin_log_mode: bool,
pub(crate) edge_weight_influence: F,
pub(crate) gravity: F,
pub(crate) strong_gravity_mode: bool,
// pub(crate) outbound_attraction_distribution: bool,
pub(crate) scaling_ratio: F,
pub(crate) slow_down: F,
pub(crate) parallel: bool,
}
impl<F: Float> Default for FA2Settings<F> {
/// Instantiate default settings for the FA2 algorithm. That is to say
/// paiwise repulsion, an `edge_weight_influence` of 1, a `gravity` of 1
/// no `strong_gravity_mode`, a `scaling_ratio` of 1, a `slow_down` of 1
/// and single-threaded.
///
// Ref: https://github.com/graphology/graphology/blob/master/src/layout-forceatlas2/defaults.js
fn default() -> Self {
Self {
repulsion_mode: RepulsionMode::Pairwise,
// lin_log_mode: false,
edge_weight_influence: F::one(),
gravity: F::one(),
strong_gravity_mode: false,
// outbound_attraction_distribution: false,
scaling_ratio: F::one(),
slow_down: F::one(),
parallel: false,
}
}
}
impl<F: Float> FA2Settings<F> {
/// Automatically inferring suitable settings based on a graph's order (its
/// number of nodes).
///
// Ref: https://github.com/graphology/graphology/blob/249ec5e668ff5e89bf37a10330981579f8759525/src/layout-forceatlas2/index.js#L87
pub fn from_graph_order(order: usize) -> Self {
Self {
repulsion_mode: if order >= 2048 {
RepulsionMode::BarnesHut {
theta: F::from(0.5).unwrap(),
}
} else {
RepulsionMode::Pairwise
},
strong_gravity_mode: true,
gravity: F::from(0.05).unwrap(),
scaling_ratio: F::from(10.0).unwrap(),
slow_down: F::one() + F::from(order).unwrap().ln(),
..Default::default()
}
}
/// Set the layout to run in parallel (using multiple threads).
pub fn parallel(mut self, yes: bool) -> Self {
self.parallel = yes;
self
}
/// Set the layout to use pairwise repulsion (`O(n²)`).
pub fn with_pairwise_repulsion(mut self) -> Self {
self.repulsion_mode = RepulsionMode::Pairwise;
self
}
/// Set the layout to use Barnes-Hut repulsion using the provided theta
/// parameter (`O(n log(n))`).
pub fn with_barnes_hut_theta(mut self, theta: F) -> Self {
self.repulsion_mode = RepulsionMode::BarnesHut { theta };
self
}
/// Set the layout to use Barnes-Hut repulsion using the default `0.5` theta
/// parameter (`O(n log(n))`).
pub fn with_barnes_hut(self) -> Self {
self.with_barnes_hut_theta(F::from(0.5).unwrap())
}
/// Set the `edge_weight_influence` setting. Defaults to `1.0`.
pub fn edge_weight_influence(mut self, edge_weight_influence: F) -> Self {
self.edge_weight_influence = edge_weight_influence;
self
}
/// Set the `gravity` setting. Defaults to `1.0`.
pub fn gravity(mut self, gravity: F) -> Self {
self.gravity = gravity;
self
}
/// Set the `gravity` setting. Defaults to `1.0`.
pub fn scaling_ratio(mut self, scaling_ratio: F) -> Self {
self.scaling_ratio = scaling_ratio;
self
}
/// Set the `slow_down` setting. Defaults to `1.0`.
pub fn slow_down(mut self, slow_down: F) -> Self {
self.slow_down = slow_down;
self
}
/// Set whether to use stronger gravity. Defaults to no.
pub fn strong_gravity_mode(mut self, yes: bool) -> Self {
self.strong_gravity_mode = yes;
self
}
pub(crate) fn unwrap_barnes_hut_theta(&self) -> F {
match &self.repulsion_mode {
RepulsionMode::BarnesHut { theta } => *theta,
_ => panic!("not using barnes hut!"),
}
}
/// Build a [`FA2Layout`] layout runner wrapping given [`FA2Data`].
pub fn build(self, data: FA2Data<F>) -> FA2Layout<F> {
FA2Layout::new(self, data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_graph_order() {
let settings = FA2Settings::<f32>::from_graph_order(32);
assert_eq!(settings.slow_down, 4.465736);
let settings = FA2Settings::<f64>::from_graph_order(32);
assert_eq!(settings.slow_down, 4.465735902799727);
}
}