use std::collections::{HashMap, HashSet};
use super::graph::LayoutGraph;
use super::types::{DummyNode, DummyType, EdgeLabelInfo, LabelSide};
use super::{
Direction, LabelDummyStrategy, LayoutConfig, LayoutResult, NodeId, Point, Rect, SelfEdge,
SelfEdgeLayout, rank,
};
pub(crate) fn make_space_for_edge_labels(lg: &mut LayoutGraph) {
for minlen in &mut lg.edge_minlens {
*minlen *= 2;
}
}
pub(crate) fn make_space_for_labeled_edges(
lg: &mut LayoutGraph,
edge_labels: &HashMap<usize, EdgeLabelInfo>,
) {
for (edge_idx, minlen) in lg.edge_minlens.iter_mut().enumerate() {
if edge_labels.contains_key(&edge_idx) {
*minlen = (*minlen).max(2);
}
}
}
pub(crate) fn switch_label_dummies(lg: &mut LayoutGraph, strategy: LabelDummyStrategy) {
if strategy == LabelDummyStrategy::Midpoint {
return;
}
let mut rank_widths: HashMap<i32, f64> = HashMap::new();
for (idx, &rank) in lg.ranks.iter().enumerate() {
*rank_widths.entry(rank).or_default() += lg.dimensions[idx].0;
}
for chain in &mut lg.dummy_chains {
let Some(label_idx) = chain.label_dummy_index else {
continue;
};
let best_idx = chain
.dummy_ids
.iter()
.enumerate()
.max_by(|(_, a_id), (_, b_id)| {
let a_rank = lg.node_index.get(a_id).map(|&i| lg.ranks[i]).unwrap_or(0);
let b_rank = lg.node_index.get(b_id).map(|&i| lg.ranks[i]).unwrap_or(0);
let a_w = rank_widths.get(&a_rank).copied().unwrap_or(0.0);
let b_w = rank_widths.get(&b_rank).copied().unwrap_or(0.0);
a_w.partial_cmp(&b_w).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
.unwrap_or(label_idx);
if best_idx != label_idx {
let label_id = chain.dummy_ids[label_idx].clone();
let target_id = chain.dummy_ids[best_idx].clone();
if let (Some(label_dummy), Some(_target_dummy)) = (
lg.dummy_nodes.get(&label_id).cloned(),
lg.dummy_nodes.get(&target_id).cloned(),
) {
let label_w = label_dummy.width;
let label_h = label_dummy.height;
let label_pos = label_dummy.label_pos;
if let Some(d) = lg.dummy_nodes.get_mut(&label_id) {
d.dummy_type = DummyType::Edge;
d.width = 0.0;
d.height = 0.0;
}
if let Some(d) = lg.dummy_nodes.get_mut(&target_id) {
d.dummy_type = DummyType::EdgeLabel;
d.width = label_w;
d.height = label_h;
d.label_pos = label_pos;
}
let label_graph_idx = lg.node_index[&label_id];
let target_graph_idx = lg.node_index[&target_id];
lg.dimensions[label_graph_idx] = (0.0, 0.0);
lg.dimensions[target_graph_idx] = (label_w, label_h);
chain.label_dummy_index = Some(best_idx);
}
}
}
}
pub(crate) fn select_label_sides(lg: &mut LayoutGraph) {
let layers = rank::by_rank_filtered(lg, |node| lg.ranks[node] >= 0);
let mut sorted_layers = layers;
for layer in &mut sorted_layers {
layer.sort_by_key(|&node| lg.order[node]);
}
for layer in &sorted_layers {
let label_dummies: Vec<usize> = layer
.iter()
.filter(|&&node_idx| {
let node_id = &lg.node_ids[node_idx];
lg.dummy_nodes
.get(node_id)
.map(|d| d.dummy_type == DummyType::EdgeLabel)
.unwrap_or(false)
})
.copied()
.collect();
let count = label_dummies.len();
if count <= 1 {
continue; }
for (i, &node_idx) in label_dummies.iter().enumerate() {
let node_id = &lg.node_ids[node_idx];
if let Some(dummy) = lg.dummy_nodes.get_mut(node_id) {
dummy.label_side = if i == 0 {
LabelSide::Above
} else if i == count - 1 {
LabelSide::Below
} else {
LabelSide::Center
};
}
}
}
}
pub(crate) fn extract_self_edges(lg: &mut LayoutGraph) {
debug_assert!(
lg.reversed_edges.is_empty(),
"extract_self_edges must run before acyclic::run()"
);
let mut to_remove = Vec::new();
for (pos, &(from, to, orig_idx)) in lg.edges.iter().enumerate() {
if from == to {
lg.self_edges.push(SelfEdge {
node_index: from,
orig_edge_index: orig_idx,
dummy_index: None,
});
to_remove.push(pos);
}
}
for &pos in to_remove.iter().rev() {
lg.edges.remove(pos);
lg.edge_weights.remove(pos);
lg.edge_minlens.remove(pos);
}
}
pub(crate) fn insert_self_edge_dummies(lg: &mut LayoutGraph) {
for (i, se) in lg.self_edges.clone().iter().enumerate() {
let node_rank = lg.ranks[se.node_index];
let node_order = lg.order[se.node_index];
let dummy_id: NodeId = format!("_self_edge_dummy_{}", i).into();
let dummy_idx = lg.node_ids.len();
lg.node_ids.push(dummy_id.clone());
lg.node_index.insert(dummy_id.clone(), dummy_idx);
lg.ranks.push(node_rank);
lg.positions.push(Point::default());
lg.dimensions.push((1.0, 1.0));
lg.original_has_predecessor.push(false);
lg.parents.push(lg.parents[se.node_index]);
lg.model_order.push(None);
for idx in 0..lg.order.len() - 1 {
if lg.ranks[idx] == node_rank && lg.order[idx] > node_order {
lg.order[idx] += 1;
}
}
lg.order.push(node_order + 1);
lg.dummy_nodes
.insert(dummy_id, DummyNode::edge(se.orig_edge_index, node_rank));
lg.self_edges[i].dummy_index = Some(dummy_idx);
}
}
pub(crate) fn position_self_edges(lg: &LayoutGraph, config: &LayoutConfig) -> Vec<SelfEdgeLayout> {
let gap = 1.0;
lg.self_edges
.iter()
.filter_map(|se| {
let dummy_idx = se.dummy_index?;
let node_pos = lg.positions[se.node_index];
let (nw, nh) = lg.dimensions[se.node_index];
let dummy_pos = lg.positions[dummy_idx];
let node_id = lg.node_ids[se.node_index].clone();
let node_cx = node_pos.x + nw / 2.0;
let node_cy = node_pos.y + nh / 2.0;
let points = match config.direction {
Direction::TopBottom => {
let loop_x = dummy_pos.x + 0.5;
let bot = node_pos.y + nh;
let top = node_pos.y;
vec![
Point { x: node_cx, y: bot },
Point {
x: node_cx,
y: bot + gap,
},
Point {
x: loop_x,
y: bot + gap,
},
Point {
x: loop_x,
y: top - gap,
},
Point {
x: node_cx,
y: top - gap,
},
Point { x: node_cx, y: top },
]
}
Direction::BottomTop => {
let loop_x = dummy_pos.x + 0.5;
let top = node_pos.y;
let bot = node_pos.y + nh;
vec![
Point { x: node_cx, y: top },
Point {
x: node_cx,
y: top - gap,
},
Point {
x: loop_x,
y: top - gap,
},
Point {
x: loop_x,
y: bot + gap,
},
Point {
x: node_cx,
y: bot + gap,
},
Point { x: node_cx, y: bot },
]
}
Direction::LeftRight => {
let loop_y = dummy_pos.y + 0.5;
let right = node_pos.x + nw;
let left = node_pos.x;
vec![
Point {
x: right,
y: node_cy,
},
Point {
x: right + gap,
y: node_cy,
},
Point {
x: right + gap,
y: loop_y,
},
Point {
x: left - gap,
y: loop_y,
},
Point {
x: left - gap,
y: node_cy,
},
Point {
x: left,
y: node_cy,
},
]
}
Direction::RightLeft => {
let loop_y = dummy_pos.y + 0.5;
let left = node_pos.x;
let right = node_pos.x + nw;
vec![
Point {
x: left,
y: node_cy,
},
Point {
x: left - gap,
y: node_cy,
},
Point {
x: left - gap,
y: loop_y,
},
Point {
x: right + gap,
y: loop_y,
},
Point {
x: right + gap,
y: node_cy,
},
Point {
x: right,
y: node_cy,
},
]
}
};
Some(SelfEdgeLayout {
node: node_id,
edge_index: se.orig_edge_index,
points,
})
})
.collect()
}
pub(crate) fn translate_layout_result(
result: &mut LayoutResult,
margin_x: f64,
margin_y: f64,
direction: Direction,
) {
let mut min_x = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut min_y = f64::INFINITY;
let mut max_y = f64::NEG_INFINITY;
macro_rules! update_rect {
($x:expr, $y:expr, $w:expr, $h:expr) => {
min_x = min_x.min($x);
max_x = max_x.max($x + $w);
min_y = min_y.min($y);
max_y = max_y.max($y + $h);
};
}
for rect in result.nodes.values() {
update_rect!(rect.x, rect.y, rect.width, rect.height);
}
for lp in result.label_positions.values() {
min_x = min_x.min(lp.point.x);
max_x = max_x.max(lp.point.x);
min_y = min_y.min(lp.point.y);
max_y = max_y.max(lp.point.y);
}
for rect in result.subgraph_bounds.values() {
update_rect!(rect.x, rect.y, rect.width, rect.height);
}
if min_x == f64::INFINITY {
return; }
min_x -= margin_x;
min_y -= margin_y;
let dx = -min_x;
let dy = -min_y;
for rect in result.nodes.values_mut() {
rect.x += dx;
rect.y += dy;
}
for edge in &mut result.edges {
for p in &mut edge.points {
p.x += dx;
p.y += dy;
}
}
for rect in result.subgraph_bounds.values_mut() {
rect.x += dx;
rect.y += dy;
}
for wps in result.edge_waypoints.values_mut() {
for wp in wps {
wp.point.x += dx;
wp.point.y += dy;
}
}
for lp in result.label_positions.values_mut() {
lp.point.x += dx;
lp.point.y += dy;
}
for se in &mut result.self_edges {
for p in &mut se.points {
p.x += dx;
p.y += dy;
}
}
let primary_delta = if direction.is_vertical() { dy } else { dx };
for (start, end) in result.rank_to_position.values_mut() {
*start += primary_delta;
*end += primary_delta;
}
result.width = max_x - min_x + margin_x;
result.height = max_y - min_y + margin_y;
}
pub(crate) fn assign_node_intersects(result: &mut LayoutResult) {
fn rect_center(rect: &Rect) -> Point {
Point {
x: rect.x + rect.width / 2.0,
y: rect.y + rect.height / 2.0,
}
}
fn intersect_rect(rect: &Rect, point: Point) -> Point {
let cx = rect.x + rect.width / 2.0;
let cy = rect.y + rect.height / 2.0;
let dx = point.x - cx;
let dy = point.y - cy;
let w = rect.width / 2.0;
let h = rect.height / 2.0;
if dx.abs() < f64::EPSILON && dy.abs() < f64::EPSILON {
return Point { x: cx, y: cy + h };
}
let (sx, sy) = if dy.abs() * w > dx.abs() * h {
let h = if dy < 0.0 { -h } else { h };
(h * dx / dy, h)
} else {
let w = if dx < 0.0 { -w } else { w };
(w, w * dy / dx)
};
Point {
x: cx + sx,
y: cy + sy,
}
}
for edge in &mut result.edges {
if edge.points.is_empty() {
continue;
}
let Some(from_rect) = result.nodes.get(&edge.from) else {
continue;
};
let Some(to_rect) = result.nodes.get(&edge.to) else {
continue;
};
let from_center = rect_center(from_rect);
let to_center = rect_center(to_rect);
let last_idx = edge.points.len() - 1;
let from_target = if edge.points.len() >= 2 {
edge.points[1]
} else {
to_center
};
let to_target = if edge.points.len() >= 2 {
edge.points[last_idx - 1]
} else {
from_center
};
edge.points[0] = intersect_rect(from_rect, from_target);
edge.points[last_idx] = intersect_rect(to_rect, to_target);
}
}
pub(crate) fn count_forward_edges_per_gap(lg: &LayoutGraph) -> HashMap<i32, usize> {
let mut counts: HashMap<i32, usize> = HashMap::new();
for (edge_pos, &(from, to, _orig_idx)) in lg.edges.iter().enumerate() {
if lg.reversed_edges.contains(&edge_pos) {
continue;
}
if lg.excluded_edges.contains(&edge_pos) {
continue;
}
if lg.border_type.contains_key(&from) || lg.border_type.contains_key(&to) {
continue;
}
let r_from = lg.ranks[from];
let r_to = lg.ranks[to];
let (r_lo, r_hi) = if r_from < r_to {
(r_from, r_to)
} else if r_to < r_from {
(r_to, r_from)
} else {
continue; };
for rank in r_lo..r_hi {
*counts.entry(rank).or_insert(0) += 1;
}
}
counts
}
pub(crate) fn compute_rank_sep_overrides(
lg: &LayoutGraph,
config: &LayoutConfig,
) -> HashMap<i32, f64> {
let edge_counts = count_forward_edges_per_gap(lg);
let threshold: usize = 3;
let mut overrides = HashMap::new();
let edge_inflation = config.edge_sep / 2.0;
for (&rank, &count) in &edge_counts {
if count > threshold {
let inflation = (count - threshold) as f64 * edge_inflation;
overrides.insert(rank, config.rank_sep + inflation);
}
}
overrides
}
pub(crate) fn label_dummy_gap_ranks(lg: &LayoutGraph) -> HashSet<i32> {
let mut gaps = HashSet::new();
for (id, dummy) in &lg.dummy_nodes {
if dummy.dummy_type == DummyType::EdgeLabel
&& let Some(&idx) = lg.node_index.get(id)
{
let rank = lg.ranks[idx];
gaps.insert(rank - 1);
}
}
gaps
}