|
1 | | -use std::{fmt, io::Write}; |
| 1 | +use std::{collections::HashMap, fmt, io::Write}; |
2 | 2 |
|
3 | 3 | use crate::EGraph; |
4 | 4 | use graphviz_rust::{ |
@@ -39,16 +39,22 @@ impl EGraph { |
39 | 39 | // and create mapping from each node ID to its class |
40 | 40 | let mut node_to_class = std::collections::HashMap::new(); |
41 | 41 | for (node_id, node) in &self.nodes { |
42 | | - let typ = self |
43 | | - .class_data |
44 | | - .get(&node.eclass) |
45 | | - .and_then(|data| data.typ.clone()); |
| 42 | + let class_data = self.class_data.get(&node.eclass); |
| 43 | + let typ = class_data.and_then(|data| data.typ.clone()); |
| 44 | + let extra = class_data.and_then(|data| { |
| 45 | + if data.extra.is_empty() { |
| 46 | + None |
| 47 | + } else { |
| 48 | + Some(data.extra.clone()) |
| 49 | + } |
| 50 | + }); |
46 | 51 | node_to_class.insert(node_id.clone(), node.eclass.clone()); |
47 | 52 | class_nodes |
48 | 53 | .entry(typ) |
49 | 54 | .or_insert_with(std::collections::HashMap::new) |
50 | 55 | .entry(node.eclass.clone()) |
51 | | - .or_insert_with(Vec::new) |
| 56 | + .or_insert_with(|| (extra, Vec::new())) |
| 57 | + .1 |
52 | 58 | .push((node_id.clone(), node)); |
53 | 59 | } |
54 | 60 | // 2. Start with configuration |
@@ -84,7 +90,7 @@ impl EGraph { |
84 | 90 | let next_color = (typ_colors.len() + INITIAL_COLOR) % N_COLORS; |
85 | 91 | let color = typ_colors.entry(typ).or_insert(next_color); |
86 | 92 | stmts.push(stmt!(attr!("fillcolor", color))); |
87 | | - for (class_id, nodes) in class_to_node { |
| 93 | + for (class_id, (extra, nodes)) in class_to_node { |
88 | 94 | let mut inner_stmts = vec![]; |
89 | 95 |
|
90 | 96 | // Add nodes |
@@ -113,14 +119,17 @@ impl EGraph { |
113 | 119 | let outer_subgraph_id = quote(&format!("outer_{subgraph_id}")); |
114 | 120 | let quoted_subgraph_id = quote(&subgraph_id); |
115 | 121 |
|
| 122 | + let mut inner_subgraph = subgraph!(quoted_subgraph_id; subgraph!("", inner_stmts)); |
| 123 | + if let Some(extra) = extra { |
| 124 | + inner_subgraph |
| 125 | + .add_stmt(stmt!(SubgraphAttributes::label(class_html_label(extra)))); |
| 126 | + } |
| 127 | + // Nest in empty sub-graph so that we can use rank=same |
| 128 | + // https://stackoverflow.com/a/55562026/907060 |
116 | 129 | let subgraph = subgraph!(outer_subgraph_id; |
117 | 130 | // Disable label for now, to reduce size |
118 | 131 | // NodeAttributes::label(subgraph_html_label(&typ)), |
119 | | - |
120 | | - // Nest in empty sub-graph so that we can use rank=same |
121 | | - // https://stackoverflow.com/a/55562026/907060 |
122 | | - subgraph!(quoted_subgraph_id; subgraph!("", inner_stmts)), |
123 | | - |
| 132 | + inner_subgraph, |
124 | 133 | // Make outer subgraph a cluster but make it invisible, so just used for padding |
125 | 134 | // https://forum.graphviz.org/t/how-to-add-space-between-clusters/1209/3 |
126 | 135 | SubgraphAttributes::style(quote("invis")), |
@@ -166,6 +175,20 @@ fn html_label(label: &str, n_args: usize) -> String { |
166 | 175 | ) |
167 | 176 | } |
168 | 177 |
|
| 178 | +fn class_html_label(extra: HashMap<String, String>) -> String { |
| 179 | + let rows = extra.iter().map(|(key, value)| { |
| 180 | + format!( |
| 181 | + "<TR><TD ALIGN=\"RIGHT\">{}</TD><TD ALIGN=\"LEFT\">{}</TD></TR>", |
| 182 | + Escape(key), |
| 183 | + Escape(value) |
| 184 | + ) |
| 185 | + }); |
| 186 | + format!( |
| 187 | + "<<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"2\">{}</TABLE>>", |
| 188 | + rows.collect::<Vec<String>>().join("") |
| 189 | + ) |
| 190 | +} |
| 191 | + |
169 | 192 | /// Adds double quotes and escapes the quotes in the string |
170 | 193 | fn quote(s: &str) -> String { |
171 | 194 | format!("{s:?}") |
|
0 commit comments