diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6abf45a12f6cfca9f0bb8c46e817b3283a31dd34 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs @@ -0,0 +1,594 @@ +#![feature(let_chains)] + +mod acyclic; +mod add_border_segment; +mod alg; +mod coordinate_system; +pub mod graph; +mod macros; +mod nesting_graph; +mod normalize; +mod order; +mod parent_dummy_chains; +mod position; +mod rank; +mod utils; + +use add_border_segment::{BorderTypeName, add_border_segments}; +use ahash::HashMap; +use graph::{Edge, Graph}; +use order::order; +use parent_dummy_chains::parent_dummy_chains; +use rank::rank; +use serde::{Deserialize, Serialize}; +use smartstring::alias::String; +use utils::{ + Rect, add_dummy_node, as_non_compound_graph, build_layer_matrix, intersect_rect, + normalize_ranks, remove_empty_ranks, transfer_node_edge_labels, +}; + +/// ## Note +/// This is the `Rust implementation` of `dagre.js`. However, due to `Rust`'s `ownership` mechanism, +/// almost all parameters are passed by pointer in `JavaScript`, while in `Rust`, it is sometimes necessary +/// to pass ownership types, leading to a large number of `memory allocations`. Therefore, considering +/// the parameter types and return value types of a function in Rust is a very troublesome but extremely +/// `performance-critical` task. +/// +/// This implementation has tried multiple alternatives, including `[&'a str]`, +/// `[Cow<'a, str>]`, and `[Rc]`. After testing and consideration, the most suitable approach might be +/// to use numbers directly as keys in `HashMaps`, thus maintaining only one HashMap from `number=>name`. +/// +/// Additionally, many of the current HashMaps use ownership types for Node and Edge rather than reference +/// types, which also needs improvement. +#[allow(dead_code)] +#[derive(Debug, Clone, Default, Serialize)] +pub struct GraphNode { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, + pub label: Option, + pub dummy: Option<&'static str>, + /// Maybe should be `Option` + pub rank: i32, + pub min_rank: i32, + pub max_rank: i32, + pub order: usize, + pub border_top: Option, + pub border_bottom: Option, + pub border_left: Option>, + pub border_right: Option>, + pub border_left_: Option, + pub border_right_: Option, + pub low: usize, + pub lim: usize, + pub parent: Option, + pub e: Option, + pub edge_label: Option, + pub edge_obj: Option, + pub label_pos: &'static str, + pub border_type: Option, + pub self_edges: Vec<(Edge, GraphEdge)>, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct GraphEdgePoint { + pub x: f32, + pub y: f32, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GraphEdge { + pub forward_name: Option, + pub reversed: bool, + pub min_len: f32, + pub weight: f32, + pub width: f32, + pub height: f32, + pub label_rank: i32, + pub label_offset: f32, + pub label_pos: &'static str, + pub nesting_edge: bool, + pub cut_value: f32, + pub points: Vec, + pub x: f32, + pub y: f32, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Serialize)] +pub struct GraphConfig { + pub width: f32, + pub height: f32, + pub node_sep: f32, + pub edge_sep: f32, + pub rank_sep: f32, + pub rank_dir: &'static str, + pub acyclic: &'static str, + pub ranker: &'static str, + pub nesting_root: Option, + pub root: Option, + pub node_rank_factor: f32, + pub dummy_chains: Option>, +} + +impl Default for GraphConfig { + fn default() -> Self { + Self { + width: 0.0, + height: 0.0, + node_sep: 50.0, + edge_sep: 20.0, + rank_sep: 50.0, + rank_dir: "tb", + acyclic: "dfs", + ranker: "longest-path", + nesting_root: None, + root: None, + node_rank_factor: 0.0, + dummy_chains: None, + } + } +} + +impl Default for GraphEdge { + fn default() -> Self { + Self { + forward_name: None, + reversed: false, + min_len: 0.0, + weight: 0.0, + width: 0.0, + height: 0.0, + label_rank: 0, + label_offset: 10.0, + label_pos: "r", + nesting_edge: false, + cut_value: 0.0, + points: vec![], + x: 0.0, + y: 0.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; + } + + let g = input_graph.config_mut(); + g.width = layout_graph.config().width; + g.height = layout_graph.config().height; +} + +pub fn build_layout_graph(input_graph: &Graph) -> Graph { + let mut g: Graph = Graph::new(true, true, true); + + g.set_config(input_graph.config().clone()); + + for node_id in &input_graph.nodes() { + if let Some(node) = input_graph.node(node_id) { + g.set_node(node_id.clone(), Some(node.clone())); + let _ = g.set_parent(node_id, input_graph.parent(node_id).cloned()); + } + } + + for edge_obj in input_graph.edges() { + if let Some(edge) = input_graph.edge_with_obj(&edge_obj) { + let _ = g.set_edge_with_obj(&edge_obj, Some(edge.clone())); + } + } + + g +} + +pub fn make_space_for_edge_labels(graph: &mut Graph) { + let graph_config = graph.config_mut(); + graph_config.rank_sep = graph_config.rank_sep / 2.0; + + { + let graph_config = graph.config().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 min_len = edge.min_len; + let label_pos = edge.label_pos; + let label_offset = edge.label_offset; + let rank_dir = graph_config.rank_dir; + + edge.min_len = min_len * 2.0; + if label_pos != "c" { + if rank_dir == "tb" || rank_dir == "bt" { + edge.width = edge.width + label_offset; + } else { + edge.height = edge.height + label_offset; + } + } + } + } +} + +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 > 0.0 && edge.height > 0.0 { + let v_rank = g.node(&e.v).map_or(0, |n| n.rank); + let w_rank = g.node(&e.v).map_or(0, |n| n.rank); + let mut label = GraphNode::default(); + label.rank = (w_rank - v_rank) / 2 + v_rank; + add_dummy_node(g, label, "edge-proxy", "_ep"); + } + } + } +} + +pub fn assign_rank_min_max(g: &mut Graph) { + let mut max_rank = 0; + for v in &g.nodes() { + if let Some(node) = g.node(v) { + if let (Some(border_top), Some(border_bottom)) = (&node.border_top, &node.border_bottom) + { + let _min_rank = g.node(border_top).map_or(0, |n| n.rank); + let _max_rank = g.node(border_bottom).map_or(0, |n| n.rank); + + let _node = g.node_mut(v).map(|n| { + n.min_rank = _min_rank; + n.max_rank = _max_rank; + }); + max_rank = max_rank.max(_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, + } + } + + fn height(&self) -> f32 { + match self { + GraphElement::Node(node) => node.height, + GraphElement::Edge(edge) => edge.height, + } + } +} + +pub fn translate_graph(g: &mut Graph) { + let mut min_x = f32::INFINITY; + let mut max_x: f32 = 0.0; + let mut min_y = f32::INFINITY; + let mut max_y: f32 = 0.0; + + fn update_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() { + g.node(&v).map(|node| { + update_extremes( + &GraphElement::Node(node), + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + ); + }); + } + + for e in g.edges() { + g.edge_with_obj(&e).map(|edge| { + (edge.x != 0.0).then(|| { + update_extremes( + &GraphElement::Edge(edge), + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + ) + }) + }); + } + + for v in g.nodes() { + if let Some(node) = g.node_mut(&v) { + node.x -= min_x; + node.y -= min_y; + } + } + + for e in g.edges() { + if let Some(edge) = g.edge_mut_with_obj(&e) { + for p in &mut edge.points { + 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; + } + } + } + + let mut graph_label = g.config().clone(); + graph_label.width = max_x - min_x; + graph_label.height = max_y - min_y; + + g.set_config(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_empty() { + (GraphEdgePoint { x: node_w.x, y: node_w.y }, GraphEdgePoint { + x: node_v.x, + y: node_v.y, + }) + } else { + let points = edge.points.clone(); + (GraphEdgePoint { x: points[0].x, y: points[0].y }, GraphEdgePoint { + x: points[points.len() - 1].x, + y: points[points.len() - 1].y, + }) + }; + + edge.points.insert( + 0, + intersect_rect( + &Rect { x: node_v.x, y: node_v.y, width: node_v.width, height: node_v.height }, + &p1, + ), + ); + + edge.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) { + for v in &g.nodes() { + if let Some(node) = g.node(v) + && node.dummy == Some("edge-proxy") + { + let rank = node.rank; + g.edge_mut_with_obj(&node.e.clone().unwrap()).map(|e| e.label_rank = rank); + g.remove_node(v); + } + } +} + +pub fn fixup_edge_label_coords(g: &mut Graph) { + let edges = g.edges(); + for e in &edges { + let edge = g.edge_mut_with_obj(e).unwrap(); + if edge.x != 0.0 { + let label_pos = edge.label_pos; + let label_offset = edge.label_offset; + if label_pos == "l" || label_pos == "r" { + edge.width -= label_offset; + } + + if label_pos == "l" { + edge.x -= edge.width.clone() / 2.0 + label_offset; + } else if label_pos == "r" { + edge.x += edge.width.clone() / 2.0 + label_offset; + } + } + } +} + +pub fn reverse_points_for_reversed_edges(g: &mut Graph) { + for e in &g.edges() { + g.edge_mut_with_obj(e).map(|e| e.reversed.then(|| e.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(); + 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, Some(node)); + } + } + + g.nodes().iter().for_each(|v| { + if let Some(node) = g.node(v) + && matches!(node.dummy, Some(dummy) if dummy == "border") + { + g.remove_node(v); + } + }); +} + +pub fn remove_self_edges(graph: &mut Graph) { + let edge_objs = graph.edges(); + for edge_obj in edge_objs { + 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 = build_layer_matrix(graph); + + for layer in &layers { + let mut order_shift = 0; + for (i, v) in layer.iter().enumerate() { + let node = graph.node_mut(v).unwrap(); + node.order = i + order_shift; + let rank = node.rank; + + let self_edges = node.self_edges.clone(); + for (edge, graph_edge) in self_edges { + let dummy_node = GraphNode { + width: graph_edge.width, + height: graph_edge.height, + rank, + order: i + order_shift + 1, + e: Some(edge.clone()), + label: Some(graph_edge.clone()), + ..GraphNode::default() + }; + add_dummy_node(graph, dummy_node, "selfedge", "_se"); + order_shift += 1; + } + } + } +} + +pub fn position_self_edges(g: &mut Graph) { + for v in g.nodes() { + let node = g.node(&v).cloned().unwrap(); + if node.dummy == Some("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 = 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); + let mut nc_graph: Graph = as_non_compound_graph(graph); + rank(&mut nc_graph); + transfer_node_edge_labels(&nc_graph, graph); + 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); +}