dotwalk/
render.rs

1use std::io;
2use std::io::Write;
3
4use crate::{Edges, GraphKind, GraphWalk, Labeller, Nodes, Style, Subgraphs};
5
6/// Renders graph `g` into the writer `w` in DOT syntax.
7/// (Simple wrapper around `render_opts` that passes a default set of options.)
8pub fn render<'a, N, E, S, G, W>(g: &'a G, w: &mut W) -> io::Result<()>
9where
10    N: Clone + 'a,
11    E: Clone + 'a,
12    S: Clone + 'a,
13    G: Labeller<'a, Node = N, Edge = E, Subgraph = S>
14        + GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
15    W: Write,
16{
17    render_opts(g, w, &[])
18}
19
20#[derive(Clone, PartialEq, Eq, Debug)]
21pub enum RenderOption {
22    NoEdgeLabels,
23    NoNodeLabels,
24    NoEdgeStyles,
25    NoEdgeColors,
26    NoNodeStyles,
27    NoNodeColors,
28
29    Fontname(String),
30    DarkTheme,
31    NoArrows,
32}
33
34/// Renders graph `g` into the writer `w` in DOT syntax.
35/// (Main entry point for the library.)
36pub fn render_opts<'a, N, E, S, G, W>(
37    g: &'a G,
38    w: &mut W,
39    options: &[RenderOption],
40) -> io::Result<()>
41where
42    N: Clone + 'a,
43    E: Clone + 'a,
44    S: Clone + 'a,
45    G: Labeller<'a, Node = N, Edge = E, Subgraph = S>
46        + GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
47    W: Write,
48{
49    writeln!(w, "{} {} {{", g.kind().as_keyword(), *g.graph_id())?;
50
51    if g.kind() == GraphKind::Directed {
52        if let Some(rankdir) = g.rank_dir() {
53            writeln!(w, "    rankdir=\"{}\";", rankdir.as_static_str())?;
54        }
55    }
56
57    for (name, value) in g.graph_attrs().iter() {
58        writeln!(w, "    {name}={value}")?;
59    }
60
61    // Global graph properties
62    let mut graph_attrs = Vec::new();
63    let mut content_attrs = Vec::new();
64    let font;
65    let fontname = options.iter().find_map(|option| {
66        if let RenderOption::Fontname(fontname) = option {
67            Some(fontname)
68        } else {
69            None
70        }
71    });
72    if let Some(fontname) = fontname {
73        font = format!(r#"fontname="{fontname}""#);
74        graph_attrs.push(&font[..]);
75        content_attrs.push(&font[..]);
76    }
77    if options.contains(&RenderOption::DarkTheme) {
78        graph_attrs.push(r#"bgcolor="black""#);
79        graph_attrs.push(r#"fontcolor="white""#);
80        content_attrs.push(r#"color="white""#);
81        content_attrs.push(r#"fontcolor="white""#);
82    }
83    if !(graph_attrs.is_empty() && content_attrs.is_empty()) {
84        writeln!(w, r#"    graph[{}];"#, graph_attrs.join(" "))?;
85        let content_attrs_str = content_attrs.join(" ");
86        writeln!(w, r#"    node[{content_attrs_str}];"#)?;
87        writeln!(w, r#"    edge[{content_attrs_str}];"#)?;
88    }
89
90    render_subgraphs(w, g, &g.subgraphs(), options)?;
91    render_nodes(w, g, &g.nodes(), options)?;
92    render_edges(w, g, &g.edges(), options)?;
93
94    writeln!(w, "}}")
95}
96
97pub fn render_nodes<'a, N, E, S, G, W>(
98    w: &mut W,
99    graph: &'a G,
100    nodes: &Nodes<'a, N>,
101    options: &[RenderOption],
102) -> io::Result<()>
103where
104    W: Write,
105    N: Clone + 'a,
106    E: Clone + 'a,
107    S: Clone + 'a,
108    G: Labeller<'a, Node = N, Edge = E, Subgraph = S>
109        + GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
110{
111    let mut text = Vec::new();
112    for n in nodes.iter() {
113        write!(text, "    {}", *graph.node_id(n)).unwrap();
114
115        if !options.contains(&RenderOption::NoNodeLabels) {
116            let label = &graph.node_label(n).to_escaped_string();
117            write!(text, "[label={label}]").unwrap();
118        }
119
120        let style = graph.node_style(n);
121        if !options.contains(&RenderOption::NoNodeStyles) && style != Style::None {
122            write!(text, "[style=\"{}\"]", style.as_static_str()).unwrap();
123        }
124
125        if !options.contains(&RenderOption::NoNodeColors) {
126            if let Some(color) = graph.node_color(n) {
127                write!(text, "[color={}]", color.to_escaped_string()).unwrap();
128            }
129        }
130
131        if let Some(shape) = graph.node_shape(n) {
132            write!(text, "[shape={}]", &shape.to_escaped_string()).unwrap();
133        }
134
135        for (name, value) in graph.node_attrs(n).into_iter() {
136            write!(text, "[{name}={value}]").unwrap();
137        }
138
139        writeln!(text, ";").unwrap();
140
141        w.write_all(&text)?;
142        text.clear();
143    }
144    Ok(())
145}
146
147pub fn render_subgraphs<'a, N, E, S, G, W>(
148    w: &mut W,
149    graph: &'a G,
150    subgraphs: &Subgraphs<'a, S>,
151    options: &[RenderOption],
152) -> io::Result<()>
153where
154    W: Write,
155    N: Clone + 'a,
156    E: Clone + 'a,
157    S: Clone + 'a,
158    G: Labeller<'a, Node = N, Edge = E, Subgraph = S>
159        + GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
160{
161    let mut text = Vec::new();
162    for s in subgraphs.iter() {
163        write!(text, "subgraph").unwrap();
164
165        if let Some(id) = graph.subgraph_id(s) {
166            write!(text, " {}", *id).unwrap();
167        }
168
169        writeln!(text, " {{").unwrap();
170
171        if !options.contains(&RenderOption::NoNodeLabels) {
172            let label = &graph.subgraph_label(s).to_escaped_string();
173            writeln!(text, "    label={label};").unwrap();
174        }
175
176        let style = graph.subgraph_style(s);
177        let var_name = style != Style::None;
178        if !options.contains(&RenderOption::NoNodeStyles) && var_name {
179            writeln!(text, "    style=\"{}\";", style.as_static_str()).unwrap();
180        }
181
182        if !options.contains(&RenderOption::NoNodeColors) {
183            if let Some(color) = graph.subgraph_color(s) {
184                writeln!(text, "    color={};", color.to_escaped_string()).unwrap();
185            }
186        }
187
188        if let Some(shape) = graph.subgraph_shape(s) {
189            writeln!(text, "    shape={};", &shape.to_escaped_string()).unwrap();
190        }
191
192        for (name, value) in graph.subgraph_attrs(s).into_iter() {
193            writeln!(text, "    {name}={value};").unwrap();
194        }
195
196        for n in graph.subgraph_nodes(s).iter() {
197            writeln!(text, "    {};", *graph.node_id(n)).unwrap();
198        }
199
200        writeln!(text, "}}").unwrap();
201
202        w.write_all(&text)?;
203        text.clear();
204    }
205    Ok(())
206}
207
208pub fn render_edges<'a, N, E, S, G, W>(
209    w: &mut W,
210    graph: &'a G,
211    edges: &Edges<'a, E>,
212    options: &[RenderOption],
213) -> io::Result<()>
214where
215    W: Write,
216    N: Clone + 'a,
217    E: Clone + 'a,
218    S: Clone + 'a,
219    G: Labeller<'a, Node = N, Edge = E, Subgraph = S>
220        + GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
221{
222    let mut text = Vec::new();
223    for e in edges.iter() {
224        let start_arrow = graph.edge_start_arrow(e);
225        let end_arrow = graph.edge_end_arrow(e);
226        let start_port = graph
227            .edge_start_port(e)
228            .map(|p| format!(":{}", p.name))
229            .unwrap_or_default();
230        let end_port = graph
231            .edge_end_port(e)
232            .map(|p| format!(":{}", p.name))
233            .unwrap_or_default();
234        let start_point = graph
235            .edge_start_point(e)
236            .map(|p| p.as_static_str())
237            .unwrap_or("");
238        let end_point = graph
239            .edge_end_point(e)
240            .map(|p| p.as_static_str())
241            .unwrap_or("");
242
243        write!(w, "    ")?;
244
245        let source_id = graph.node_id(&graph.source(e));
246        let target_id = graph.node_id(&graph.target(e));
247
248        write!(
249            text,
250            "{}{}{} {} {}{}{}",
251            *source_id,
252            start_port,
253            start_point,
254            graph.kind().as_edge_op(),
255            *target_id,
256            end_port,
257            end_point,
258        )
259        .unwrap();
260
261        if !options.contains(&RenderOption::NoEdgeLabels) {
262            let label = graph.edge_label(e).to_escaped_string();
263            write!(text, "[label={label}]").unwrap();
264        }
265
266        let style = graph.edge_style(e);
267        if !options.contains(&RenderOption::NoEdgeStyles) && style != Style::None {
268            write!(text, "[style=\"{}\"]", style.as_static_str()).unwrap();
269        }
270
271        if !options.contains(&RenderOption::NoEdgeColors) {
272            if let Some(color) = graph.edge_color(e) {
273                write!(text, "[color={}]", color.to_escaped_string()).unwrap();
274            }
275        }
276
277        if !options.contains(&RenderOption::NoArrows)
278            && (!start_arrow.is_default() || !end_arrow.is_default())
279        {
280            write!(text, "[").unwrap();
281            if !end_arrow.is_default() {
282                write!(text, "arrowhead=\"{}\"", end_arrow.to_dot_string()).unwrap();
283            }
284            if !start_arrow.is_default() {
285                if *text.last().unwrap() != b'[' {
286                    write!(text, " ").unwrap();
287                }
288                write!(
289                    text,
290                    "dir=\"both\" arrowtail=\"{}\"",
291                    start_arrow.to_dot_string()
292                )
293                .unwrap();
294            }
295            write!(text, "]").unwrap();
296        }
297
298        for (name, value) in graph.edge_attrs(e).into_iter() {
299            write!(text, "{name}={value}").unwrap();
300        }
301
302        writeln!(text, ";").unwrap();
303
304        w.write_all(&text)?;
305        text.clear();
306    }
307    Ok(())
308}