1use std::io;
2use std::io::Write;
3
4use crate::{Edges, GraphKind, GraphWalk, Labeller, Nodes, Style, Subgraphs};
5
6pub 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
34pub 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 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}