petgraph/
dot.rs

1//! Simple graphviz dot file format output.
2
3use std::fmt::{self, Display, Write};
4
5use 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/// ```
50pub struct Dot<'a, G>
51where
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
60static TYPE: [&str; 2] = ["graph", "digraph"];
61static EDGE: [&str; 2] = ["--", "->"];
62static INDENT: &str = "    ";
63
64impl<'a, G> Dot<'a, G>
65where
66    G: IntoNodeReferences + IntoEdgeReferences,
67{
68    /// Create a `Dot` formatting wrapper with default configuration.
69    #[inline]
70    pub fn new(graph: G) -> Self {
71        Self::with_config(graph, &[])
72    }
73
74    /// Create a `Dot` formatting wrapper with custom configuration.
75    #[inline]
76    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]
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        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)]
103pub 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}
117macro_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}
139make_config_struct!(
140    NodeIndexLabel,
141    EdgeIndexLabel,
142    EdgeNoLabel,
143    NodeNoLabel,
144    GraphContentOnly,
145);
146
147impl<G> Dot<'_, G>
148where
149    G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable + GraphProp,
150{
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
204impl<G> fmt::Display for Dot<'_, G>
205where
206    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
207    G::EdgeWeight: fmt::Display,
208    G::NodeWeight: fmt::Display,
209{
210    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211        self.graph_fmt(f, fmt::Display::fmt, fmt::Display::fmt)
212    }
213}
214
215impl<G> fmt::LowerHex for Dot<'_, G>
216where
217    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
218    G::EdgeWeight: fmt::LowerHex,
219    G::NodeWeight: fmt::LowerHex,
220{
221    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222        self.graph_fmt(f, fmt::LowerHex::fmt, fmt::LowerHex::fmt)
223    }
224}
225
226impl<G> fmt::UpperHex for Dot<'_, G>
227where
228    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
229    G::EdgeWeight: fmt::UpperHex,
230    G::NodeWeight: fmt::UpperHex,
231{
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        self.graph_fmt(f, fmt::UpperHex::fmt, fmt::UpperHex::fmt)
234    }
235}
236
237impl<G> fmt::Debug for Dot<'_, G>
238where
239    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
240    G::EdgeWeight: fmt::Debug,
241    G::NodeWeight: fmt::Debug,
242{
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt)
245    }
246}
247
248/// Escape for Graphviz
249struct Escaper<W>(W);
250
251impl<W> fmt::Write for Escaper<W>
252where
253    W: fmt::Write,
254{
255    fn write_str(&mut self, s: &str) -> fmt::Result {
256        for c in s.chars() {
257            self.write_char(c)?;
258        }
259        Ok(())
260    }
261
262    fn write_char(&mut self, c: char) -> fmt::Result {
263        match c {
264            '"' | '\\' => self.0.write_char('\\')?,
265            // \l is for left justified linebreak
266            '\n' => return self.0.write_str("\\l"),
267            _ => {}
268        }
269        self.0.write_char(c)
270    }
271}
272
273/// Pass Display formatting through a simple escaping filter
274struct Escaped<T>(T);
275
276impl<T> fmt::Display for Escaped<T>
277where
278    T: fmt::Display,
279{
280    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281        if f.alternate() {
282            writeln!(&mut Escaper(f), "{:#}", &self.0)
283        } else {
284            write!(&mut Escaper(f), "{}", &self.0)
285        }
286    }
287}
288
289/// Format data using a specific format function
290struct FnFmt<'a, T, F>(&'a T, F);
291
292impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
293where
294    F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
295{
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        self.1(self.0, f)
298    }
299}
300
301#[cfg(test)]
302mod test {
303    use super::{Config, Dot, Escaper};
304    use crate::prelude::Graph;
305    use crate::visit::NodeRef;
306    use std::fmt::Write;
307
308    #[test]
309    fn test_escape() {
310        let mut buff = String::new();
311        {
312            let mut e = Escaper(&mut buff);
313            let _ = e.write_str("\" \\ \n");
314        }
315        assert_eq!(buff, "\\\" \\\\ \\l");
316    }
317
318    fn simple_graph() -> Graph<&'static str, &'static str> {
319        let mut graph = Graph::<&str, &str>::new();
320        let a = graph.add_node("A");
321        let b = graph.add_node("B");
322        graph.add_edge(a, b, "edge_label");
323        graph
324    }
325
326    #[test]
327    fn test_nodeindexlable_option() {
328        let graph = simple_graph();
329        let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
330        assert_eq!(dot, "digraph {\n    0 [ label = \"0\" ]\n    1 [ label = \"1\" ]\n    0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
331    }
332
333    #[test]
334    fn test_edgeindexlable_option() {
335        let graph = simple_graph();
336        let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
337        assert_eq!(dot, "digraph {\n    0 [ label = \"\\\"A\\\"\" ]\n    1 [ label = \"\\\"B\\\"\" ]\n    0 -> 1 [ label = \"0\" ]\n}\n");
338    }
339
340    #[test]
341    fn test_edgenolable_option() {
342        let graph = simple_graph();
343        let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
344        assert_eq!(dot, "digraph {\n    0 [ label = \"\\\"A\\\"\" ]\n    1 [ label = \"\\\"B\\\"\" ]\n    0 -> 1 [ ]\n}\n");
345    }
346
347    #[test]
348    fn test_nodenolable_option() {
349        let graph = simple_graph();
350        let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
351        assert_eq!(
352            dot,
353            "digraph {\n    0 [ ]\n    1 [ ]\n    0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
354        );
355    }
356
357    #[test]
358    fn test_with_attr_getters() {
359        let graph = simple_graph();
360        let dot = format!(
361            "{:?}",
362            Dot::with_attr_getters(
363                &graph,
364                &[Config::NodeNoLabel, Config::EdgeNoLabel],
365                &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
366                &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
367            ),
368        );
369        assert_eq!(dot, "digraph {\n    0 [ label = \"a\"]\n    1 [ label = \"b\"]\n    0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
370    }
371}