1pub mod cdt;
4pub mod circogen;
5pub mod common;
6pub mod cutil;
7pub mod dotgen;
8pub mod fdpgen;
9pub mod neatogen;
10pub mod nop;
11pub mod ortho;
12pub mod osage;
13pub mod pack;
14pub mod patchwork;
15pub mod sfdpgen;
16pub mod sparse;
17pub mod twopigen;
18pub mod vpsc;
19
20use graphviz_pure_core::{Graph, GraphvizError, Result};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum LayoutEngine {
24 Dot,
25 Neato,
26 Fdp,
27 Sfdp,
28 Twopi,
29 Circo,
30 Osage,
31 Patchwork,
32 Nop1,
34 Nop2,
36}
37
38#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub struct LayoutOptions {
41 pub nop: u8,
43}
44
45impl LayoutEngine {
46 pub fn parse(name: &str) -> Result<Self> {
47 match name.trim().to_ascii_lowercase().as_str() {
48 "dot" => Ok(Self::Dot),
49 "neato" => Ok(Self::Neato),
50 "fdp" => Ok(Self::Fdp),
51 "sfdp" => Ok(Self::Sfdp),
52 "twopi" => Ok(Self::Twopi),
53 "circo" => Ok(Self::Circo),
54 "osage" => Ok(Self::Osage),
55 "patchwork" => Ok(Self::Patchwork),
56 "nop" | "nop1" => Ok(Self::Nop1),
57 "nop2" => Ok(Self::Nop2),
58 other => Err(GraphvizError::UnsupportedEngine(other.to_string())),
59 }
60 }
61}
62
63pub fn layout_graph(graph: &mut Graph, engine: LayoutEngine) -> Result<()> {
64 layout_graph_with_options(graph, engine, LayoutOptions::default())
65}
66
67pub fn layout_graph_with_options(
68 graph: &mut Graph,
69 engine: LayoutEngine,
70 options: LayoutOptions,
71) -> Result<()> {
72 graphviz_pure_core::charset::begin_layout(
73 &graph.name,
74 graph.graph_attrs.get("charset").map(String::as_str),
75 );
76 let key = graphviz_pure_core::CLUSTER_LIST_KEY;
79 graph.graph_attrs.remove(key);
80 for s in &mut graph.subgraphs {
81 s.graph_attrs.remove(key);
82 }
83 let _ = graphviz_pure_geom::graph_label_geometry_in(
87 &graph.name,
88 &graph.graph_attrs,
89 true,
90 false,
91 &graphviz_pure_geom::NodeSizeEnv::from_graph(graph),
92 );
93 let effective_engine = if engine == LayoutEngine::Dot
94 && let Some(attr_layout) = graph.graph_attrs.get("layout")
95 && let Ok(parsed) = LayoutEngine::parse(attr_layout)
96 {
97 parsed
98 } else {
99 engine
100 };
101 for n in &mut graph.nodes {
103 n.attrs.remove(graphviz_pure_core::NODE_LW_RW_KEY);
104 }
105
106 match effective_engine {
107 LayoutEngine::Dot => dotgen::layout_dot(graph),
108 LayoutEngine::Neato if options.nop > 0 => nop::layout_nop(graph, options.nop),
109 LayoutEngine::Nop1 => nop::layout_nop(graph, 1),
110 LayoutEngine::Nop2 => nop::layout_nop(graph, 2),
111 LayoutEngine::Neato => neatogen::neatoinit::neato_layout(graph),
112 LayoutEngine::Fdp => fdpgen::layout::fdp_layout(graph),
113 LayoutEngine::Sfdp => sfdpgen::sfdpinit::sfdp_layout(graph),
114 LayoutEngine::Twopi => twopigen::twopiinit::twopi_layout(graph),
115 LayoutEngine::Circo => circogen::circularinit::circo_layout(graph),
116 LayoutEngine::Osage => osage::osage_layout(graph),
117 LayoutEngine::Patchwork => patchwork::patchwork_layout(graph),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use graphviz_pure_core::{AttrMap, Graph, GraphKind, Point, SubgraphData, SubgraphId};
125
126 fn make_chain_graph() -> Graph {
127 let mut g = Graph::new("G", GraphKind::Directed, false);
128 let a = g.ensure_node("a");
129 let b = g.ensure_node("b");
130 let c = g.ensure_node("c");
131 g.add_edge(a, b, AttrMap::new());
132 g.add_edge(b, c, AttrMap::new());
133 g
134 }
135
136 #[test]
137 fn relayout_drops_the_previous_lw_rw_split() {
138 let mut g = Graph::new("G", GraphKind::Directed, false);
139 let a = g.ensure_node("a");
140 g.add_edge(a, a, AttrMap::new());
141 let key = graphviz_pure_core::NODE_LW_RW_KEY;
142 g.graph_attrs.insert("splines".into(), String::new());
143 layout_graph(&mut g, LayoutEngine::Dot).unwrap();
144 assert!(g.nodes[a.0 as usize].attrs.contains_key(key));
145 g.graph_attrs.insert("splines".into(), "true".into());
146 layout_graph(&mut g, LayoutEngine::Dot).unwrap();
147 assert!(!g.nodes[a.0 as usize].attrs.contains_key(key));
148 }
149
150 #[test]
151 fn test_engine_parse() {
152 assert_eq!(LayoutEngine::parse("dot").unwrap(), LayoutEngine::Dot);
153 assert_eq!(LayoutEngine::parse("NEATO").unwrap(), LayoutEngine::Neato);
154 assert_eq!(LayoutEngine::parse("fdp").unwrap(), LayoutEngine::Fdp);
155 assert_eq!(LayoutEngine::parse("sfdp").unwrap(), LayoutEngine::Sfdp);
156 assert_eq!(LayoutEngine::parse("twopi").unwrap(), LayoutEngine::Twopi);
157 assert_eq!(LayoutEngine::parse("circo").unwrap(), LayoutEngine::Circo);
158 assert_eq!(LayoutEngine::parse("osage").unwrap(), LayoutEngine::Osage);
159 assert_eq!(
160 LayoutEngine::parse("patchwork").unwrap(),
161 LayoutEngine::Patchwork
162 );
163 assert!(LayoutEngine::parse("unknown").is_err());
164 }
165
166 #[test]
167 fn test_dot_rank_assignment_and_tb_coordinates() {
168 let mut g = make_chain_graph();
169 layout_graph(&mut g, LayoutEngine::Dot).unwrap();
170
171 assert_eq!(g.nodes[0].rank, 0);
172 assert_eq!(g.nodes[1].rank, 1);
173 assert_eq!(g.nodes[2].rank, 2);
174
175 assert!(g.nodes[0].pos.y > g.nodes[1].pos.y);
177 assert!(g.nodes[1].pos.y > g.nodes[2].pos.y);
178 assert!((g.nodes[0].pos.x - g.nodes[1].pos.x).abs() < 1e-4);
180 assert!((g.nodes[1].pos.x - g.nodes[2].pos.x).abs() < 1e-4);
181
182 assert_eq!(g.bb.ll, Point::new(0.0, 0.0));
184 assert!(g.bb.width() > 0.0 && g.bb.height() > 0.0);
185 }
186
187 #[test]
188 fn test_dot_rankdir_variants() {
189 let mut g_bt = make_chain_graph();
191 g_bt.graph_attrs
192 .insert("rankdir".to_string(), "BT".to_string());
193 layout_graph(&mut g_bt, LayoutEngine::Dot).unwrap();
194 assert!(g_bt.nodes[0].pos.y < g_bt.nodes[1].pos.y);
195 assert!(g_bt.nodes[1].pos.y < g_bt.nodes[2].pos.y);
196
197 let mut g_lr = make_chain_graph();
199 g_lr.graph_attrs
200 .insert("rankdir".to_string(), "LR".to_string());
201 layout_graph(&mut g_lr, LayoutEngine::Dot).unwrap();
202 assert!(g_lr.nodes[0].pos.x < g_lr.nodes[1].pos.x);
203 assert!(g_lr.nodes[1].pos.x < g_lr.nodes[2].pos.x);
204
205 let mut g_rl = make_chain_graph();
207 g_rl.graph_attrs
208 .insert("rankdir".to_string(), "RL".to_string());
209 layout_graph(&mut g_rl, LayoutEngine::Dot).unwrap();
210 assert!(g_rl.nodes[0].pos.x > g_rl.nodes[1].pos.x);
211 assert!(g_rl.nodes[1].pos.x > g_rl.nodes[2].pos.x);
212 }
213
214 #[test]
215 fn test_dot_cycle_breaking_and_rank_same() {
216 let mut g = Graph::new("Cycle", GraphKind::Directed, false);
217 let a = g.ensure_node("a");
218 let b = g.ensure_node("b");
219 let c = g.ensure_node("c");
220 let d = g.ensure_node("d");
221
222 let mut heavy = AttrMap::new();
223 heavy.insert("weight".to_string(), "10".to_string());
224 g.add_edge(a, b, heavy.clone());
225 g.add_edge(b, c, heavy);
226 g.add_edge(c, a, AttrMap::new());
228 g.add_edge(b, d, AttrMap::new());
229
230 let mut sub_attrs = AttrMap::new();
232 sub_attrs.insert("rank".to_string(), "same".to_string());
233 g.subgraphs.push(SubgraphData {
234 id: SubgraphId(0),
235 name: "same_cd".to_string(),
236 is_cluster: false,
237 graph_attrs: sub_attrs,
238 node_defaults: AttrMap::new(),
239 edge_defaults: AttrMap::new(),
240 nodes: vec![c, d],
241 edges: Vec::new(),
242 subgraphs: Vec::new(),
243 bb: Default::default(),
244 });
245
246 layout_graph(&mut g, LayoutEngine::Dot).unwrap();
247 assert!(g.nodes[a.0 as usize].rank < g.nodes[b.0 as usize].rank);
248 assert!(g.nodes[b.0 as usize].rank < g.nodes[c.0 as usize].rank);
249 assert_eq!(g.nodes[c.0 as usize].rank, g.nodes[d.0 as usize].rank);
250 }
251
252 #[test]
253 fn test_dot_crossing_minimization() {
254 let mut g = Graph::new("Cross", GraphKind::Directed, false);
257 let a1 = g.ensure_node("a1");
258 let a2 = g.ensure_node("a2");
259 let b1 = g.ensure_node("b1");
260 let b2 = g.ensure_node("b2");
261 g.add_edge(a1, b2, AttrMap::new());
262 g.add_edge(a2, b1, AttrMap::new());
263
264 layout_graph(&mut g, LayoutEngine::Dot).unwrap();
265 let a_diff = g.nodes[a1.0 as usize].pos.x - g.nodes[a2.0 as usize].pos.x;
267 let b_diff = g.nodes[b2.0 as usize].pos.x - g.nodes[b1.0 as usize].pos.x;
268 assert!(a_diff * b_diff > 0.0);
269 }
270
271 #[test]
272 fn test_all_eight_layout_engines_succeed() {
273 let engines = [
274 LayoutEngine::Dot,
275 LayoutEngine::Neato,
276 LayoutEngine::Fdp,
277 LayoutEngine::Sfdp,
278 LayoutEngine::Twopi,
279 LayoutEngine::Circo,
280 LayoutEngine::Osage,
281 LayoutEngine::Patchwork,
282 ];
283
284 for engine in engines {
285 let mut g = Graph::new("TestAll", GraphKind::Directed, false);
286 let a = g.ensure_node("a");
287 let b = g.ensure_node("b");
288 let c = g.ensure_node("c");
289 let d = g.ensure_node("d");
290 g.add_edge(a, b, AttrMap::new());
291 g.add_edge(b, c, AttrMap::new());
292 g.add_edge(c, d, AttrMap::new());
293 g.add_edge(d, a, AttrMap::new());
294
295 layout_graph(&mut g, engine).unwrap();
296 assert_eq!(g.bb.ll, Point::new(0.0, 0.0));
297 assert!(g.bb.width() > 0.0 && g.bb.height() > 0.0);
298 for node in &g.nodes {
299 assert!(node.pos.x.is_finite() && node.pos.y.is_finite());
300 assert!(node.pos.x >= 0.0 && node.pos.y >= 0.0);
301 }
302 if engine != LayoutEngine::Patchwork {
304 for edge in &g.edges {
305 assert!(!edge.spline.segments.is_empty());
306 }
307 }
308 }
309 }
310}