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
15pub 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
29pub 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 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}