fdg_sim/force/
mod.rs

1use glam::Vec3;
2use petgraph::{EdgeType, Undirected};
3
4use crate::ForceGraph;
5use std::ops::RangeInclusive;
6
7mod fruchterman_reingold;
8mod handy;
9
10pub use {
11    fruchterman_reingold::{fruchterman_reingold, fruchterman_reingold_weighted},
12    handy::handy,
13    hashlink::LinkedHashMap,
14};
15
16/// A value that you can change in a [`Force`]'s dictionary.
17#[derive(Clone, Debug, PartialEq)]
18pub enum Value {
19    Number(f32, RangeInclusive<f32>),
20    Bool(bool),
21}
22
23impl Value {
24    /// Retrieves the bool from a value.
25    pub const fn bool(&self) -> Option<bool> {
26        match self {
27            Self::Bool(b) => Some(*b),
28            _ => None,
29        }
30    }
31
32    /// Same as bool but returns a mutable version.
33    pub fn bool_mut(&mut self) -> Option<&mut bool> {
34        match self {
35            Self::Bool(b) => Some(b),
36            _ => None,
37        }
38    }
39
40    /// Retrieves the number from a value. If you mess up and call it on a bool it will return 0.0.
41    pub const fn number(&self) -> Option<f32> {
42        match self {
43            Self::Number(n, _) => Some(*n),
44            _ => None,
45        }
46    }
47
48    /// Same as number but returns a mutable version.
49    pub fn number_mut(&mut self) -> Option<&mut f32> {
50        match self {
51            Self::Number(n, _) => Some(n),
52            _ => None,
53        }
54    }
55}
56
57/// A struct that defines how your force behaves.
58#[derive(Clone)]
59pub struct Force<N, E, Ty = Undirected> {
60    /// Current dictionary
61    pub dict: LinkedHashMap<String, Value>,
62    /// Default dictionary
63    pub dict_default: LinkedHashMap<String, Value>,
64    /// Name of the force
65    pub name: &'static str,
66    /// Whether or not the force should be run on each frame.
67    pub continuous: bool,
68    /// A short description of the force.
69    pub info: Option<&'static str>,
70    /// Force callback function.
71    pub update: fn(dict: &LinkedHashMap<String, Value>, graph: &mut ForceGraph<N, E, Ty>, dt: f32),
72}
73
74impl<N, E, Ty> Force<N, E, Ty> {
75    /// Retrieve the name of the force.
76    pub fn name(&self) -> &'static str {
77        self.name
78    }
79
80    /// Retrieve the force's information.
81    pub fn info(&self) -> Option<&'static str> {
82        self.info
83    }
84
85    /// Update the graph's node's positions for a given interval.
86    pub fn update(&self, graph: &mut ForceGraph<N, E, Ty>, dt: f32) {
87        (self.update)(&self.dict, graph, dt);
88    }
89
90    /// Retrieve a mutable reference to the force's internal dictionary.
91    pub fn dict_mut(&mut self) -> &mut LinkedHashMap<String, Value> {
92        &mut self.dict
93    }
94
95    /// Retrieve a reference to the force's internal dictionary.
96    pub fn dict(&self) -> &LinkedHashMap<String, Value> {
97        &self.dict
98    }
99
100    /// Reset the force's internal dictionary.
101    pub fn reset(&mut self) {
102        self.dict = self.dict_default.clone();
103    }
104
105    /// Retrieve if the force is continuous.
106    /// Continuous forces run their update on every frame, non-continuous forces run their update every time the user clicks a "Run" button.
107    pub fn continuous(&self) -> bool {
108        self.continuous
109    }
110}
111
112impl<N, E, Ty> PartialEq for Force<N, E, Ty> {
113    fn eq(&self, other: &Self) -> bool {
114        self.dict_default == other.dict_default
115            && self.name == other.name
116            && self.continuous == other.continuous
117            && self.info == other.info
118    }
119}
120
121/// A force for scaling the layout around its center.
122pub fn scale<N, E, Ty: EdgeType>() -> Force<N, E, Ty> {
123    fn update<N, E, Ty: EdgeType>(
124        dict: &LinkedHashMap<String, Value>,
125        graph: &mut ForceGraph<N, E, Ty>,
126        _dt: f32,
127    ) {
128        let scale = dict.get("Scale Factor").unwrap().number().unwrap();
129
130        let center = Iterator::sum::<Vec3>(
131            graph
132                .node_weights()
133                .map(|x| x.location)
134                .collect::<Vec<Vec3>>()
135                .iter(),
136        ) / graph.node_count() as f32;
137
138        for node in graph.node_weights_mut() {
139            node.location = ((node.location - center) * scale) + center;
140        }
141    }
142
143    let mut dict = LinkedHashMap::new();
144    dict.insert("Scale Factor".to_string(), Value::Number(1.5, 0.1..=2.0));
145
146    Force {
147        dict: dict.clone(),
148        dict_default: dict,
149        name: "Scale",
150        continuous: false,
151        info: Some("Scales the graph around its center."),
152        update,
153    }
154}
155
156/// A force for translating the graph in any direction.
157pub fn translate<N, E, Ty: EdgeType>() -> Force<N, E, Ty> {
158    fn update<N, E, Ty: EdgeType>(
159        dict: &LinkedHashMap<String, Value>,
160        graph: &mut ForceGraph<N, E, Ty>,
161        _dt: f32,
162    ) {
163        let distance = dict.get("Distance").unwrap().number().unwrap();
164
165        for node in graph.node_weights_mut() {
166            if dict.get("Up").unwrap().bool().unwrap() {
167                node.location.y -= distance;
168            }
169
170            if dict.get("Down").unwrap().bool().unwrap() {
171                node.location.y += distance;
172            }
173
174            if dict.get("Left").unwrap().bool().unwrap() {
175                node.location.x -= distance;
176            }
177
178            if dict.get("Right").unwrap().bool().unwrap() {
179                node.location.x += distance;
180            }
181        }
182    }
183
184    let mut dict = LinkedHashMap::new();
185    dict.insert("Distance".to_string(), Value::Number(7.0, 0.0..=100.0));
186    dict.insert("Up".to_string(), Value::Bool(false));
187    dict.insert("Down".to_string(), Value::Bool(false));
188    dict.insert("Left".to_string(), Value::Bool(false));
189    dict.insert("Right".to_string(), Value::Bool(false));
190
191    Force {
192        dict: dict.clone(),
193        dict_default: dict,
194        name: "Translate",
195        continuous: false,
196        info: Some("Moves the entire layout in any direction."),
197        update,
198    }
199}
200
201#[doc(hidden)]
202pub fn unit_vector(a: Vec3, b: Vec3) -> Vec3 {
203    (b - a) / a.distance(b)
204}