Publish harness and TUI open-source

initial sync from the monorepo
This commit is contained in:
grokkybara[bot] 2026-07-16 06:46:02 +01:00
commit c68e39f604
2734 changed files with 1437016 additions and 0 deletions

View file

@ -0,0 +1,87 @@
use crate::layout::util::unique_id;
use crate::layout::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::{Edge, Graph};
use ordered_hashmap::OrderedHashMap;
pub fn run(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let mut fas: Option<Vec<Edge>> = None;
let graph_config = graph.graph();
if graph_config.acyclicer.is_some()
&& graph_config.acyclicer.clone().unwrap() == "greedy".to_string()
{
// TODO: need to implement this algorithm
println!("greedy_fas");
// greedyFAS
} else {
fas = Some(dfs_fas(graph));
// println!("dfs_fas");
}
let _fas = fas.unwrap_or(vec![]);
for edge in _fas {
let _edge_label = graph.edge_with_obj(&edge);
if _edge_label.is_none() {
continue;
}
let mut edge_label = _edge_label.cloned().unwrap();
graph.remove_edge_with_obj(&edge);
edge_label.forward_name = edge.name.clone();
edge_label.reversed = Some(true);
let _ = graph.set_edge(
&edge.w,
&edge.v,
Some(edge_label),
Some(format!("rev{}", unique_id())),
);
}
}
fn dfs_fas(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) -> Vec<Edge> {
let mut fas: Vec<Edge> = vec![];
let mut stack: OrderedHashMap<String, bool> = OrderedHashMap::new();
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
fn dfs(
node_id: String,
stack: &mut OrderedHashMap<String, bool>,
visited: &mut OrderedHashMap<String, bool>,
graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
fas: &mut Vec<Edge>,
) {
if visited.contains_key(&node_id) {
return ();
}
visited.insert(node_id.clone(), true);
stack.insert(node_id.clone(), true);
let out_edges = graph.out_edges(&node_id, None).unwrap_or(vec![]);
for edge in out_edges.into_iter() {
if stack.contains_key(&edge.w) {
fas.push(edge.clone());
} else {
dfs(edge.w.clone(), stack, visited, graph, fas);
}
}
stack.remove(&node_id);
}
for node_id in graph.nodes() {
dfs(node_id, &mut stack, &mut visited, graph, &mut fas);
}
return fas;
}
pub fn undo(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
for e in g.edges() {
let Some(edge) = g.edge_mut_with_obj(&e) else {
continue;
};
if edge.reversed.clone().unwrap_or(false) {
let forward_name = edge.forward_name.clone();
let mut label = edge.clone();
label.reversed = None;
label.forward_name = None;
let _ = g.set_edge(&e.w, &e.v, Some(label), forward_name);
}
}
}

View file

@ -0,0 +1,73 @@
use crate::layout::util::add_dummy_node;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::graph::GRAPH_NODE;
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum BorderTypeName {
BorderLeft,
BorderRight,
}
pub fn add_border_segments(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
fn dfs(v: &String, g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let children = g.children(v);
if children.len() > 0 {
for cv in children.iter() {
dfs(cv, g);
}
}
let node = g.node_mut(v).unwrap();
if node.min_rank.is_some() {
node.border_left = Some(OrderedHashMap::new());
node.border_right = Some(OrderedHashMap::new());
let mut rank = node.min_rank.clone().unwrap_or(0);
let max_rank = node.max_rank.clone().unwrap_or(0) + 1;
while rank < max_rank {
add_border_node(g, BorderTypeName::BorderLeft, "_bl", v, &rank);
add_border_node(g, BorderTypeName::BorderRight, "_br", v, &rank);
rank += 1;
}
}
}
let children = g.children(&GRAPH_NODE.to_string());
for v in children.iter() {
dfs(v, g);
}
}
fn add_border_node(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
prop: BorderTypeName,
prefix: &str,
sg: &String,
rank: &i32,
) {
let mut label = GraphNode::default();
label.rank = Some(rank.clone());
label.border_type = Some(prop.clone());
let curr = add_dummy_node(g, "border".to_string(), label, prefix.to_string());
let sg_node = g.node_mut(sg).unwrap();
let mut border = sg_node.border_left.as_mut().unwrap();
match prop {
BorderTypeName::BorderRight => {
border = sg_node.border_right.as_mut().unwrap();
}
_ => (),
}
border.insert(rank.clone(), curr.clone());
let prev = border.get(&(rank - 1));
if prev.is_some() {
let prev_v = prev.cloned().unwrap();
let mut graph_edge = GraphEdge::default();
graph_edge.weight = Some(1.0);
let _ = g.set_edge(&prev_v, &curr, Some(graph_edge), None);
}
let _ = g.set_parent(&curr, Some(sg.clone()));
}

View file

@ -0,0 +1,89 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
pub fn adjust(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let rank_dir = g.graph().rankdir.clone().unwrap();
if &rank_dir == "lr" || &rank_dir == "rl" {
swap_width_height(g);
}
}
pub fn undo(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let rank_dir = g.graph().rankdir.clone().unwrap();
if &rank_dir == "bt" || &rank_dir == "rl" {
reverse_y(g);
}
if &rank_dir == "lr" || &rank_dir == "rl" {
swap_x_y(g);
swap_width_height(g);
}
}
fn swap_width_height(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let nodes = g.nodes();
// =swapWidthHeightOne
nodes.iter().for_each(|v| {
let node = g.node_mut(v).unwrap();
let w = node.width.clone();
node.width = node.height;
node.height = w;
});
let edges = g.edges();
// =swapWidthHeightOne
edges.iter().for_each(|e| {
let edge_label = g.edge_mut_with_obj(&e).unwrap();
let w = edge_label.width.clone();
edge_label.width = edge_label.height;
edge_label.height = w;
});
}
fn reverse_y(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let nodes = g.nodes();
nodes.iter().for_each(|v| {
// =reverseYOne
let node = g.node_mut(v).unwrap();
node.y = -node.y;
});
let edges = g.edges();
edges.iter().for_each(|e| {
// =reverseYOne
let edge_label = g.edge_mut_with_obj(&e).unwrap();
let mut points = edge_label.points.clone().unwrap_or(vec![]);
points.iter_mut().for_each(|point| {
point.y = -point.y;
});
edge_label.points = Some(points);
edge_label.y = -edge_label.y;
});
}
fn swap_x_y(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let nodes = g.nodes();
nodes.iter().for_each(|v| {
// =swapXYOne
let node = g.node_mut(v).unwrap();
let x = node.x.clone();
node.x = node.y;
node.y = x;
});
let edges = g.edges();
edges.iter().for_each(|e| {
// =swapXYOne
let edge_label = g.edge_mut_with_obj(&e).unwrap();
let mut points = edge_label.points.clone().unwrap_or(vec![]);
points.iter_mut().for_each(|point| {
let x = point.x.clone();
point.x = point.y;
point.y = x;
});
edge_label.points = Some(points);
let x = edge_label.x.clone();
edge_label.x = edge_label.y;
edge_label.y = x;
});
}

645
third_party/dagre_rust/src/layout/mod.rs vendored Normal file
View file

@ -0,0 +1,645 @@
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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>,
layout_graph: &Graph<GraphConfig, GraphNode, GraphEdge>,
) {
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<GraphConfig, GraphNode, GraphEdge>,
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
let mut g: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
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.w);
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);
label.width = edge.width.unwrap_or(0.0);
label.height = edge.height.unwrap_or(0.0);
label.labelpos = edge.labelpos.clone();
label.e = Some(e.clone());
util::add_dummy_node(g, "edge-proxy".to_string(), label, "_ep".to_string());
}
}
}
}
pub fn assign_rank_min_max(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
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.width.unwrap_or(0.0) > 0.0 && edge.height.unwrap_or(0.0) > 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();
let has_label = edge.width.unwrap_or(0.0) > 0.0 && edge.height.unwrap_or(0.0) > 0.0;
if edge.points.is_some() {
for p in edge.points.as_mut().unwrap() {
p.x -= min_x;
p.y -= min_y;
}
}
if has_label {
edge.x -= min_x;
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<GraphConfig, GraphNode, GraphEdge>) {
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.as_ref().map_or(true, |p| p.is_empty()) {
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<GraphConfig, GraphNode, GraphEdge>) {
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);
let graph_edge_ = g.edge_mut_with_obj(&node.e.clone().unwrap());
if let Some(graph_edge) = graph_edge_ {
graph_edge.label_rank = Some(rank);
}
g.remove_node(v);
}
}
}
pub fn fixup_edge_label_coords(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
for v in g.nodes() {
if g.children(&v).len() > 0 {
let mut node = g.node(&v).cloned().unwrap();
let Some(border_top) = node.border_top.as_ref() else {
continue;
};
let Some(border_bottom) = node.border_bottom.as_ref() else {
continue;
};
let Some(t) = g.node(border_top).cloned() else {
continue;
};
let Some(b) = g.node(border_bottom).cloned() else {
continue;
};
let Some(border_left) = node.border_left.clone() else {
continue;
};
let Some(border_right) = node.border_right.clone() else {
continue;
};
let mut l_keys: Vec<i32> = border_left.keys().cloned().collect();
if l_keys.is_empty() {
continue;
}
l_keys.sort();
let mut r_keys: Vec<i32> = border_right.keys().cloned().collect();
if r_keys.is_empty() {
continue;
}
r_keys.sort();
let Some(l_node_id) = border_left.get(&l_keys[l_keys.len() - 1]) else {
continue;
};
let Some(r_node_id) = border_right.get(&r_keys[r_keys.len() - 1]) else {
continue;
};
let Some(l) = g.node(l_node_id).cloned() else {
continue;
};
let Some(r) = g.node(r_node_id).cloned() else {
continue;
};
node.width = (r.x - l.x).abs();
node.height = (b.y - t.y).abs();
node.x = l.x + node.width / 2.0;
node.y = t.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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
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<GraphConfig, GraphNode, GraphEdge>) {
make_space_for_edge_labels(graph);
remove_self_edges(graph);
acyclic::run(graph);
nesting_graph::run(graph);
// calculating ranks
let mut nc_graph: Graph<GraphConfig, GraphNode, GraphEdge> = 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);
}

View file

@ -0,0 +1,213 @@
use crate::layout::{util, GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::graph::GRAPH_NODE;
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
/*
* A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
* adds appropriate edges to ensure that all cluster nodes are placed between
* these boundries, and ensures that the graph is connected.
*
* In addition we ensure, through the use of the minlen property, that nodes
* and subgraph border nodes to not end up on the same rank.
*
* Preconditions:
*
* 1. Input graph is a DAG
* 2. Nodes in the input graph has a minlen attribute
*
* Postconditions:
*
* 1. Input graph is connected.
* 2. Dummy nodes are added for the tops and bottoms of subgraphs.
* 3. The minlen attribute for nodes is adjusted to ensure nodes do not
* get placed on the same rank as subgraph border nodes.
*
* The nesting graph idea comes from Sander, "Layout of Compound Directed
* Graphs."
*/
pub fn run(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let graph_node = GraphNode::default();
let root = util::add_dummy_node(graph, "root".to_string(), graph_node, "_root".to_string());
let depths = tree_depths(graph);
let mut height: usize = 0;
for depth in depths.values() {
if depth > &height {
height = depth.to_owned();
}
}
if height > 0 {
height -= 1;
}
let node_sep = (2 * height + 1) as f32;
graph.graph_mut().nesting_root = Some(root.clone());
// Multiply minlen by nodeSep to align nodes on non-border ranks.
let edge_objs = graph.edges();
for edge_obj in edge_objs.into_iter() {
let _edge_label = graph.edge_mut_with_obj(&edge_obj);
if _edge_label.is_none() {
continue;
}
let edge_label = _edge_label.unwrap();
edge_label.minlen = Some(edge_label.minlen.unwrap_or(1.0) * node_sep);
}
// Calculate a weight that is sufficient to keep subgraphs vertically compact
let weight = sum_weights(graph) + 1.0;
// Create border nodes and link them up
let children = graph.children(&GRAPH_NODE.to_string());
for child_id in children.into_iter() {
dfs(
graph, &root, &node_sep, &weight, &height, &depths, &child_id,
);
}
// Save the multiplier for node layers for later removal of empty border
// layers.
graph.graph_mut().node_rank_factor = Some(node_sep);
}
fn tree_depths(graph: &Graph<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, usize> {
let mut depths: OrderedHashMap<String, usize> = OrderedHashMap::new();
fn dfs(
node_id: String,
depth: usize,
depths: &mut OrderedHashMap<String, usize>,
graph: &Graph<GraphConfig, GraphNode, GraphEdge>,
) {
let children = graph.children(&node_id);
for child_id in children.iter() {
// recursion for child node ids
dfs(child_id.clone(), depth + 1, depths, graph);
}
// setting for current node
depths.insert(node_id.clone(), depth);
}
// processing root nodes
for node_id in graph.children(&GRAPH_NODE.to_string()) {
dfs(node_id, 1, &mut depths, graph);
}
return depths;
}
fn dfs(
graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
root: &String,
node_sep: &f32,
weight: &f32,
height: &usize,
depths: &OrderedHashMap<String, usize>,
node_id: &String,
) {
let children = graph.children(node_id);
if children.len() == 0 {
if node_id != root {
let mut graph_edge = GraphEdge::default();
graph_edge.minlen = Some(node_sep.clone());
graph_edge.weight = Some(0.0);
let _ = graph.set_edge(&root, &node_id, Some(graph_edge), None);
}
return ();
}
let top = util::add_border_node(graph, "_bt", None, None);
let bottom = util::add_border_node(graph, "_bb", None, None);
let _pt = graph.set_parent(&top, Some(node_id.clone()));
let _pb = graph.set_parent(&bottom, Some(node_id.clone()));
let _label = graph.node_mut(node_id);
if let Some(label) = _label {
label.border_top = Some(top.clone());
label.border_bottom = Some(bottom.clone());
}
for child_id in children.into_iter() {
dfs(graph, root, node_sep, weight, height, depths, &child_id);
let _child_node = graph.node(&child_id);
if _child_node.is_none() {
continue;
}
let child_node = _child_node.unwrap();
let border_top = child_node.border_top.clone();
let border_bottom = child_node.border_bottom.clone();
let mut child_top = child_id.clone();
let mut child_bottom = child_id.clone();
let mut this_weight: f32 = 2.0 * weight.clone();
let mut minlen: usize = 1;
if border_top.is_some() {
child_top = border_top.clone().unwrap();
}
if border_bottom.is_some() {
child_bottom = border_bottom.unwrap();
}
if border_top.is_some() {
this_weight = weight.clone();
}
if child_top == child_bottom {
minlen = height - depths.get(node_id).cloned().unwrap_or(0) + 1;
}
let mut _ct_graph_edge = GraphEdge::default();
_ct_graph_edge.minlen = Some(minlen.clone() as f32);
_ct_graph_edge.weight = Some(this_weight.clone());
_ct_graph_edge.nesting_edge = Some(true);
let _ct = graph.set_edge(&top, &child_top, Some(_ct_graph_edge), None);
let mut _cb_graph_edge = GraphEdge::default();
_cb_graph_edge.minlen = Some(minlen.clone() as f32);
_cb_graph_edge.weight = Some(this_weight.clone());
_cb_graph_edge.nesting_edge = Some(true);
let _cb = graph.set_edge(&child_bottom, &bottom, Some(_cb_graph_edge), None);
}
if graph.parent(node_id).is_none() {
let mut graph_edge = GraphEdge::default();
graph_edge.minlen =
Some((depths.get(node_id).cloned().unwrap_or(0) + height.clone()) as f32);
graph_edge.weight = Some(0.0);
graph_edge.nesting_edge = Some(true);
let _ = graph.set_edge(&root, &top, Some(graph_edge), None);
}
}
fn sum_weights(graph: &Graph<GraphConfig, GraphNode, GraphEdge>) -> f32 {
let mut total_weights: f32 = 0.0;
for edge in graph.edges() {
if let Some(edge_label) = graph.edge_with_obj(&edge) {
if let Some(weight) = edge_label.weight {
total_weights += weight;
}
}
}
return total_weights;
}
pub fn cleanup(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let graph_label = graph.graph();
if graph_label.nesting_root.is_some() {
graph.remove_node(&graph_label.nesting_root.clone().unwrap());
}
graph.graph_mut().nesting_root = None;
// removing nesting edge
let edges = graph.edges();
for edge in edges.into_iter() {
let _edge_label = graph.edge_with_obj(&edge);
if let Some(edge_label) = _edge_label {
if edge_label.nesting_edge.clone().unwrap_or(false) {
graph.remove_edge_with_obj(&edge);
}
}
}
}

View file

@ -0,0 +1,135 @@
use crate::layout::util::add_dummy_node;
use crate::{GraphConfig, GraphEdge, GraphEdgePoint, GraphNode};
use graphlib_rust::{Edge, Graph};
/*
* Breaks any long edges in the graph into short segments that span 1 layer
* each. This operation is undoable with the denormalize function.
*
* Pre-conditions:
*
* 1. The input graph is a DAG.
* 2. Each node in the graph has a "rank" property.
*
* Post-condition:
*
* 1. All edges in the graph have a length of 1.
* 2. Dummy nodes are added where edges have been split into segments.
* 3. The graph is augmented with a "dummyChains" attribute which contains
* the first dummy in each chain of dummy nodes produced.
*/
pub fn run(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
g.graph_mut().dummy_chains = Some(vec![]);
let edges = g.edges();
for edge_obj in edges.into_iter() {
normalize_edge(g, &edge_obj);
}
}
fn normalize_edge(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>, e: &Edge) {
let mut v = e.v.clone();
let w = e.w.clone();
let mut v_rank = g
.node(&v)
.unwrap_or(&GraphNode::default())
.rank
.clone()
.unwrap_or(0);
let w_rank = g
.node(&w)
.unwrap_or(&GraphNode::default())
.rank
.clone()
.unwrap_or(0);
// let name = e.name.clone(); // TODO: it was creating error for multi-graph option
let Some(edge_label) = g.edge_mut_with_obj(&e) else {
return;
};
edge_label.points = Some(vec![]);
let weight = edge_label.weight.clone();
let label_rank = edge_label.label_rank.unwrap_or(0);
if w_rank == v_rank + 1 {
return ();
}
let _edge_label = edge_label.clone();
g.remove_edge_with_obj(&e);
let mut i = 0;
v_rank += 1;
while v_rank < w_rank {
let mut attrs = GraphNode::default();
attrs.edge_label = Some(_edge_label.clone());
attrs.edge_obj = Some(e.clone());
attrs.rank = Some(v_rank.clone());
let mut dummy_type = "edge".to_string();
if v_rank == label_rank {
attrs.width = _edge_label.width.clone().unwrap_or(0.0);
attrs.height = _edge_label.height.clone().unwrap_or(0.0);
attrs.labelpos = _edge_label.labelpos.clone();
dummy_type = "edge-label".to_string();
}
let dummy = add_dummy_node(g, dummy_type, attrs, "_d".to_string());
let mut dummy_edge_label = GraphEdge::default();
dummy_edge_label.weight = weight.clone();
let _ = g.set_edge(&v, &dummy, Some(dummy_edge_label), None); // remove name from here
if i == 0 {
let graph_label = g.graph_mut();
if graph_label.dummy_chains.is_none() {
graph_label.dummy_chains = Some(vec![]);
}
let dummy_chains = graph_label.dummy_chains.as_mut().unwrap();
dummy_chains.push(dummy.clone());
}
v = dummy.clone();
i += 1;
v_rank += 1;
}
let mut graph_edge = GraphEdge::default();
graph_edge.weight = weight;
let _ = g.set_edge(
&v,
&w,
Some(graph_edge),
None, // removed name from here
);
}
pub fn undo(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
if g.graph().dummy_chains.is_none() {
return ();
}
let dummy_chains = g.graph().dummy_chains.clone().unwrap();
for v_ in dummy_chains.iter() {
let node_ = g.node(v_);
if node_.is_none() {
continue;
}
let mut node = node_.cloned().unwrap();
let mut orig_label = node.edge_label.clone().unwrap_or(GraphEdge::default());
let edge_obj = node.edge_obj.unwrap();
let mut v = v_.clone();
while node.dummy.is_some() {
let sucs = g.successors(&v).unwrap_or(vec![]);
let default_w = "".to_string();
let w = sucs.first().unwrap_or(&default_w);
g.remove_node(&v);
let points = orig_label.points.as_mut().unwrap();
points.push(GraphEdgePoint {
x: node.x.clone(),
y: node.y.clone(),
});
if node.dummy.as_ref().unwrap() == "edge-label" {
orig_label.x = node.x.clone();
orig_label.y = node.y.clone();
orig_label.width = Some(node.width.clone());
orig_label.height = Some(node.height.clone());
}
v = w.clone();
node = g.node(&v).cloned().unwrap();
}
let _ = g.set_edge_with_obj(&edge_obj, Some(orig_label));
}
}

View file

@ -0,0 +1,38 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
pub fn add_subgraph_constraints(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
cg: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
vs: &Vec<String>,
) {
let mut prev: OrderedHashMap<String, String> = OrderedHashMap::new();
let mut _root_prev: Option<String> = None;
vs.iter().for_each(|v| {
let mut child = g.parent(v).cloned();
let mut _parent: Option<String> = None;
let mut _prev_child: Option<String> = None;
while child.is_some() {
_parent = g.parent(&child.clone().unwrap()).cloned();
if _parent.is_some() {
_prev_child = prev
.get(&_parent.clone().unwrap_or("".to_string()))
.cloned();
prev.insert(_parent.clone().unwrap(), child.clone().unwrap());
} else {
_prev_child = _root_prev.clone();
_root_prev = child.clone();
}
let prev_child = _prev_child.clone().unwrap_or("".to_string());
let child_ = child.clone().unwrap_or("".to_string());
if _prev_child.is_some() && prev_child != child_ {
let _ = cg.set_edge(&prev_child, &child_, None, None);
return ();
}
child = _parent.clone();
}
});
}

View file

@ -0,0 +1,45 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
#[derive(Debug, Clone)]
pub struct Barycenter {
pub v: String,
pub barycenter: Option<f32>,
pub weight: Option<f32>,
}
pub fn barycenter(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
movable: &Vec<String>,
) -> Vec<Barycenter> {
movable
.iter()
.map(|v| {
let in_v = g.in_edges(v, None).unwrap_or(vec![]);
if in_v.len() == 0 {
return Barycenter {
v: v.clone(),
barycenter: None,
weight: None,
};
}
//( sum, weight )
let mut sum = 0.0_f64;
let mut weight = 0.0_f64;
in_v.iter().for_each(|e| {
let edge = g.edge_with_obj(&e).unwrap();
let node_u = g.node(&e.v).unwrap();
let edge_weight = edge.weight.clone().unwrap_or(0.0) as f64;
sum += edge_weight * (node_u.order.clone().unwrap_or(0) as f64);
weight += edge_weight;
});
return Barycenter {
v: v.clone(),
barycenter: Some((sum / weight) as f32),
weight: Some(weight as f32),
};
})
.collect()
}

View file

@ -0,0 +1,116 @@
use crate::layout::util::unique_id;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::{Edge, Graph, GraphOption};
/*
* Constructs a graph that can be used to sort a layer of nodes. The graph will
* contain all base and subgraph nodes from the request layer in their original
* hierarchy and any edges that are incident on these nodes and are of the type
* requested by the "relationship" parameter.
*
* Nodes from the requested rank that do not have parents are assigned a root
* node in the output graph, which is set in the root graph attribute. This
* makes it easy to walk the hierarchy of movable nodes during ordering.
*
* Pre-conditions:
*
* 1. Input graph is a DAG
* 2. Base nodes in the input graph have a rank attribute
* 3. Subgraph nodes in the input graph has minRank and maxRank attributes
* 4. Edges have an assigned weight
*
* Post-conditions:
*
* 1. Output graph has all nodes in the movable rank with preserved
* hierarchy.
* 2. Root nodes in the movable layer are made children of the node
* indicated by the root attribute of the graph.
* 3. Non-movable nodes incident on movable nodes, selected by the
* relationship parameter, are included in the graph (without hierarchy).
* 4. Edges incident on movable nodes, selected by the relationship
* parameter, are added to the output graph.
* 5. The weights for copied edges are aggregated as need, since the output
* graph is not a multi-graph.
*/
#[derive(Debug, Copy, Clone)]
pub enum GraphRelationship {
InEdges,
OutEdges,
}
pub fn build_layer_graph(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
rank: &i32,
relationship: GraphRelationship,
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
let root = create_root_node(g);
let mut result: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(Some(GraphOption {
directed: Some(true),
compound: Some(true),
multigraph: None,
}));
let graph_label = result.graph_mut();
graph_label.root = Some(root.clone());
g.nodes().iter().for_each(|v| {
let node = g.node(v).unwrap();
let parent = g.parent(v);
let in_rank = node.rank == Some(*rank);
let in_subgraph_rank = match (node.min_rank, node.max_rank) {
(Some(min_rank), Some(max_rank)) => min_rank <= *rank && *rank <= max_rank,
_ => false,
};
let mut _relationship: Vec<Edge> = g.in_edges(v, None).unwrap_or(vec![]);
match relationship {
GraphRelationship::OutEdges => {
_relationship = g.out_edges(v, None).unwrap_or(vec![]);
}
_ => (),
}
if in_rank || in_subgraph_rank {
result.set_node(v.clone(), Some(node.clone()));
if parent.is_some() {
let _ = result.set_parent(v, parent.cloned());
} else {
let _ = result.set_parent(v, Some(root.clone()));
}
// This assumes we have only short edges!
_relationship.iter().for_each(|e| {
let u = if &e.v == v { e.w.clone() } else { e.v.clone() };
let edge = result.edge(&u, &v, None);
let weight = if edge.is_some() {
edge.unwrap().weight.clone().unwrap_or(0.0)
} else {
0.0
};
let mut edge_label = GraphEdge::default();
edge_label.weight =
Some(g.edge_with_obj(&e).unwrap().weight.clone().unwrap_or(0.0) + weight);
let _ = result.set_edge(&u, &v, Some(edge_label), None);
});
if node.min_rank.is_some() {
let mut graph_node = GraphNode::default();
graph_node.border_left_ = node.border_left.as_ref().unwrap().get(rank).cloned();
graph_node.border_right_ = node.border_right.as_ref().unwrap().get(rank).cloned();
result.set_node(v.clone(), Some(graph_node));
}
}
});
result
}
pub fn create_root_node(g: &Graph<GraphConfig, GraphNode, GraphEdge>) -> String {
let mut v = format!("_root{}", unique_id());
while g.has_node(&v) {
v = format!("_root{}", unique_id());
}
v
}

View file

@ -0,0 +1,91 @@
use std::collections::HashMap;
use graphlib_rust::Graph;
use crate::{GraphConfig, GraphEdge, GraphNode};
/*
* A function that takes a layering (an array of layers, each with an array of
* ordererd nodes) and a graph and returns a weighted crossing count.
*
* Pre-conditions:
*
* 1. Input graph must be simple (not a multigraph), directed, and include
* only simple edges.
* 2. Edges in the input graph must have assigned weights.
*
* Post-conditions:
*
* 1. The graph and layering matrix are left unchanged.
*
* This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
*/
pub fn cross_count(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
) -> f32 {
let mut cc = 0.0;
for i in 1..layering.len() {
cc += two_layer_cross_count(g, &layering[i - 1], &layering[i]);
}
cc
}
pub fn two_layer_cross_count(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
north_layer: &Vec<String>,
south_layer: &Vec<String>,
) -> f32 {
let mut south_pos: HashMap<String, usize> = HashMap::new();
for (i, v) in south_layer.iter().enumerate() {
south_pos.insert(v.clone(), i);
}
let mut south_entries: Vec<(usize, f32)> = vec![];
for v in north_layer {
let mut out_edges: Vec<(usize, f32)> = g
.out_edges(v, None)
.unwrap_or_default()
.into_iter()
.filter_map(|e| {
let pos = south_pos.get(&e.w)?;
let weight = g
.edge_with_obj(&e)
.and_then(|edge| edge.weight)
.unwrap_or(0.0);
Some((*pos, weight))
})
.collect();
out_edges.sort_by(|e1, e2| e1.0.cmp(&e2.0));
south_entries.extend(out_edges);
}
let mut first_index: usize = 1;
while first_index < south_layer.len() {
first_index <<= 1;
}
let tree_size = 2 * first_index - 1;
first_index -= 1;
let mut tree: Vec<f32> = vec![0.0; tree_size];
let mut cc = 0.0;
for (pos, weight) in south_entries {
let mut index = pos + first_index;
tree[index] += weight;
let mut weight_sum = 0.0;
while index > 0 {
if index % 2 != 0 {
weight_sum += tree[index + 1];
}
index = (index - 1) >> 1;
tree[index] += weight;
}
cc += weight * weight_sum;
}
cc
}

View file

@ -0,0 +1,68 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
/*
* Assigns an initial order value for each node by performing a DFS search
* starting from nodes in the first rank. Nodes are assigned an order in their
* rank as they are first visited.
*
* This approach comes from Gansner, et al., "A Technique for Drawing Directed
* Graphs."
*
* Returns a layering matrix with an array per layer and each layer sorted by
* the order of its nodes.
*/
pub fn init_order(g: &Graph<GraphConfig, GraphNode, GraphEdge>) -> Vec<Vec<String>> {
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
let mut simple_nodes: Vec<String> = g
.nodes()
.into_iter()
.filter(|v| g.children(v).len() == 0)
.collect();
let max_rank = simple_nodes
.iter()
.map(|v| g.node(v).unwrap().rank.clone().unwrap_or(0))
.max()
.unwrap_or(0);
let mut layers: Vec<Vec<String>> = (0..=max_rank).map(|_| -> Vec<String> { vec![] }).collect();
fn dfs(
v: &String,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
visited: &mut OrderedHashMap<String, bool>,
layers: &mut Vec<Vec<String>>,
) {
if visited.contains_key(v) {
return ();
}
visited.insert(v.clone(), true);
let node = g.node(v).unwrap();
let node_rank = node.rank.unwrap_or(0) as usize;
if layers.get(node_rank.clone()).is_none() {
layers.insert(node_rank.clone(), vec![]);
}
let layer: &mut Vec<String> = layers.get_mut(node_rank.clone()).unwrap();
layer.push(v.clone());
let sucs = g.successors(v).unwrap_or(vec![]);
for sv in sucs.iter() {
dfs(sv, g, visited, layers)
}
}
simple_nodes.sort_by(|v1, v2| {
let v1_rank = g.node(v1).unwrap().rank.clone().unwrap_or(0);
let v2_rank = g.node(v2).unwrap().rank.clone().unwrap_or(0);
v1_rank.cmp(&v2_rank)
});
for v in simple_nodes.iter() {
dfs(v, g, &mut visited, &mut layers);
}
return layers;
}

View file

@ -0,0 +1,107 @@
pub mod add_subgraph_constraints;
pub mod barycenter;
pub mod build_layer_graph;
pub mod cross_count;
pub mod init_order;
pub mod resolve_conflicts;
pub mod sort;
pub mod sort_subgraph;
use crate::layout::order::add_subgraph_constraints::add_subgraph_constraints;
use crate::layout::order::build_layer_graph::{build_layer_graph, GraphRelationship};
use crate::layout::order::cross_count::cross_count;
use crate::layout::order::init_order::init_order;
use crate::layout::order::sort_subgraph::sort_subgraph;
use crate::layout::util;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::graph::GRAPH_NODE;
use graphlib_rust::Graph;
/*
* Applies heuristics to minimize edge crossings in the graph and sets the best
* order solution as an order attribute on each node.
*
* Pre-conditions:
*
* 1. Graph must be DAG
* 2. Graph nodes must be objects with a "rank" attribute
* 3. Graph edges must have the "weight" attribute
*
* Post-conditions:
*
* 1. Graph nodes will have an "order" attribute based on the results of the
* algorithm.
*/
pub fn order(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let max_rank = util::max_rank(g);
let down_layer_ranks: Vec<i32> = (1..=max_rank).collect();
let up_layer_ranks: Vec<i32> = (0..max_rank).rev().collect();
let mut layering = init_order(g);
assign_order(g, &layering);
// Start with the init ordering as the best candidate.
// The original dagre.js code starts with bestCC = Infinity, which means
// the first sweep's result always replaces it. However, if the init
// ordering already has minimal crossings, using it as the starting point
// ensures the sweep loop doesn't accidentally select a different ordering
// with the same crossing count but worse visual layout (e.g., a mirrored
// ordering produced by a biased sweep).
let init_cc = cross_count(g, &layering) as f64;
let mut best_cc = init_cc;
let mut best: Vec<Vec<String>> = layering.clone();
let mut i = 0;
let mut last_best = 0;
while last_best < 4 {
if i % 2 != 0 {
sweep_layer_graphs(g, &down_layer_ranks, GraphRelationship::InEdges, i % 4 >= 2);
} else {
sweep_layer_graphs(g, &up_layer_ranks, GraphRelationship::OutEdges, i % 4 >= 2);
}
layering = util::build_layer_matrix(g);
let cc = cross_count(g, &layering) as f64;
if cc < best_cc {
last_best = 0;
best = layering.clone();
best_cc = cc;
}
last_best += 1;
i += 1;
}
assign_order(g, &best);
}
fn sweep_layer_graphs(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
ranks: &Vec<i32>,
relationship: GraphRelationship,
bias_right: bool,
) {
let mut cg: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(None);
ranks.iter().for_each(|rank| {
let lg = build_layer_graph(g, rank, relationship);
let root = lg.graph().root.clone().unwrap_or(GRAPH_NODE.to_string());
let sorted = sort_subgraph(&lg, &root, &cg, &bias_right);
sorted.vs.iter().enumerate().for_each(|(i, v)| {
if let Some(node) = g.node_mut(v) {
node.order = Some(i);
}
});
add_subgraph_constraints(&lg, &mut cg, &sorted.vs);
});
}
fn assign_order(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>, layering: &Vec<Vec<String>>) {
for layer in layering {
for (i, v) in layer.iter().enumerate() {
let node_label = g.node_mut(v).unwrap();
node_label.order = Some(i);
}
}
}

View file

@ -0,0 +1,173 @@
/*
* Given a list of entries of the form {v, barycenter, weight} and a
* constraint graph this function will resolve any conflicts between the
* constraint graph and the barycenters for the entries. If the barycenters for
* an entry would violate a constraint in the constraint graph then we coalesce
* the nodes in the conflict into a new node that respects the contraint and
* aggregates barycenter and weight information.
*
* This implementation is based on the description in Forster, "A Fast and
* Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
* differs in some specific details.
*
* Pre-conditions:
*
* 1. Each entry has the form {v, barycenter, weight}, or if the node has
* no barycenter, then {v}.
*
* Returns:
*
* A new list of entries of the form {vs, i, barycenter, weight}. The list
* `vs` may either be a singleton or it may be an aggregation of nodes
* ordered such that they do not violate constraints from the constraint
* graph. The property `i` is the lowest original index of any of the
* elements in `vs`.
*/
use std::collections::HashMap;
use graphlib_rust::Graph;
use crate::layout::order::barycenter::Barycenter;
use crate::{GraphConfig, GraphEdge, GraphNode};
#[derive(Debug, Clone)]
pub struct ResolvedBaryEntry {
pub vs: Vec<String>,
pub i: usize,
pub barycenter: Option<f32>,
pub weight: Option<f32>,
}
#[derive(Debug, Clone)]
struct ConflictEntry {
indegree: i32,
ins: Vec<usize>,
outs: Vec<usize>,
vs: Vec<String>,
i: usize,
barycenter: Option<f32>,
weight: Option<f32>,
merged: bool,
}
pub fn resolve_conflicts(
entries: &Vec<Barycenter>,
cg: &Graph<GraphConfig, GraphNode, GraphEdge>,
) -> Vec<ResolvedBaryEntry> {
let mut id_to_idx: HashMap<String, usize> = HashMap::new();
let mut mapped_entries: Vec<ConflictEntry> = Vec::with_capacity(entries.len());
for (i, entry) in entries.iter().enumerate() {
id_to_idx.insert(entry.v.clone(), i);
mapped_entries.push(ConflictEntry {
indegree: 0,
ins: vec![],
outs: vec![],
vs: vec![entry.v.clone()],
i,
barycenter: entry.barycenter,
weight: entry.weight,
merged: false,
});
}
cg.edges().iter().for_each(|e| {
if let (Some(&v_idx), Some(&w_idx)) = (id_to_idx.get(&e.v), id_to_idx.get(&e.w)) {
mapped_entries[w_idx].indegree += 1;
mapped_entries[v_idx].outs.push(w_idx);
}
});
let mut source_set: Vec<usize> = mapped_entries
.iter()
.enumerate()
.filter_map(|(idx, entry)| if entry.indegree == 0 { Some(idx) } else { None })
.collect();
let mut entries_order: Vec<usize> = Vec::new();
while let Some(v_idx) = source_set.pop() {
entries_order.push(v_idx);
let ins = mapped_entries[v_idx].ins.clone();
ins.into_iter().rev().for_each(|u_idx| {
handle_in(&mut mapped_entries, v_idx, u_idx);
});
let outs = mapped_entries[v_idx].outs.clone();
outs.into_iter().for_each(|w_idx| {
handle_out(&mut mapped_entries, v_idx, w_idx, &mut source_set);
});
}
entries_order
.into_iter()
.filter(|idx| !mapped_entries[*idx].merged)
.map(|idx| {
let entry = &mapped_entries[idx];
ResolvedBaryEntry {
vs: entry.vs.clone(),
i: entry.i,
barycenter: entry.barycenter,
weight: entry.weight,
}
})
.collect()
}
fn handle_in(entries: &mut [ConflictEntry], v_idx: usize, u_idx: usize) {
if entries[u_idx].merged {
return;
}
let u_barycenter = entries[u_idx].barycenter;
let v_barycenter = entries[v_idx].barycenter;
if u_barycenter.is_none()
|| v_barycenter.is_none()
|| u_barycenter.unwrap() >= v_barycenter.unwrap()
{
merge_entries(entries, v_idx, u_idx);
}
}
fn handle_out(
entries: &mut [ConflictEntry],
v_idx: usize,
w_idx: usize,
source_set: &mut Vec<usize>,
) {
entries[w_idx].ins.push(v_idx);
entries[w_idx].indegree -= 1;
if entries[w_idx].indegree == 0 {
source_set.push(w_idx);
}
}
fn merge_entries(entries: &mut [ConflictEntry], target_idx: usize, source_idx: usize) {
let mut sum = 0.0;
let mut weight = 0.0;
if let (Some(target_barycenter), Some(target_weight)) =
(entries[target_idx].barycenter, entries[target_idx].weight)
{
sum += target_barycenter * target_weight;
weight += target_weight;
}
if let (Some(source_barycenter), Some(source_weight)) =
(entries[source_idx].barycenter, entries[source_idx].weight)
{
sum += source_barycenter * source_weight;
weight += source_weight;
}
let mut vs = entries[source_idx].vs.clone();
vs.extend(entries[target_idx].vs.clone());
entries[target_idx].vs = vs;
entries[target_idx].barycenter = Some(sum / weight);
entries[target_idx].weight = Some(weight);
entries[target_idx].i = std::cmp::min(entries[source_idx].i, entries[target_idx].i);
entries[source_idx].merged = true;
}

View file

@ -0,0 +1,83 @@
use std::cmp::Ordering;
use crate::layout::order::resolve_conflicts::ResolvedBaryEntry;
use crate::layout::order::sort_subgraph::SubgraphResult;
use crate::layout::util;
use crate::layout::util::PartitionResponse;
pub fn sort(entries: &Vec<ResolvedBaryEntry>, bias_right: &bool) -> SubgraphResult {
let parts: PartitionResponse<ResolvedBaryEntry> = util::partition(
entries,
Box::new(|val: &ResolvedBaryEntry| -> bool { val.barycenter.is_some() }),
);
let mut sortable = parts.lhs.clone();
let mut unsortable = parts.rhs.clone();
sortable.sort_by(|e1, e2| compare_with_bias(e1, e2, bias_right));
unsortable.sort_by(|e1, e2| e2.i.cmp(&e1.i));
let mut vs: Vec<Vec<String>> = vec![];
let mut sum = 0.0;
let mut weight = 0.0;
let mut vs_index: usize = 0;
vs_index = consume_unsortable(&mut vs, &mut unsortable, vs_index);
sortable.iter().for_each(|entry| {
vs_index += entry.vs.len();
vs.push(entry.vs.clone());
let entry_weight = entry.weight.unwrap_or(0.0);
sum += entry.barycenter.unwrap_or(0.0) * entry_weight;
weight += entry_weight;
vs_index = consume_unsortable(&mut vs, &mut unsortable, vs_index);
});
let mut result = SubgraphResult::default();
result.vs = vs.into_iter().flatten().collect();
if weight != 0.0 {
result.barycenter = Some(sum / weight);
result.weight = Some(weight);
}
result
}
fn consume_unsortable(
vs: &mut Vec<Vec<String>>,
unsortable: &mut Vec<ResolvedBaryEntry>,
mut index: usize,
) -> usize {
loop {
let last = match unsortable.last() {
Some(last) => last,
None => return index,
};
if last.i > index {
return index;
}
let last = unsortable.pop().unwrap();
vs.push(last.vs);
index += 1;
}
}
fn compare_with_bias(
entry_v: &ResolvedBaryEntry,
entry_w: &ResolvedBaryEntry,
bias: &bool,
) -> Ordering {
let barycenter_v = entry_v.barycenter.unwrap_or(0.0);
let barycenter_w = entry_w.barycenter.unwrap_or(0.0);
if barycenter_v < barycenter_w {
return Ordering::Less;
} else if barycenter_v > barycenter_w {
return Ordering::Greater;
}
if !bias {
entry_v.i.cmp(&entry_w.i)
} else {
entry_w.i.cmp(&entry_v.i)
}
}

View file

@ -0,0 +1,114 @@
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
use crate::layout::order::barycenter::{barycenter, Barycenter};
use crate::layout::order::resolve_conflicts::{resolve_conflicts, ResolvedBaryEntry};
use crate::layout::order::sort::sort;
use crate::{GraphConfig, GraphEdge, GraphNode};
#[derive(Debug, Clone, Default)]
pub struct SubgraphResult {
pub vs: Vec<String>,
pub barycenter: Option<f32>,
pub weight: Option<f32>,
}
pub fn sort_subgraph(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
v: &String,
cg: &Graph<GraphConfig, GraphNode, GraphEdge>,
bias_right: &bool,
) -> SubgraphResult {
let mut movable = g.children(v);
let node = g.node(v);
let bl = node.and_then(|n| n.border_left_.clone());
let br = node.and_then(|n| n.border_right_.clone());
let mut subgraphs: OrderedHashMap<String, SubgraphResult> = OrderedHashMap::new();
if let (Some(bl_), Some(br_)) = (bl.as_ref(), br.as_ref()) {
movable = movable
.into_iter()
.filter(|w| w != bl_ && w != br_)
.collect();
}
let mut barycenters = barycenter(g, &movable);
barycenters.iter_mut().for_each(|entry| {
if !g.children(&entry.v).is_empty() {
let subgraph_result = sort_subgraph(g, &entry.v, cg, bias_right);
subgraphs.insert(entry.v.clone(), subgraph_result.clone());
if subgraph_result.barycenter.is_some() {
merge_barycenters(entry, &subgraph_result);
}
}
});
let mut entries = resolve_conflicts(&barycenters, cg);
expand_subgraphs(&mut entries, &subgraphs);
let mut result = sort(&entries, bias_right);
if let (Some(bl_), Some(br_)) = (bl, br) {
let mut vs: Vec<String> = Vec::with_capacity(result.vs.len() + 2);
vs.push(bl_.clone());
vs.extend(result.vs.clone());
vs.push(br_.clone());
result.vs = vs;
let bl_preds = g.predecessors(&bl_).unwrap_or_default();
if !bl_preds.is_empty() {
let br_preds = g.predecessors(&br_).unwrap_or_default();
if !br_preds.is_empty() {
let bl_pred = g.node(&bl_preds[0]).unwrap();
let br_pred = g.node(&br_preds[0]).unwrap();
let bl_pred_order = bl_pred.order.unwrap_or(0) as f32;
let br_pred_order = br_pred.order.unwrap_or(0) as f32;
let result_barycenter = result.barycenter.unwrap_or(0.0);
let result_weight = result.weight.unwrap_or(0.0);
result.barycenter = Some(
(result_barycenter * result_weight + bl_pred_order + br_pred_order)
/ (result_weight + 2.0),
);
result.weight = Some(result_weight + 2.0);
}
}
}
result
}
fn expand_subgraphs(
entries: &mut Vec<ResolvedBaryEntry>,
subgraphs: &OrderedHashMap<String, SubgraphResult>,
) {
entries.iter_mut().for_each(|entry| {
let mut vs: Vec<String> = vec![];
entry.vs.iter().for_each(|v| {
if let Some(subgraph) = subgraphs.get(v) {
vs.extend(subgraph.vs.clone());
return;
}
vs.push(v.clone());
});
entry.vs = vs;
});
}
fn merge_barycenters(target: &mut Barycenter, other: &SubgraphResult) {
let (other_barycenter, other_weight) = match (other.barycenter, other.weight) {
(Some(barycenter), Some(weight)) => (barycenter, weight),
_ => return,
};
if let (Some(target_barycenter), Some(target_weight)) = (target.barycenter, target.weight) {
target.barycenter = Some(
(target_barycenter * target_weight + other_barycenter * other_weight)
/ (target_weight + other_weight),
);
target.weight = Some(target_weight + other_weight);
} else {
target.barycenter = Some(other_barycenter);
target.weight = Some(other_weight);
}
}

View file

@ -0,0 +1,154 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::graph::GRAPH_NODE;
use graphlib_rust::Graph;
use ordered_hashmap::OrderedHashMap;
pub fn parent_dummy_chains(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let post_order_nums: OrderedHashMap<String, (i32, i32)> = postorder(g);
let dummy_chains = g.graph().dummy_chains.clone().unwrap_or(vec![]);
for v_ in dummy_chains.iter() {
let mut v = v_.clone();
let mut node = g.node(&v).unwrap();
let edge_obj = node.edge_obj.clone().unwrap();
let path_data = find_path(g, &post_order_nums, &edge_obj.v, &edge_obj.w);
let path = path_data.0;
let lca = path_data.1;
let mut path_idx = 0;
let mut path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
let mut ascending = true;
while v != edge_obj.w {
node = g.node(&v).unwrap();
let node_rank = node.rank.unwrap_or(0);
if ascending {
loop {
path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
if path_v == lca {
ascending = false;
break;
}
let Some(path_v_id) = path_v.as_ref() else {
break;
};
let max_rank = g.node(path_v_id).unwrap().max_rank.unwrap_or(0);
if max_rank < node_rank {
path_idx += 1;
continue;
}
break;
}
}
if !ascending {
while path_idx < path.len().saturating_sub(1) {
let Some(next) = path.get(path_idx + 1).cloned() else {
break;
};
let Some(next_id) = next.as_ref() else {
break;
};
if g.node(next_id).unwrap().min_rank.unwrap_or(0) <= node_rank {
path_idx += 1;
} else {
break;
}
}
path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
}
let _ = g.set_parent(&v, path_v.clone());
let Some(next) = g.successors(&v).unwrap_or_default().first().cloned() else {
break;
};
v = next;
}
}
}
// Find a path from v to w through the lowest common ancestor (LCA). Return the
// full path and the LCA.
fn find_path(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
post_order_nums: &OrderedHashMap<String, (i32, i32)>,
v: &String,
w: &String,
) -> (Vec<Option<String>>, Option<String>) {
let mut v_path: Vec<Option<String>> = vec![];
let mut w_path: Vec<Option<String>> = vec![];
let v_post_order_num = post_order_nums.get(v).cloned().unwrap_or((0, 0));
let w_post_order_num = post_order_nums.get(w).cloned().unwrap_or((0, 0));
let low = std::cmp::min(v_post_order_num.0, w_post_order_num.0);
let lim = std::cmp::max(v_post_order_num.1, w_post_order_num.1);
let mut parent: Option<String> = Some(v.clone());
loop {
parent = parent.and_then(|p| g.parent(&p).cloned());
v_path.push(parent.clone());
let Some(parent_id) = parent.as_ref() else {
break;
};
let Some(post_order_num) = post_order_nums.get(parent_id) else {
break;
};
if post_order_num.0 <= low && lim <= post_order_num.1 {
break;
}
}
let lca = parent.clone();
parent = Some(w.clone());
loop {
parent = parent.and_then(|p| g.parent(&p).cloned());
if parent == lca {
break;
}
w_path.push(parent.clone());
if parent.is_none() {
break;
}
}
w_path.reverse();
v_path.extend(w_path);
(v_path, lca)
}
fn postorder(g: &Graph<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, (i32, i32)> {
let mut result: OrderedHashMap<String, (i32, i32)> = OrderedHashMap::new();
let mut lim = 0;
fn dfs(
v: &String,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
lim: &mut i32,
result: &mut OrderedHashMap<String, (i32, i32)>,
) {
let low = lim.clone();
g.children(&v).iter().for_each(|v_| {
dfs(v_, g, lim, result);
});
result.insert(v.clone(), (low, lim.clone()));
*lim += 1;
}
g.children(&GRAPH_NODE.to_string()).iter().for_each(|v| {
dfs(v, g, &mut lim, &mut result);
});
return result;
}

View file

@ -0,0 +1,734 @@
/*
* This module provides coordinate assignment based on Brandes and Köpf, "Fast
* and Simple Horizontal Coordinate Assignment."
*/
use crate::layout::add_border_segments::BorderTypeName;
use crate::layout::util;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::{Graph, GraphOption};
use ordered_hashmap::OrderedHashMap;
use std::cmp::Ordering;
/*
* Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
* property. A type-1 conflict is one where a non-inner segment crosses an
* inner segment. An inner segment is an edge with both incident nodes marked
* with the "dummy" property.
*
* This algorithm scans layer by layer, starting with the second, for type-1
* conflicts between the current layer and the previous layer. For each layer
* it scans the nodes from left to right until it reaches one that is incident
* on an inner segment. It then scans predecessors to determine if they have
* edges that cross that inner segment. At the end a final scan is done for all
* nodes on the current rank to see if they cross the last visited inner
* segment.
*
* This algorithm (safely) assumes that a dummy node will only be incident on a
* single node in the layers being scanned.
*/
fn find_type_1_conflicts(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
) -> OrderedHashMap<String, OrderedHashMap<String, bool>> {
let mut conflicts = OrderedHashMap::new();
fn visit_layer(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
prev_layer: &Vec<String>,
layer: &Vec<String>,
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
) {
// last visited node in the previous layer that is incident on an inner
// segment.
let mut k0 = 0;
// Tracks the last node in this layer scanned for crossings with a type-1
// segment.
let mut scan_pos = 0;
let prev_layer_length = prev_layer.len();
let last_node = layer.last().unwrap().clone();
for (i, v) in layer.iter().enumerate() {
let w = find_other_inner_segment_node(g, v);
let k1 = if let Some(ref w) = w {
g.node(w).unwrap().order.unwrap_or(0)
} else {
prev_layer_length
};
if w.is_some() || *v == last_node {
for scan_node in layer[scan_pos..=i].iter() {
for u in g.predecessors(scan_node).unwrap() {
let u_label = g.node(&u).unwrap();
let u_pos = u_label.order.unwrap_or(0);
if (u_pos < k0 || k1 < u_pos)
&& !(u_label.dummy.is_some()
&& g.node(scan_node).unwrap().dummy.is_some())
{
add_conflict(conflicts, &u, scan_node);
}
}
}
scan_pos = i + 1;
k0 = k1;
}
}
}
layering
.iter()
.filter(|layer| !layer.is_empty())
.reduce(|prev_layer, layer| {
visit_layer(g, &prev_layer, layer, &mut conflicts);
layer
});
conflicts
}
pub fn find_type_2_conflicts(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
) -> OrderedHashMap<String, OrderedHashMap<String, bool>> {
let mut conflicts: OrderedHashMap<String, OrderedHashMap<String, bool>> = OrderedHashMap::new();
fn scan(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
south: &Vec<String>,
south_pos: usize,
south_end: usize,
prev_north_border: i32,
next_north_border: i32,
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
) {
for i in south_pos..south_end {
let v = south.get(i).cloned().unwrap();
if g.node(&v).is_some() && g.node(&v).unwrap().dummy.is_some() {
let preds = g.predecessors(&v).unwrap_or(vec![]);
preds.iter().for_each(|u| {
let u_node_ = g.node(u);
if let Some(u_node) = u_node_ {
let u_node_order = u_node.order.unwrap_or(0) as i32;
if u_node.dummy.is_some()
&& (u_node_order < prev_north_border
|| u_node_order > next_north_border)
{
add_conflict(conflicts, u, &v);
}
}
});
}
}
}
fn visit_layer(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
north: &Vec<String>,
south: &Vec<String>,
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
) {
let mut prev_north_pos: i32 = -1;
let mut next_north_pos: i32 = -1;
let mut south_pos: usize = 0;
for (south_lookahead, v) in south.iter().enumerate() {
if let Some(v_node) = g.node(v) {
if v_node.dummy.as_deref() == Some("border") {
let predecessors = g.predecessors(v).unwrap_or(vec![]);
if !predecessors.is_empty() {
next_north_pos =
g.node(&predecessors[0]).unwrap().order.unwrap_or(0) as i32;
scan(
g,
south,
south_pos,
south_lookahead,
prev_north_pos,
next_north_pos,
conflicts,
);
south_pos = south_lookahead;
prev_north_pos = next_north_pos;
}
}
}
scan(
g,
south,
south_pos,
south.len(),
next_north_pos,
north.len() as i32,
conflicts,
);
}
}
for i in 1..layering.len() {
if layering[i - 1].is_empty() || layering[i].is_empty() {
continue;
}
visit_layer(g, &layering[i - 1], &layering[i], &mut conflicts);
}
conflicts
}
fn find_other_inner_segment_node(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
v: &String,
) -> Option<String> {
if g.node(v).unwrap().dummy.is_some() {
let preds = g.predecessors(v).unwrap_or(vec![]);
return preds
.iter()
.find(|u| g.node(u).unwrap().dummy.is_some())
.cloned();
}
None
}
pub fn add_conflict(
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
v_: &String,
w_: &String,
) {
let mut v = v_.clone();
let mut w = w_.clone();
if v.cmp(&w) == Ordering::Greater {
let tmp = v;
v = w;
w = tmp;
}
let _conflicts_v = conflicts.get(&v);
if _conflicts_v.is_none() {
conflicts.insert(v.clone(), OrderedHashMap::new());
}
let conflicts_v = conflicts.get_mut(&v).unwrap();
conflicts_v.insert(w.clone(), true);
}
pub fn has_conflict(
conflicts: &OrderedHashMap<String, OrderedHashMap<String, bool>>,
v_: &String,
w_: &String,
) -> bool {
let mut v = v_;
let mut w = w_;
if v_.cmp(w_) == Ordering::Greater {
let tmp = v;
v = w;
w = tmp;
}
let empty_hashmap: OrderedHashMap<String, bool> = OrderedHashMap::new();
conflicts.get(v).unwrap_or(&empty_hashmap).contains_key(w)
}
/*
* Try to align nodes into vertical "blocks" where possible. This algorithm
* attempts to align a node with one of its median neighbors. If the edge
* connecting a neighbor is a type-1 conflict then we ignore that possibility.
* If a previous node has already formed a block with a node after the node
* we're trying to form a block with, we also ignore that possibility - our
* blocks would be split in that scenario.
*/
// root -> 0, align -> 1
pub fn vertical_alignment(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
conflicts: &OrderedHashMap<String, OrderedHashMap<String, bool>>,
neighbor_fn: Box<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &String) -> Vec<String>>,
) -> (OrderedHashMap<String, String>, Vec<String>) {
let mut root: OrderedHashMap<String, String> = OrderedHashMap::new();
let mut align: OrderedHashMap<String, String> = OrderedHashMap::new();
let mut pos: OrderedHashMap<String, usize> = OrderedHashMap::new();
// We cache the position here based on the layering because the graph and
// layering may be out of sync. The layering matrix is manipulated to
// generate different extreme alignments.
layering.iter().for_each(|layer| {
layer.iter().enumerate().for_each(|(order, v)| {
root.insert(v.clone(), v.clone());
align.insert(v.clone(), v.clone());
pos.insert(v.clone(), order);
});
});
layering.iter().for_each(|layer| {
let mut prev_idx: i32 = -1;
layer.iter().for_each(|v| {
let mut ws: Vec<String> = neighbor_fn(g, v)
.into_iter()
.filter(|w| pos.contains_key(w))
.collect();
if ws.len() > 0 {
ws.sort_by(|w1, w2| pos.get(w1).unwrap().cmp(pos.get(w2).unwrap()));
let mp = (ws.len() as f32 - 1.0) / 2.0;
let mut i = mp as usize;
let il = mp.ceil() as usize;
while i <= il {
let w = ws[i].clone();
if align.get(v).unwrap() == v
&& prev_idx < (pos.get(&w).cloned().unwrap() as i32)
&& !has_conflict(conflicts, v, &w)
{
align.insert(w.clone(), v.clone());
root.insert(v.clone(), root.get(&w).unwrap().clone());
align.insert(v.clone(), root.get(&w).unwrap().clone());
prev_idx = pos.get(&w).unwrap().clone() as i32;
}
i += 1;
}
}
});
});
let align_keys: Vec<String> = align.keys().cloned().collect();
return (root, align_keys);
}
pub fn horizontal_compaction(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
root: &OrderedHashMap<String, String>,
align: &Vec<String>,
reverse_sep: bool,
) -> OrderedHashMap<String, f32> {
// This portion of the algorithm differs from BK due to a number of problems.
// Instead of their algorithm we construct a new block graph and do two
// sweeps. The first sweep places blocks with the smallest possible
// coordinates. The second sweep removes unused space by moving blocks to the
// greatest coordinates without violating separation.
let mut xs: OrderedHashMap<String, f32> = OrderedHashMap::new();
let block_g: Graph<GraphOption, String, f32> =
build_block_graph(g, layering, root, reverse_sep);
let border_type = if reverse_sep {
BorderTypeName::BorderLeft
} else {
BorderTypeName::BorderRight
};
fn iterate(
set_xs_func: fn(
&String,
&mut OrderedHashMap<String, f32>,
&Graph<GraphOption, String, f32>,
&Graph<GraphConfig, GraphNode, GraphEdge>,
&BorderTypeName,
),
next_nodes_func: Box<dyn Fn(&Graph<GraphOption, String, f32>, &String) -> Vec<String>>,
block_g: &Graph<GraphOption, String, f32>,
xs: &mut OrderedHashMap<String, f32>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
border_type: &BorderTypeName,
) {
let mut stack = block_g.nodes();
let mut elem = stack.pop();
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
while elem.is_some() {
let elem_ = elem.unwrap();
if visited.contains_key(&elem_) {
set_xs_func(&elem_, xs, block_g, g, border_type);
} else {
visited.insert(elem_.clone(), true);
stack.push(elem_.clone());
stack.append(&mut (next_nodes_func(block_g, &elem_) as Vec<String>));
}
elem = stack.pop();
}
}
// First pass, assign smallest coordinates
fn pass1(
elem: &String,
xs: &mut OrderedHashMap<String, f32>,
block_g: &Graph<GraphOption, String, f32>,
_g: &Graph<GraphConfig, GraphNode, GraphEdge>,
_border_type: &BorderTypeName,
) {
let in_edges = block_g.in_edges(elem, None).unwrap_or(vec![]);
let val: f32 = in_edges.iter().fold(0.0, |acc, e| {
let pred_x = xs.get(&e.v).cloned().unwrap_or(0.0);
let sep = block_g.edge_with_obj(&e).cloned().unwrap_or(0.0);
let ev = pred_x + sep;
acc.max(ev)
});
xs.insert(elem.clone(), val);
}
// Second pass, assign greatest coordinates
fn pass2(
elem: &String,
xs: &mut OrderedHashMap<String, f32>,
block_g: &Graph<GraphOption, String, f32>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
border_type: &BorderTypeName,
) {
let out_edges = block_g.out_edges(elem, None).unwrap_or(vec![]);
let min: f64 = out_edges.iter().fold(f64::INFINITY, |acc, e| {
let succ_x = xs.get(&e.w).cloned().unwrap_or(0.0);
let sep = block_g.edge_with_obj(&e).cloned().unwrap_or(0.0);
let ev = succ_x - sep;
acc.min(ev as f64)
});
if min != f64::INFINITY {
let Some(node) = g.node(elem) else {
return;
};
if node.border_type.as_ref() != Some(border_type) {
xs.insert(
elem.clone(),
xs.get(elem).cloned().unwrap_or(0.0).max(min as f32),
);
}
}
}
iterate(
pass1,
Box::new(|block_g, v| -> Vec<String> { block_g.predecessors(v).unwrap_or(vec![]) }),
&block_g,
&mut xs,
g,
&border_type,
);
iterate(
pass2,
Box::new(|block_g, v| -> Vec<String> { block_g.successors(v).unwrap_or(vec![]) }),
&block_g,
&mut xs,
g,
&border_type,
);
// Assign x coordinates to all nodes
align.iter().for_each(|v| {
let Some(root_id) = root.get(v) else {
return;
};
let Some(root_x) = xs.get(root_id).cloned() else {
return;
};
xs.insert(v.clone(), root_x);
});
xs
}
pub fn build_block_graph(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
layering: &Vec<Vec<String>>,
root: &OrderedHashMap<String, String>,
reverse_sep: bool,
) -> Graph<GraphOption, String, f32> {
let mut block_graph: Graph<GraphOption, String, f32> = Graph::new(None);
let graph_label = g.graph();
let sep_fn: Box<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &String, &String) -> f32> =
sep(
graph_label.nodesep.as_ref().unwrap(),
graph_label.edgesep.as_ref().unwrap(),
&reverse_sep,
);
layering.iter().for_each(|layer| {
let mut u: Option<String> = None;
layer.iter().for_each(|v| {
let v_root = root.get(v).unwrap();
block_graph.set_node(v_root.clone(), None);
if u.is_some() {
let u_ = u.as_ref().unwrap();
let u_root = root.get(u_).unwrap();
if u_root != v_root {
let prev_max = block_graph
.edge(&u_root, &v_root, None)
.cloned()
.unwrap_or(0.0);
let _ = block_graph.set_edge(
&u_root,
&v_root,
Some((sep_fn(g, v, u_) as f32).max(prev_max)),
None,
);
}
}
u = Some(v.clone());
});
});
block_graph
}
/*
* Returns the alignment that has the smallest width of the given alignments.
*/
pub fn find_smallest_width_alignment<'a>(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
xss: &'a OrderedHashMap<String, OrderedHashMap<String, f32>>,
) -> &'a OrderedHashMap<String, f32> {
xss.values()
.min_by(|xs1, xs2| {
let mut max1 = f64::NEG_INFINITY;
let mut min1 = f64::INFINITY;
xs1.iter().for_each(|(v, x)| {
let half_width = width(g, v) / 2.0;
max1 = max1.max((*x + half_width) as f64);
min1 = min1.min((*x - half_width) as f64);
});
let r1 = max1 - min1;
let mut max2 = f64::NEG_INFINITY;
let mut min2 = f64::INFINITY;
xs2.iter().for_each(|(v, x)| {
let half_width = width(g, v) / 2.0;
max2 = max2.max((*x + half_width) as f64);
min2 = min2.min((*x - half_width) as f64);
});
let r2 = max2 - min2;
r1.total_cmp(&r2)
})
.unwrap()
}
/*
* Align the coordinates of each of the layout alignments such that
* left-biased alignments have their minimum coordinate at the same point as
* the minimum coordinate of the smallest width alignment and right-biased
* alignments have their maximum coordinate at the same point as the maximum
* coordinate of the smallest width alignment.
*/
fn align_coordinates(
xss: &mut OrderedHashMap<String, OrderedHashMap<String, f32>>,
align_to: &OrderedHashMap<String, f32>,
) {
let align_to_vals: Vec<f32> = align_to.values().cloned().collect();
let align_to_min = align_to_vals
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
.clone();
let align_to_max = align_to_vals
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
.clone();
vec!["u", "d"].iter().for_each(|vert| {
vec!["l", "r"].iter().for_each(|horiz| {
let alignment = vert.to_string() + horiz;
let xs = xss.get(&alignment).unwrap();
if xs == align_to {
return;
}
let xs_vals: Vec<f32> = xs.values().cloned().collect();
let delta = if *horiz == "l" {
align_to_min
- *xs_vals
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
} else {
align_to_max
- *xs_vals
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
};
if delta != 0.0 {
let _xs = xss.get_mut(&alignment).unwrap();
_xs.values_mut().for_each(|x| {
*x += delta;
});
}
})
})
}
pub fn balance(
xss: &OrderedHashMap<String, OrderedHashMap<String, f32>>,
align: Option<String>,
) -> OrderedHashMap<String, f32> {
let mut xss_clone = xss.clone();
if let Some(ul) = xss_clone.get_mut(&"ul".to_string()) {
let keys: Vec<String> = ul.keys().cloned().collect();
keys.iter().for_each(|v| {
if align.is_some() {
let empty_hash: OrderedHashMap<String, f32> = OrderedHashMap::new();
let empty_string = "".to_string();
let _balance = xss
.get(align.as_ref().unwrap_or(&empty_string))
.unwrap_or(&empty_hash)
.get(v)
.cloned()
.unwrap_or(0.0);
let item = ul.get_mut(v).unwrap();
*item = _balance;
} else {
let mut xs: Vec<f32> = xss
.values()
.map(|_xs| _xs.get(v).cloned().unwrap_or(f64::INFINITY as f32))
.collect();
xs.sort_by(|f1, f2| f1.total_cmp(f2));
let xs1 = xs.get(1).cloned().unwrap_or(0.0);
let xs2 = xs.get(2).cloned().unwrap_or(0.0);
let item = ul.get_mut(v).unwrap();
*item = (xs1 + xs2) / 2.0;
}
});
}
xss_clone.get_mut(&"ul".to_string()).unwrap().to_owned()
}
pub fn position_x(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, f32> {
let layering = util::build_layer_matrix(g);
if layering.iter().all(|layer| layer.is_empty()) {
return OrderedHashMap::new();
}
let mut conflicts = find_type_1_conflicts(g, &layering);
conflicts.extend(find_type_2_conflicts(g, &layering));
let mut xss: OrderedHashMap<String, OrderedHashMap<String, f32>> = OrderedHashMap::new();
let mut adjusted_layering: Option<Vec<Vec<String>>> = None;
vec!["u", "d"].iter().for_each(|vert| {
adjusted_layering = Some(if vert == &"u" {
layering.clone()
} else {
let mut layering_ = layering.clone();
layering_.reverse();
layering_
});
vec!["l", "r"].iter().for_each(|horiz| {
if horiz == &"r" {
adjusted_layering
.as_mut()
.unwrap()
.iter_mut()
.for_each(|inner| inner.reverse());
}
let neighbor_fn: Box<
dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &String) -> Vec<String>,
> = if vert == &"u" {
Box::new(
|g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String| -> Vec<String> {
g.predecessors(v).unwrap_or(vec![])
},
)
} else {
Box::new(
|g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String| -> Vec<String> {
g.successors(v).unwrap_or(vec![])
},
)
};
let align = vertical_alignment(
g,
adjusted_layering.as_ref().unwrap(),
&conflicts,
neighbor_fn,
);
let mut xs = horizontal_compaction(
g,
adjusted_layering.as_ref().unwrap(),
&align.0,
&align.1,
horiz == &"r",
);
if horiz == &"r" {
let mut xs_: OrderedHashMap<String, f32> = OrderedHashMap::new();
xs.iter().for_each(|(k, v)| {
xs_.insert(k.clone(), -v.clone());
});
xs = xs_;
}
xss.insert(String::from(vert.to_string() + horiz), xs);
});
});
let smallest_width = find_smallest_width_alignment(g, &xss).clone();
align_coordinates(&mut xss, &smallest_width);
return balance(&xss, g.graph().align.clone());
}
fn sep(
node_sep: &f32,
edge_sep: &f32,
reverse_sep: &bool,
) -> Box<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &String, &String) -> f32> {
let node_sep_ = node_sep.clone();
let edge_sep_ = edge_sep.clone();
let reverse_sep_ = reverse_sep.clone();
Box::new(
move |g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String, w: &String| -> f32 {
let v_label = g.node(v).unwrap();
let w_label = g.node(w).unwrap();
let mut sum: f32 = 0.0;
let mut delta: f32 = 0.0;
sum += v_label.width / 2.0;
if let Some(v_label_labelpos) = v_label.labelpos.as_ref() {
if v_label_labelpos == "l" {
delta = -v_label.width / 2.0;
} else if v_label_labelpos == "r" {
delta = v_label.width / 2.0;
}
}
if delta != 0.0 {
sum += if reverse_sep_ { delta } else { -delta }
}
sum += if v_label.dummy.is_some() {
edge_sep_
} else {
node_sep_
} / 2.0;
sum += if w_label.dummy.is_some() {
edge_sep_
} else {
node_sep_
} / 2.0;
sum += w_label.width / 2.0;
delta = 0.0;
if let Some(w_label_labelpos) = w_label.labelpos.as_ref() {
if w_label_labelpos == "l" {
delta = w_label.width / 2.0;
} else if w_label_labelpos == "r" {
delta = -w_label.width / 2.0;
}
}
if delta != 0.0 {
sum += if reverse_sep_ { delta } else { -delta }
}
sum
},
)
}
fn width(g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String) -> f32 {
g.node(v).unwrap().width
}

View file

@ -0,0 +1,36 @@
pub mod bk;
use crate::layout::position::bk::position_x;
use crate::layout::util;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
pub fn position(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let mut ncg: Graph<GraphConfig, GraphNode, GraphEdge> = util::as_non_compound_graph(g);
position_y(&mut ncg);
position_x(&mut ncg).iter().for_each(|(v, x)| {
g.node_mut(v).unwrap().x = x.clone();
g.node_mut(v).unwrap().y = ncg.node(v).unwrap().y;
});
}
fn position_y(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let layering = util::build_layer_matrix(g);
let rank_sep = g.graph().ranksep.clone().unwrap();
let mut prev_y = 0.0;
layering.iter().for_each(|layer| {
let max_height: f32 = layer
.iter()
.map(|v| g.node(v).unwrap().height as i32)
.max()
.unwrap_or(0) as f32;
layer.iter().for_each(|v| {
let node = g.node_mut(v).unwrap();
node.y = prev_y + max_height / 2.0;
});
prev_y += max_height + rank_sep;
});
}

View file

@ -0,0 +1,137 @@
use crate::layout::rank::util::slack;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::{Edge, Graph, GraphOption};
/*
* Constructs a spanning tree with tight edges and adjusted the input node's
* ranks to achieve this. A tight edge is one that is has a length that matches
* its "minlen" attribute.
*
* The basic structure for this function is derived from Gansner, et al., "A
* Technique for Drawing Directed Graphs."
*
* Pre-conditions:
*
* 1. Graph must be a DAG.
* 2. Graph must be connected.
* 3. Graph must have at least one node.
* 5. Graph nodes must have been previously assigned a "rank" property that
* respects the "minlen" property of incident edges.
* 6. Graph edges must have a "minlen" property.
*
* Post-conditions:
*
* - Graph nodes will have their rank adjusted to ensure that all edges are
* tight.
*
* Returns a tree (undirected graph) that is constructed using only "tight"
* edges.
*/
pub fn feasible_tree(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(Some(GraphOption {
directed: Some(false),
multigraph: Some(false),
compound: Some(false),
}));
// Choose arbitrary node from which to start our tree
let start = g.nodes().first().cloned().unwrap_or("".to_string());
let size = g.node_count();
t.set_node(start, Some(GraphNode::default()));
while tight_tree(&mut t, g) < size {
if let Some(edge) = find_min_stack_edge(&t, g) {
let delta = if t.has_node(&edge.v) {
slack(g, &edge)
} else {
-1 * slack(g, &edge)
};
shift_ranks(&t, g, delta);
}
}
t
}
/*
* Finds a maximal tree of tight edges and returns the number of nodes in the
* tree.
*/
fn tight_tree(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
) -> usize {
fn dfs(
v: &String,
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
) {
let node_edges = g.node_edges(v, None).unwrap_or(vec![]);
for node_edge in node_edges {
let edge_v = node_edge.v.clone();
let mut _w: Option<&String> = None;
if v == &edge_v {
_w = Some(&node_edge.w);
} else {
_w = Some(&edge_v);
}
let w = _w.unwrap().clone();
if !t.has_node(&w) && slack(g, &node_edge) == 0 {
t.set_node(w.clone(), Some(GraphNode::default()));
let _ = t.set_edge(&v, &w, Some(GraphEdge::default()), None);
dfs(&w, t, g);
}
}
}
let nodes = t.nodes();
for node_id in nodes.into_iter() {
dfs(&node_id, t, g);
}
return t.node_count();
}
/*
* Finds the edge with the smallest slack that is incident on tree and returns
* it.
*/
fn find_min_stack_edge(
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
) -> Option<Edge> {
let edges = g.edges();
let result = edges
.iter()
.map(|e| {
let mut e_: Option<i32> = None;
if t.has_node(&e.v) != t.has_node(&e.w) {
e_ = Some(slack(g, e));
}
(e, e_)
})
.filter(|(_, e_)| e_.is_some())
.min_by(|(_, e1_), (_, e2_)| e1_.unwrap().cmp(&e2_.unwrap()));
if result.is_some() {
Some(result.unwrap().0.clone())
} else {
None
}
}
fn shift_ranks(
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
delta: i32,
) {
let nodes = t.nodes();
for node_id in nodes.into_iter() {
let node_ = g.node_mut(&node_id);
if let Some(node) = node_ {
node.rank = Some(node.rank.unwrap_or(0) + delta);
}
}
}

View file

@ -0,0 +1,53 @@
pub mod feasible_tree;
pub mod network_simplex;
pub mod util;
use crate::layout::rank::feasible_tree::feasible_tree;
use crate::layout::rank::network_simplex::network_simplex;
use crate::layout::rank::util::longest_path;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::Graph;
/*
* Assigns a rank to each node in the input graph that respects the "minlen"
* constraint specified on edges between nodes.
*
* This basic structure is derived from Gansner, et al., "A Technique for
* Drawing Directed Graphs."
*
* Pre-conditions:
*
* 1. Graph must be a connected DAG
* 2. Graph nodes must be objects
* 3. Graph edges must have "weight" and "minlen" attributes
*
* Post-conditions:
*
* 1. Graph nodes will have a "rank" attribute based on the results of the
* algorithm. Ranks can start at any index (including negative), we'll
* fix them up later.
*/
pub fn rank(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let _ranker = g.graph().ranker.clone();
match _ranker {
Some(ranker) => {
let ranker_str = &*ranker;
if ranker_str == "network-simplex" {
network_simplex(g);
} else if ranker_str == "tight-tree" {
tight_tree_ranker(g);
} else if ranker_str == "longest-path" {
longest_path(g);
}
}
_ => {
network_simplex(g);
}
}
}
fn tight_tree_ranker(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
longest_path(g);
feasible_tree(g);
}

View file

@ -0,0 +1,365 @@
use std::collections::HashMap;
use crate::layout::rank::feasible_tree::feasible_tree;
use crate::layout::rank::util::{longest_path, slack};
use crate::layout::util;
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::algo::postorder::postorder;
use graphlib_rust::{Edge, Graph};
use ordered_hashmap::OrderedHashMap;
/*
* The network simplex algorithm assigns ranks to each node in the input graph
* and iteratively improves the ranking to reduce the length of edges.
*
* Preconditions:
*
* 1. The input graph must be a DAG.
* 2. All nodes in the graph must have an object value.
* 3. All edges in the graph must have "minlen" and "weight" attributes.
*
* Postconditions:
*
* 1. All nodes in the graph will have an assigned "rank" attribute that has
* been optimized by the network simplex algorithm. Ranks start at 0.
*
*
* A rough sketch of the algorithm is as follows:
*
* 1. Assign initial ranks to each node. We use the longest path algorithm,
* which assigns ranks to the lowest position possible. In general this
* leads to very wide bottom ranks and unnecessarily long edges.
* 2. Construct a feasible tight tree. A tight tree is one such that all
* edges in the tree have no slack (difference between length of edge
* and minlen for the edge). This by itself greatly improves the assigned
* rankings by shorting edges.
* 3. Iteratively find edges that have negative cut values. Generally a
* negative cut value indicates that the edge could be removed and a new
* tree edge could be added to produce a more compact graph.
*
* Much of the algorithms here are derived from Gansner, et al., "A Technique
* for Drawing Directed Graphs." The structure of the file roughly follows the
* structure of the overall algorithm.
*/
pub fn network_simplex(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let mut simplified = util::simplify(g);
longest_path(&mut simplified);
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = feasible_tree(&mut simplified);
init_low_lim_values(&mut t, None);
init_cut_values(&mut t, &mut simplified);
while let Some(e) = leave_edge(&t) {
let Some(f) = enter_edge(&t, &simplified, &e) else {
break;
};
exchange_edges(&mut t, &mut simplified, &e, f);
}
for v in g.nodes() {
let Some(node) = g.node_mut(&v) else {
continue;
};
let Some(simple_node) = simplified.node(&v) else {
continue;
};
node.rank = simple_node.rank;
}
}
/*
* Initializes cut values for all edges in the tree.
*/
fn init_cut_values(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
) {
let node_ids = t.nodes();
let mut vs = postorder(t, &node_ids);
vs.pop();
for node_id in vs {
assign_cut_value(t, g, &node_id);
}
}
/*
* Given the tight tree, its graph, and a child in the graph calculate and
* return the cut value for the edge between the child and its parent.
*/
fn assign_cut_value(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
child: &String,
) {
let cutvalue = calc_cut_value(t, g, child);
let child_lab_ = t.node_mut(child);
if let Some(child_lab) = child_lab_ {
let parent = child_lab.parent.clone().unwrap_or("".to_string());
let edge_label_ = t.edge_mut(&child, &parent, None);
if let Some(edge_label) = edge_label_ {
edge_label.cutvalue = Some(cutvalue);
}
}
}
/*
* Given the tight tree, its graph, and a child in the graph calculate and
* return the cut value for the edge between the child and its parent.
*/
fn calc_cut_value(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
child: &String,
) -> f32 {
// The accumulated cut value for the edge between this node and its parent
let mut cut_value = 0.0;
let child_lab_ = t.node_mut(child);
if let Some(child_lab) = child_lab_ {
let parent = child_lab.parent.clone().unwrap_or("".to_string());
// True if the child is on the tail end of the edge in the directed graph
let mut child_is_tail = true;
// The graph's view of the tree edge we're inspecting
let mut graph_edge = g.edge_mut(child, &parent, None);
if graph_edge.is_none() {
child_is_tail = false;
graph_edge = g.edge_mut(&parent, &child, None);
}
cut_value = graph_edge
.cloned()
.unwrap_or(GraphEdge::default())
.weight
.unwrap_or(0.0);
let edge_objs_ = g.node_edges(child, None);
if let Some(edge_objs) = edge_objs_ {
for e in edge_objs {
let is_out_edge = &e.v == child;
let other = if is_out_edge {
e.w.clone()
} else {
e.v.clone()
};
if other != parent {
let points_to_head = is_out_edge == child_is_tail;
let other_weight = g
.edge_with_obj(&e)
.unwrap_or(&GraphEdge::default())
.weight
.unwrap_or(0.0);
cut_value += if points_to_head {
other_weight
} else {
-other_weight
};
if is_tree_edge(t, child, &other) {
let out_cut_value = t
.edge(&child, &other, None)
.unwrap_or(&GraphEdge::default())
.cutvalue
.unwrap_or(0.0);
cut_value += if points_to_head {
-out_cut_value
} else {
out_cut_value
}
}
}
}
}
}
cut_value
}
fn init_low_lim_values(tree: &mut Graph<GraphConfig, GraphNode, GraphEdge>, root_: Option<String>) {
let mut root = tree.nodes().first().cloned().unwrap_or("".to_string());
if root_.is_some() {
root = root_.unwrap();
}
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
dfs_assign_low_lim(tree, &mut visited, 1, &root, None);
}
fn dfs_assign_low_lim(
tree: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
visited: &mut OrderedHashMap<String, bool>,
next_lim_: usize,
v: &String,
parent: Option<&String>,
) -> usize {
let low = next_lim_.clone();
let mut next_lim = next_lim_.clone();
visited.entry(v.clone()).or_insert(true);
let neighbors_ = tree.neighbors(v);
if let Some(neighbors) = neighbors_ {
for w in neighbors.into_iter() {
if !visited.contains_key(&w) {
next_lim = dfs_assign_low_lim(tree, visited, next_lim.clone(), &w, Some(v));
}
}
}
let label_ = tree.node_mut(v);
if let Some(label) = label_ {
label.low = Some(low);
label.lim = Some(next_lim.clone());
next_lim += 1;
if parent.is_some() {
label.parent = Some(parent.cloned().unwrap());
} else {
// TODO should be able to remove this when we incrementally update low lim
label.parent = None;
}
}
next_lim
}
fn leave_edge(tree: &Graph<GraphConfig, GraphNode, GraphEdge>) -> Option<Edge> {
let edge_objs = tree.edges();
edge_objs
.iter()
.find(|edge_obj| {
tree.edge_with_obj(edge_obj)
.unwrap_or(&GraphEdge::default())
.cutvalue
.unwrap_or(0.0)
< 0.0
})
.cloned()
}
fn enter_edge(
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
edge: &Edge,
) -> Option<Edge> {
let mut v = edge.v.clone();
let mut w = edge.w.clone();
// For the rest of this function we assume that v is the tail and w is the
// head, so if we don't have this edge in the graph we should flip it to
// match the correct orientation.
if !g.has_edge(&v, &w, None) {
v = edge.w.clone();
w = edge.v.clone();
}
let v_label = t.node(&v).cloned().unwrap_or(GraphNode::default());
let w_label = t.node(&w).cloned().unwrap_or(GraphNode::default());
let mut tail_label = &v_label;
let mut flip = false;
// If the root is in the tail of the edge then we need to flip the logic that
// checks for the head and tail nodes in the candidates function below.
if v_label.lim.clone().unwrap_or(0) > w_label.lim.clone().unwrap_or(0) {
tail_label = &w_label;
flip = true;
}
let edge_objs = g.edges();
let candidates = edge_objs.iter().filter(|edge_obj| {
let v_node = t.node(&edge_obj.v).cloned().unwrap_or(GraphNode::default());
let w_node = t.node(&edge_obj.w).cloned().unwrap_or(GraphNode::default());
flip == is_descendant(&v_node, tail_label) && flip != is_descendant(&w_node, tail_label)
});
candidates
.min_by(|e1, e2| slack(g, e1).cmp(&slack(g, e2)))
.cloned()
}
fn exchange_edges(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
e: &Edge,
f: Edge,
) {
let v = e.v.clone();
let w = e.w.clone();
t.remove_edge(&v, &w, None);
let _ = t.set_edge(&f.v, &f.w, Some(GraphEdge::default()), None);
init_low_lim_values(t, None);
init_cut_values(t, g);
update_ranks(t, g);
}
fn update_ranks(
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
) {
let Some(root) = t.nodes().first().cloned() else {
return;
};
let mut children: HashMap<String, Vec<String>> = HashMap::new();
for v in t.nodes() {
let parent = t.node(&v).unwrap_or(&GraphNode::default()).parent.clone();
let Some(parent) = parent else {
continue;
};
children.entry(parent).or_default().push(v);
}
let mut stack: Vec<String> = vec![root.clone()];
while let Some(parent) = stack.pop() {
let Some(vs) = children.get(&parent).cloned() else {
continue;
};
for v in vs {
let mut edge = g.edge(&v, &parent, None);
let mut flipped = false;
if edge.is_none() {
edge = g.edge(&parent, &v, None);
flipped = true;
}
let minlen = edge.unwrap_or(&GraphEdge::default()).minlen.unwrap_or(0.0);
let parent_rank = g
.node(&parent)
.unwrap_or(&GraphNode::default())
.rank
.unwrap_or(0);
if let Some(v_node) = g.node_mut(&v) {
v_node.rank = Some(
parent_rank
+ if flipped {
minlen as i32
} else {
-(minlen as i32)
},
);
}
stack.push(v);
}
}
}
/*
* Returns true if the edge is in the tree.
*/
fn is_tree_edge(tree: &Graph<GraphConfig, GraphNode, GraphEdge>, u: &String, v: &String) -> bool {
tree.has_edge(&u, &v, None)
}
/*
* Returns true if the specified node is descendant of the root node per the
* assigned low and lim attributes in the tree.
*/
fn is_descendant(v_label: &GraphNode, root_label: &GraphNode) -> bool {
let low = root_label.low.clone().unwrap_or(0);
let v_lim = v_label.lim.clone().unwrap_or(0);
let root_lim = root_label.lim.clone().unwrap_or(0);
low <= v_lim && v_lim <= root_lim
}

View file

@ -0,0 +1,99 @@
use crate::{GraphConfig, GraphEdge, GraphNode};
use graphlib_rust::{Edge, Graph};
use ordered_hashmap::OrderedHashMap;
/*
* Initializes ranks for the input graph using the longest path algorithm. This
* algorithm scales well and is fast in practice, it yields rather poor
* solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
* ranks wide and leaving edges longer than necessary. However, due to its
* speed, this algorithm is good for getting an initial ranking that can be fed
* into other algorithms.
*
* This algorithm does not normalize layers because it will be used by other
* algorithms in most cases. If using this algorithm directly, be sure to
* run normalize at the end.
*
* Pre-conditions:
*
* 1. Input graph is a DAG.
* 2. Input graph node labels can be assigned properties.
*
* Post-conditions:
*
* 1. Each node will be assign an (unnormalized) "rank" property.
*/
pub fn longest_path(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
fn dfs(
v: &String,
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
visited: &mut OrderedHashMap<String, bool>,
) -> i32 {
let node_label = g.node(v);
if visited.contains_key(v) {
return node_label
.cloned()
.unwrap_or(GraphNode::default())
.rank
.unwrap_or(0);
}
visited.insert(v.clone(), true);
let ranks: Vec<i32> = g
.out_edges(v, None)
.unwrap_or(vec![])
.iter()
.map(|e| {
dfs(&e.w, g, visited)
- (g.edge_with_obj(&e)
.cloned()
.unwrap_or(GraphEdge::default())
.minlen
.unwrap_or(0.0)
.round() as i32)
})
.collect();
let rank: i32 = ranks.iter().min().cloned().unwrap_or(0) as i32;
{
let _node_label = g.node_mut(v);
if let Some(node_label) = _node_label {
node_label.rank = Some(rank.clone());
}
}
return rank;
}
for node_id in g.sources().into_iter() {
dfs(&node_id, g, &mut visited);
}
}
/*
* Returns the amount of slack for the given edge. The slack is defined as the
* difference between the length of the edge and its minimum length.
*/
pub fn slack(g: &Graph<GraphConfig, GraphNode, GraphEdge>, e: &Edge) -> i32 {
let w_rank = g
.node(&e.w)
.cloned()
.unwrap_or(GraphNode::default())
.rank
.unwrap_or(0);
let v_rank = g
.node(&e.v)
.cloned()
.unwrap_or(GraphNode::default())
.rank
.unwrap_or(0);
let minlen = g
.edge_with_obj(e)
.cloned()
.unwrap_or(GraphEdge::default())
.minlen
.unwrap_or(10.0)
.round() as i32;
return w_rank - v_rank - minlen;
}

View file

@ -0,0 +1,393 @@
use crate::layout::{GraphConfig, GraphEdge, GraphNode};
use crate::GraphEdgePoint;
use graphlib_rust::{Graph, GraphOption};
use ordered_hashmap::OrderedHashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
// VENDORING PATCH: upstream used a `static mut` mutated in an `unsafe` block,
// which is a data race when the engine renders on multiple threads (e.g. the
// parallel `cargo test` suite). An `AtomicUsize` is behaviour-preserving (still
// hands out monotonic unique ids) and removes the only `unsafe` in this crate.
static UNIQUE_STARTER: AtomicUsize = AtomicUsize::new(0);
pub fn unique_id() -> usize {
UNIQUE_STARTER.fetch_add(1, Ordering::Relaxed) + 1
}
/*
* Adds a dummy node to the graph and return v.
*/
pub fn add_dummy_node(
graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
node_type: String,
data: GraphNode,
name: String,
) -> String {
// Generating Random Id
let mut node_id = format!("{}{}", name, unique_id());
while graph.has_node(&node_id) {
node_id = format!("{}{}", name, unique_id());
}
// Setting in Graph
let mut node_data = data.clone();
node_data.dummy = Some(node_type);
graph.set_node(node_id.clone(), Some(node_data));
return node_id;
}
/*
* Returns a new graph with only simple edges. Handles aggregation of data
* associated with multi-edges.
*/
pub fn simplify(
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
let mut simplified: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(Some(GraphOption {
directed: Some(true),
multigraph: None,
compound: None,
}));
simplified.set_graph(g.graph().clone());
let nodes = g.nodes();
let edges = g.edges();
for node_id in nodes.into_iter() {
simplified.set_node(node_id.clone(), g.node(&node_id).cloned());
}
for edge_obj in edges.into_iter() {
let edge_label_ = g.edge_with_obj(&edge_obj);
let mut simple_label = simplified
.edge(&edge_obj.v, &edge_obj.w, None)
.cloned()
.unwrap_or_else(|| {
let mut edge = GraphEdge::default();
edge.weight = Some(0.0);
edge.minlen = Some(1.0);
edge
});
let edge_label = edge_label_.cloned().unwrap_or_else(|| {
let mut edge = GraphEdge::default();
edge.weight = Some(0.0);
edge.minlen = Some(1.0);
edge
});
let minlen = edge_label.minlen.unwrap_or(1.0);
simple_label.minlen = Some(simple_label.minlen.unwrap_or(1.0).max(minlen));
let weight = edge_label.weight.unwrap_or(0.0);
simple_label.weight = Some(simple_label.weight.unwrap_or(0.0) + weight);
let _ = simplified.set_edge(&edge_obj.v, &edge_obj.w, Some(simple_label), None);
}
simplified
}
/*
* it implement same logic as simplify do but, it uses Ref instead of creating new graph
*/
pub fn simplify_ref(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let edges = g.edges();
for edge_obj in edges.into_iter() {
let edge_label_ = g.edge_mut_with_obj(&edge_obj);
if let Some(edge_label) = edge_label_ {
if edge_label.weight.is_none() {
edge_label.weight = Some(0.0);
}
if edge_label.minlen.is_none() {
edge_label.minlen = Some(1.0);
}
}
}
}
pub fn as_non_compound_graph(
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
let mut simplified: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(Some(GraphOption {
directed: Some(true),
multigraph: Some(true),
compound: Some(false),
}));
simplified.set_graph(g.graph().clone());
let nodes = g.nodes();
for v in nodes.into_iter() {
if g.children(&v).len() == 0 {
simplified.set_node(
v.clone(),
Some(g.node(&v).cloned().unwrap_or(GraphNode::default())),
);
}
}
let edge_objs = g.edges();
for e in edge_objs.into_iter() {
let _ = simplified.set_edge_with_obj(&e, g.edge_with_obj(&e).cloned());
}
return simplified;
}
pub fn transfer_node_edge_labels(
source: &Graph<GraphConfig, GraphNode, GraphEdge>,
destination: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
) {
let nodes = source.nodes();
for v in nodes.into_iter() {
if source.children(&v).len() == 0 {
destination.set_node(
v.clone(),
Some(source.node(&v).cloned().unwrap_or(GraphNode::default())),
);
}
}
let edge_objs = source.edges();
for e in edge_objs.into_iter() {
let _ = destination.set_edge_with_obj(&e, source.edge_with_obj(&e).cloned());
}
}
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
/*
* Finds where a line starting at point ({x, y}) would intersect a rectangle
* ({x, y, width, height}) if it were pointing at the rectangle's center.
*/
pub fn intersect_rect(rect: &Rect, point: &GraphEdgePoint) -> GraphEdgePoint {
let x = rect.x;
let y = rect.y;
// Rectangle intersection algorithm from:
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
let dx = point.x - x;
let dy = point.y - y;
let w = rect.width / 2.0;
let h = rect.height / 2.0;
if dx == 0.0 && dy == 0.0 {
return GraphEdgePoint { x: x + w, y };
}
let (sx, sy) = if (dy.abs() * w) > (dx.abs() * h) {
// Intersection is top or bottom of rect.
if dy < 0.0 {
(-h * dx / dy, -h)
} else {
(h * dx / dy, h)
}
} else {
// Intersection is left or right of rect.
if dx < 0.0 {
(-w, -w * dy / dx)
} else {
(w, w * dy / dx)
}
};
GraphEdgePoint {
x: x + sx,
y: y + sy,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_close(left: f32, right: f32) {
assert!((left - right).abs() < f32::EPSILON);
}
#[test]
fn intersect_rect_returns_boundary_point_for_center_point() {
let rect = Rect {
x: 10.0,
y: 20.0,
width: 8.0,
height: 4.0,
};
let point = GraphEdgePoint { x: 10.0, y: 20.0 };
let intersection = intersect_rect(&rect, &point);
assert_close(intersection.x, 14.0);
assert_close(intersection.y, 20.0);
}
}
/*
* Given a DAG with each node assigned "rank" and "order" properties, this
* function will produce a matrix with the ids of each node.
*/
pub fn build_layer_matrix(g: &Graph<GraphConfig, GraphNode, GraphEdge>) -> Vec<Vec<String>> {
let mut layering: Vec<OrderedHashMap<usize, String>> =
(0..=max_rank(g)).map(|_| OrderedHashMap::new()).collect();
g.nodes().iter().for_each(|v| {
let node = g.node(v).unwrap();
let Some(rank) = node.rank else {
return;
};
let layer: &mut OrderedHashMap<usize, String> = layering.get_mut(rank as usize).unwrap();
layer.insert(node.order.unwrap_or(0), v.clone());
});
layering
.into_iter()
.map(|layer| {
let mut keys: Vec<usize> = layer.keys().cloned().collect();
keys.sort();
keys.iter()
.map(|key| layer.get(key).cloned().unwrap())
.collect()
})
.collect()
}
/*
* Adjusts the ranks for all nodes in the graph such that all nodes v have
* rank(v) >= 0 and at least one node w has rank(w) = 0.
*/
pub fn normalize_ranks(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let node_ids = graph.nodes();
let node_ranks: Vec<i32> = node_ids
.iter()
.map(|v| {
graph
.node(v)
.unwrap_or(&GraphNode::default())
.rank
.clone()
.unwrap_or(0)
})
.collect();
let min = node_ranks.iter().min().cloned().unwrap_or(0);
node_ids.iter().for_each(|node_id| {
let node_ = graph.node_mut(node_id);
if let Some(node) = node_ {
if node.rank.is_some() {
node.rank = Some(node.rank.unwrap() - min);
}
}
})
}
pub fn remove_empty_ranks(graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
let nodes: Vec<String> = graph.nodes();
if nodes.is_empty() {
return;
}
let node_ranks: Vec<i32> = nodes
.iter()
.map(|v| {
graph
.node(v)
.cloned()
.unwrap_or(GraphNode::default())
.rank
.unwrap_or(0)
})
.collect();
let offset: i32 = node_ranks.iter().min().cloned().unwrap_or(0);
let max_rank: i32 = node_ranks.iter().max().cloned().unwrap_or(0) - offset;
let mut layers: Vec<Vec<String>> = vec![Vec::new(); (max_rank + 1).max(0) as usize];
for v in &nodes {
let rank = graph
.node(v)
.unwrap_or(&GraphNode::default())
.rank
.unwrap_or(0)
- offset;
if rank >= 0 {
layers[rank as usize].push(v.clone());
}
}
let node_rank_factor = graph.graph().node_rank_factor.clone().unwrap_or(0.0) as i32;
if node_rank_factor <= 0 {
return;
}
let mut delta = 0;
for (i, vs) in layers.iter().enumerate() {
let i = i as i32;
if vs.is_empty() && i % node_rank_factor != 0 {
delta -= 1;
} else if delta != 0 {
for v in vs {
if let Some(node) = graph.node_mut(v) {
node.rank = Some(node.rank.unwrap_or(0) + delta);
}
}
}
}
}
pub fn add_border_node(
graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
prefix: &str,
rank: Option<&usize>,
order: Option<&usize>,
) -> String {
let mut node = GraphNode::default();
if rank.is_some() {
node.rank = Some(rank.cloned().unwrap_or(0) as i32);
}
if order.is_some() {
node.order = Some(order.cloned().unwrap_or(0));
}
return add_dummy_node(graph, "border".to_string(), node, prefix.to_string());
}
pub fn max_rank(g: &Graph<GraphConfig, GraphNode, GraphEdge>) -> i32 {
g.nodes()
.iter()
.filter_map(|v| g.node(v).and_then(|n| n.rank))
.max()
.unwrap_or(0)
}
#[derive(Debug, Clone)]
pub struct PartitionResponse<V> {
pub lhs: Vec<V>,
pub rhs: Vec<V>,
}
/*
* Partition a collection into two groups: `lhs` and `rhs`. If the supplied
* function returns true for an entry it goes into `lhs`. Otherwise it goes
* into `rhs.
*/
pub fn partition<V: Clone>(
collection: &Vec<V>,
fn_: Box<dyn Fn(&V) -> bool>,
) -> PartitionResponse<V> {
let mut result: PartitionResponse<V> = PartitionResponse {
lhs: vec![],
rhs: vec![],
};
collection.iter().for_each(|val| {
if fn_(val) {
result.lhs.push(val.clone());
} else {
result.rhs.push(val.clone());
}
});
return result;
}

131
third_party/dagre_rust/src/lib.rs vendored Normal file
View file

@ -0,0 +1,131 @@
pub mod layout;
use crate::layout::add_border_segments::BorderTypeName;
use graphlib_rust::Edge;
use ordered_hashmap::OrderedHashMap;
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct GraphNode {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub class: Option<String>,
pub label: Option<GraphEdge>,
pub padding: Option<f32>,
pub padding_x: Option<f32>,
pub padding_y: Option<f32>,
pub rx: Option<f32>,
pub ry: Option<f32>,
pub shape: Option<String>,
pub dummy: Option<String>,
pub rank: Option<i32>,
pub min_rank: Option<i32>,
pub max_rank: Option<i32>,
pub order: Option<usize>,
pub border_top: Option<String>,
pub border_bottom: Option<String>,
pub border_left: Option<OrderedHashMap<i32, String>>,
pub border_right: Option<OrderedHashMap<i32, String>>,
pub border_left_: Option<String>,
pub border_right_: Option<String>,
pub low: Option<usize>,
pub lim: Option<usize>,
pub parent: Option<String>,
pub e: Option<Edge>,
pub edge_label: Option<GraphEdge>,
pub edge_obj: Option<Edge>,
pub labelpos: Option<String>,
pub border_type: Option<BorderTypeName>,
pub self_edges: Vec<(Edge, GraphEdge)>,
}
#[derive(Debug, Clone, Default)]
pub struct GraphEdgePoint {
pub x: f32,
pub y: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GraphEdge {
pub forward_name: Option<String>,
pub reversed: Option<bool>,
pub minlen: Option<f32>,
pub weight: Option<f32>,
pub width: Option<f32>,
pub height: Option<f32>,
pub label_rank: Option<i32>,
pub labeloffset: Option<f32>,
pub labelpos: Option<String>,
pub nesting_edge: Option<bool>,
pub cutvalue: Option<f32>,
pub points: Option<Vec<GraphEdgePoint>>,
pub x: f32,
pub y: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GraphConfig {
pub width: f32,
pub height: f32,
pub nodesep: Option<f32>, // default 50
pub edgesep: Option<f32>, // default 20
pub ranksep: Option<f32>, // default 50
pub marginx: Option<f32>, // default 0
pub marginy: Option<f32>, // default 0
pub rankdir: Option<String>, // lr, lr, tb, bt // default tb
pub acyclicer: Option<String>, // greedy, dfs, unknown-should-still-work
pub ranker: Option<String>, // "longest-path", "tight-tree", "network-simplex", "unknown-should-still-work"
pub align: Option<String>,
pub nesting_root: Option<String>, // id of dummy nesting root
pub root: Option<String>,
pub node_rank_factor: Option<f32>, // default 0
pub dummy_chains: Option<Vec<String>>,
}
impl Default for GraphConfig {
fn default() -> Self {
Self {
width: 0.0,
height: 0.0,
nodesep: Some(50.0),
edgesep: Some(20.0),
ranksep: Some(50.0),
marginx: None,
marginy: None,
rankdir: Some("tb".to_string()),
acyclicer: None,
ranker: None,
align: None,
nesting_root: None,
root: None,
node_rank_factor: None,
dummy_chains: None,
}
}
}
impl Default for GraphEdge {
fn default() -> Self {
Self {
forward_name: None,
reversed: None,
minlen: Some(1.0),
weight: Some(1.0),
width: Some(0.0),
height: Some(0.0),
label_rank: None,
labeloffset: Some(0.0),
labelpos: Some("r".to_string()),
nesting_edge: None,
cutvalue: None,
points: None,
x: 0.0,
y: 0.0,
}
}
}