use crate::model::{Direction, Graph, Node, Shape};
use std::collections::VecDeque;
pub struct Placed {
pub b: f64,
pub l: f64,
pub bsize: f64,
pub lsize: f64,
pub layer: usize,
}
pub struct LayoutResult {
pub nodes: Vec<Placed>,
pub total_b: f64,
pub total_l: f64,
}
const PAD_X: f64 = 16.0;
const BASE_H: f64 = 38.0;
const MIN_W: f64 = 54.0;
const GAP_B: f64 = 48.0; const GAP_L: f64 = 64.0; const MARGIN: f64 = 28.0;
pub const LINE_H: f64 = 17.0;
pub fn text_width(s: &str) -> f64 {
s.split('\n').map(line_width).fold(0.0, f64::max)
}
pub fn line_count(s: &str) -> usize {
s.split('\n').count().max(1)
}
fn line_width(s: &str) -> f64 {
s.chars()
.map(|c| match c {
'i' | 'l' | 'j' => 3.4,
' ' | '.' | ',' | ':' | ';' | '!' | '\'' | 't' | 'f' | 'I' | '|' => 3.9,
'r' | '(' | ')' | '[' | ']' | '-' | '/' => 4.7,
's' | 'c' | 'k' | 'v' | 'x' | 'y' | 'z' | 'J' => 7.0,
'm' | 'M' => 11.7,
'w' => 10.1,
'W' => 13.2,
'A'..='Z' => 9.7,
c if (c as u32) >= 0x2E80 => 14.0, _ => 7.8,
})
.sum()
}
pub fn intrinsic_size(node: &Node) -> (f64, f64) {
let tw = text_width(&node.label);
let extra = (line_count(&node.label) - 1) as f64 * LINE_H;
let base_h = BASE_H + extra;
match node.shape {
Shape::Rect | Shape::Rounded => ((tw + 2.0 * PAD_X).max(MIN_W), base_h),
Shape::Stadium => ((tw + 2.0 * PAD_X + 12.0).max(MIN_W + 12.0), base_h),
Shape::Subroutine | Shape::Parallelogram | Shape::ParallelogramAlt => {
((tw + 2.0 * PAD_X + 24.0).max(MIN_W + 24.0), base_h)
}
Shape::Hexagon => ((tw + 2.0 * PAD_X + 28.0).max(MIN_W + 28.0), base_h),
Shape::Cylinder => ((tw + 2.0 * PAD_X).max(MIN_W), base_h + 16.0),
Shape::Diamond => (((tw + 24.0) * 1.6).max(80.0), base_h * 1.7),
Shape::Circle => {
let d = (tw + 24.0).max(52.0).max(base_h);
(d, d)
}
Shape::DoubleCircle => {
let d = (tw + 32.0).max(60.0).max(base_h);
(d, d)
}
Shape::StateStart => (14.0, 14.0),
Shape::StateEnd => (18.0, 18.0),
Shape::ForkBar => (60.0, 8.0),
}
}
pub fn layout(g: &Graph) -> LayoutResult {
let sizes: Vec<(f64, f64)> = g.nodes.iter().map(intrinsic_size).collect();
layout_sized(g, &sizes)
}
pub fn layout_sized(g: &Graph, sizes: &[(f64, f64)]) -> LayoutResult {
assert_eq!(
sizes.len(),
g.nodes.len(),
"number of sizes must match number of nodes"
);
let n = g.nodes.len();
let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
for (ei, e) in g.edges.iter().enumerate() {
adj[e.from].push((e.to, ei));
}
let mut state = vec![0u8; n]; let mut back = vec![false; g.edges.len()];
for s in 0..n {
if state[s] != 0 {
continue;
}
state[s] = 1;
let mut stack: Vec<(usize, usize)> = vec![(s, 0)];
while !stack.is_empty() {
let (u, ci) = *stack.last().unwrap();
if ci < adj[u].len() {
stack.last_mut().unwrap().1 += 1;
let (v, ei) = adj[u][ci];
if v == u {
back[ei] = true; continue;
}
match state[v] {
0 => {
state[v] = 1;
stack.push((v, 0));
}
1 => back[ei] = true, _ => {}
}
} else {
state[u] = 2;
stack.pop();
}
}
}
let mut indeg = vec![0usize; n];
for (ei, e) in g.edges.iter().enumerate() {
if !back[ei] {
indeg[e.to] += 1;
}
}
let mut layer = vec![0usize; n];
let mut q: VecDeque<usize> = (0..n).filter(|&v| indeg[v] == 0).collect();
while let Some(u) = q.pop_front() {
for &(v, ei) in &adj[u] {
if back[ei] {
continue;
}
if layer[u] + 1 > layer[v] {
layer[v] = layer[u] + 1;
}
indeg[v] -= 1;
if indeg[v] == 0 {
q.push_back(v);
}
}
}
let nlayers = layer.iter().copied().max().unwrap_or(0) + 1;
let mut layers: Vec<Vec<usize>> = vec![Vec::new(); nlayers];
for v in 0..n {
layers[layer[v]].push(v);
}
let mut preds: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut succs: Vec<Vec<usize>> = vec![Vec::new(); n];
for e in &g.edges {
if e.from == e.to {
continue;
}
succs[e.from].push(e.to);
preds[e.to].push(e.from);
}
let mut pos = vec![0.0f64; n];
for lv in &layers {
for (i, &v) in lv.iter().enumerate() {
pos[v] = i as f64;
}
}
for _ in 0..4 {
for li in 1..nlayers {
reorder(&mut layers[li], &preds, &mut pos);
}
for li in (0..nlayers.saturating_sub(1)).rev() {
reorder(&mut layers[li], &succs, &mut pos);
}
}
let horizontal = matches!(g.direction, Direction::LR | Direction::RL);
let mut bsize = vec![0.0f64; n];
let mut lsize = vec![0.0f64; n];
for v in 0..n {
let (w, h) = sizes[v];
if horizontal {
bsize[v] = h;
lsize[v] = w;
} else {
bsize[v] = w;
lsize[v] = h;
}
}
let mut lcoord = vec![0.0f64; nlayers];
let mut cursor = MARGIN;
for li in 0..nlayers {
let lh = layers[li].iter().map(|&v| lsize[v]).fold(0.0f64, f64::max);
lcoord[li] = cursor + lh / 2.0;
cursor += lh + GAP_L;
}
let total_l = cursor - GAP_L + MARGIN;
let mut bpos = vec![0.0f64; n];
let mut widths = vec![0.0f64; nlayers];
for li in 0..nlayers {
let mut c = 0.0;
for &v in &layers[li] {
bpos[v] = c + bsize[v] / 2.0;
c += bsize[v] + GAP_B;
}
widths[li] = if layers[li].is_empty() { 0.0 } else { c - GAP_B };
}
let maxw = widths.iter().fold(0.0f64, |a, &b| a.max(b));
for li in 0..nlayers {
let off = MARGIN + (maxw - widths[li]) / 2.0;
for &v in &layers[li] {
bpos[v] += off;
}
}
for li in 1..nlayers {
align_pass(&layers[li], &preds, &mut bpos, &bsize);
}
for li in (0..nlayers.saturating_sub(1)).rev() {
align_pass(&layers[li], &succs, &mut bpos, &bsize);
}
for li in 1..nlayers {
align_pass(&layers[li], &preds, &mut bpos, &bsize);
}
let mut minb = f64::INFINITY;
let mut maxb = f64::NEG_INFINITY;
for v in 0..n {
minb = minb.min(bpos[v] - bsize[v] / 2.0);
maxb = maxb.max(bpos[v] + bsize[v] / 2.0);
}
if n == 0 {
minb = 0.0;
maxb = 0.0;
}
let shift = MARGIN - minb;
for v in 0..n {
bpos[v] += shift;
}
let total_b = (maxb - minb) + 2.0 * MARGIN;
let nodes = (0..n)
.map(|v| Placed {
b: bpos[v],
l: lcoord[layer[v]],
bsize: bsize[v],
lsize: lsize[v],
layer: layer[v],
})
.collect();
LayoutResult {
nodes,
total_b,
total_l,
}
}
fn reorder(layer: &mut Vec<usize>, nbrs: &[Vec<usize>], pos: &mut [f64]) {
let mut keyed: Vec<(f64, usize)> = layer
.iter()
.map(|&v| {
let ns = &nbrs[v];
let key = if ns.is_empty() {
pos[v]
} else {
ns.iter().map(|&u| pos[u]).sum::<f64>() / ns.len() as f64
};
(key, v)
})
.collect();
keyed.sort_by(|a, b| a.0.total_cmp(&b.0));
layer.clear();
for (i, (_, v)) in keyed.into_iter().enumerate() {
layer.push(v);
pos[v] = i as f64;
}
}
fn align_pass(order: &[usize], nbrs: &[Vec<usize>], bpos: &mut [f64], bsize: &[f64]) {
let mut min_edge = f64::NEG_INFINITY;
for &v in order {
let ns = &nbrs[v];
let desired = if ns.is_empty() {
bpos[v]
} else {
ns.iter().map(|&u| bpos[u]).sum::<f64>() / ns.len() as f64
};
let c = desired.max(min_edge + bsize[v] / 2.0);
bpos[v] = c;
min_edge = c + bsize[v] / 2.0 + GAP_B;
}
}