| //! Simple graphviz dot file format output. |
| |
| use std::fmt::{self, Display, Write}; |
| |
| use crate::visit::{ |
| Data, EdgeRef, GraphBase, GraphProp, GraphRef, IntoEdgeReferences, IntoNodeReferences, |
| NodeIndexable, NodeRef, |
| }; |
| |
| /// `Dot` implements output to graphviz .dot format for a graph. |
| /// |
| /// Formatting and options are rather simple, this is mostly intended |
| /// for debugging. Exact output may change. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use petgraph::Graph; |
| /// use petgraph::dot::{Dot, Config}; |
| /// |
| /// let mut graph = Graph::<_, ()>::new(); |
| /// graph.add_node("A"); |
| /// graph.add_node("B"); |
| /// graph.add_node("C"); |
| /// graph.add_node("D"); |
| /// graph.extend_with_edges(&[ |
| /// (0, 1), (0, 2), (0, 3), |
| /// (1, 2), (1, 3), |
| /// (2, 3), |
| /// ]); |
| /// |
| /// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); |
| /// |
| /// // In this case the output looks like this: |
| /// // |
| /// // digraph { |
| /// // 0 [label="\"A\""] |
| /// // 1 [label="\"B\""] |
| /// // 2 [label="\"C\""] |
| /// // 3 [label="\"D\""] |
| /// // 0 -> 1 |
| /// // 0 -> 2 |
| /// // 0 -> 3 |
| /// // 1 -> 2 |
| /// // 1 -> 3 |
| /// // 2 -> 3 |
| /// // } |
| /// |
| /// // If you need multiple config options, just list them all in the slice. |
| /// ``` |
| pub struct Dot<'a, G> |
| where |
| G: IntoEdgeReferences + IntoNodeReferences, |
| { |
| 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, |
| } |
| |
| static TYPE: [&str; 2] = ["graph", "digraph"]; |
| static EDGE: [&str; 2] = ["--", "->"]; |
| static INDENT: &str = " "; |
| |
| impl<'a, G> Dot<'a, G> |
| where |
| G: GraphRef + IntoEdgeReferences + IntoNodeReferences, |
| { |
| /// Create a `Dot` formatting wrapper with default configuration. |
| pub fn new(graph: G) -> Self { |
| Self::with_config(graph, &[]) |
| } |
| |
| /// Create a `Dot` formatting wrapper with custom configuration. |
| pub fn with_config(graph: G, config: &'a [Config]) -> Self { |
| Self::with_attr_getters(graph, config, &|_, _| "".to_string(), &|_, _| { |
| "".to_string() |
| }) |
| } |
| |
| pub fn 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, |
| ) -> Self { |
| Dot { |
| graph, |
| config, |
| get_edge_attributes, |
| get_node_attributes, |
| } |
| } |
| } |
| |
| /// `Dot` configuration. |
| /// |
| /// This enum does not have an exhaustive definition (will be expanded) |
| #[derive(Debug, PartialEq, Eq)] |
| pub enum Config { |
| /// Use indices for node labels. |
| NodeIndexLabel, |
| /// Use indices for edge labels. |
| EdgeIndexLabel, |
| /// Use no edge labels. |
| EdgeNoLabel, |
| /// Use no node labels. |
| NodeNoLabel, |
| /// Do not print the graph/digraph string. |
| GraphContentOnly, |
| #[doc(hidden)] |
| _Incomplete(()), |
| } |
| |
| impl<'a, G> Dot<'a, G> |
| where |
| G: GraphBase + IntoNodeReferences + IntoEdgeReferences, |
| { |
| fn 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, |
| { |
| if !self.config.contains(&Config::GraphContentOnly) { |
| writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?; |
| } |
| |
| // output all labels |
| for node in g.node_references() { |
| write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?; |
| if !self.config.contains(&Config::NodeNoLabel) { |
| write!(f, "label = \"")?; |
| if self.config.contains(&Config::NodeIndexLabel) { |
| write!(f, "{}", g.to_index(node.id()))?; |
| } else { |
| node_fmt(node.weight(), &mut |d| Escaped(d).fmt(f))?; |
| } |
| write!(f, "\" ")?; |
| } |
| writeln!(f, "{}]", (self.get_node_attributes)(g, node))?; |
| } |
| // output all edges |
| for (i, edge) in g.edge_references().enumerate() { |
| write!( |
| f, |
| "{}{} {} {} [ ", |
| INDENT, |
| g.to_index(edge.source()), |
| EDGE[g.is_directed() as usize], |
| g.to_index(edge.target()), |
| )?; |
| if !self.config.contains(&Config::EdgeNoLabel) { |
| write!(f, "label = \"")?; |
| if self.config.contains(&Config::EdgeIndexLabel) { |
| write!(f, "{}", i)?; |
| } else { |
| edge_fmt(edge.weight(), &mut |d| Escaped(d).fmt(f))?; |
| } |
| write!(f, "\" ")?; |
| } |
| writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?; |
| } |
| |
| if !self.config.contains(&Config::GraphContentOnly) { |
| writeln!(f, "}}")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| impl<'a, G> fmt::Display for Dot<'a, G> |
| where |
| G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp, |
| G::EdgeWeight: fmt::Display, |
| G::NodeWeight: fmt::Display, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| self.graph_fmt(self.graph, f, |n, cb| cb(n), |e, cb| cb(e)) |
| } |
| } |
| |
| impl<'a, G> fmt::Debug for Dot<'a, G> |
| where |
| G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp, |
| G::EdgeWeight: fmt::Debug, |
| G::NodeWeight: fmt::Debug, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| self.graph_fmt( |
| self.graph, |
| f, |
| |n, cb| cb(&DebugFmt(n)), |
| |e, cb| cb(&DebugFmt(e)), |
| ) |
| } |
| } |
| |
| /// Escape for Graphviz |
| struct Escaper<W>(W); |
| |
| impl<W> fmt::Write for Escaper<W> |
| where |
| W: fmt::Write, |
| { |
| fn write_str(&mut self, s: &str) -> fmt::Result { |
| for c in s.chars() { |
| self.write_char(c)?; |
| } |
| Ok(()) |
| } |
| |
| fn write_char(&mut self, c: char) -> fmt::Result { |
| match c { |
| '"' | '\\' => self.0.write_char('\\')?, |
| // \l is for left justified linebreak |
| '\n' => return self.0.write_str("\\l"), |
| _ => {} |
| } |
| self.0.write_char(c) |
| } |
| } |
| |
| /// Pass Display formatting through a simple escaping filter |
| struct Escaped<T>(T); |
| |
| impl<T> fmt::Display for Escaped<T> |
| where |
| T: fmt::Display, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| if f.alternate() { |
| writeln!(&mut Escaper(f), "{:#}", &self.0) |
| } else { |
| write!(&mut Escaper(f), "{}", &self.0) |
| } |
| } |
| } |
| |
| /// Pass Debug formatting to Display |
| struct DebugFmt<T>(T); |
| |
| impl<T> fmt::Display for DebugFmt<T> |
| where |
| T: fmt::Debug, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| self.0.fmt(f) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::{Config, Dot, Escaper}; |
| use crate::prelude::Graph; |
| use crate::visit::NodeRef; |
| use std::fmt::Write; |
| |
| #[test] |
| fn test_escape() { |
| let mut buff = String::new(); |
| { |
| let mut e = Escaper(&mut buff); |
| let _ = e.write_str("\" \\ \n"); |
| } |
| assert_eq!(buff, "\\\" \\\\ \\l"); |
| } |
| |
| fn simple_graph() -> Graph<&'static str, &'static str> { |
| let mut graph = Graph::<&str, &str>::new(); |
| let a = graph.add_node("A"); |
| let b = graph.add_node("B"); |
| graph.add_edge(a, b, "edge_label"); |
| graph |
| } |
| |
| #[test] |
| fn test_nodeindexlable_option() { |
| let graph = simple_graph(); |
| let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel])); |
| assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"); |
| } |
| |
| #[test] |
| fn test_edgeindexlable_option() { |
| let graph = simple_graph(); |
| let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel])); |
| assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n"); |
| } |
| |
| #[test] |
| fn test_edgenolable_option() { |
| let graph = simple_graph(); |
| let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); |
| assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n"); |
| } |
| |
| #[test] |
| fn test_nodenolable_option() { |
| let graph = simple_graph(); |
| let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel])); |
| assert_eq!( |
| dot, |
| "digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n" |
| ); |
| } |
| |
| #[test] |
| fn test_with_attr_getters() { |
| let graph = simple_graph(); |
| let dot = format!( |
| "{:?}", |
| Dot::with_attr_getters( |
| &graph, |
| &[Config::NodeNoLabel, Config::EdgeNoLabel], |
| &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()), |
| &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()), |
| ), |
| ); |
| assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n"); |
| } |
| } |