dot2/
render.rs

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