use unicode_width::UnicodeWidthStr;
use super::ordering::assign_positions;
use super::{NodeSizes, Placed};
use crate::tui::markdown::mermaid::{
model::Graph,
painter::{GAP_X, GAP_Y, MAX_LABEL},
};
fn bus_spans_td(
graph: &Graph,
ranks: &[usize],
centers: &[usize],
r: usize,
exact: bool,
) -> Vec<(usize, usize, usize, usize, usize)> {
graph
.edges
.iter()
.enumerate()
.filter(|(_, e)| {
let jogs = if exact {
centers[e.from] != centers[e.to]
} else {
centers[e.from].abs_diff(centers[e.to]) > 1
};
e.from != e.to && ranks[e.from] == r && ranks[e.to] == r + 1 && jogs
})
.map(|(i, e)| {
let a = centers[e.from].min(centers[e.to]);
let b = centers[e.from].max(centers[e.to]);
(a, b, e.from, e.to, i)
})
.collect()
}
fn lane_spans(
graph: &Graph,
ranks: &[usize],
placed: &[Placed],
vertical: bool,
) -> Vec<(usize, usize, usize, usize, usize)> {
graph
.edges
.iter()
.enumerate()
.filter(|(_, e)| e.from != e.to && ranks[e.to] != ranks[e.from] + 1)
.map(|(i, e)| {
let (pf, pt) = (&placed[e.from], &placed[e.to]);
let (a, b) = if vertical {
(pf.cy.min(pt.cy), pf.cy.max(pt.cy))
} else {
(pf.cx.min(pt.cx), pf.cx.max(pt.cx))
};
(a, b, e.from, e.to, i)
})
.collect()
}
pub(super) fn place_td(
ranks: &[usize],
max_rank: usize,
by_rank: &[Vec<usize>],
sizes: &NodeSizes,
graph: &Graph,
placed: &mut [Placed],
) -> RoutePlan {
let centers = assign_positions(by_rank, &sizes.lay_w, GAP_X, &graph.edges, ranks);
let mut edge_bus = vec![0usize; graph.edges.len()];
let mut bus_tracks = vec![0usize; max_rank + 1];
for (r, tracks) in bus_tracks.iter_mut().enumerate().take(max_rank) {
let spans = bus_spans_td(graph, ranks, ¢ers, r, false);
if spans.is_empty() {
continue;
}
let (assigned, count) = assign_tracks(&spans);
for (idx, slot) in assigned {
edge_bus[idx] = slot;
}
*tracks = count;
}
let rank_h: Vec<usize> = by_rank
.iter()
.map(|row| {
row.iter()
.map(|&i| sizes.box_h[i] + sizes.extra_h[i])
.max()
.unwrap_or(3)
})
.collect();
let mut rank_y = vec![0usize; max_rank + 1];
for r in 1..=max_rank {
let gap = GAP_Y.max(bus_tracks[r - 1] + 1);
rank_y[r] = rank_y[r - 1] + rank_h[r - 1] + gap;
}
let canvas_h = rank_y[max_rank] + rank_h[max_rank];
let band_end: Vec<usize> = (0..=max_rank).map(|r| rank_y[r] + rank_h[r]).collect();
let mut diagram_w = 1;
for (r, row) in by_rank.iter().enumerate() {
for &idx in row {
let w = sizes.box_w[idx];
let h = sizes.box_h[idx];
let cx = centers[idx];
let x = cx.saturating_sub(w / 2);
let y = rank_y[r] + (rank_h[r] - h - sizes.extra_h[idx]) / 2;
placed[idx] = Placed {
x,
y,
w,
h,
cx,
cy: y + h / 2,
rank: r,
};
diagram_w = diagram_w.max(x + w);
if sizes.extra_h[idx] > 0 && sizes.self_label_w[idx] > 0 {
diagram_w = diagram_w.max(x + w + 2 + sizes.self_label_w[idx]);
}
}
}
let mut content_w = diagram_w;
for e in &graph.edges {
if e.from == e.to {
continue;
}
if let Some(label) = &e.label {
let lw = label.width().min(MAX_LABEL);
if ranks[e.to] == ranks[e.from] + 1 {
content_w = content_w.max(placed[e.to].cx + 2 + lw);
} else {
content_w = content_w.max(diagram_w + lw + 1);
}
}
}
let mut edge_lane = vec![0usize; graph.edges.len()];
let lanes = lane_spans(graph, ranks, placed, true);
let (canvas_w, lane_base) = if lanes.is_empty() {
(content_w, 0)
} else {
let (assigned, count) = assign_tracks(&lanes);
for (idx, slot) in assigned {
edge_lane[idx] = slot;
}
(content_w + 1 + count, content_w + 1)
};
RoutePlan {
canvas: (canvas_w, canvas_h),
band_end,
edge_bus,
lane_base,
edge_lane,
}
}
pub(super) fn place_lr(
ranks: &[usize],
max_rank: usize,
by_rank: &[Vec<usize>],
sizes: &NodeSizes,
graph: &Graph,
placed: &mut [Placed],
) -> RoutePlan {
let col_w: Vec<usize> = by_rank
.iter()
.map(|row| row.iter().map(|&i| sizes.box_w[i]).max().unwrap_or(0))
.collect();
let max_label = graph
.edges
.iter()
.filter(|e| e.from == e.to || ranks[e.to] == ranks[e.from] + 1)
.filter_map(|e| e.label.as_ref().map(|l| l.width().min(MAX_LABEL)))
.max()
.unwrap_or(0);
let base_gap = (GAP_X + 1).max(max_label + 3);
let centers = assign_positions(by_rank, &sizes.lay_h, 1, &graph.edges, ranks);
let mut edge_bus = vec![0usize; graph.edges.len()];
let mut bus_tracks = vec![0usize; max_rank + 1];
for (r, tracks) in bus_tracks.iter_mut().enumerate().take(max_rank) {
let spans = bus_spans_td(graph, ranks, ¢ers, r, true);
if spans.is_empty() {
continue;
}
let (assigned, count) = assign_tracks(&spans);
for (idx, slot) in assigned {
edge_bus[idx] = slot;
}
*tracks = count;
}
let mut rank_x = vec![0usize; max_rank + 1];
for r in 1..=max_rank {
let gap = base_gap.max(bus_tracks[r - 1] + 1);
rank_x[r] = rank_x[r - 1] + col_w[r - 1] + gap;
}
let canvas_w = rank_x[max_rank]
+ col_w[max_rank]
+ by_rank[max_rank]
.iter()
.filter(|&&i| sizes.extra_h[i] > 0 && sizes.self_label_w[i] > 0)
.map(|&i| 2 + sizes.self_label_w[i])
.max()
.unwrap_or(0);
let band_end: Vec<usize> = (0..=max_rank).map(|r| rank_x[r] + col_w[r]).collect();
let mut diagram_h = 1;
for (r, row) in by_rank.iter().enumerate() {
let x = rank_x[r];
for &idx in row {
let w = sizes.box_w[idx];
let h = sizes.box_h[idx];
let cy = centers[idx];
let y = cy.saturating_sub((h + sizes.extra_h[idx]) / 2);
placed[idx] = Placed {
x,
y,
w,
h,
cx: x + w / 2,
cy: y + h / 2,
rank: r,
};
diagram_h = diagram_h.max(y + h + sizes.extra_h[idx]);
}
}
let mut edge_lane = vec![0usize; graph.edges.len()];
let lanes = lane_spans(graph, ranks, placed, false);
let (canvas_h, lane_base) = if lanes.is_empty() {
(diagram_h, 0)
} else {
let (assigned, count) = assign_tracks(&lanes);
for (idx, slot) in assigned {
edge_lane[idx] = slot;
}
(diagram_h + 1 + count, diagram_h + 1)
};
RoutePlan {
canvas: (canvas_w, canvas_h),
band_end,
edge_bus,
lane_base,
edge_lane,
}
}
pub(super) struct RoutePlan {
pub(super) canvas: (usize, usize),
pub(super) band_end: Vec<usize>,
pub(super) edge_bus: Vec<usize>,
pub(super) lane_base: usize,
pub(super) edge_lane: Vec<usize>,
}
fn assign_tracks(spans: &[(usize, usize, usize, usize, usize)]) -> (Vec<(usize, usize)>, usize) {
let mut sorted = spans.to_vec();
sorted.sort_unstable();
let mut tracks: Vec<Vec<(usize, usize, usize, usize)>> = Vec::new();
let mut out = Vec::with_capacity(sorted.len());
for &(s, e, f, t, idx) in &sorted {
let compatible = |members: &Vec<(usize, usize, usize, usize)>| {
members
.iter()
.all(|&(s2, e2, f2, t2)| e2 + 2 <= s || e + 2 <= s2 || f2 == f || t2 == t)
};
let slot = match tracks.iter().position(compatible) {
Some(x) => x,
None => {
tracks.push(Vec::new());
tracks.len() - 1
}
};
tracks[slot].push((s, e, f, t));
out.push((idx, slot));
}
(out, tracks.len())
}