1use dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Port, Stmt, Vertex};
2use open_hypergraphs::lax::OpenHypergraph;
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::hash::Hash;
6use std::fmt;
7
8#[derive(Debug, Clone, Copy)]
10pub enum Orientation {
11 LR,
13 TB,
15}
16
17impl fmt::Display for Orientation {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 Orientation::LR => write!(f, "LR"),
21 Orientation::TB => write!(f, "TB"),
22 }
23 }
24}
25
26pub struct Theme {
28 pub bgcolor: String,
29 pub fontcolor: String,
30 pub color: String,
31 pub orientation: Orientation,
32}
33
34impl Default for Theme {
35 fn default() -> Self {
36 Theme {
37 bgcolor: String::from("white"),
38 fontcolor: String::from("black"),
39 color: String::from("black"),
40 orientation: Orientation::LR,
41 }
42 }
43}
44
45pub fn dark_theme() -> Theme {
47 Theme {
48 bgcolor: String::from("#4a4a4a"),
49 fontcolor: String::from("white"),
50 color: String::from("white"),
51 orientation: Orientation::LR,
52 }
53}
54
55pub fn generate_dot<O, A>(graph: &OpenHypergraph<O, A>, theme: &Theme) -> Graph
57where
58 O: Clone + Debug + PartialEq + Hash,
59 A: Clone + Debug + PartialEq,
60{
61 let mut dot_graph = Graph::DiGraph {
63 id: Id::Plain(String::from("G")),
64 strict: false,
65 stmts: Vec::new(),
66 };
67
68 dot_graph.add_stmt(Stmt::Attribute(Attribute(
70 Id::Plain(String::from("rankdir")),
71 Id::Plain(theme.orientation.to_string()),
72 )));
73
74 dot_graph.add_stmt(Stmt::Attribute(Attribute(
76 Id::Plain(String::from("bgcolor")),
77 Id::Plain(format!("\"{}\"", theme.bgcolor.clone())),
78 )));
79
80 dot_graph.add_stmt(Stmt::Node(Node {
82 id: NodeId(Id::Plain(String::from("node")), None),
83 attributes: vec![
84 Attribute(
85 Id::Plain(String::from("shape")),
86 Id::Plain(String::from("record")),
87 ),
88 Attribute(
89 Id::Plain(String::from("style")),
90 Id::Plain(String::from("rounded")),
91 ),
92 Attribute(
93 Id::Plain(String::from("fontcolor")),
94 Id::Plain(format!("\"{}\"", theme.fontcolor.clone())),
95 ),
96 Attribute(
97 Id::Plain(String::from("color")),
98 Id::Plain(format!("\"{}\"", theme.color.clone())),
99 ),
100 ],
101 }));
102
103 dot_graph.add_stmt(Stmt::Node(Node {
105 id: NodeId(Id::Plain(String::from("edge")), None),
106 attributes: vec![
107 Attribute(
108 Id::Plain(String::from("fontcolor")),
109 Id::Plain(format!("\"{}\"", theme.fontcolor.clone())),
110 ),
111 Attribute(
112 Id::Plain(String::from("color")),
113 Id::Plain(format!("\"{}\"", theme.color.clone())),
114 ),
115 Attribute(
116 Id::Plain(String::from("arrowhead")),
117 Id::Plain(String::from("none")),
118 ),
119 ],
120 }));
121
122 let node_stmts = generate_node_stmts(graph);
124 for stmt in node_stmts {
125 dot_graph.add_stmt(stmt);
126 }
127
128 let edge_stmts = generate_edge_stmts(graph);
130 for stmt in edge_stmts {
131 dot_graph.add_stmt(stmt);
132 }
133
134 let interface_stmts = generate_interface_stmts(graph);
136 for stmt in interface_stmts {
137 dot_graph.add_stmt(stmt);
138 }
139
140 let connection_stmts = generate_connection_stmts(graph);
142 for stmt in connection_stmts {
143 dot_graph.add_stmt(stmt);
144 }
145
146 let quotient_stmts = generate_quotient_stmts(graph);
148 for stmt in quotient_stmts {
149 dot_graph.add_stmt(stmt);
150 }
151
152 dot_graph
153}
154
155fn generate_node_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
157where
158 O: Clone + Debug + PartialEq,
159 A: Clone + Debug + PartialEq,
160{
161 let mut stmts = Vec::new();
162
163 for i in 0..graph.hypergraph.nodes.len() {
164 let label = format!("{:?}", graph.hypergraph.nodes[i]);
165
166 stmts.push(Stmt::Node(Node {
167 id: NodeId(Id::Plain(format!("n_{}", i)), None),
168 attributes: vec![
169 Attribute(
170 Id::Plain(String::from("shape")),
171 Id::Plain(String::from("point")),
172 ),
173 Attribute(
174 Id::Plain(String::from("xlabel")),
175 Id::Plain(format!("\"{}\"", label)),
176 ),
177 ],
178 }));
179 }
180
181 stmts
182}
183
184fn generate_edge_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
186where
187 O: Clone + Debug + PartialEq,
188 A: Clone + Debug + PartialEq,
189{
190 let mut stmts = Vec::new();
191
192 for i in 0..graph.hypergraph.edges.len() {
193 let hyperedge = &graph.hypergraph.adjacency[i];
194 let label = format!("{:?}", graph.hypergraph.edges[i]);
195
196 let mut source_ports = String::new();
198 for j in 0..hyperedge.sources.len() {
199 source_ports.push_str(&format!("<s_{j}> | "));
200 }
201 if !source_ports.is_empty() {
202 source_ports.truncate(source_ports.len() - 3); }
204
205 let mut target_ports = String::new();
207 for j in 0..hyperedge.targets.len() {
208 target_ports.push_str(&format!("<t_{j}> | "));
209 }
210 if !target_ports.is_empty() {
211 target_ports.truncate(target_ports.len() - 3); }
213
214 let record_label = if source_ports.is_empty() && target_ports.is_empty() {
216 format!("\"{}\"", label)
217 } else if source_ports.is_empty() {
218 format!("\"{{ {} | {{ {} }} }}\"", label, target_ports)
219 } else if target_ports.is_empty() {
220 format!("\"{{ {{ {} }} | {} }}\"", source_ports, label)
221 } else {
222 format!(
223 "\"{{ {{ {} }} | {} | {{ {} }} }}\"",
224 source_ports, label, target_ports
225 )
226 };
227
228 stmts.push(Stmt::Node(Node {
229 id: NodeId(Id::Plain(format!("e_{}", i)), None),
230 attributes: vec![
231 Attribute(Id::Plain(String::from("label")), Id::Plain(record_label)),
232 Attribute(
233 Id::Plain(String::from("shape")),
234 Id::Plain(String::from("record")),
235 ),
236 ],
237 }));
238 }
239
240 stmts
241}
242
243fn generate_connection_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
245where
246 O: Clone + Debug + PartialEq,
247 A: Clone + Debug + PartialEq,
248{
249 let mut stmts = Vec::new();
250
251 for (i, hyperedge) in graph.hypergraph.adjacency.iter().enumerate() {
253 for (_j, &node_id) in hyperedge.sources.iter().enumerate() {
254 let node_idx = node_id.0; let edge = Edge {
257 ty: EdgeTy::Pair(
258 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
259 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), None)),
260 ),
261 attributes: vec![],
262 };
263 stmts.push(Stmt::Edge(edge));
264 }
265
266 for (j, &node_id) in hyperedge.targets.iter().enumerate() {
268 let node_idx = node_id.0; let port = Some(Port(None, Some(format!("t_{}", j))));
272
273 let edge = Edge {
274 ty: EdgeTy::Pair(
275 Vertex::N(NodeId(Id::Plain(format!("e_{}", i)), port)),
276 Vertex::N(NodeId(Id::Plain(format!("n_{}", node_idx)), None)),
277 ),
278 attributes: vec![],
279 };
280 stmts.push(Stmt::Edge(edge));
281 }
282 }
283
284 stmts
285}
286
287fn generate_interface_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
289where
290 O: Clone + Debug + PartialEq,
291 A: Clone + Debug + PartialEq,
292{
293 let mut stmts = Vec::new();
294
295 if !graph.sources.is_empty() {
297 let mut source_ports = String::new();
299 for i in 0..graph.sources.len() {
300 source_ports.push_str(&format!("<p_{i}> | "));
301 }
302 if !source_ports.is_empty() {
304 source_ports.truncate(source_ports.len() - 3);
305 }
306
307 stmts.push(Stmt::Node(Node {
309 id: NodeId(Id::Plain(String::from("sources")), None),
310 attributes: vec![
311 Attribute(
312 Id::Plain(String::from("label")),
313 Id::Plain(format!("\"{{ {{}} | {{ {} }} }}\"", source_ports)),
314 ),
315 Attribute(
316 Id::Plain(String::from("shape")),
317 Id::Plain(String::from("record")),
318 ),
319 Attribute(
320 Id::Plain(String::from("style")),
321 Id::Plain(String::from("invisible")),
322 ),
323 Attribute(
324 Id::Plain(String::from("rank")),
325 Id::Plain(String::from("source")),
326 ),
327 ],
328 }));
329
330 for (i, &source_node_id) in graph.sources.iter().enumerate() {
332 let edge = Edge {
333 ty: EdgeTy::Pair(
334 Vertex::N(NodeId(
335 Id::Plain(String::from("sources")),
336 Some(Port(None, Some(format!("p_{}", i)))),
337 )),
338 Vertex::N(NodeId(Id::Plain(format!("n_{}", source_node_id.0)), None)),
339 ),
340 attributes: vec![Attribute(
341 Id::Plain(String::from("style")),
342 Id::Plain(String::from("dashed")),
343 )],
344 };
345 stmts.push(Stmt::Edge(edge));
346 }
347 }
348
349 if !graph.targets.is_empty() {
351 let mut target_ports = String::new();
353 for i in 0..graph.targets.len() {
354 target_ports.push_str(&format!("<p_{i}> | "));
355 }
356 if !target_ports.is_empty() {
358 target_ports.truncate(target_ports.len() - 3);
359 }
360
361 stmts.push(Stmt::Node(Node {
363 id: NodeId(Id::Plain(String::from("targets")), None),
364 attributes: vec![
365 Attribute(
366 Id::Plain(String::from("label")),
367 Id::Plain(format!("\"{{ {{ {} }} | {{}} }}\"", target_ports)),
368 ),
369 Attribute(
370 Id::Plain(String::from("shape")),
371 Id::Plain(String::from("record")),
372 ),
373 Attribute(
374 Id::Plain(String::from("style")),
375 Id::Plain(String::from("invisible")),
376 ),
377 Attribute(
378 Id::Plain(String::from("rank")),
379 Id::Plain(String::from("sink")),
380 ),
381 ],
382 }));
383
384 for (i, &target_node_id) in graph.targets.iter().enumerate() {
386 let edge = Edge {
387 ty: EdgeTy::Pair(
388 Vertex::N(NodeId(Id::Plain(format!("n_{}", target_node_id.0)), None)),
389 Vertex::N(NodeId(
390 Id::Plain(String::from("targets")),
391 Some(Port(None, Some(format!("p_{}", i)))),
392 )),
393 ),
394 attributes: vec![Attribute(
395 Id::Plain(String::from("style")),
396 Id::Plain(String::from("dashed")),
397 )],
398 };
399 stmts.push(Stmt::Edge(edge));
400 }
401 }
402
403 stmts
404}
405
406fn generate_quotient_stmts<O, A>(graph: &OpenHypergraph<O, A>) -> Vec<Stmt>
408where
409 O: Clone + Debug + PartialEq,
410 A: Clone + Debug + PartialEq,
411{
412 let mut stmts = Vec::new();
413
414 let (lefts, rights) = &graph.hypergraph.quotient;
416
417 let mut unified_nodes = HashMap::new();
419
420 for (left, right) in lefts.iter().zip(rights.iter()) {
421 let left_idx = left.0; let right_idx = right.0;
423
424 let pair_key = if left_idx < right_idx {
426 (left_idx, right_idx)
427 } else {
428 (right_idx, left_idx)
429 };
430
431 if unified_nodes.insert(pair_key, true).is_none() {
432 let edge = Edge {
434 ty: EdgeTy::Pair(
435 Vertex::N(NodeId(Id::Plain(format!("n_{}", left_idx)), None)),
436 Vertex::N(NodeId(Id::Plain(format!("n_{}", right_idx)), None)),
437 ),
438 attributes: vec![
439 Attribute(
440 Id::Plain(String::from("style")),
441 Id::Plain(String::from("dotted")),
442 ),
443 Attribute(
444 Id::Plain(String::from("dir")),
445 Id::Plain(String::from("none")),
446 ),
447 ],
448 };
449 stmts.push(Stmt::Edge(edge));
450 }
451 }
452
453 stmts
454}