use super::record::record_builder;
use crate::adt::dag::NodeHandle;
use crate::adt::map::ScopedMap;
use crate::core::base::Orientation;
use crate::core::color::Color;
use crate::core::style::*;
use crate::gv::parser::ast;
use crate::std_shapes::render::get_shape_size;
use crate::std_shapes::shapes::ShapeKind;
use crate::std_shapes::shapes::*;
use crate::topo::layout::VisualGraph;
use std::collections::HashMap;
type PropertyList = HashMap<String, String>;
#[derive(Debug)]
struct EdgeDesc {
from: String,
to: String,
props: PropertyList,
is_directed: bool,
from_port: Option<String>,
to_port: Option<String>,
}
#[derive(Debug)]
pub struct GraphBuilder {
global_state: PropertyList,
node_order: Vec<String>,
nodes: HashMap<String, PropertyList>,
edges: Vec<EdgeDesc>,
global_attr: ScopedMap<String, String>,
node_attr: ScopedMap<String, String>,
edge_attr: ScopedMap<String, String>,
}
impl Default for GraphBuilder {
fn default() -> Self {
Self::new()
}
}
impl GraphBuilder {
pub fn new() -> Self {
Self {
global_state: PropertyList::new(),
node_order: Vec::new(),
nodes: HashMap::new(),
edges: Vec::new(),
global_attr: ScopedMap::new(),
node_attr: ScopedMap::new(),
edge_attr: ScopedMap::new(),
}
}
pub fn visit_graph(&mut self, graph: &ast::Graph) {
self.global_attr.push();
self.node_attr.push();
self.edge_attr.push();
for stmt in &graph.list.list {
self.visit_stmt(stmt);
}
self.global_state = self.global_attr.flatten();
self.global_attr.pop();
self.node_attr.pop();
self.edge_attr.pop();
}
fn visit_stmt(&mut self, stmt: &ast::Stmt) {
match stmt {
ast::Stmt::Edge(e) => {
self.visit_edge(e);
}
ast::Stmt::Node(n) => {
self.visit_node(n);
}
ast::Stmt::Attribute(a) => {
self.visit_att(a);
}
ast::Stmt::SubGraph(g) => {
self.visit_graph(g);
}
}
}
fn visit_edge(&mut self, e: &ast::EdgeStmt) {
self.edge_attr.push();
for att in e.list.iter() {
self.edge_attr.insert(&att.0, &att.1);
}
self.init_node_with_name(&e.from.name, false);
let mut prev = &e.from.name;
for dest in &e.to {
let curr = &dest.0.name;
self.init_node_with_name(curr, false);
let has_arrow = matches!(dest.1, ast::ArrowKind::Arrow);
let prop_list = self.edge_attr.flatten();
let edge = EdgeDesc {
from: prev.clone(),
to: curr.clone(),
props: prop_list,
is_directed: has_arrow,
from_port: e.from.port.clone(),
to_port: dest.0.port.clone(),
};
self.edges.push(edge);
prev = curr;
}
self.edge_attr.pop();
}
fn init_node_with_name(&mut self, name: &str, overwrite: bool) {
let node_attr = self.node_attr.flatten();
if let Option::Some(prop_list) = self.nodes.get_mut(name) {
if !overwrite {
return;
}
for p in node_attr {
prop_list.insert(p.0, p.1);
}
} else {
self.node_order.push(name.to_string());
self.nodes.insert(name.to_string(), node_attr);
}
}
fn visit_node(&mut self, n: &ast::NodeStmt) {
self.node_attr.push();
for att in n.list.iter() {
self.node_attr.insert(&att.0, &att.1);
}
self.init_node_with_name(&n.id.name, true);
self.node_attr.pop();
}
fn visit_att(&mut self, att: &ast::AttrStmt) {
match att.target {
ast::AttrStmtTarget::Graph => {
for att in att.list.iter() {
self.global_attr.insert(&att.0, &att.1);
}
}
ast::AttrStmtTarget::Node => {
for att in att.list.iter() {
self.node_attr.insert(&att.0, &att.1);
}
}
ast::AttrStmtTarget::Edge => {
for att in att.list.iter() {
self.edge_attr.insert(&att.0, &att.1);
}
}
}
}
pub fn get(&self) -> VisualGraph {
let mut dir = Orientation::TopToBottom;
if let Option::Some(rd) = self.global_state.get("rankdir") {
if rd == "LR" {
dir = Orientation::LeftToRight;
}
}
let mut vg = VisualGraph::new(dir);
let mut node_map: HashMap<String, NodeHandle> = HashMap::new();
assert_eq!(self.nodes.len(), self.node_order.len());
for node_name in self.node_order.iter() {
let node_prop = self.nodes.get(node_name).unwrap();
let shape =
Self::get_shape_from_attributes(dir, node_prop, node_name);
let handle = vg.add_node(shape);
node_map.insert(node_name.to_string(), handle);
}
for edge_prop in &self.edges {
let shape = Self::get_arrow_from_attributes(
&edge_prop.props,
edge_prop.is_directed,
edge_prop.from_port.clone(),
edge_prop.to_port.clone(),
);
let from = node_map.get(&edge_prop.from).unwrap();
let to = node_map.get(&edge_prop.to).unwrap();
vg.add_edge(shape, *from, *to);
}
vg
}
fn get_arrow_from_attributes(
lst: &PropertyList,
has_arrow: bool,
from_port: Option<String>,
to_port: Option<String>,
) -> Arrow {
let mut line_width = 1;
let mut font_size: usize = 14;
let start = LineEndKind::None;
let end = if has_arrow {
LineEndKind::Arrow
} else {
LineEndKind::None
};
let mut label = String::from("");
let mut color = String::from("black");
let mut line_style = LineStyleKind::Normal;
if let Option::Some(val) = lst.get(&"label".to_string()) {
label = val.clone();
}
if let Option::Some(stl) = lst.get(&"style".to_string()) {
if stl == "dashed" {
line_style = LineStyleKind::Dashed;
}
}
if let Option::Some(x) = lst.get(&"color".to_string()) {
color = x.clone();
color = Self::normalize_color(color);
}
if let Option::Some(pw) = lst.get(&"penwidth".to_string()) {
if let Result::Ok(x) = pw.parse::<usize>() {
line_width = x;
} else {
#[cfg(feature = "log")]
log::info!("Can't parse integer \"{}\"", pw);
}
}
if let Option::Some(fx) = lst.get(&"fontsize".to_string()) {
if let Result::Ok(x) = fx.parse::<usize>() {
font_size = x;
} else {
#[cfg(feature = "log")]
log::info!("Can't parse integer \"{}\"", fx);
}
}
let color = Color::fast(&color);
let look = StyleAttr::new(color, line_width, None, 0, font_size);
Arrow::new(start, end, line_style, &label, &look, &from_port, &to_port)
}
fn normalize_color(color: String) -> String {
let mut color = color;
if let Option::Some(idx) = color.find(':') {
color = color[0..idx].to_string();
}
if color == "transparent" {
color = "white".to_string();
}
color
}
fn get_shape_from_attributes(
dir: Orientation,
lst: &PropertyList,
default_name: &str,
) -> Element {
let mut label = default_name.to_string();
let mut edge_color = String::from("black");
let mut fill_color = String::from("white");
let mut font_size: usize = 14;
let mut line_width: usize = 1;
let mut make_xy_same = false;
let mut rounded_corder_value = 0;
if let Option::Some(val) = lst.get(&"label".to_string()) {
label = val.clone();
}
let mut shape = ShapeKind::Circle(label.clone());
if let Option::Some(val) = lst.get(&"shape".to_string()) {
match &val[..] {
"box" => {
shape = ShapeKind::Box(label);
make_xy_same = false;
}
"doublecircle" => {
shape = ShapeKind::DoubleCircle(label);
make_xy_same = true;
}
"record" => {
shape = record_builder(&label);
}
"Mrecord" => {
rounded_corder_value = 15;
shape = record_builder(&label);
}
_ => shape = ShapeKind::Circle(label),
}
}
if let Option::Some(x) = lst.get(&"color".to_string()) {
edge_color = x.clone();
edge_color = Self::normalize_color(edge_color);
}
if let Option::Some(style) = lst.get(&"style".to_string()) {
if style == "filled" && !lst.contains_key("fillcolor") {
fill_color = "lightgray".to_string();
}
}
if let Option::Some(x) = lst.get(&"fillcolor".to_string()) {
fill_color = x.clone();
fill_color = Self::normalize_color(fill_color);
}
if let Option::Some(fx) = lst.get(&"fontsize".to_string()) {
if let Result::Ok(x) = fx.parse::<usize>() {
font_size = x;
} else {
#[cfg(feature = "log")]
log::info!("Can't parse integer \"{}\"", fx);
}
}
if let Option::Some(pw) = lst.get(&"width".to_string()) {
if let Result::Ok(x) = pw.parse::<usize>() {
line_width = x;
} else {
#[cfg(feature = "log")]
log::info!("Can't parse integer \"{}\"", pw);
}
}
let dir = dir.flip();
let sz = get_shape_size(dir, &shape, font_size, make_xy_same);
let look = StyleAttr::new(
Color::fast(&edge_color),
line_width,
Option::Some(Color::fast(&fill_color)),
rounded_corder_value,
font_size,
);
Element::create(shape, look, dir, sz)
}
}