1use dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Port, Stmt, Vertex};
2use open_hypergraphs::lax::OpenHypergraph;
3use std::fmt::Debug;
4
5pub mod options;
6pub use 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 + Debug + PartialEq,
20 A: Clone + Debug + 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 + Debug + PartialEq,
141 A: Clone + Debug + 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 + Debug + PartialEq,
173 A: Clone + Debug + 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 + Debug + PartialEq,
233 A: Clone + Debug + PartialEq,
234{
235 let mut stmts = Vec::new();
236
237 for (i, hyperedge) in graph.hypergraph.adjacency.iter().enumerate() {
239 for &node_id in hyperedge.sources.iter() {
240 let node_idx = node_id.0; let edge = Edge {
243 ty: EdgeTy::Pair(
244 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
245 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), None)),
246 ),
247 attributes: vec![],
248 };
249 stmts.push(Stmt::Edge(edge));
250 }
251
252 for (j, &node_id) in hyperedge.targets.iter().enumerate() {
254 let node_idx = node_id.0; let port = Some(Port(None, Some(format!("t_{}", j))));
258
259 let edge = Edge {
260 ty: EdgeTy::Pair(
261 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), port)),
262 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
263 ),
264 attributes: vec![],
265 };
266 stmts.push(Stmt::Edge(edge));
267 }
268 }
269
270 stmts
271}
272
273fn generate_interface_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
275where
276 O: Clone + Debug + PartialEq,
277 A: Clone + Debug + PartialEq,
278{
279 let mut stmts = Vec::new();
280
281 if !graph.sources.is_empty() {
283 let mut source_ports = String::new();
285 for i in 0..graph.sources.len() {
286 source_ports.push_str(&format!("<p_{i}> | "));
287 }
288 if !source_ports.is_empty() {
290 source_ports.truncate(source_ports.len() - 3);
291 }
292
293 stmts.push(Stmt::Node(Node {
295 id: NodeId(Id::Plain(String::from("sources")), None),
296 attributes: vec![
297 Attribute(
298 Id::Plain(String::from("label")),
299 Id::Plain(format!("\"{{ {{}} | {{ {} }} }}\"", source_ports)),
300 ),
301 Attribute(
302 Id::Plain(String::from("shape")),
303 Id::Plain(String::from("record")),
304 ),
305 Attribute(
306 Id::Plain(String::from("style")),
307 Id::Plain(String::from("invisible")),
308 ),
309 Attribute(
310 Id::Plain(String::from("rank")),
311 Id::Plain(String::from("source")),
312 ),
313 ],
314 }));
315
316 for (i, &source_node_id) in graph.sources.iter().enumerate() {
318 let edge = Edge {
319 ty: EdgeTy::Pair(
320 Vertex::N(NodeId(
321 Id::Plain(String::from("sources")),
322 Some(Port(None, Some(format!("p_{}", i)))),
323 )),
324 Vertex::N(NodeId(Id::Plain(format!("n_{}", source_node_id.0)), None)),
325 ),
326 attributes: vec![Attribute(
327 Id::Plain(String::from("style")),
328 Id::Plain(String::from("dashed")),
329 )],
330 };
331 stmts.push(Stmt::Edge(edge));
332 }
333 }
334
335 if !graph.targets.is_empty() {
337 let mut target_ports = String::new();
339 for i in 0..graph.targets.len() {
340 target_ports.push_str(&format!("<p_{i}> | "));
341 }
342 if !target_ports.is_empty() {
344 target_ports.truncate(target_ports.len() - 3);
345 }
346
347 stmts.push(Stmt::Node(Node {
349 id: NodeId(Id::Plain(String::from("targets")), None),
350 attributes: vec![
351 Attribute(
352 Id::Plain(String::from("label")),
353 Id::Plain(format!("\"{{ {{ {} }} | {{}} }}\"", target_ports)),
354 ),
355 Attribute(
356 Id::Plain(String::from("shape")),
357 Id::Plain(String::from("record")),
358 ),
359 Attribute(
360 Id::Plain(String::from("style")),
361 Id::Plain(String::from("invisible")),
362 ),
363 Attribute(
364 Id::Plain(String::from("rank")),
365 Id::Plain(String::from("sink")),
366 ),
367 ],
368 }));
369
370 for (i, &target_node_id) in graph.targets.iter().enumerate() {
372 let edge = Edge {
373 ty: EdgeTy::Pair(
374 Vertex::N(NodeId(Id::Plain(format!("n_{}", target_node_id.0)), None)),
375 Vertex::N(NodeId(
376 Id::Plain(String::from("targets")),
377 Some(Port(None, Some(format!("p_{}", i)))),
378 )),
379 ),
380 attributes: vec![Attribute(
381 Id::Plain(String::from("style")),
382 Id::Plain(String::from("dashed")),
383 )],
384 };
385 stmts.push(Stmt::Edge(edge));
386 }
387 }
388
389 stmts
390}
391
392fn generate_quotient_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
394where
395 O: Clone + Debug + PartialEq,
396 A: Clone + Debug + PartialEq,
397{
398 let mut stmts = Vec::new();
399
400 let (lefts, rights) = &graph.hypergraph.quotient;
402
403 let mut unified_nodes = std::collections::HashMap::new();
405
406 for (left, right) in lefts.iter().zip(rights.iter()) {
407 let left_idx = left.0; let right_idx = right.0;
409
410 let pair_key = if left_idx < right_idx {
412 (left_idx, right_idx)
413 } else {
414 (right_idx, left_idx)
415 };
416
417 if unified_nodes.insert(pair_key, true).is_none() {
418 let edge = Edge {
420 ty: EdgeTy::Pair(
421 Vertex::N(NodeId(Id::Plain(format!("n_{}", left_idx)), None)),
422 Vertex::N(NodeId(Id::Plain(format!("n_{}", right_idx)), None)),
423 ),
424 attributes: vec![
425 Attribute(
426 Id::Plain(String::from("style")),
427 Id::Plain(String::from("dotted")),
428 ),
429 Attribute(
430 Id::Plain(String::from("dir")),
431 Id::Plain(String::from("none")),
432 ),
433 ],
434 };
435 stmts.push(Stmt::Edge(edge));
436 }
437 }
438
439 stmts
440}