use crate::layout::add_border_segments::add_border_segments; use crate::layout::order::order; use crate::layout::parent_dummy_chains::parent_dummy_chains; use crate::layout::rank::rank; use crate::layout::util::{ as_non_compound_graph, intersect_rect, normalize_ranks, remove_empty_ranks, transfer_node_edge_labels, Rect, }; use crate::{GraphConfig, GraphEdge, GraphEdgePoint, GraphNode}; use graphlib_rust::{Graph, GraphOption}; pub mod acyclic; pub mod add_border_segments; pub mod coordinate_system; pub mod nesting_graph; pub mod normalize; pub mod order; pub mod parent_dummy_chains; pub mod position; pub mod rank; pub mod util; const DEFAULT_RANK_SEP: f32 = 50.0; pub fn layout(g: &mut Graph) { let mut layout_graph = build_layout_graph(g); run_layout(&mut layout_graph); update_input_graph(g, &layout_graph); } pub fn update_input_graph( input_graph: &mut Graph, layout_graph: &Graph, ) { for v in input_graph.nodes() { let input_label_ = input_graph.node_mut(&v); let layout_label = layout_graph.node(&v).unwrap(); if let Some(input_label) = input_label_ { input_label.x = layout_label.x; input_label.y = layout_label.y; if layout_graph.children(&v).len() > 0 { input_label.width = layout_label.width; input_label.height = layout_label.height; } } } for e in input_graph.edges() { let input_label = input_graph.edge_mut_with_obj(&e).unwrap(); let layout_label = layout_graph.edge_with_obj(&e).unwrap(); input_label.points = layout_label.points.clone(); input_label.x = layout_label.x; input_label.y = layout_label.y; } input_graph.graph_mut().width = layout_graph.graph().width; input_graph.graph_mut().height = layout_graph.graph().height; } pub fn set_graph_label_default_values(graph_label: &mut GraphConfig) { if graph_label.ranksep.is_none() { graph_label.ranksep = Some(50.0); } if graph_label.edgesep.is_none() { graph_label.edgesep = Some(20.0); } if graph_label.nodesep.is_none() { graph_label.nodesep = Some(50.0); } if graph_label.rankdir.is_none() { graph_label.rankdir = Some("tb".to_string()); } if graph_label.marginx.is_none() { graph_label.marginx = Some(0.0); } if graph_label.marginy.is_none() { graph_label.marginy = Some(0.0); } } pub fn set_edge_label_default_values(edge_label: &mut GraphEdge) { if edge_label.minlen.is_none() { edge_label.minlen = Some(1.0); } if edge_label.weight.is_none() { edge_label.weight = Some(1.0); } if edge_label.width.is_none() { edge_label.width = Some(0.0); } if edge_label.height.is_none() { edge_label.height = Some(0.0); } if edge_label.labeloffset.is_none() { edge_label.labeloffset = Some(10.0); } if edge_label.labelpos.is_none() { edge_label.labelpos = Some("r".to_string()); } } /* * Constructs a new graph from the input graph, which can be used for layout. * This process copies only whitelisted attributes from the input graph to the * layout graph. Thus this function serves as a good place to determine what * attributes can influence layout. */ pub fn build_layout_graph( input_graph: &Graph, ) -> Graph { let mut g: Graph = Graph::new(Some(GraphOption { directed: Some(true), multigraph: Some(true), compound: Some(true), })); let mut graph_label: GraphConfig = input_graph.graph().clone(); set_graph_label_default_values(&mut graph_label); g.set_graph(graph_label); for node_id in input_graph.nodes().iter() { let _node = input_graph.node(node_id); if _node.is_none() { continue; } g.set_node(node_id.clone(), _node.cloned()); let _ = g.set_parent(node_id, input_graph.parent(node_id).cloned()); } for edge_obj in input_graph.edges() { let _edge = input_graph.edge_with_obj(&edge_obj); if _edge.is_none() { continue; } let mut edge_label = _edge.cloned().unwrap(); set_edge_label_default_values(&mut edge_label); let _ = g.set_edge_with_obj(&edge_obj, Some(edge_label)); } return g; } /* * This idea comes from the Gansner paper: to account for edge labels in our * layout we split each rank in half by doubling minlen and halving ranksep. * Then we can place labels at these mid-points between nodes. * * We also add some minimal padding to the width to push the label for the edge * away from the edge itself a bit. */ pub fn make_space_for_edge_labels(graph: &mut Graph) { let graph_config = graph.graph_mut(); graph_config.ranksep = Some(graph_config.ranksep.unwrap_or(DEFAULT_RANK_SEP) / 2.0); // moving in nested block due to borrow checker { let graph_config = graph.graph().clone(); let edge_objs = graph.edges(); for edge_obj in edge_objs.into_iter() { let _edge = graph.edge_mut_with_obj(&edge_obj); if _edge.is_none() { continue; } let edge = _edge.unwrap(); let minlen = edge.minlen.unwrap_or(1.0); let labelpos = edge.labelpos.clone().unwrap_or("".to_string()); let labeloffset = edge.labeloffset.unwrap_or(10.0); let rankdir = graph_config.rankdir.clone().unwrap_or("".to_string()); edge.minlen = Some(minlen * 2.0); if labelpos != "c" { if rankdir == "tb" || rankdir == "bt" { edge.width = Some(edge.width.unwrap_or(0.0) + labeloffset); } else { edge.height = Some(edge.height.unwrap_or(0.0) + labeloffset); } } } } } /* * Creates temporary dummy nodes that capture the rank in which each edge's * label is going to, if it has one of non-zero width and height. We do this * so that we can safely remove empty ranks while preserving balance for the * label's position. */ pub fn inject_edge_label_proxies(g: &mut Graph) { let edges = g.edges(); for e in edges.into_iter() { let edge_ = g.edge_with_obj(&e); if let Some(edge) = edge_ { if edge.width.clone().unwrap_or(0.0) > 0.0 && edge.height.clone().unwrap_or(0.0) > 0.0 { let v = g.node(&e.v); let w = g.node(&e.v); let v_rank = v.cloned().unwrap_or(GraphNode::default()).rank.unwrap_or(0); let w_rank = w.cloned().unwrap_or(GraphNode::default()).rank.unwrap_or(0); let mut label = GraphNode::default(); label.rank = Some((w_rank - v_rank) / 2 + v_rank); util::add_dummy_node(g, "edge-proxy".to_string(), label, "_ep".to_string()); } } } } pub fn assign_rank_min_max(g: &mut Graph) { let mut max_rank = 0; let vs = g.nodes(); for v in vs.iter() { let node_ = g.node(v); if node_.is_none() { continue; } let node = node_.unwrap(); if node.border_top.is_some() { let border_top = node.border_top.clone().unwrap(); let border_bottom = node.border_bottom.clone().unwrap(); let _min_rank = g.node(&border_top).cloned().unwrap().rank.unwrap_or(0); let _max_rank = g.node(&border_bottom).cloned().unwrap().rank.unwrap_or(0); let _node = g.node_mut(v).unwrap(); _node.min_rank = Some(_min_rank); _node.max_rank = Some(_max_rank.clone()); max_rank = std::cmp::max(max_rank, _max_rank); } } } enum GraphElement<'a> { Node(&'a GraphNode), Edge(&'a GraphEdge), } impl<'a> GraphElement<'a> { fn x(&self) -> f32 { match self { GraphElement::Node(node) => node.x, GraphElement::Edge(edge) => edge.x, } } fn y(&self) -> f32 { match self { GraphElement::Node(node) => node.y, GraphElement::Edge(edge) => edge.y, } } fn width(&self) -> f32 { match self { GraphElement::Node(node) => node.width, GraphElement::Edge(edge) => edge.width.unwrap_or(0.0), } } fn height(&self) -> f32 { match self { GraphElement::Node(node) => node.height, GraphElement::Edge(edge) => edge.height.unwrap_or(0.0), } } } pub fn translate_graph(g: &mut Graph) { let mut min_x = f64::INFINITY as f32; let mut max_x: f32 = 0.0; let mut min_y = f64::INFINITY as f32; let mut max_y: f32 = 0.0; let mut graph_label = g.graph().clone(); let margin_x = graph_label.marginx.unwrap_or(0.0); let margin_y = graph_label.marginy.unwrap_or(0.0); fn get_extremes( attrs: &GraphElement, min_x: &mut f32, max_x: &mut f32, min_y: &mut f32, max_y: &mut f32, ) { let x = attrs.x(); let y = attrs.y(); let w = attrs.width(); let h = attrs.height(); *min_x = min_x.min(x - w / 2.0); *max_x = max_x.max(x + w / 2.0); *min_y = min_y.min(y - h / 2.0); *max_y = max_y.max(y + h / 2.0); } for v in g.nodes() { get_extremes( &GraphElement::Node(g.node(&v).unwrap()), &mut min_x, &mut max_x, &mut min_y, &mut max_y, ); } for e in g.edges() { let edge = g.edge_with_obj(&e).unwrap(); if edge.x != 0.0 { get_extremes( &GraphElement::Edge(edge), &mut min_x, &mut max_x, &mut min_y, &mut max_y, ); } } min_x -= margin_x; min_y -= margin_y; for v in g.nodes() { let node = g.node_mut(&v).unwrap(); node.x -= min_x; node.y -= min_y; } for e in g.edges() { let edge = g.edge_mut_with_obj(&e).unwrap(); if edge.points.is_some() { for p in edge.points.as_mut().unwrap() { p.x -= min_x; p.y -= min_y; } } if edge.x != 0.0 { edge.x = min_x; } if edge.y != 0.0 { edge.y = min_y; } } graph_label.width = max_x - min_x + margin_x; graph_label.height = max_y - min_y + margin_y; g.set_graph(graph_label); } pub fn assign_node_intersects(g: &mut Graph) { for e in g.edges() { let mut edge = g.edge_mut_with_obj(&e).cloned().unwrap(); let node_v = g.node(&e.v).cloned().unwrap(); let node_w = g.node(&e.w).cloned().unwrap(); let (p1, p2) = if edge.points.is_none() { edge.points = Some(vec![]); ( GraphEdgePoint { x: node_w.x, y: node_w.y, }, GraphEdgePoint { x: node_v.x, y: node_v.y, }, ) } else { let points = edge.points.clone().unwrap(); let r1 = GraphEdgePoint { x: points[0].x, y: points[0].y, }; let r2 = GraphEdgePoint { x: points[points.len() - 1].x, y: points[points.len() - 1].y, }; (r1, r2) }; let points = edge.points.as_mut().unwrap(); points.insert( 0, intersect_rect( &Rect { x: node_v.x, y: node_v.y, width: node_v.width, height: node_v.height, }, &p1, ), ); points.push(intersect_rect( &Rect { x: node_w.x, y: node_w.y, width: node_w.width, height: node_w.height, }, &p2, )); let _ = g.set_edge_with_obj(&e, Some(edge)); } } pub fn remove_edge_label_proxies(g: &mut Graph) { let vs = g.nodes(); for v in vs.iter() { let node = g.node(v).unwrap(); if node.dummy.is_some() && node.dummy.clone().unwrap() == "edge-proxy" { let rank = node.rank.unwrap_or(0); if let Some(edge_obj) = node.e.clone() { if let Some(graph_edge) = g.edge_mut_with_obj(&edge_obj) { graph_edge.label_rank = Some(rank); } } g.remove_node(v); } } } pub fn fixup_edge_label_coords(g: &mut Graph) { g.edges().iter().for_each(|e| { let edge = g.edge_mut_with_obj(&e.to_owned()).unwrap(); if edge.x != 0.0 { let labelpos = edge.labelpos.clone().unwrap_or("".to_string()); let labeloffset = edge.labeloffset.clone().unwrap_or(0.0); if labelpos == "l" || labelpos == "r" { edge.width = Some(edge.width.unwrap_or(0.0) - labeloffset); } if labelpos == "l" { edge.x -= edge.width.clone().unwrap_or(0.0) / 2.0 + labeloffset; } else if labelpos == "r" { edge.x += edge.width.clone().unwrap_or(0.0) / 2.0 + labeloffset; } } }); } pub fn reverse_points_for_reversed_edges(g: &mut Graph) { for e in g.edges() { let edge = g.edge_mut_with_obj(&e.to_owned()).unwrap(); if edge.reversed.clone().unwrap_or(false) { if edge.points.is_some() { let points = edge.points.as_mut().unwrap(); points.reverse(); } } } } pub fn remove_border_nodes(g: &mut Graph) { for v in g.nodes() { if g.children(&v).len() > 0 { let mut node = g.node(&v).cloned().unwrap(); let t = g.node(node.border_top.as_ref().unwrap()).cloned().unwrap(); let b = g .node(node.border_bottom.as_ref().unwrap()) .cloned() .unwrap(); // TODO: improve this after wasm implementation let border_left = node.border_left.clone().unwrap(); let mut l_keys: Vec = border_left.keys().cloned().collect(); l_keys.sort(); let border_right = node.border_right.clone().unwrap(); let mut r_keys: Vec = border_right.keys().cloned().collect(); r_keys.sort(); let l = g .node(border_left.get(&l_keys[l_keys.len() - 1]).unwrap()) .cloned() .unwrap(); let r = g .node(border_right.get(&r_keys[r_keys.len() - 1]).unwrap()) .cloned() .unwrap(); node.width = (r.x - l.x).abs(); node.height = (b.y - t.y).abs(); node.x = l.x + node.width / 2.0; node.y = l.y + node.height / 2.0; g.set_node(v.clone(), Some(node)); } } g.nodes().iter().for_each(|v| { let node = g.node(v).unwrap(); if node.dummy.is_some() && node.dummy.clone().unwrap() == "border" { g.remove_node(v); } }); } pub fn remove_self_edges(graph: &mut Graph) { let edge_objs = graph.edges(); for edge_obj in edge_objs.into_iter() { if edge_obj.v == edge_obj.w { let edge_label = graph.edge_with_obj(&edge_obj).cloned().unwrap(); let node = graph.node_mut(&edge_obj.v).unwrap(); node.self_edges.push((edge_obj.clone(), edge_label)); graph.remove_edge_with_obj(&edge_obj); } } } pub fn insert_self_edges(graph: &mut Graph) { let layers = util::build_layer_matrix(graph); layers.iter().for_each(|layer| { let mut order_shift = 0; layer.iter().enumerate().for_each(|(i, v)| { let node = graph.node_mut(v).unwrap(); node.order = Some(i + order_shift); let rank = node.rank.clone(); let self_edges = node.self_edges.clone(); self_edges.into_iter().for_each(|(edge, graph_edge)| { let mut _graph_node = GraphNode::default(); _graph_node.width = graph_edge.width.clone().unwrap_or(0.0); _graph_node.height = graph_edge.height.clone().unwrap_or(0.0); _graph_node.rank = rank.clone(); order_shift += 1; _graph_node.order = Some(i + order_shift); _graph_node.e = Some(edge.clone()); _graph_node.label = Some(graph_edge.clone()); util::add_dummy_node( graph, "selfedge".to_string(), _graph_node, "_se".to_string(), ); }); }); }) } pub fn position_self_edges(g: &mut Graph) { for v in g.nodes() { let node = g.node(&v).cloned().unwrap(); if node.dummy.unwrap_or("".to_string()) == "selfedge" { let self_node = g.node(&node.e.as_ref().unwrap().v).unwrap(); let x = self_node.x + self_node.width / 2.0; let y = self_node.y; let dx = node.x - x; let dy = self_node.height / 2.0; let mut graph_edge = node.label.clone().unwrap(); graph_edge.points = Some(vec![ GraphEdgePoint { x: x + 2.0 * dx / 3.0, y: y - dy, }, GraphEdgePoint { x: x + 2.0 * dx / 3.0, y: y - dy, }, GraphEdgePoint { x: x + 5.0 * dx / 6.0, y: y - dy, }, GraphEdgePoint { x: x + dx, y }, GraphEdgePoint { x: x + 5.0 * dx / 6.0, y: y + dy, }, GraphEdgePoint { x: x + 2.0 * dx / 3.0, y: y + dy, }, ]); graph_edge.x = node.x; graph_edge.y = node.y; let _ = g.set_edge_with_obj(&node.e.unwrap(), Some(graph_edge)); g.remove_node(&v); } } } pub fn run_layout(graph: &mut Graph) { make_space_for_edge_labels(graph); remove_self_edges(graph); acyclic::run(graph); nesting_graph::run(graph); // calculating ranks let mut nc_graph: Graph = as_non_compound_graph(graph); rank(&mut nc_graph); transfer_node_edge_labels(&nc_graph, graph); // done with calculating ranks inject_edge_label_proxies(graph); remove_empty_ranks(graph); nesting_graph::cleanup(graph); normalize_ranks(graph); assign_rank_min_max(graph); remove_edge_label_proxies(graph); normalize::run(graph); parent_dummy_chains(graph); add_border_segments(graph); order(graph); insert_self_edges(graph); coordinate_system::adjust(graph); position::position(graph); position_self_edges(graph); remove_border_nodes(graph); normalize::undo(graph); fixup_edge_label_coords(graph); coordinate_system::undo(graph); translate_graph(graph); assign_node_intersects(graph); reverse_points_for_reversed_edges(graph); acyclic::undo(graph); }