rust_sugiyama/
configure.rs

1use std::env;
2
3use log::error;
4use petgraph::stable_graph::StableDiGraph;
5
6// Default values for configuration
7pub const MINIMUM_LENGTH_DEFAULT: u32 = 1;
8pub const VERTEX_SPACING_DEFAULT: f64 = 10.0;
9pub const DUMMY_VERTICES_DEFAULT: bool = true;
10pub const RANKING_TYPE_DEFAULT: RankingType = RankingType::MinimizeEdgeLength;
11pub const C_MINIMIZATION_DEFAULT: CrossingMinimization = CrossingMinimization::Barycenter;
12pub const TRANSPOSE_DEFAULT: bool = true;
13pub const DUMMY_SIZE_DEFAULT: f64 = 1.0;
14
15const ENV_MINIMUM_LENGTH: &str = "RUST_GRAPH_MIN_LEN";
16const ENV_VERTEX_SPACING: &str = "RUST_GRAPH_V_SPACING";
17const ENV_DUMMY_VERTICES: &str = "RUST_GRAPH_DUMMIES";
18const ENV_RANKING_TYPE: &str = "RUST_GRAPH_R_TYPE";
19const ENV_CROSSING_MINIMIZATION: &str = "RUST_GRAPH_CROSS_MIN";
20const ENV_TRANSPOSE: &str = "RUST_GRAPH_TRANSPOSE";
21const ENV_DUMMY_SIZE: &str = "RUST_GRAPH_DUMMY_SIZE";
22
23pub trait IntoCoordinates {}
24
25impl<V, E> IntoCoordinates for StableDiGraph<V, E> {}
26impl IntoCoordinates for &[(u32, u32)] {}
27impl IntoCoordinates for (&[u32], &[(u32, u32)]) {}
28
29macro_rules! read_env {
30    ($field:expr, $cb:tt, $env:ident) => {
31        #[allow(unused_parens)]
32        match env::var($env).map($cb) {
33            Ok(Ok(v)) => $field = v,
34            Ok(Err(e)) => {
35                error!(target: "initialization", "{e}");
36            }
37            _ => (),
38        }
39    };
40}
41
42/// Used to configure parameters of the graph layout.
43#[derive(Clone, Copy, Debug)]
44pub struct Config {
45    /// Length between layers.
46    pub minimum_length: u32,
47    /// The minimum spacing between vertices on the same layer and between
48    /// layers.
49    pub vertex_spacing: f64,
50    /// Whether to include dummy vertices when calculating the layout.
51    pub dummy_vertices: bool,
52    /// How much space a dummy should take up, as a multiplier of the
53    /// [`Self::vertex_spacing`].
54    pub dummy_size: f64,
55    /// Defines how vertices are placed vertically.
56    pub ranking_type: RankingType,
57    /// Which heuristic to use when minimizing edge crossings.
58    pub c_minimization: CrossingMinimization,
59    /// Whether to attempt to further reduce crossings by swapping vertices in a
60    /// layer. This may increase runtime significantly.
61    pub transpose: bool,
62}
63
64impl Config {
65    /// Read in configuration values from environment variables.
66    ///
67    /// Envs that can be set include:
68    ///
69    /// | ENV | values | default | description |
70    /// | --- | ------ | ------- | ----------- |
71    /// | RUST_GRAPH_MIN_LEN    | integer, > 0         | 1          | minimum edge length between layers |
72    /// | RUST_GRAPH_V_SPACING  | integer, > 0         | 10         | minimum spacing between vertices on the same layer |
73    /// | RUST_GRAPH_DUMMIES    | y \| n               | y          | if dummy vertices are included in the final layout |
74    /// | RUST_GRAPH_R_TYPE     | original \| minimize \| up \| down | minimize   | defines how vertices are places vertically |
75    /// | RUST_GRAPH_CROSS_MIN  | barycenter \| median | barycenter | which heuristic to use for crossing reduction |
76    /// | RUST_GRAPH_TRANSPOSE  | y \| n               | y          | if transpose function is used to further try to reduce crossings (may increase runtime significally for large graphs) |
77    /// | RUST_GRAPH_DUMMY_SIZE | float, 1 >= v > 0    | 1.0        |size of dummy vertices in final layout, if dummy vertices are included. this will squish the graph horizontally |
78    pub fn new_from_env() -> Self {
79        let mut config = Self::default();
80
81        let parse_bool = |x: String| match x.as_str() {
82            "y" => Ok(true),
83            "n" => Ok(false),
84            v => Err(format!("Invalid argument for dummy vertex env: {v}")),
85        };
86
87        read_env!(
88            config.minimum_length,
89            (|x| x.parse::<u32>()),
90            ENV_MINIMUM_LENGTH
91        );
92
93        read_env!(
94            config.c_minimization,
95            (TryFrom::try_from),
96            ENV_CROSSING_MINIMIZATION
97        );
98
99        read_env!(config.ranking_type, (TryFrom::try_from), ENV_RANKING_TYPE);
100
101        read_env!(
102            config.vertex_spacing,
103            (|x| x.parse::<f64>()),
104            ENV_VERTEX_SPACING
105        );
106
107        read_env!(config.dummy_vertices, parse_bool, ENV_DUMMY_VERTICES);
108
109        read_env!(config.dummy_size, (|x| x.parse::<f64>()), ENV_DUMMY_SIZE);
110
111        read_env!(config.transpose, parse_bool, ENV_TRANSPOSE);
112
113        config
114    }
115}
116
117impl Default for Config {
118    fn default() -> Self {
119        Self {
120            minimum_length: MINIMUM_LENGTH_DEFAULT,
121            vertex_spacing: VERTEX_SPACING_DEFAULT,
122            dummy_vertices: DUMMY_VERTICES_DEFAULT,
123            ranking_type: RANKING_TYPE_DEFAULT,
124            c_minimization: C_MINIMIZATION_DEFAULT,
125            transpose: TRANSPOSE_DEFAULT,
126            dummy_size: DUMMY_SIZE_DEFAULT,
127        }
128    }
129}
130
131/// Defines the Ranking type, i.e. how vertices are placed on each layer.
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub enum RankingType {
134    /// First moves vertices as far up as possible, and then as low as possible
135    Original,
136    /// Tries to minimize edge lengths across layers
137    MinimizeEdgeLength,
138    /// Move vertices as far up as possible
139    Up,
140    /// Move vertices as far down as possible
141    Down,
142}
143
144impl TryFrom<String> for RankingType {
145    type Error = String;
146
147    fn try_from(value: String) -> Result<Self, Self::Error> {
148        match value.as_str() {
149            "original" => Ok(Self::Original),
150            "minimize" => Ok(Self::MinimizeEdgeLength),
151            "up" => Ok(Self::Up),
152            "down" => Ok(Self::Down),
153            s => Err(format!("invalid value for ranking type: {s}")),
154        }
155    }
156}
157
158impl From<RankingType> for &'static str {
159    fn from(value: RankingType) -> Self {
160        match value {
161            RankingType::Up => "up",
162            RankingType::Down => "down",
163            RankingType::Original => "original",
164            RankingType::MinimizeEdgeLength => "minimize",
165        }
166    }
167}
168
169/// Defines the heuristic used for crossing minimization.
170/// During crossing minimization, the vertices of one layer are
171/// ordered, so they're as close to neighboring vertices as possible.
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum CrossingMinimization {
174    /// Calculates the average of the positions of adjacent neighbors
175    Barycenter,
176    /// Calculates the weighted median of the positions of adjacent neighbors
177    Median,
178}
179
180impl TryFrom<String> for CrossingMinimization {
181    type Error = String;
182
183    fn try_from(value: String) -> Result<Self, Self::Error> {
184        match value.as_str() {
185            "barycenter" => Ok(Self::Barycenter),
186            "median" => Ok(Self::Median),
187            s => Err(format!("invalid value for crossing minimization: {s}")),
188        }
189    }
190}
191
192impl From<CrossingMinimization> for &'static str {
193    fn from(value: CrossingMinimization) -> Self {
194        match value {
195            CrossingMinimization::Median => "median",
196            CrossingMinimization::Barycenter => "barycenter",
197        }
198    }
199}
200
201#[test]
202fn from_env_all_valid() {
203    use std::env;
204    env::set_var(ENV_MINIMUM_LENGTH, "5");
205    env::set_var(ENV_DUMMY_VERTICES, "y");
206    env::set_var(ENV_DUMMY_SIZE, "0.1");
207    env::set_var(ENV_RANKING_TYPE, "up");
208    env::set_var(ENV_CROSSING_MINIMIZATION, "median");
209    env::set_var(ENV_TRANSPOSE, "n");
210    env::set_var(ENV_VERTEX_SPACING, "20");
211    let cfg = Config::new_from_env();
212    assert_eq!(cfg.minimum_length, 5);
213    assert_eq!(cfg.dummy_vertices, true);
214    assert_eq!(cfg.dummy_size, 0.1);
215    assert_eq!(cfg.ranking_type, RankingType::Up);
216    assert_eq!(cfg.c_minimization, CrossingMinimization::Median);
217    assert_eq!(cfg.transpose, false);
218    assert_eq!(cfg.vertex_spacing, 20.0);
219}
220
221#[test]
222fn from_env_invalid_value() {
223    use std::env;
224
225    env::set_var(ENV_CROSSING_MINIMIZATION, "flubbeldiflap");
226    env::set_var(ENV_VERTEX_SPACING, "1bleh0");
227    let cfg = Config::new_from_env();
228    let default = Config::default();
229    assert_eq!(default.c_minimization, cfg.c_minimization);
230    assert_eq!(default.vertex_spacing, cfg.vertex_spacing);
231}