1use std::{env, marker::PhantomData};
2
3use log::{error, trace};
4use petgraph::stable_graph::{NodeIndex, StableDiGraph};
5
6use crate::{
7 algorithm::{self, Edge, Vertex},
8 Layouts,
9};
10
11pub static MINIMUM_LENGTH_DEFAULT: u32 = 1;
13pub static VERTEX_SPACING_DEFAULT: usize = 10;
14pub static DUMMY_VERTICES_DEFAULT: bool = true;
15pub static RANKING_TYPE_DEFAULT: RankingType = RankingType::MinimizeEdgeLength;
16pub static C_MINIMIZATION_DEFAULT: CrossingMinimization = CrossingMinimization::Barycenter;
17pub static TRANSPOSE_DEFAULT: bool = true;
18pub static DUMMY_SIZE_DEFAULT: f64 = 1.0;
19
20static ENV_MINIMUM_LENGTH: &str = "RUST_GRAPH_MIN_LEN";
21static ENV_VERTEX_SPACING: &str = "RUST_GRAPH_V_SPACING";
22static ENV_DUMMY_VERTICES: &str = "RUST_GRAPH_DUMMIES";
23static ENV_RANKING_TYPE: &str = "RUST_GRAPH_R_TYPE";
24static ENV_CROSSING_MINIMIZATION: &str = "RUST_GRAPH_CROSS_MIN";
25static ENV_TRANSPOSE: &str = "RUST_GRAPH_TRANSPOSE";
26static ENV_DUMMY_SIZE: &str = "RUST_GRAPH_DUMMY_SIZE";
27
28pub trait IntoCoordinates {}
29
30impl<V, E> IntoCoordinates for StableDiGraph<V, E> {}
31impl IntoCoordinates for &[(u32, u32)] {}
32impl IntoCoordinates for (&[u32], &[(u32, u32)]) {}
33
34macro_rules! read_env {
35 ($field:expr, $cb:tt, $env:ident) => {
36 #[allow(unused_parens)]
37 match env::var($env).map($cb) {
38 Ok(Ok(v)) => $field = v,
39 Ok(Err(e)) => {
40 error!(target: "initialization", "{e}");
41 }
42 _ => (),
43 }
44 };
45}
46
47#[derive(Clone, Copy, Debug)]
57pub struct Config {
58 pub minimum_length: u32,
59 pub vertex_spacing: usize,
60 pub dummy_vertices: bool,
61 pub dummy_size: f64,
62 pub ranking_type: RankingType,
63 pub c_minimization: CrossingMinimization,
64 pub transpose: bool,
65}
66
67impl Config {
68 pub fn new_from_env() -> Self {
71 let config = Self::default();
72 config.read_env()
73 }
74
75 pub fn read_env(mut self) -> Self {
78 let parse_bool = |x: String| match x.as_str() {
79 "y" => Ok(true),
80 "n" => Ok(false),
81 v => Err(format!("Invalid argument for dummy vertex env: {v}")),
82 };
83
84 read_env!(
85 self.minimum_length,
86 (|x| x.parse::<u32>()),
87 ENV_MINIMUM_LENGTH
88 );
89
90 read_env!(
91 self.c_minimization,
92 (TryFrom::try_from),
93 ENV_CROSSING_MINIMIZATION
94 );
95
96 read_env!(self.ranking_type, (TryFrom::try_from), ENV_RANKING_TYPE);
97
98 read_env!(
99 self.vertex_spacing,
100 (|x| x.parse::<usize>()),
101 ENV_VERTEX_SPACING
102 );
103
104 read_env!(self.dummy_vertices, parse_bool, ENV_DUMMY_VERTICES);
105
106 read_env!(self.dummy_size, (|x| x.parse::<f64>()), ENV_DUMMY_SIZE);
107
108 read_env!(self.transpose, parse_bool, ENV_TRANSPOSE);
109
110 self
111 }
112}
113
114impl Default for Config {
115 fn default() -> Self {
116 Self {
117 minimum_length: MINIMUM_LENGTH_DEFAULT,
118 vertex_spacing: VERTEX_SPACING_DEFAULT,
119 dummy_vertices: DUMMY_VERTICES_DEFAULT,
120 ranking_type: RANKING_TYPE_DEFAULT,
121 c_minimization: C_MINIMIZATION_DEFAULT,
122 transpose: TRANSPOSE_DEFAULT,
123 dummy_size: DUMMY_SIZE_DEFAULT,
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum RankingType {
131 Original,
133 MinimizeEdgeLength,
135 Up,
137 Down,
139}
140
141impl TryFrom<String> for RankingType {
142 type Error = String;
143
144 fn try_from(value: String) -> Result<Self, Self::Error> {
145 match value.as_str() {
146 "original" => Ok(Self::Original),
147 "minimize" => Ok(Self::MinimizeEdgeLength),
148 "up" => Ok(Self::Up),
149 "down" => Ok(Self::Down),
150 s => Err(format!("invalid value for ranking type: {s}")),
151 }
152 }
153}
154
155impl From<RankingType> for &'static str {
156 fn from(value: RankingType) -> Self {
157 match value {
158 RankingType::Up => "up",
159 RankingType::Down => "down",
160 RankingType::Original => "original",
161 RankingType::MinimizeEdgeLength => "minimize",
162 }
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
170pub enum CrossingMinimization {
171 Barycenter,
173 Median,
175}
176
177impl TryFrom<String> for CrossingMinimization {
178 type Error = String;
179
180 fn try_from(value: String) -> Result<Self, Self::Error> {
181 match value.as_str() {
182 "barycenter" => Ok(Self::Barycenter),
183 "median" => Ok(Self::Median),
184 s => Err(format!("invalid value for crossing minimization: {s}")),
185 }
186 }
187}
188
189impl From<CrossingMinimization> for &'static str {
190 fn from(value: CrossingMinimization) -> Self {
191 match value {
192 CrossingMinimization::Median => "median",
193 CrossingMinimization::Barycenter => "barycenter",
194 }
195 }
196}
197
198pub struct CoordinatesBuilder<Input: IntoCoordinates> {
211 config: Config,
212 _inner: StableDiGraph<Vertex, Edge>,
213 pd: PhantomData<Input>,
214}
215
216impl<Input: IntoCoordinates> CoordinatesBuilder<Input> {
217 pub(super) fn new(graph: StableDiGraph<Vertex, Edge>) -> Self {
218 Self {
219 config: Config::default(),
220 _inner: graph,
221 pd: PhantomData,
222 }
223 }
224
225 pub fn minimum_length(mut self, v: u32) -> Self {
227 trace!(target: "initializing",
228 "Setting minimum length to: {v}");
229 self.config.minimum_length = v;
230 self
231 }
232
233 pub fn vertex_spacing(mut self, v: usize) -> Self {
235 trace!(target: "initializing",
236 "Setting vertex spacing to: {v}");
237 self.config.vertex_spacing = v;
238 self
239 }
240
241 pub fn dummy_vertices(mut self, v: bool) -> Self {
243 trace!(target: "initializing",
244 "Has dummy vertices: {v}");
245 self.config.dummy_vertices = v;
246 self
247 }
248
249 pub fn layering_type(mut self, v: RankingType) -> Self {
251 trace!(target: "initializing",
252 "using layering type: {v:?}");
253 self.config.ranking_type = v;
254 self
255 }
256
257 pub fn crossing_minimization(mut self, v: CrossingMinimization) -> Self {
259 trace!(target: "initializing",
260 "Heuristic for crossing minimization: {v:?}");
261 self.config.c_minimization = v;
262 self
263 }
264
265 pub fn transpose(mut self, v: bool) -> Self {
267 trace!(target: "initializing",
268 "Use transpose to further reduce crossings: {v}");
269 self.config.transpose = v;
270 self
271 }
272
273 pub fn dummy_size(mut self, v: f64) -> Self {
275 trace!(target: "initializing",
276 "Dummy size in regards to vertex size: {v}");
277 self.config.dummy_size = v;
278 self
279 }
280
281 pub fn with_config(mut self, config: Config) -> Self {
282 trace!(target: "initializing",
283 "With config {:?}", config);
284 self.config = config;
285 self
286 }
287
288 pub fn configure_from_env(mut self) -> Self {
302 self.config = self.config.read_env();
303 self
304 }
305}
306
307impl<V, E> CoordinatesBuilder<StableDiGraph<V, E>> {
308 pub fn build(self) -> Layouts<NodeIndex> {
310 let Self {
311 config,
312 _inner: graph,
313 ..
314 } = self;
315 algorithm::start(
316 graph.map(|_, _| Vertex::default(), |_, _| Edge::default()),
317 config,
318 )
319 .into_iter()
320 .map(|(l, w, h)| {
321 (
322 l.into_iter()
323 .map(|(id, coords)| (NodeIndex::from(id as u32), coords))
324 .collect(),
325 w,
326 h,
327 )
328 })
329 .collect()
330 }
331}
332
333impl CoordinatesBuilder<&[(u32, u32)]> {
334 pub fn build(self) -> Layouts<usize> {
336 let Self {
337 config,
338 _inner: graph,
339 ..
340 } = self;
341 algorithm::start(graph, config)
342 }
343}
344
345impl CoordinatesBuilder<(&[u32], &[(u32, u32)])> {
346 pub fn build(self) -> Layouts<usize> {
348 let Self {
349 config,
350 _inner: graph,
351 ..
352 } = self;
353 algorithm::start(graph, config)
354 }
355}
356
357#[test]
358fn from_env_all_valid() {
359 use super::from_edges;
360 use std::env;
361 let edges = [(1, 2), (2, 3)];
362 env::set_var(ENV_MINIMUM_LENGTH, "5");
363 env::set_var(ENV_DUMMY_VERTICES, "y");
364 env::set_var(ENV_DUMMY_SIZE, "0.1");
365 env::set_var(ENV_RANKING_TYPE, "up");
366 env::set_var(ENV_CROSSING_MINIMIZATION, "median");
367 env::set_var(ENV_TRANSPOSE, "n");
368 env::set_var(ENV_VERTEX_SPACING, "20");
369 let cfg = from_edges(&edges).configure_from_env();
370 assert_eq!(cfg.config.minimum_length, 5);
371 assert_eq!(cfg.config.dummy_vertices, true);
372 assert_eq!(cfg.config.dummy_size, 0.1);
373 assert_eq!(cfg.config.ranking_type, RankingType::Up);
374 assert_eq!(cfg.config.c_minimization, CrossingMinimization::Median);
375 assert_eq!(cfg.config.transpose, false);
376 assert_eq!(cfg.config.vertex_spacing, 20);
377}
378
379#[test]
380fn from_env_invalid_value() {
381 use super::from_edges;
382 use std::env;
383
384 let edges = [(1, 2)];
385 env::set_var(ENV_CROSSING_MINIMIZATION, "flubbeldiflap");
386 env::set_var(ENV_VERTEX_SPACING, "1.5");
387 let cfg = from_edges(&edges);
388 let default = Config::default();
389 assert_eq!(default.c_minimization, cfg.config.c_minimization);
390 assert_eq!(default.vertex_spacing, cfg.config.vertex_spacing);
391}
392
393#[test]
394fn run_algo_empty_graph() {
395 use super::from_edges;
396 let edges = [];
397 let g = from_edges(&edges).build();
398 assert!(g.is_empty());
399}