1use std::fmt::Debug;
2
3use dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Port, Stmt, Vertex};
4use open_hypergraphs::lax::OpenHypergraph;
5
6use crate::options::*;
7
8pub fn generate_dot<O, A>(graph: &OpenHypergraph<O, A>) -> Graph
9where
10 O: Clone + Debug + PartialEq,
11 A: Clone + Debug + PartialEq,
12{
13 generate_dot_with(graph, &Options::default())
14}
15
16pub fn generate_dot_with<O, A>(graph: &OpenHypergraph<O, A>, opts: &Options<O, A>) -> Graph
18where
19 O: Clone + PartialEq,
20 A: Clone + PartialEq,
21{
22 let theme = &opts.theme;
23
24 let mut dot_graph = Graph::DiGraph {
26 id: Id::Plain(String::from("G")),
27 strict: false,
28 stmts: Vec::new(),
29 };
30
31 dot_graph.add_stmt(Stmt::Attribute(Attribute(
33 Id::Plain(String::from("rankdir")),
34 Id::Plain(opts.orientation.to_string()),
35 )));
36
37 dot_graph.add_stmt(Stmt::Attribute(Attribute(
39 Id::Plain(String::from("bgcolor")),
40 Id::Plain(format!("\"{}\"", theme.bgcolor.clone())),
41 )));
42
43 dot_graph.add_stmt(Stmt::Node(Node {
45 id: NodeId(Id::Plain(String::from("node")), None),
46 attributes: vec![
47 Attribute(
48 Id::Plain(String::from("shape")),
49 Id::Plain(String::from("record")),
50 ),
51 Attribute(
52 Id::Plain(String::from("style")),
53 Id::Plain(String::from("rounded")),
54 ),
55 Attribute(
56 Id::Plain(String::from("fontcolor")),
57 Id::Plain(format!("\"{}\"", theme.fontcolor.clone())),
58 ),
59 Attribute(
60 Id::Plain(String::from("color")),
61 Id::Plain(format!("\"{}\"", theme.color.clone())),
62 ),
63 ],
64 }));
65
66 dot_graph.add_stmt(Stmt::Node(Node {
68 id: NodeId(Id::Plain(String::from("edge")), None),
69 attributes: vec![
70 Attribute(
71 Id::Plain(String::from("fontcolor")),
72 Id::Plain(format!("\"{}\"", theme.fontcolor.clone())),
73 ),
74 Attribute(
75 Id::Plain(String::from("color")),
76 Id::Plain(format!("\"{}\"", theme.color.clone())),
77 ),
78 Attribute(
79 Id::Plain(String::from("arrowhead")),
80 Id::Plain(String::from("none")),
81 ),
82 ],
83 }));
84
85 let node_stmts = generate_node_stmts(graph, opts);
87 for stmt in node_stmts {
88 dot_graph.add_stmt(stmt);
89 }
90
91 let edge_stmts = generate_edge_stmts(graph, opts);
93 for stmt in edge_stmts {
94 dot_graph.add_stmt(stmt);
95 }
96
97 let interface_stmts = generate_interface_stmts(graph);
99 for stmt in interface_stmts {
100 dot_graph.add_stmt(stmt);
101 }
102
103 let connection_stmts = generate_connection_stmts(graph);
105 for stmt in connection_stmts {
106 dot_graph.add_stmt(stmt);
107 }
108
109 let quotient_stmts = generate_quotient_stmts(graph);
111 for stmt in quotient_stmts {
112 dot_graph.add_stmt(stmt);
113 }
114
115 dot_graph
116}
117
118fn escape_dot_label(s: &str) -> String {
123 s.chars()
124 .flat_map(|c| match c {
125 '\\' => Some("\\\\".to_string()),
126 '"' => Some("\\\"".to_string()),
127 '{' => Some("\\{".to_string()),
128 '}' => Some("\\}".to_string()),
129 '|' => Some("\\|".to_string()),
130 '<' => Some("\\<".to_string()),
131 '>' => Some("\\>".to_string()),
132 _ => Some(c.to_string()),
133 })
134 .collect()
135}
136
137fn generate_node_stmts<O, A>(graph: &OpenHypergraph<O, A>, opts: &Options<O, A>) -> Vec<Stmt>
139where
140 O: Clone + PartialEq,
141 A: Clone + PartialEq,
142{
143 let mut stmts = Vec::new();
144
145 for i in 0..graph.hypergraph.nodes.len() {
146 let label = (opts.node_label)(&graph.hypergraph.nodes[i]);
147
148 let label = escape_dot_label(&label);
150
151 stmts.push(Stmt::Node(Node {
152 id: NodeId(Id::Plain(format!("n_{}", i)), None),
153 attributes: vec![
154 Attribute(
155 Id::Plain(String::from("shape")),
156 Id::Plain(String::from("point")),
157 ),
158 Attribute(
159 Id::Plain(String::from("xlabel")),
160 Id::Plain(format!("\"{}\"", label)),
161 ),
162 ],
163 }));
164 }
165
166 stmts
167}
168
169fn generate_edge_stmts<O, A>(graph: &OpenHypergraph<O, A>, opts: &Options<O, A>) -> Vec<Stmt>
171where
172 O: Clone + PartialEq,
173 A: Clone + PartialEq,
174{
175 let mut stmts = Vec::new();
176
177 for i in 0..graph.hypergraph.edges.len() {
178 let hyperedge = &graph.hypergraph.adjacency[i];
179 let label = (opts.edge_label)(&graph.hypergraph.edges[i]);
180 let label = escape_dot_label(&label);
181
182 let mut source_ports = String::new();
184 for j in 0..hyperedge.sources.len() {
185 source_ports.push_str(&format!("<s_{j}> | "));
186 }
187 if !source_ports.is_empty() {
188 source_ports.truncate(source_ports.len() - 3); }
190
191 let mut target_ports = String::new();
193 for j in 0..hyperedge.targets.len() {
194 target_ports.push_str(&format!("<t_{j}> | "));
195 }
196 if !target_ports.is_empty() {
197 target_ports.truncate(target_ports.len() - 3); }
199
200 let record_label = if source_ports.is_empty() && target_ports.is_empty() {
202 format!("\"{}\"", label)
203 } else if source_ports.is_empty() {
204 format!("\"{{ {} | {{ {} }} }}\"", label, target_ports)
205 } else if target_ports.is_empty() {
206 format!("\"{{ {{ {} }} | {} }}\"", source_ports, label)
207 } else {
208 format!(
209 "\"{{ {{ {} }} | {} | {{ {} }} }}\"",
210 source_ports, label, target_ports
211 )
212 };
213
214 stmts.push(Stmt::Node(Node {
215 id: NodeId(Id::Plain(format!("e_{}", i)), None),
216 attributes: vec![
217 Attribute(Id::Plain(String::from("label")), Id::Plain(record_label)),
218 Attribute(
219 Id::Plain(String::from("shape")),
220 Id::Plain(String::from("record")),
221 ),
222 ],
223 }));
224 }
225
226 stmts
227}
228
229fn generate_connection_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
231where
232 O: Clone + PartialEq,
233 A: Clone + PartialEq,
234{
235 let mut stmts = Vec::new();
236
237 for (i, hyperedge) in graph.hypergraph.adjacency.iter().enumerate() {
238 for (j, &node_id) in hyperedge.sources.iter().enumerate() {
240 let node_idx = node_id.0; let port = Some(Port(None, Some(format!("s_{}", j))));
244
245 let edge = Edge {
246 ty: EdgeTy::Pair(
247 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
248 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), port)),
249 ),
250 attributes: vec![],
251 };
252 stmts.push(Stmt::Edge(edge));
253 }
254
255 for (j, &node_id) in hyperedge.targets.iter().enumerate() {
258 let node_idx = node_id.0; let port = Some(Port(None, Some(format!("t_{}", j))));
262
263 let edge = Edge {
264 ty: EdgeTy::Pair(
265 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), port)),
266 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
267 ),
268 attributes: vec![],
269 };
270 stmts.push(Stmt::Edge(edge));
271 }
272 }
273
274 stmts
275}
276
277fn generate_interface_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
279where
280 O: Clone + PartialEq,
281 A: Clone + PartialEq,
282{
283 let mut stmts = Vec::new();
284
285 if !graph.sources.is_empty() {
287 let mut source_ports = String::new();
289 for i in 0..graph.sources.len() {
290 source_ports.push_str(&format!("<p_{i}> | "));
291 }
292 if !source_ports.is_empty() {
294 source_ports.truncate(source_ports.len() - 3);
295 }
296
297 stmts.push(Stmt::Node(Node {
299 id: NodeId(Id::Plain(String::from("sources")), None),
300 attributes: vec![
301 Attribute(
302 Id::Plain(String::from("label")),
303 Id::Plain(format!("\"{{ {{}} | {{ {} }} }}\"", source_ports)),
304 ),
305 Attribute(
306 Id::Plain(String::from("shape")),
307 Id::Plain(String::from("record")),
308 ),
309 Attribute(
310 Id::Plain(String::from("style")),
311 Id::Plain(String::from("invisible")),
312 ),
313 Attribute(
314 Id::Plain(String::from("rank")),
315 Id::Plain(String::from("source")),
316 ),
317 ],
318 }));
319
320 for (i, &source_node_id) in graph.sources.iter().enumerate() {
322 let edge = Edge {
323 ty: EdgeTy::Pair(
324 Vertex::N(NodeId(
325 Id::Plain(String::from("sources")),
326 Some(Port(None, Some(format!("p_{}", i)))),
327 )),
328 Vertex::N(NodeId(Id::Plain(format!("n_{}", source_node_id.0)), None)),
329 ),
330 attributes: vec![Attribute(
331 Id::Plain(String::from("style")),
332 Id::Plain(String::from("dashed")),
333 )],
334 };
335 stmts.push(Stmt::Edge(edge));
336 }
337 }
338
339 if !graph.targets.is_empty() {
341 let mut target_ports = String::new();
343 for i in 0..graph.targets.len() {
344 target_ports.push_str(&format!("<p_{i}> | "));
345 }
346 if !target_ports.is_empty() {
348 target_ports.truncate(target_ports.len() - 3);
349 }
350
351 stmts.push(Stmt::Node(Node {
353 id: NodeId(Id::Plain(String::from("targets")), None),
354 attributes: vec![
355 Attribute(
356 Id::Plain(String::from("label")),
357 Id::Plain(format!("\"{{ {{ {} }} | {{}} }}\"", target_ports)),
358 ),
359 Attribute(
360 Id::Plain(String::from("shape")),
361 Id::Plain(String::from("record")),
362 ),
363 Attribute(
364 Id::Plain(String::from("style")),
365 Id::Plain(String::from("invisible")),
366 ),
367 Attribute(
368 Id::Plain(String::from("rank")),
369 Id::Plain(String::from("sink")),
370 ),
371 ],
372 }));
373
374 for (i, &target_node_id) in graph.targets.iter().enumerate() {
376 let edge = Edge {
377 ty: EdgeTy::Pair(
378 Vertex::N(NodeId(Id::Plain(format!("n_{}", target_node_id.0)), None)),
379 Vertex::N(NodeId(
380 Id::Plain(String::from("targets")),
381 Some(Port(None, Some(format!("p_{}", i)))),
382 )),
383 ),
384 attributes: vec![Attribute(
385 Id::Plain(String::from("style")),
386 Id::Plain(String::from("dashed")),
387 )],
388 };
389 stmts.push(Stmt::Edge(edge));
390 }
391 }
392
393 stmts
394}
395
396fn generate_quotient_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
398where
399 O: Clone + PartialEq,
400 A: Clone + PartialEq,
401{
402 let mut stmts = Vec::new();
403
404 let (lefts, rights) = &graph.hypergraph.quotient;
406
407 let mut unified_nodes = std::collections::HashMap::new();
409
410 for (left, right) in lefts.iter().zip(rights.iter()) {
411 let left_idx = left.0; let right_idx = right.0;
413
414 let pair_key = if left_idx < right_idx {
416 (left_idx, right_idx)
417 } else {
418 (right_idx, left_idx)
419 };
420
421 if unified_nodes.insert(pair_key, true).is_none() {
422 let edge = Edge {
424 ty: EdgeTy::Pair(
425 Vertex::N(NodeId(Id::Plain(format!("n_{}", left_idx)), None)),
426 Vertex::N(NodeId(Id::Plain(format!("n_{}", right_idx)), None)),
427 ),
428 attributes: vec![
429 Attribute(
430 Id::Plain(String::from("style")),
431 Id::Plain(String::from("dotted")),
432 ),
433 Attribute(
434 Id::Plain(String::from("dir")),
435 Id::Plain(String::from("none")),
436 ),
437 ],
438 };
439 stmts.push(Stmt::Edge(edge));
440 }
441 }
442
443 stmts
444}