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#[derive(Clone, Debug, PartialEq)]
18pub enum Value {
19 Number(f32, RangeInclusive<f32>),
20 Bool(bool),
21}
22
23impl Value {
24 pub const fn bool(&self) -> Option<bool> {
26 match self {
27 Self::Bool(b) => Some(*b),
28 _ => None,
29 }
30 }
31
32 pub fn bool_mut(&mut self) -> Option<&mut bool> {
34 match self {
35 Self::Bool(b) => Some(b),
36 _ => None,
37 }
38 }
39
40 pub const fn number(&self) -> Option<f32> {
42 match self {
43 Self::Number(n, _) => Some(*n),
44 _ => None,
45 }
46 }
47
48 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#[derive(Clone)]
59pub struct Force<N, E, Ty = Undirected> {
60 pub dict: LinkedHashMap<String, Value>,
62 pub dict_default: LinkedHashMap<String, Value>,
64 pub name: &'static str,
66 pub continuous: bool,
68 pub info: Option<&'static str>,
70 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 pub fn name(&self) -> &'static str {
77 self.name
78 }
79
80 pub fn info(&self) -> Option<&'static str> {
82 self.info
83 }
84
85 pub fn update(&self, graph: &mut ForceGraph<N, E, Ty>, dt: f32) {
87 (self.update)(&self.dict, graph, dt);
88 }
89
90 pub fn dict_mut(&mut self) -> &mut LinkedHashMap<String, Value> {
92 &mut self.dict
93 }
94
95 pub fn dict(&self) -> &LinkedHashMap<String, Value> {
97 &self.dict
98 }
99
100 pub fn reset(&mut self) {
102 self.dict = self.dict_default.clone();
103 }
104
105 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
121pub 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
156pub 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}