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<'a, G> Dot<'a, 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<'a, G> fmt::Display for Dot<'a, 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<'a, G> fmt::Debug for Dot<'a, G>
216where
217    G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
218    G::EdgeWeight: fmt::Debug,
219    G::NodeWeight: fmt::Debug,
220{
221    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
227struct Escaper<W>(W);
228
229impl<W> fmt::Write for Escaper<W>
230where
231    W: fmt::Write,
232{
233    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
240    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
252struct Escaped<T>(T);
253
254impl<T> fmt::Display for Escaped<T>
255where
256    T: fmt::Display,
257{
258    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
268struct FnFmt<'a, T, F>(&'a T, F);
269
270impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
271where
272    F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
273{
274    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275        self.1(self.0, f)
276    }
277}
278
279#[cfg(test)]
280mod 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]
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
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]
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]
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]
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]
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]
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}