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