Skip to main content

petgraph/
dot.rs

1//! Simple graphviz dot file format output.
2
3use std::fmt::{self, Display, Write};
4
5use 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/// ```
51pub struct Dot<'a, G>
52where
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
61static TYPE: [&str; 2] = ["graph", "digraph"];
62static EDGE: [&str; 2] = ["--", "->"];
63static INDENT: &str = "    ";
64
65impl<'a, G> Dot<'a, G>
66where
67    G: GraphRef + IntoEdgeReferences + IntoNodeReferences,
68{
69    /// Create a `Dot` formatting wrapper with default configuration.
70    pub fn new(graph: G) -> Self {
71        Self::with_config(graph, &[])
72    }
73
74    /// Create a `Dot` formatting wrapper with custom configuration.
75    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
81    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)]
100pub 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
115impl<'a, G> Dot<'a, G>
116where
117    G: GraphBase + IntoNodeReferences + IntoEdgeReferences,
118{
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
180impl<'a, G> fmt::Display for Dot<'a, G>
181where
182    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
183    G::EdgeWeight: fmt::Display,
184    G::NodeWeight: fmt::Display,
185{
186    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
191impl<'a, G> fmt::Debug for Dot<'a, G>
192where
193    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
194    G::EdgeWeight: fmt::Debug,
195    G::NodeWeight: fmt::Debug,
196{
197    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
208struct Escaper<W>(W);
209
210impl<W> fmt::Write for Escaper<W>
211where
212    W: fmt::Write,
213{
214    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
221    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
233struct Escaped<T>(T);
234
235impl<T> fmt::Display for Escaped<T>
236where
237    T: fmt::Display,
238{
239    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
249struct DebugFmt<T>(T);
250
251impl<T> fmt::Display for DebugFmt<T>
252where
253    T: fmt::Debug,
254{
255    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
256        self.0.fmt(f)
257    }
258}
259
260#[cfg(test)]
261mod 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]
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
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]
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]
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]
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]
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]
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}