1 //! Simple graphviz dot file format output. 2 3 use std::fmt::{self, Display, Write}; 4 5 use crate::visit::{ 6 EdgeRef, GraphProp, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef, 7 }; 8 9 /// `Dot` implements output to graphviz .dot format for a graph. 10 /// 11 /// Formatting and options are rather simple, this is mostly intended 12 /// for debugging. Exact output may change. 13 /// 14 /// # Examples 15 /// 16 /// ``` 17 /// use petgraph::Graph; 18 /// use petgraph::dot::{Dot, Config}; 19 /// 20 /// let mut graph = Graph::<_, ()>::new(); 21 /// graph.add_node("A"); 22 /// graph.add_node("B"); 23 /// graph.add_node("C"); 24 /// graph.add_node("D"); 25 /// graph.extend_with_edges(&[ 26 /// (0, 1), (0, 2), (0, 3), 27 /// (1, 2), (1, 3), 28 /// (2, 3), 29 /// ]); 30 /// 31 /// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); 32 /// 33 /// // In this case the output looks like this: 34 /// // 35 /// // digraph { 36 /// // 0 [label="\"A\""] 37 /// // 1 [label="\"B\""] 38 /// // 2 [label="\"C\""] 39 /// // 3 [label="\"D\""] 40 /// // 0 -> 1 41 /// // 0 -> 2 42 /// // 0 -> 3 43 /// // 1 -> 2 44 /// // 1 -> 3 45 /// // 2 -> 3 46 /// // } 47 /// 48 /// // If you need multiple config options, just list them all in the slice. 49 /// ``` 50 pub struct Dot<'a, G> 51 where 52 G: IntoEdgeReferences + IntoNodeReferences, 53 { 54 graph: G, 55 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String, 56 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String, 57 config: Configs, 58 } 59 60 static TYPE: [&str; 2] = ["graph", "digraph"]; 61 static EDGE: [&str; 2] = ["--", "->"]; 62 static INDENT: &str = " "; 63 64 impl<'a, G> Dot<'a, G> 65 where 66 G: IntoNodeReferences + IntoEdgeReferences, 67 { 68 /// Create a `Dot` formatting wrapper with default configuration. 69 #[inline] new(graph: G) -> Self70 pub fn new(graph: G) -> Self { 71 Self::with_config(graph, &[]) 72 } 73 74 /// Create a `Dot` formatting wrapper with custom configuration. 75 #[inline] with_config(graph: G, config: &'a [Config]) -> Self76 pub fn with_config(graph: G, config: &'a [Config]) -> Self { 77 Self::with_attr_getters(graph, config, &|_, _| String::new(), &|_, _| String::new()) 78 } 79 80 #[inline] with_attr_getters( graph: G, config: &'a [Config], get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String, get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String, ) -> Self81 pub fn with_attr_getters( 82 graph: G, 83 config: &'a [Config], 84 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String, 85 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String, 86 ) -> Self { 87 let config = Configs::extract(config); 88 Dot { 89 graph, 90 get_edge_attributes, 91 get_node_attributes, 92 config, 93 } 94 } 95 } 96 97 /// `Dot` configuration. 98 /// 99 /// This enum does not have an exhaustive definition (will be expanded) 100 // TODO: #[non_exhaustive] once MSRV >= 1.40, 101 // and/or for a breaking change make this something like an EnumSet: https://docs.rs/enumset 102 #[derive(Debug, PartialEq, Eq)] 103 pub enum Config { 104 /// Use indices for node labels. 105 NodeIndexLabel, 106 /// Use indices for edge labels. 107 EdgeIndexLabel, 108 /// Use no edge labels. 109 EdgeNoLabel, 110 /// Use no node labels. 111 NodeNoLabel, 112 /// Do not print the graph/digraph string. 113 GraphContentOnly, 114 #[doc(hidden)] 115 _Incomplete(()), 116 } 117 macro_rules! make_config_struct { 118 ($($variant:ident,)*) => { 119 #[allow(non_snake_case)] 120 #[derive(Default)] 121 struct Configs { 122 $($variant: bool,)* 123 } 124 impl Configs { 125 #[inline] 126 fn extract(configs: &[Config]) -> Self { 127 let mut conf = Self::default(); 128 for c in configs { 129 match *c { 130 $(Config::$variant => conf.$variant = true,)* 131 Config::_Incomplete(()) => {} 132 } 133 } 134 conf 135 } 136 } 137 } 138 } 139 make_config_struct!( 140 NodeIndexLabel, 141 EdgeIndexLabel, 142 EdgeNoLabel, 143 NodeNoLabel, 144 GraphContentOnly, 145 ); 146 147 impl<'a, G> Dot<'a, G> 148 where 149 G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable + GraphProp, 150 { graph_fmt<NF, EF>(&self, f: &mut fmt::Formatter, node_fmt: NF, edge_fmt: EF) -> fmt::Result where NF: Fn(&G::NodeWeight, &mut fmt::Formatter) -> fmt::Result, EF: Fn(&G::EdgeWeight, &mut fmt::Formatter) -> fmt::Result,151 fn graph_fmt<NF, EF>(&self, f: &mut fmt::Formatter, node_fmt: NF, edge_fmt: EF) -> fmt::Result 152 where 153 NF: Fn(&G::NodeWeight, &mut fmt::Formatter) -> fmt::Result, 154 EF: Fn(&G::EdgeWeight, &mut fmt::Formatter) -> fmt::Result, 155 { 156 let g = self.graph; 157 if !self.config.GraphContentOnly { 158 writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?; 159 } 160 161 // output all labels 162 for node in g.node_references() { 163 write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?; 164 if !self.config.NodeNoLabel { 165 write!(f, "label = \"")?; 166 if self.config.NodeIndexLabel { 167 write!(f, "{}", g.to_index(node.id()))?; 168 } else { 169 Escaped(FnFmt(node.weight(), &node_fmt)).fmt(f)?; 170 } 171 write!(f, "\" ")?; 172 } 173 writeln!(f, "{}]", (self.get_node_attributes)(g, node))?; 174 } 175 // output all edges 176 for (i, edge) in g.edge_references().enumerate() { 177 write!( 178 f, 179 "{}{} {} {} [ ", 180 INDENT, 181 g.to_index(edge.source()), 182 EDGE[g.is_directed() as usize], 183 g.to_index(edge.target()), 184 )?; 185 if !self.config.EdgeNoLabel { 186 write!(f, "label = \"")?; 187 if self.config.EdgeIndexLabel { 188 write!(f, "{}", i)?; 189 } else { 190 Escaped(FnFmt(edge.weight(), &edge_fmt)).fmt(f)?; 191 } 192 write!(f, "\" ")?; 193 } 194 writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?; 195 } 196 197 if !self.config.GraphContentOnly { 198 writeln!(f, "}}")?; 199 } 200 Ok(()) 201 } 202 } 203 204 impl<'a, G> fmt::Display for Dot<'a, G> 205 where 206 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp, 207 G::EdgeWeight: fmt::Display, 208 G::NodeWeight: fmt::Display, 209 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 211 self.graph_fmt(f, fmt::Display::fmt, fmt::Display::fmt) 212 } 213 } 214 215 impl<'a, G> fmt::Debug for Dot<'a, G> 216 where 217 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp, 218 G::EdgeWeight: fmt::Debug, 219 G::NodeWeight: fmt::Debug, 220 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 222 self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt) 223 } 224 } 225 226 /// Escape for Graphviz 227 struct Escaper<W>(W); 228 229 impl<W> fmt::Write for Escaper<W> 230 where 231 W: fmt::Write, 232 { write_str(&mut self, s: &str) -> fmt::Result233 fn write_str(&mut self, s: &str) -> fmt::Result { 234 for c in s.chars() { 235 self.write_char(c)?; 236 } 237 Ok(()) 238 } 239 write_char(&mut self, c: char) -> fmt::Result240 fn write_char(&mut self, c: char) -> fmt::Result { 241 match c { 242 '"' | '\\' => self.0.write_char('\\')?, 243 // \l is for left justified linebreak 244 '\n' => return self.0.write_str("\\l"), 245 _ => {} 246 } 247 self.0.write_char(c) 248 } 249 } 250 251 /// Pass Display formatting through a simple escaping filter 252 struct Escaped<T>(T); 253 254 impl<T> fmt::Display for Escaped<T> 255 where 256 T: fmt::Display, 257 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 259 if f.alternate() { 260 writeln!(&mut Escaper(f), "{:#}", &self.0) 261 } else { 262 write!(&mut Escaper(f), "{}", &self.0) 263 } 264 } 265 } 266 267 /// Format data using a specific format function 268 struct FnFmt<'a, T, F>(&'a T, F); 269 270 impl<'a, T, F> fmt::Display for FnFmt<'a, T, F> 271 where 272 F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result, 273 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result274 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 275 self.1(self.0, f) 276 } 277 } 278 279 #[cfg(test)] 280 mod test { 281 use super::{Config, Dot, Escaper}; 282 use crate::prelude::Graph; 283 use crate::visit::NodeRef; 284 use std::fmt::Write; 285 286 #[test] test_escape()287 fn test_escape() { 288 let mut buff = String::new(); 289 { 290 let mut e = Escaper(&mut buff); 291 let _ = e.write_str("\" \\ \n"); 292 } 293 assert_eq!(buff, "\\\" \\\\ \\l"); 294 } 295 simple_graph() -> Graph<&'static str, &'static str>296 fn simple_graph() -> Graph<&'static str, &'static str> { 297 let mut graph = Graph::<&str, &str>::new(); 298 let a = graph.add_node("A"); 299 let b = graph.add_node("B"); 300 graph.add_edge(a, b, "edge_label"); 301 graph 302 } 303 304 #[test] test_nodeindexlable_option()305 fn test_nodeindexlable_option() { 306 let graph = simple_graph(); 307 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel])); 308 assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"); 309 } 310 311 #[test] test_edgeindexlable_option()312 fn test_edgeindexlable_option() { 313 let graph = simple_graph(); 314 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel])); 315 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n"); 316 } 317 318 #[test] test_edgenolable_option()319 fn test_edgenolable_option() { 320 let graph = simple_graph(); 321 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); 322 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n"); 323 } 324 325 #[test] test_nodenolable_option()326 fn test_nodenolable_option() { 327 let graph = simple_graph(); 328 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel])); 329 assert_eq!( 330 dot, 331 "digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n" 332 ); 333 } 334 335 #[test] test_with_attr_getters()336 fn test_with_attr_getters() { 337 let graph = simple_graph(); 338 let dot = format!( 339 "{:?}", 340 Dot::with_attr_getters( 341 &graph, 342 &[Config::NodeNoLabel, Config::EdgeNoLabel], 343 &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()), 344 &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()), 345 ), 346 ); 347 assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n"); 348 } 349 } 350