petgraph/
dot.rs

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