1use super::record::record_builder;
4use crate::adt::dag::NodeHandle;
5use crate::adt::map::ScopedMap;
6use crate::core::base::Orientation;
7use crate::core::color::Color;
8use crate::core::style::*;
9use crate::gv::parser::ast;
10use crate::std_shapes::render::get_shape_size;
11use crate::std_shapes::shapes::ShapeKind;
12use crate::std_shapes::shapes::*;
13use crate::topo::layout::VisualGraph;
14use std::collections::HashMap;
15
16type PropertyList = HashMap<String, String>;
17
18#[derive(Debug)]
23struct EdgeDesc {
24 from: String,
25 to: String,
26 props: PropertyList,
27 is_directed: bool,
28 from_port: Option<String>,
29 to_port: Option<String>,
30}
31
32#[derive(Debug)]
34pub struct GraphBuilder {
35 global_state: PropertyList,
37 node_order: Vec<String>,
40 nodes: HashMap<String, PropertyList>,
42 edges: Vec<EdgeDesc>,
44 global_attr: ScopedMap<String, String>,
47 node_attr: ScopedMap<String, String>,
48 edge_attr: ScopedMap<String, String>,
49}
50impl Default for GraphBuilder {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl GraphBuilder {
57 pub fn new() -> Self {
58 Self {
59 global_state: PropertyList::new(),
60 node_order: Vec::new(),
61 nodes: HashMap::new(),
62 edges: Vec::new(),
63 global_attr: ScopedMap::new(),
64 node_attr: ScopedMap::new(),
65 edge_attr: ScopedMap::new(),
66 }
67 }
68 pub fn visit_graph(&mut self, graph: &ast::Graph) {
69 self.global_attr.push();
70 self.node_attr.push();
71 self.edge_attr.push();
72 for stmt in &graph.list.list {
73 self.visit_stmt(stmt);
74 }
75
76 self.global_state = self.global_attr.flatten();
79
80 self.global_attr.pop();
81 self.node_attr.pop();
82 self.edge_attr.pop();
83 }
84 fn visit_stmt(&mut self, stmt: &ast::Stmt) {
85 match stmt {
86 ast::Stmt::Edge(e) => {
87 self.visit_edge(e);
88 }
89 ast::Stmt::Node(n) => {
90 self.visit_node(n);
91 }
92 ast::Stmt::Attribute(a) => {
93 self.visit_att(a);
94 }
95 ast::Stmt::SubGraph(g) => {
96 self.visit_graph(g);
97 }
98 }
99 }
100
101 fn visit_edge(&mut self, e: &ast::EdgeStmt) {
102 self.edge_attr.push();
103
104 for att in e.list.iter() {
105 self.edge_attr.insert(&att.0, &att.1);
106 }
107
108 self.init_node_with_name(&e.from.name, false);
109
110 let mut prev = &e.from.name;
111 for dest in &e.to {
112 let curr = &dest.0.name;
113 self.init_node_with_name(curr, false);
114
115 let has_arrow = matches!(dest.1, ast::ArrowKind::Arrow);
116 let prop_list = self.edge_attr.flatten();
117
118 let edge = EdgeDesc {
119 from: prev.clone(),
120 to: curr.clone(),
121 props: prop_list,
122 is_directed: has_arrow,
123 from_port: e.from.port.clone(),
124 to_port: dest.0.port.clone(),
125 };
126 self.edges.push(edge);
127 prev = curr;
128 }
129 self.edge_attr.pop();
130 }
131
132 fn init_node_with_name(&mut self, name: &str, overwrite: bool) {
135 let node_attr = self.node_attr.flatten();
136
137 if let Option::Some(prop_list) = self.nodes.get_mut(name) {
138 if !overwrite {
139 return;
140 }
141 for p in node_attr {
142 prop_list.insert(p.0, p.1);
143 }
144 } else {
145 self.node_order.push(name.to_string());
146 self.nodes.insert(name.to_string(), node_attr);
147 }
148 }
149
150 fn visit_node(&mut self, n: &ast::NodeStmt) {
151 self.node_attr.push();
152
153 for att in n.list.iter() {
154 self.node_attr.insert(&att.0, &att.1);
155 }
156
157 self.init_node_with_name(&n.id.name, true);
158 self.node_attr.pop();
159 }
160
161 fn visit_att(&mut self, att: &ast::AttrStmt) {
162 match att.target {
163 ast::AttrStmtTarget::Graph => {
164 for att in att.list.iter() {
165 self.global_attr.insert(&att.0, &att.1);
166 }
167 }
168 ast::AttrStmtTarget::Node => {
169 for att in att.list.iter() {
170 self.node_attr.insert(&att.0, &att.1);
171 }
172 }
173 ast::AttrStmtTarget::Edge => {
174 for att in att.list.iter() {
175 self.edge_attr.insert(&att.0, &att.1);
176 }
177 }
178 }
179 }
180
181 pub fn get(&self) -> VisualGraph {
182 let mut dir = Orientation::TopToBottom;
183
184 if let Option::Some(rd) = self.global_state.get("rankdir") {
186 if rd == "LR" {
187 dir = Orientation::LeftToRight;
188 }
189 }
190
191 let mut vg = VisualGraph::new(dir);
192
193 let mut node_map: HashMap<String, NodeHandle> = HashMap::new();
195
196 assert_eq!(self.nodes.len(), self.node_order.len());
197
198 for node_name in self.node_order.iter() {
200 let node_prop = self.nodes.get(node_name).unwrap();
201
202 let shape =
203 Self::get_shape_from_attributes(dir, node_prop, node_name);
204 let handle = vg.add_node(shape);
205 node_map.insert(node_name.to_string(), handle);
206 }
207
208 for edge_prop in &self.edges {
210 let shape = Self::get_arrow_from_attributes(
211 &edge_prop.props,
212 edge_prop.is_directed,
213 edge_prop.from_port.clone(),
214 edge_prop.to_port.clone(),
215 );
216 let from = node_map.get(&edge_prop.from).unwrap();
217 let to = node_map.get(&edge_prop.to).unwrap();
218 vg.add_edge(shape, *from, *to);
219 }
220
221 vg
222 }
223
224 fn get_arrow_from_attributes(
225 lst: &PropertyList,
226 has_arrow: bool,
227 from_port: Option<String>,
228 to_port: Option<String>,
229 ) -> Arrow {
230 let mut line_width = 1;
231 let mut font_size: usize = 14;
232 let start = LineEndKind::None;
233 let end = if has_arrow {
234 LineEndKind::Arrow
235 } else {
236 LineEndKind::None
237 };
238 let mut label = String::from("");
239 let mut color = String::from("black");
240 let mut line_style = LineStyleKind::Normal;
241
242 if let Option::Some(val) = lst.get(&"label".to_string()) {
243 label = val.clone();
244 }
245
246 if let Option::Some(stl) = lst.get(&"style".to_string()) {
247 if stl == "dashed" {
248 line_style = LineStyleKind::Dashed;
249 }
250 }
251
252 if let Option::Some(x) = lst.get(&"color".to_string()) {
253 color = x.clone();
254 color = Self::normalize_color(color);
255 }
256
257 if let Option::Some(pw) = lst.get(&"penwidth".to_string()) {
258 if let Result::Ok(x) = pw.parse::<usize>() {
259 line_width = x;
260 } else {
261 #[cfg(feature = "log")]
262 log::info!("Can't parse integer \"{}\"", pw);
263 }
264 }
265
266 if let Option::Some(fx) = lst.get(&"fontsize".to_string()) {
267 if let Result::Ok(x) = fx.parse::<usize>() {
268 font_size = x;
269 } else {
270 #[cfg(feature = "log")]
271 log::info!("Can't parse integer \"{}\"", fx);
272 }
273 }
274
275 let color = Color::fast(&color);
276 let look = StyleAttr::new(color, line_width, None, 0, font_size);
277 Arrow::new(start, end, line_style, &label, &look, &from_port, &to_port)
278 }
279
280 fn normalize_color(color: String) -> String {
282 let mut color = color;
283 if let Option::Some(idx) = color.find(':') {
284 color = color[0..idx].to_string();
285 }
286 if color == "transparent" {
287 color = "white".to_string();
288 }
289 color
290 }
291
292 fn get_shape_from_attributes(
293 dir: Orientation,
294 lst: &PropertyList,
295 default_name: &str,
296 ) -> Element {
297 let mut label = default_name.to_string();
298 let mut edge_color = String::from("black");
299 let mut fill_color = String::from("white");
300 let mut font_size: usize = 14;
301 let mut line_width: usize = 1;
302 let mut make_xy_same = false;
303 let mut rounded_corder_value = 0;
304
305 if let Option::Some(val) = lst.get(&"label".to_string()) {
306 label = val.clone();
307 }
308
309 let mut shape = ShapeKind::Circle(label.clone());
310
311 if let Option::Some(val) = lst.get(&"shape".to_string()) {
313 match &val[..] {
314 "box" => {
315 shape = ShapeKind::Box(label);
316 make_xy_same = false;
317 }
318 "doublecircle" => {
319 shape = ShapeKind::DoubleCircle(label);
320 make_xy_same = true;
321 }
322 "record" => {
323 shape = record_builder(&label);
324 }
325 "Mrecord" => {
326 rounded_corder_value = 15;
327 shape = record_builder(&label);
328 }
329 _ => shape = ShapeKind::Circle(label),
330 }
331 }
332
333 if let Option::Some(x) = lst.get(&"color".to_string()) {
334 edge_color = x.clone();
335 edge_color = Self::normalize_color(edge_color);
336 }
337
338 if let Option::Some(style) = lst.get(&"style".to_string()) {
339 if style == "filled" && !lst.contains_key("fillcolor") {
340 fill_color = "lightgray".to_string();
341 }
342 }
343
344 if let Option::Some(x) = lst.get(&"fillcolor".to_string()) {
345 fill_color = x.clone();
346 fill_color = Self::normalize_color(fill_color);
347 }
348
349 if let Option::Some(fx) = lst.get(&"fontsize".to_string()) {
350 if let Result::Ok(x) = fx.parse::<usize>() {
351 font_size = x;
352 } else {
353 #[cfg(feature = "log")]
354 log::info!("Can't parse integer \"{}\"", fx);
355 }
356 }
357
358 if let Option::Some(pw) = lst.get(&"width".to_string()) {
359 if let Result::Ok(x) = pw.parse::<usize>() {
360 line_width = x;
361 } else {
362 #[cfg(feature = "log")]
363 log::info!("Can't parse integer \"{}\"", pw);
364 }
365 }
366
367 let dir = dir.flip();
370
371 let sz = get_shape_size(dir, &shape, font_size, make_xy_same);
372 let look = StyleAttr::new(
373 Color::fast(&edge_color),
374 line_width,
375 Option::Some(Color::fast(&fill_color)),
376 rounded_corder_value,
377 font_size,
378 );
379 Element::create(shape, look, dir, sz)
380 }
381}