Skip to main content

ggplot_rs/
graph_layout.rs

1//! Layered (Sugiyama-style) layout of a directed graph of boxes — nodes in
2//! layers (stages) along one axis, ordered across it to keep crossings low.
3//!
4//! The steps: cycles broken by a DFS in input order; ranks by longest path
5//! (a pinned layer wins; sources pulled up to just before their nearest
6//! successor); a dummy vertex per layer a long edge passes; barycenter sweeps
7//! plus adjacent transposition, the fewest crossings kept (counted per layer
8//! pair with a Fenwick tree); a weighted least-squares (isotonic) placement
9//! along each layer; ports spread along a box's side; optional swimlanes as
10//! bands. A graph whose every node is pinned in both axes is drawn on a grid
11//! where it is placed, with straight edges.
12//!
13//! Node sizes come from the caller (the box contents are the drawing's
14//! business); the result is in px with the origin at the top-left margin.
15//! Moved here from anofox-visualization's `::DIAGRAM` (sipemu/ggplot-rs#497,
16//! DataZooDE/anofox-visualization#1153).
17
18/// Which way the layers run.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
20pub enum Direction {
21    /// Layers left to right (the default).
22    #[default]
23    LeftRight,
24    /// Layers top to bottom.
25    TopBottom,
26}
27
28/// A node: its box size, its swimlane, and optional pins (a layer, and a
29/// position across it; both pinned on every node → the grid layout).
30#[derive(Clone, Debug, Default)]
31pub struct GraphNode {
32    pub w: f64,
33    pub h: f64,
34    /// Index of the node's swimlane (< [`LayeredParams::lanes`]), if any.
35    pub lane: Option<usize>,
36    /// Pinned layer and pinned position (grid units in the explicit layout).
37    pub pin: (Option<f64>, Option<f64>),
38}
39
40/// A directed edge; `label_w` is the width (px) of its label, if it has one:
41/// left to right, the gap between two layers is at least as wide as the
42/// labels that cross it.
43#[derive(Clone, Debug, Default)]
44pub struct GraphEdge {
45    pub from: usize,
46    pub to: usize,
47    pub label_w: Option<f64>,
48}
49
50/// Spacing and options; the defaults are anofox-visualization's diagram.
51#[derive(Clone, Debug)]
52pub struct LayeredParams {
53    pub direction: Direction,
54    /// Number of swimlanes (nodes carry the index).
55    pub lanes: usize,
56    pub margin: f64,
57    /// Arrowhead length (an edge label's gap leaves room for two).
58    pub arrow: f64,
59    /// A self-loop's reach beyond its box.
60    pub loop_size: f64,
61    /// Extra space between swimlanes, and a lane's label strip.
62    pub lane_gap: f64,
63    pub lane_head: f64,
64}
65
66impl Default for LayeredParams {
67    fn default() -> Self {
68        LayeredParams {
69            direction: Direction::LeftRight,
70            lanes: 0,
71            margin: 10.0,
72            arrow: 7.0,
73            loop_size: 16.0,
74            lane_gap: 22.0,
75            lane_head: 16.0,
76        }
77    }
78}
79
80/// A placed node: centre, size, layer, and rank in reading order (layer by
81/// layer, then along it).
82#[derive(Clone, Debug, PartialEq)]
83pub struct PlacedNode {
84    pub cx: f64,
85    pub cy: f64,
86    pub w: f64,
87    pub h: f64,
88    pub layer: usize,
89    pub order: usize,
90}
91
92/// The laid-out graph.
93#[derive(Clone, Debug, PartialEq)]
94pub struct GraphLayout {
95    pub w: f64,
96    pub h: f64,
97    pub nodes: Vec<PlacedNode>,
98    /// Per edge (input order): its points, source → target.
99    pub edges: Vec<Vec<(f64, f64)>>,
100    /// Edges drawn against the layer order (they close a cycle).
101    pub back: Vec<bool>,
102    pub layers: usize,
103    /// Swimlane bands: (lane, x0, y0, x1, y1).
104    pub bands: Vec<(usize, f64, f64, f64, f64)>,
105}
106
107/// Lay out `nodes` and `edges` in layers (see the module docs).
108pub fn layered(nodes: &[GraphNode], edges: &[GraphEdge], p: &LayeredParams) -> GraphLayout {
109    let explicit =
110        !nodes.is_empty() && nodes.iter().all(|n| n.pin.0.is_some() && n.pin.1.is_some());
111    if explicit {
112        layout_explicit(nodes, edges, p)
113    } else {
114        layout(nodes, edges, p)
115    }
116}
117
118/// Spacing of [`banded`].
119#[derive(Clone, Debug)]
120pub struct BandedParams {
121    /// Number of bands (nodes carry the index in [`GraphNode::lane`]; a
122    /// node without one goes in band 0).
123    pub bands: usize,
124    pub margin: f64,
125    /// Space between columns, and between stacked nodes in a band.
126    pub col_gap: f64,
127    pub row_gap: f64,
128    /// Space between bands, and a band's label strip.
129    pub band_gap: f64,
130    pub band_head: f64,
131}
132
133impl Default for BandedParams {
134    fn default() -> Self {
135        BandedParams {
136            bands: 0,
137            margin: 10.0,
138            col_gap: 36.0,
139            row_gap: 18.0,
140            band_gap: 22.0,
141            band_head: 16.0,
142        }
143    }
144}
145
146/// Lay out a graph in horizontal bands (a factor graph's layers: control,
147/// parameters, states, observations, …), left to right. A column is a step
148/// along an edge **within** a band; an edge into another band keeps the
149/// column, so a branch drops straight down (or up) into its band — the
150/// state chain stays one straight row with each observation below its
151/// state. A leaf hanging off one node of its band (an observed variable
152/// under its factor) sits right below it; nodes dropping in from other
153/// bands side by side fan out over the band's free columns. Nodes that
154/// still share a band and a column stack in tracks, a node staying on the
155/// track of the node before it in its band. A source is
156/// pulled right to just before its first successor in its band (a
157/// constant next to its factor). Edges are straight; a cycle's closing
158/// edge is drawn as is and marked `back`.
159pub fn banded(nodes: &[GraphNode], edges: &[GraphEdge], p: &BandedParams) -> GraphLayout {
160    let n = nodes.len();
161    let band = |v: usize| nodes[v].lane.unwrap_or(0).min(p.bands.saturating_sub(1));
162    let (dag, loops) = break_cycles(n, edges);
163    let mut preds: Vec<Vec<usize>> = vec![Vec::new(); n];
164    let mut succs: Vec<Vec<usize>> = vec![Vec::new(); n];
165    for &(u, v, _, _) in &dag {
166        preds[v].push(u);
167        succs[u].push(v);
168    }
169    // topological order, the first ready node in input order each time
170    let mut indeg: Vec<usize> = preds.iter().map(Vec::len).collect();
171    let mut done = vec![false; n];
172    let mut topo = Vec::with_capacity(n);
173    while let Some(v) = (0..n).find(|&v| !done[v] && indeg[v] == 0) {
174        done[v] = true;
175        topo.push(v);
176        for &w in &succs[v] {
177            indeg[w] -= 1;
178        }
179    }
180    let step = |u: usize, v: usize| usize::from(band(u) == band(v));
181    // a leaf hanging off one node of its own band (an observed variable
182    // under its factor): same column, the track below it
183    let below =
184        |v: usize| succs[v].is_empty() && preds[v].len() == 1 && band(preds[v][0]) == band(v);
185    // a node fed only from other bands (a branch dropping in): it fans out
186    // to the next free column of its band rather than stacking
187    let drops = |v: usize| !preds[v].is_empty() && preds[v].iter().all(|&u| band(u) != band(v));
188    let mut col = vec![0usize; n];
189    let mut used: std::collections::HashSet<(usize, usize)> = Default::default();
190    for &v in &topo {
191        col[v] = if below(v) {
192            col[preds[v][0]]
193        } else {
194            preds[v]
195                .iter()
196                .map(|&u| col[u] + step(u, v))
197                .max()
198                .unwrap_or(0)
199        };
200        if drops(v) {
201            while used.contains(&(band(v), col[v])) {
202                col[v] += 1;
203            }
204        }
205        used.insert((band(v), col[v]));
206    }
207    for &v in topo.iter().rev() {
208        if preds[v].is_empty() {
209            if let Some(m) = succs[v]
210                .iter()
211                .map(|&w| col[w].saturating_sub(step(v, w)))
212                .min()
213            {
214                col[v] = col[v].max(m);
215            }
216        }
217    }
218    // tracks: per (band, column) the slots taken
219    let mut track = vec![0usize; n];
220    let mut taken: std::collections::HashSet<(usize, usize, usize)> = Default::default();
221    for &v in &topo {
222        let want = if below(v) {
223            track[preds[v][0]] + 1
224        } else {
225            preds[v]
226                .iter()
227                .filter(|&&u| band(u) == band(v))
228                .map(|&u| track[u])
229                .min()
230                .unwrap_or(0)
231        };
232        let mut t = want;
233        while taken.contains(&(band(v), col[v], t)) {
234            t += 1;
235        }
236        taken.insert((band(v), col[v], t));
237        track[v] = t;
238    }
239    // geometry: column widths over all bands, track heights per band
240    let ncol = col.iter().max().map_or(0, |m| m + 1);
241    let mut col_w = vec![0.0f64; ncol];
242    for v in 0..n {
243        col_w[col[v]] = col_w[col[v]].max(nodes[v].w);
244    }
245    let mut col_x = vec![0.0f64; ncol];
246    let mut x = 0.0;
247    for c in 0..ncol {
248        col_x[c] = x + col_w[c] / 2.0;
249        x += col_w[c] + p.col_gap;
250    }
251    let nb = p.bands.max(1);
252    let ntr: Vec<usize> = (0..nb)
253        .map(|b| {
254            (0..n)
255                .filter(|&v| band(v) == b)
256                .map(|v| track[v] + 1)
257                .max()
258                .unwrap_or(0)
259        })
260        .collect();
261    let mut track_h: Vec<Vec<f64>> = ntr.iter().map(|&k| vec![0.0; k]).collect();
262    for v in 0..n {
263        let h = &mut track_h[band(v)][track[v]];
264        *h = h.max(nodes[v].h);
265    }
266    let lanes = p.bands > 0;
267    let mut track_y: Vec<Vec<f64>> = Vec::with_capacity(nb);
268    let mut band_span: Vec<(f64, f64)> = Vec::with_capacity(nb);
269    let mut y = 0.0;
270    for heights in &track_h {
271        if lanes {
272            y += p.band_head;
273        }
274        let top = y;
275        let mut ys = Vec::new();
276        for &h in heights {
277            ys.push(y + h / 2.0);
278            y += h + p.row_gap;
279        }
280        let bottom = if heights.is_empty() { y } else { y - p.row_gap };
281        band_span.push((top, bottom));
282        track_y.push(ys);
283        y = bottom + if lanes { p.band_gap } else { p.row_gap };
284    }
285    let mut order: Vec<usize> = (0..n).collect();
286    order.sort_by_key(|&v| (col[v], band(v), track[v], v));
287    let mut rank = vec![0usize; n];
288    for (i, &v) in order.iter().enumerate() {
289        rank[v] = i;
290    }
291    let pn: Vec<PlacedNode> = (0..n)
292        .map(|v| PlacedNode {
293            cx: col_x[col[v]],
294            cy: track_y[band(v)][track[v]],
295            w: nodes[v].w,
296            h: nodes[v].h,
297            layer: col[v],
298            order: rank[v],
299        })
300        .collect();
301    let mut edge_pts = vec![Vec::new(); edges.len()];
302    let mut back = vec![false; edges.len()];
303    for &(_, _, k, rev) in &dag {
304        edge_pts[k] = straight(&pn, edges[k].from, edges[k].to);
305        back[k] = rev;
306    }
307    for &k in &loops {
308        edge_pts[k] = self_loop(&pn[edges[k].from], true, 16.0);
309    }
310    let bands = if lanes {
311        let (x0, x1) = (-12.0, x - p.col_gap + 12.0);
312        (0..nb)
313            .filter(|&b| ntr[b] > 0)
314            .map(|b| {
315                let (top, bottom) = band_span[b];
316                (b, x0, top - p.band_head - 4.0, x1, bottom + 8.0)
317            })
318            .collect()
319    } else {
320        Vec::new()
321    };
322    finish(pn, edge_pts, back, ncol, bands, p.margin)
323}
324
325/// A DAG edge: `(u, v, edge index, reversed)`.
326type DagEdge = (usize, usize, usize, bool);
327
328/// The cycle-free orientation (a DFS in input order reverses every edge
329/// that closes a cycle), and the self-loops (edge indices).
330fn break_cycles(n: usize, edges: &[GraphEdge]) -> (Vec<DagEdge>, Vec<usize>) {
331    let mut out: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
332    let mut loops = Vec::new();
333    for (k, e) in edges.iter().enumerate() {
334        if e.from == e.to {
335            loops.push(k);
336        } else {
337            out[e.from].push((e.to, k));
338        }
339    }
340    let mut state = vec![0u8; n]; // 0 new, 1 on the DFS stack, 2 done
341    let mut rev = vec![false; edges.len()];
342    for s in 0..n {
343        if state[s] != 0 {
344            continue;
345        }
346        state[s] = 1;
347        let mut stack = vec![(s, 0usize)];
348        while let Some(top) = stack.last_mut() {
349            let v = top.0;
350            if top.1 < out[v].len() {
351                let (w, k) = out[v][top.1];
352                top.1 += 1;
353                match state[w] {
354                    0 => {
355                        state[w] = 1;
356                        stack.push((w, 0));
357                    }
358                    1 => rev[k] = true,
359                    _ => {}
360                }
361            } else {
362                state[v] = 2;
363                stack.pop();
364            }
365        }
366    }
367    let dag = edges
368        .iter()
369        .enumerate()
370        .filter(|(_, e)| e.from != e.to)
371        .map(|(k, e)| {
372            if rev[k] {
373                (e.to, e.from, k, true)
374            } else {
375                (e.from, e.to, k, false)
376            }
377        })
378        .collect();
379    (dag, loops)
380}
381
382/// `keys` ordered by `(primary, secondary, index)`, as the indices.
383fn ordered(mut keys: Vec<(f64, f64, usize)>) -> Vec<usize> {
384    keys.sort_unstable_by(|a, b| {
385        a.0.total_cmp(&b.0)
386            .then(a.1.total_cmp(&b.1))
387            .then(a.2.cmp(&b.2))
388    });
389    keys.into_iter().map(|k| k.2).collect()
390}
391
392/// Crossings between two adjacent layers (Barth–Mutzel–Jünger: the edges by
393/// their upper end, inversions of the lower ends counted with a Fenwick tree).
394fn layer_crossings(upper: &[usize], succ: &[Vec<usize>], pos: &[usize], lower_len: usize) -> u64 {
395    let mut ends: Vec<(usize, usize)> = Vec::new();
396    for &u in upper {
397        for &v in &succ[u] {
398            ends.push((pos[u], pos[v]));
399        }
400    }
401    let lower: Vec<usize> = ordered(
402        ends.iter()
403            .enumerate()
404            .map(|(i, &(u, v))| (u as f64, v as f64, i))
405            .collect(),
406    )
407    .into_iter()
408    .map(|i| ends[i].1)
409    .collect();
410    let mut tree = vec![0u64; lower_len + 1];
411    let mut c = 0u64;
412    for (seen, &p) in lower.iter().enumerate() {
413        let mut i = p + 1;
414        let mut le = 0u64;
415        while i > 0 {
416            le += tree[i];
417            i -= 1 << i.trailing_zeros();
418        }
419        c += seen as u64 - le;
420        let mut i = p + 1;
421        while i <= lower_len {
422            tree[i] += 1;
423            i += 1 << i.trailing_zeros();
424        }
425    }
426    c
427}
428
429/// Isotonic placement: positions as close as possible (weighted least
430/// squares) to `want`, in the given order, at least `gap[i]` after the
431/// previous one.
432fn place(want: &[f64], weight: &[f64], gap: &[f64]) -> Vec<f64> {
433    let n = want.len();
434    let mut off = vec![0.0; n];
435    for i in 1..n {
436        off[i] = off[i - 1] + gap[i];
437    }
438    let mut blocks: Vec<(f64, f64, usize)> = Vec::new();
439    for i in 0..n {
440        blocks.push((weight[i] * (want[i] - off[i]), weight[i], 1));
441        while blocks.len() >= 2 {
442            let (b, a) = (blocks[blocks.len() - 1], blocks[blocks.len() - 2]);
443            if a.0 / a.1 > b.0 / b.1 {
444                blocks.pop();
445                let last = blocks.last_mut().expect("two blocks");
446                *last = (a.0 + b.0, a.1 + b.1, a.2 + b.2);
447            } else {
448                break;
449            }
450        }
451    }
452    let mut out = Vec::with_capacity(n);
453    for (s, w, c) in blocks {
454        for _ in 0..c {
455            let i = out.len();
456            out.push(s / w + off[i]);
457        }
458    }
459    out
460}
461
462/// Where a line from a box's centre towards `to` leaves the box.
463fn exit(p: &PlacedNode, to: (f64, f64)) -> (f64, f64) {
464    let (dx, dy) = (to.0 - p.cx, to.1 - p.cy);
465    let sx = if dx != 0.0 {
466        p.w / 2.0 / dx.abs()
467    } else {
468        f64::INFINITY
469    };
470    let sy = if dy != 0.0 {
471        p.h / 2.0 / dy.abs()
472    } else {
473        f64::INFINITY
474    };
475    let k = sx.min(sy);
476    if k.is_finite() {
477        (p.cx + dx * k, p.cy + dy * k)
478    } else {
479        (p.cx, p.cy)
480    }
481}
482
483fn straight(pn: &[PlacedNode], u: usize, v: usize) -> Vec<(f64, f64)> {
484    let (a, b) = (&pn[u], &pn[v]);
485    vec![exit(a, (b.cx, b.cy)), exit(b, (a.cx, a.cy))]
486}
487
488/// A self-loop over (left to right) or beside (top to bottom) its box.
489fn self_loop(p: &PlacedNode, lr: bool, reach: f64) -> Vec<(f64, f64)> {
490    if lr {
491        let top = p.cy - p.h / 2.0;
492        vec![
493            (p.cx + p.w / 4.0, top),
494            (p.cx + p.w / 4.0, top - reach),
495            (p.cx - p.w / 4.0, top - reach),
496            (p.cx - p.w / 4.0, top),
497        ]
498    } else {
499        let right = p.cx + p.w / 2.0;
500        vec![
501            (right, p.cy - p.h / 4.0),
502            (right + reach, p.cy - p.h / 4.0),
503            (right + reach, p.cy + p.h / 4.0),
504            (right, p.cy + p.h / 4.0),
505        ]
506    }
507}
508
509/// Every node pinned in both axes: a grid of the widest / tallest box.
510fn layout_explicit(nodes: &[GraphNode], edges: &[GraphEdge], p: &LayeredParams) -> GraphLayout {
511    let sx = nodes.iter().map(|b| b.w).fold(0.0, f64::max) + 48.0;
512    let sy = nodes.iter().map(|b| b.h).fold(0.0, f64::max) + 24.0;
513    let pin = |v: usize| (nodes[v].pin.0.unwrap_or(0.0), nodes[v].pin.1.unwrap_or(0.0));
514    let mut xs: Vec<f64> = (0..nodes.len()).map(|v| pin(v).0).collect();
515    xs.sort_by(f64::total_cmp);
516    xs.dedup();
517    let reading = ordered((0..nodes.len()).map(|v| (pin(v).0, pin(v).1, v)).collect());
518    let mut pn: Vec<PlacedNode> = nodes
519        .iter()
520        .enumerate()
521        .map(|(v, b)| PlacedNode {
522            cx: pin(v).0 * sx,
523            cy: pin(v).1 * sy,
524            w: b.w,
525            h: b.h,
526            layer: xs.iter().position(|&x| x == pin(v).0).unwrap_or(0),
527            order: 0,
528        })
529        .collect();
530    for (i, &v) in reading.iter().enumerate() {
531        pn[v].order = i;
532    }
533    let lr = p.direction == Direction::LeftRight;
534    let pts = edges
535        .iter()
536        .map(|e| {
537            if e.from == e.to {
538                self_loop(&pn[e.from], lr, p.loop_size)
539            } else {
540                straight(&pn, e.from, e.to)
541            }
542        })
543        .collect();
544    finish(
545        pn,
546        pts,
547        vec![false; edges.len()],
548        xs.len().max(1),
549        Vec::new(),
550        p.margin,
551    )
552}
553
554fn layout(nodes: &[GraphNode], edges: &[GraphEdge], p: &LayeredParams) -> GraphLayout {
555    let n = nodes.len();
556    let (dag, loops) = break_cycles(n, edges);
557    let lr = p.direction == Direction::LeftRight;
558    let along = |b: &GraphNode| if lr { b.w } else { b.h };
559    let across = |b: &GraphNode| if lr { b.h } else { b.w };
560    let (rank_gap, node_gap) = if lr { (64.0, 20.0) } else { (48.0, 28.0) };
561    let (lane_gap, lane_head) = (p.lane_gap, p.lane_head);
562
563    // ranks: longest path in topological order (input order first), then
564    // sources pulled up to just before their nearest successor
565    let mut preds: Vec<Vec<usize>> = vec![Vec::new(); n];
566    let mut succs: Vec<Vec<usize>> = vec![Vec::new(); n];
567    for &(u, v, _, _) in &dag {
568        preds[v].push(u);
569        succs[u].push(v);
570    }
571    let mut indeg: Vec<usize> = preds.iter().map(Vec::len).collect();
572    let mut done = vec![false; n];
573    let mut topo = Vec::with_capacity(n);
574    while let Some(v) = (0..n).find(|&v| !done[v] && indeg[v] == 0) {
575        done[v] = true;
576        topo.push(v);
577        for &w in &succs[v] {
578            indeg[w] -= 1;
579        }
580    }
581    // a pinned layer wins over the longest path
582    let pinned = |v: usize| {
583        nodes[v]
584            .pin
585            .0
586            .map(|x| x.round().clamp(0.0, 1000.0) as usize)
587    };
588    let any_pin = (0..n).any(|v| pinned(v).is_some());
589    let mut rank = vec![0usize; n];
590    for &v in &topo {
591        rank[v] =
592            pinned(v).unwrap_or_else(|| preds[v].iter().map(|&q| rank[q] + 1).max().unwrap_or(0));
593    }
594    for &v in topo.iter().rev() {
595        if preds[v].is_empty() && pinned(v).is_none() {
596            if let Some(m) = succs[v].iter().map(|&s| rank[s]).min() {
597                rank[v] = rank[v].max(m.saturating_sub(1));
598            }
599        }
600    }
601    // drop empty ranks (pinned layers keep their numbers)
602    let mut used = vec![false; rank.iter().max().map_or(1, |m| m + 1)];
603    for &r in &rank {
604        used[r] = true;
605    }
606    let mut renumber = vec![0usize; used.len()];
607    let mut nl = 0;
608    for (r, u) in used.iter().enumerate() {
609        renumber[r] = nl;
610        nl += usize::from(*u || any_pin);
611    }
612    for r in &mut rank {
613        *r = renumber[*r];
614    }
615    let nl = nl.max(1);
616    // edges the pins make run within or against the layers: drawn straight
617    let (dag, free): (Vec<DagEdge>, Vec<DagEdge>) =
618        dag.into_iter().partition(|&(u, v, _, _)| rank[v] > rank[u]);
619
620    // vertices: the nodes, then one dummy per layer an edge passes
621    let mut vrank: Vec<usize> = rank.clone();
622    let mut vsucc: Vec<Vec<usize>> = vec![Vec::new(); n];
623    let mut vpred: Vec<Vec<usize>> = vec![Vec::new(); n];
624    let mut chains: Vec<Vec<usize>> = Vec::with_capacity(dag.len());
625    for &(u, v, _, _) in &dag {
626        let mut chain = vec![u];
627        let mut prev = u;
628        for r in rank[u] + 1..rank[v] {
629            let dmy = vrank.len();
630            vrank.push(r);
631            vsucc.push(Vec::new());
632            vpred.push(Vec::new());
633            vsucc[prev].push(dmy);
634            vpred[dmy].push(prev);
635            chain.push(dmy);
636            prev = dmy;
637        }
638        vsucc[prev].push(v);
639        vpred[v].push(prev);
640        chain.push(v);
641        chains.push(chain);
642    }
643    let nv = vrank.len();
644    let dummy = |v: usize| v >= n;
645    // a vertex's swimlane (unlaned nodes last; a dummy runs in its source's)
646    let mut vlane: Vec<usize> = (0..n).map(|v| nodes[v].lane.unwrap_or(p.lanes)).collect();
647    vlane.resize(nv, 0);
648    for ch in &chains {
649        for &dm in &ch[1..ch.len() - 1] {
650            vlane[dm] = vlane[ch[0]];
651        }
652    }
653    let laned = p.lanes > 0;
654
655    // initial order: DFS from the vertices by (rank, index)
656    let mut layers: Vec<Vec<usize>> = vec![Vec::new(); nl];
657    let mut seen = vec![false; nv];
658    for s in ordered((0..nv).map(|v| (vrank[v] as f64, 0.0, v)).collect()) {
659        if seen[s] {
660            continue;
661        }
662        let mut stack = vec![s];
663        while let Some(v) = stack.pop() {
664            if seen[v] {
665                continue;
666            }
667            seen[v] = true;
668            layers[vrank[v]].push(v);
669            for &w in vsucc[v].iter().rev() {
670                if !seen[w] {
671                    stack.push(w);
672                }
673            }
674        }
675    }
676    if laned {
677        for l in &mut layers {
678            *l = ordered(
679                l.iter()
680                    .enumerate()
681                    .map(|(i, &v)| (vlane[v] as f64, i as f64, v))
682                    .collect(),
683            );
684        }
685    }
686    let mut pos = vec![0usize; nv];
687    let index = |layers: &[Vec<usize>], pos: &mut [usize]| {
688        for l in layers {
689            for (i, &v) in l.iter().enumerate() {
690                pos[v] = i;
691            }
692        }
693    };
694    index(&layers, &mut pos);
695    let crossings = |layers: &[Vec<usize>], pos: &[usize]| -> u64 {
696        (0..layers.len().saturating_sub(1))
697            .map(|r| layer_crossings(&layers[r], &vsucc, pos, layers[r + 1].len()))
698            .sum()
699    };
700
701    // crossing reduction: barycenter sweeps + adjacent transposition, best kept
702    let mut best = layers.clone();
703    let mut best_c = crossings(&layers, &pos);
704    for it in 0..8 {
705        if best_c == 0 {
706            break;
707        }
708        let down = it % 2 == 0;
709        let rs: Vec<usize> = if down {
710            (1..nl).collect()
711        } else {
712            (0..nl.saturating_sub(1)).rev().collect()
713        };
714        for r in rs {
715            let nb = if down { &vpred } else { &vsucc };
716            let keyed: Vec<(f64, f64, usize)> = layers[r]
717                .iter()
718                .map(|&v| {
719                    let k = if nb[v].is_empty() {
720                        pos[v] as f64
721                    } else {
722                        nb[v].iter().map(|&w| pos[w] as f64).sum::<f64>() / nb[v].len() as f64
723                    };
724                    (vlane[v] as f64 * 1e6 + k, pos[v] as f64, v)
725                })
726                .collect();
727            layers[r] = ordered(keyed);
728            for (i, &v) in layers[r].iter().enumerate() {
729                pos[v] = i;
730            }
731        }
732        for _ in 0..4 {
733            let mut better = false;
734            for layer in layers.iter_mut() {
735                for i in 0..layer.len().saturating_sub(1) {
736                    let (a, b) = (layer[i], layer[i + 1]);
737                    if vlane[a] != vlane[b] {
738                        continue;
739                    }
740                    let pair = |x: usize, y: usize| -> usize {
741                        let c = |l: &[Vec<usize>]| {
742                            l[x].iter()
743                                .flat_map(|&q| l[y].iter().map(move |&r| (q, r)))
744                                .filter(|&(q, r)| pos[q] > pos[r])
745                                .count()
746                        };
747                        c(&vpred) + c(&vsucc)
748                    };
749                    if pair(b, a) < pair(a, b) {
750                        layer.swap(i, i + 1);
751                        pos[a] = i + 1;
752                        pos[b] = i;
753                        better = true;
754                    }
755                }
756            }
757            if !better {
758                break;
759            }
760        }
761        let c = crossings(&layers, &pos);
762        if c < best_c {
763            best_c = c;
764            best = layers.clone();
765        }
766    }
767    let layers = best;
768    index(&layers, &mut pos);
769
770    // positions across each layer
771    let size = |v: usize| if dummy(v) { 0.0 } else { across(&nodes[v]) };
772    let gap_between = |a: usize, b: usize| -> f64 {
773        let g = match (dummy(a), dummy(b)) {
774            (true, true) => 8.0,
775            (false, false) => node_gap,
776            _ => 12.0,
777        };
778        let lane = if vlane[a] != vlane[b] {
779            lane_gap + lane_head
780        } else {
781            0.0
782        };
783        size(a) / 2.0 + g + lane + size(b) / 2.0
784    };
785    let mut y = vec![0.0f64; nv];
786    for l in &layers {
787        let mut at = 0.0;
788        for (i, &v) in l.iter().enumerate() {
789            if i > 0 {
790                at += gap_between(l[i - 1], v);
791            }
792            y[v] = at;
793        }
794        let mid = at / 2.0;
795        for &v in l {
796            y[v] -= mid;
797        }
798    }
799    let relax = |y: &mut Vec<f64>, r: usize, up: bool, down: bool| {
800        let l = &layers[r];
801        if l.is_empty() {
802            return;
803        }
804        let want: Vec<f64> = l
805            .iter()
806            .map(|&v| {
807                let mut nb: Vec<usize> = Vec::new();
808                if up {
809                    nb.extend(&vpred[v]);
810                }
811                if down {
812                    nb.extend(&vsucc[v]);
813                }
814                if nb.is_empty() {
815                    y[v]
816                } else {
817                    nb.iter().map(|&w| y[w]).sum::<f64>() / nb.len() as f64
818                }
819            })
820            .collect();
821        let weight: Vec<f64> = l
822            .iter()
823            .map(|&v| if dummy(v) { 2.0 } else { 1.0 })
824            .collect();
825        let gaps: Vec<f64> = (0..l.len())
826            .map(|i| {
827                if i == 0 {
828                    0.0
829                } else {
830                    gap_between(l[i - 1], l[i])
831                }
832            })
833            .collect();
834        for (&v, q) in l.iter().zip(place(&want, &weight, &gaps)) {
835            y[v] = q;
836        }
837    };
838    for _ in 0..6 {
839        for r in 1..nl {
840            relax(&mut y, r, true, false);
841        }
842        for r in (0..nl.saturating_sub(1)).rev() {
843            relax(&mut y, r, false, true);
844        }
845    }
846    for r in 0..nl {
847        relax(&mut y, r, true, true);
848    }
849    // swimlanes: each lane shifted clear of the one before (across all layers)
850    let mut lane_span: Vec<(f64, f64)> = Vec::new();
851    if laned {
852        let nlanes = p.lanes + 1;
853        let span = |y: &[f64], l: usize| {
854            (0..nv)
855                .filter(|&v| vlane[v] == l)
856                .map(|v| (y[v] - size(v) / 2.0, y[v] + size(v) / 2.0))
857                .fold(None, |acc: Option<(f64, f64)>, (a, b)| match acc {
858                    None => Some((a, b)),
859                    Some((x, z)) => Some((x.min(a), z.max(b))),
860                })
861        };
862        let mut floor: Option<f64> = None;
863        for l in 0..nlanes {
864            let Some((lo, _)) = span(&y, l) else {
865                lane_span.push((f64::NAN, f64::NAN));
866                continue;
867            };
868            if let Some(f) = floor {
869                let need = f + lane_gap + lane_head - lo;
870                if need > 0.0 {
871                    for v in 0..nv {
872                        if vlane[v] == l {
873                            y[v] += need;
874                        }
875                    }
876                }
877            }
878            let (lo, hi) = span(&y, l).expect("lane has vertices");
879            lane_span.push((lo, hi));
880            floor = Some(hi);
881        }
882    }
883
884    // positions along the layer axis: each layer as thick as its widest node;
885    // left to right, a gap is at least as wide as the edge labels crossing it
886    let mut thick = vec![0.0f64; nl];
887    for v in 0..n {
888        thick[rank[v]] = thick[rank[v]].max(along(&nodes[v]));
889    }
890    let mut gap: Vec<f64> = vec![rank_gap; nl];
891    if lr {
892        for e in edges {
893            let (a, b) = (rank[e.from].min(rank[e.to]), rank[e.from].max(rank[e.to]));
894            if b != a + 1 {
895                continue;
896            }
897            if let Some(w) = e.label_w {
898                gap[a] = gap[a].max(w + 2.0 * p.arrow + 18.0);
899            }
900        }
901    }
902    let mut start_at = vec![0.0f64; nl];
903    for r in 1..nl {
904        start_at[r] = start_at[r - 1] + thick[r - 1] + gap[r - 1];
905    }
906    let centre = |r: usize| start_at[r] + thick[r] / 2.0;
907    let xy = |rk: f64, o: f64| if lr { (rk, o) } else { (o, rk) };
908
909    let mut pn: Vec<PlacedNode> = Vec::with_capacity(n);
910    for (v, b) in nodes.iter().enumerate() {
911        let (cx, cy) = xy(centre(rank[v]), y[v]);
912        pn.push(PlacedNode {
913            cx,
914            cy,
915            w: b.w,
916            h: b.h,
917            layer: rank[v],
918            order: 0,
919        });
920    }
921    let reading = ordered((0..n).map(|v| (rank[v] as f64, y[v], v)).collect());
922    for (i, &v) in reading.iter().enumerate() {
923        pn[v].order = i;
924    }
925
926    // ports: a node's outgoing (incoming) chains spread along its side,
927    // sorted by where they go (come from)
928    let half_along = |v: usize| if lr { pn[v].w / 2.0 } else { pn[v].h / 2.0 };
929    let side = |v: usize| if lr { pn[v].h } else { pn[v].w };
930    let mut out_ports: Vec<Vec<usize>> = vec![Vec::new(); n];
931    let mut in_ports: Vec<Vec<usize>> = vec![Vec::new(); n];
932    for (c, ch) in chains.iter().enumerate() {
933        out_ports[ch[0]].push(c);
934        in_ports[ch[ch.len() - 1]].push(c);
935    }
936    for ports in &mut out_ports {
937        *ports = ordered(ports.iter().map(|&c| (y[chains[c][1]], 0.0, c)).collect());
938    }
939    for ports in &mut in_ports {
940        *ports = ordered(
941            ports
942                .iter()
943                .map(|&c| (y[chains[c][chains[c].len() - 2]], 0.0, c))
944                .collect(),
945        );
946    }
947    let spread = |list: &[usize], c: usize, v: usize| -> f64 {
948        let m = list.len();
949        if m <= 1 {
950            return 0.0;
951        }
952        let k = list.iter().position(|&x| x == c).unwrap_or(0);
953        let span = (side(v) * 0.6).min(14.0 * (m - 1) as f64);
954        -span / 2.0 + span * k as f64 / (m - 1) as f64
955    };
956    let mut edge_pts: Vec<Vec<(f64, f64)>> = vec![Vec::new(); edges.len()];
957    let mut back = vec![false; edges.len()];
958    for (c, ch) in chains.iter().enumerate() {
959        let (u, v, k, rev) = dag[c];
960        let mut pts = Vec::with_capacity(ch.len() * 2);
961        pts.push(xy(
962            centre(rank[u]) + half_along(u),
963            y[u] + spread(&out_ports[u], c, u),
964        ));
965        for &dm in &ch[1..ch.len() - 1] {
966            let r = vrank[dm];
967            pts.push(xy(start_at[r], y[dm]));
968            pts.push(xy(start_at[r] + thick[r], y[dm]));
969        }
970        pts.push(xy(
971            centre(rank[v]) - half_along(v),
972            y[v] + spread(&in_ports[v], c, v),
973        ));
974        if rev {
975            pts.reverse();
976        }
977        edge_pts[k] = pts;
978        back[k] = rev;
979    }
980    for k in loops {
981        edge_pts[k] = self_loop(&pn[edges[k].from], lr, p.loop_size);
982    }
983    for &(u, v, k, rev) in &free {
984        let mut pts = straight(&pn, u, v);
985        if rev {
986            pts.reverse();
987        }
988        edge_pts[k] = pts;
989        back[k] = rev;
990    }
991    // swimlane bands across every layer, with the lane's label strip
992    let total = start_at[nl - 1] + thick[nl - 1];
993    let bands = lane_span
994        .iter()
995        .enumerate()
996        .filter(|(l, (lo, _))| *l < p.lanes && lo.is_finite())
997        .map(|(l, &(lo, hi))| {
998            let (a, b) = if lr {
999                xy(-12.0, lo - lane_head - 6.0)
1000            } else {
1001                xy(-lane_head - 10.0, lo - 8.0)
1002            };
1003            let (c, e) = xy(total + 12.0, hi + 8.0);
1004            (l, a, b, c, e)
1005        })
1006        .collect();
1007    finish(pn, edge_pts, back, nl, bands, p.margin)
1008}
1009
1010/// Move a layout to the margin and round it to half pixels.
1011fn finish(
1012    mut pn: Vec<PlacedNode>,
1013    mut edge_pts: Vec<Vec<(f64, f64)>>,
1014    back: Vec<bool>,
1015    layers: usize,
1016    mut bands: Vec<(usize, f64, f64, f64, f64)>,
1017    margin: f64,
1018) -> GraphLayout {
1019    let (mut x0, mut y0, mut x1, mut y1) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
1020    for q in &pn {
1021        x0 = x0.min(q.cx - q.w / 2.0);
1022        x1 = x1.max(q.cx + q.w / 2.0);
1023        y0 = y0.min(q.cy - q.h / 2.0);
1024        y1 = y1.max(q.cy + q.h / 2.0);
1025    }
1026    for &(px, py) in edge_pts.iter().flatten() {
1027        x0 = x0.min(px);
1028        x1 = x1.max(px);
1029        y0 = y0.min(py);
1030        y1 = y1.max(py);
1031    }
1032    for &(_, a, b, c, e) in &bands {
1033        x0 = x0.min(a.min(c));
1034        x1 = x1.max(a.max(c));
1035        y0 = y0.min(b.min(e));
1036        y1 = y1.max(b.max(e));
1037    }
1038    if pn.is_empty() {
1039        (x0, y0, x1, y1) = (0.0, 0.0, 0.0, 0.0);
1040    }
1041    let r = |v: f64| (v * 2.0).round() / 2.0;
1042    let (dx, dy) = (margin - x0, margin - y0);
1043    for q in &mut pn {
1044        q.cx = r(q.cx + dx);
1045        q.cy = r(q.cy + dy);
1046    }
1047    for pts in &mut edge_pts {
1048        for pt in pts.iter_mut() {
1049            *pt = (r(pt.0 + dx), r(pt.1 + dy));
1050        }
1051    }
1052    for b in &mut bands {
1053        *b = (b.0, r(b.1 + dx), r(b.2 + dy), r(b.3 + dx), r(b.4 + dy));
1054    }
1055    GraphLayout {
1056        w: r(x1 - x0 + 2.0 * margin),
1057        h: r(y1 - y0 + 2.0 * margin),
1058        nodes: pn,
1059        edges: edge_pts,
1060        back,
1061        layers,
1062        bands,
1063    }
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068    use super::*;
1069
1070    fn node(w: f64, h: f64) -> GraphNode {
1071        GraphNode {
1072            w,
1073            h,
1074            ..Default::default()
1075        }
1076    }
1077    fn edge(from: usize, to: usize) -> GraphEdge {
1078        GraphEdge {
1079            from,
1080            to,
1081            label_w: None,
1082        }
1083    }
1084
1085    #[test]
1086    fn a_chain_runs_left_to_right_in_layers() {
1087        let l = layered(
1088            &[node(100.0, 40.0), node(100.0, 40.0), node(100.0, 40.0)],
1089            &[edge(0, 1), edge(1, 2)],
1090            &LayeredParams::default(),
1091        );
1092        assert_eq!(l.layers, 3);
1093        assert!(l.nodes[0].cx < l.nodes[1].cx && l.nodes[1].cx < l.nodes[2].cx);
1094        assert_eq!(l.nodes[0].cy, l.nodes[1].cy);
1095        // an edge leaves its source's right side and enters its target's left
1096        assert_eq!(l.edges[0][0].0, l.nodes[0].cx + 50.0);
1097        assert_eq!(l.edges[0].last().unwrap().0, l.nodes[1].cx - 50.0);
1098    }
1099
1100    #[test]
1101    fn a_cycle_is_broken_and_marked_back() {
1102        let l = layered(
1103            &[node(80.0, 30.0), node(80.0, 30.0)],
1104            &[edge(0, 1), edge(1, 0)],
1105            &LayeredParams::default(),
1106        );
1107        assert_eq!(l.back, vec![false, true]);
1108        assert_eq!(l.layers, 2);
1109    }
1110
1111    #[test]
1112    fn crossings_are_removed_where_possible() {
1113        // a→d, b→c drawn in input order would cross; the layout uncrosses them
1114        let ns = vec![node(60.0, 30.0); 4];
1115        let l = layered(&ns, &[edge(0, 3), edge(1, 2)], &LayeredParams::default());
1116        let (a, b, c, d) = (&l.nodes[0], &l.nodes[1], &l.nodes[2], &l.nodes[3]);
1117        assert_eq!(a.cy < b.cy, d.cy < c.cy, "{a:?} {b:?} {c:?} {d:?}");
1118    }
1119
1120    #[test]
1121    fn pins_and_the_explicit_grid() {
1122        let mut ns = vec![node(60.0, 30.0); 3];
1123        ns[2].pin.0 = Some(3.0);
1124        let l = layered(&ns, &[edge(0, 1)], &LayeredParams::default());
1125        assert_eq!(l.nodes[2].layer, 3, "a pinned layer keeps its number");
1126        let grid: Vec<GraphNode> = (0..4)
1127            .map(|i| GraphNode {
1128                w: 60.0,
1129                h: 30.0,
1130                pin: (Some((i % 2) as f64), Some((i / 2) as f64)),
1131                ..Default::default()
1132            })
1133            .collect();
1134        let g = layered(&grid, &[edge(0, 3)], &LayeredParams::default());
1135        assert_eq!(g.layers, 2);
1136        assert_eq!(g.nodes[0].cy, g.nodes[1].cy);
1137        assert_eq!(g.edges[0].len(), 2, "straight");
1138    }
1139
1140    #[test]
1141    fn lanes_become_bands_and_top_bottom_swaps_the_axes() {
1142        let mut ns = vec![node(60.0, 30.0); 2];
1143        ns[0].lane = Some(0);
1144        ns[1].lane = Some(1);
1145        let l = layered(
1146            &ns,
1147            &[edge(0, 1)],
1148            &LayeredParams {
1149                lanes: 2,
1150                ..Default::default()
1151            },
1152        );
1153        assert_eq!(l.bands.len(), 2);
1154        let tb = layered(
1155            &ns[..1],
1156            &[],
1157            &LayeredParams {
1158                direction: Direction::TopBottom,
1159                ..Default::default()
1160            },
1161        );
1162        assert_eq!(tb.nodes.len(), 1);
1163        let t = layered(
1164            &[node(60.0, 30.0), node(60.0, 30.0)],
1165            &[edge(0, 1)],
1166            &LayeredParams {
1167                direction: Direction::TopBottom,
1168                ..Default::default()
1169            },
1170        );
1171        assert!(t.nodes[0].cy < t.nodes[1].cy && t.nodes[0].cx == t.nodes[1].cx);
1172    }
1173
1174    #[test]
1175    fn edge_cases_loops_labels_ports_lanes_and_empty() {
1176        let p = LayeredParams::default();
1177        let empty = layered(&[], &[], &p);
1178        assert!(empty.nodes.is_empty() && empty.layers == 1);
1179        // self-loops left to right and top to bottom, in layers and on a grid
1180        for dir in [Direction::LeftRight, Direction::TopBottom] {
1181            let q = LayeredParams {
1182                direction: dir,
1183                ..Default::default()
1184            };
1185            let l = layered(
1186                &[node(60.0, 30.0), node(60.0, 30.0)],
1187                &[edge(0, 0), edge(0, 1)],
1188                &q,
1189            );
1190            assert_eq!(l.edges[0].len(), 4, "a self-loop");
1191            let g: Vec<GraphNode> = (0..2)
1192                .map(|i| GraphNode {
1193                    w: 60.0,
1194                    h: 30.0,
1195                    pin: (Some(i as f64), Some(0.0)),
1196                    ..Default::default()
1197                })
1198                .collect();
1199            let gl = layered(&g, &[edge(1, 1), edge(0, 1)], &q);
1200            assert_eq!(gl.edges[0].len(), 4);
1201        }
1202        // an edge label widens its gap; a long edge gets dummies (bends)
1203        let ns = vec![node(60.0, 30.0); 4];
1204        let plain = layered(
1205            &ns,
1206            &[edge(0, 1), edge(1, 2), edge(0, 2), edge(0, 3), edge(2, 3)],
1207            &p,
1208        );
1209        let labelled = layered(
1210            &ns,
1211            &[
1212                GraphEdge {
1213                    from: 0,
1214                    to: 1,
1215                    label_w: Some(300.0),
1216                },
1217                edge(1, 2),
1218                edge(0, 2),
1219                edge(0, 3),
1220                edge(2, 3),
1221            ],
1222            &p,
1223        );
1224        assert!(
1225            labelled.nodes[1].cx - labelled.nodes[0].cx > plain.nodes[1].cx - plain.nodes[0].cx
1226        );
1227        assert!(
1228            plain.edges[3].len() > 2,
1229            "a long edge bends through its dummies"
1230        );
1231        // several edges leave one node: their ports spread along its side
1232        let starts: Vec<f64> = [0, 2, 3].iter().map(|&k| plain.edges[k][0].1).collect();
1233        assert!(starts.windows(2).any(|w| w[0] != w[1]), "{starts:?}");
1234        // a pinned layer against the flow: a straight edge drawn back
1235        let mut pn = vec![node(60.0, 30.0); 3];
1236        pn[0].pin.0 = Some(2.0);
1237        pn[1].pin.0 = Some(0.0);
1238        let back = layered(&pn, &[edge(0, 1), edge(1, 2)], &p);
1239        assert_eq!(back.edges[0].len(), 2);
1240        // swimlanes top to bottom, an unlaned node, crossings between lanes
1241        let mut lanes = vec![node(60.0, 30.0); 5];
1242        for (i, n) in lanes.iter_mut().enumerate().take(4) {
1243            n.lane = Some(i % 2);
1244        }
1245        let q = LayeredParams {
1246            direction: Direction::TopBottom,
1247            lanes: 2,
1248            ..Default::default()
1249        };
1250        let tb = layered(
1251            &lanes,
1252            &[edge(0, 3), edge(1, 2), edge(2, 4), edge(3, 4)],
1253            &q,
1254        );
1255        assert_eq!(tb.bands.len(), 2);
1256        assert!(tb.bands.iter().all(|b| b.3 > b.1 && b.4 > b.2));
1257    }
1258
1259    /// A coin toss in bands param / obs: the Bernoulli factors dropping from
1260    /// θ fan out side by side, each observation right below its factor.
1261    #[test]
1262    fn banded_fans_out_drops_and_hangs_leaves_below() {
1263        // 0 θ (param), 1..=3 Bernoulli (obs), 4..=6 y (obs)
1264        let node = |lane| GraphNode {
1265            w: 40.0,
1266            h: 20.0,
1267            lane: Some(lane),
1268            ..Default::default()
1269        };
1270        let mut nodes = vec![node(0)];
1271        nodes.extend((0..3).map(|_| node(1)));
1272        nodes.extend((0..3).map(|_| node(1)));
1273        let edge = |from, to| GraphEdge {
1274            from,
1275            to,
1276            label_w: None,
1277        };
1278        let mut edges = Vec::new();
1279        for i in 0..3 {
1280            edges.push(edge(0, 1 + i));
1281            edges.push(edge(1 + i, 4 + i));
1282        }
1283        let l = banded(
1284            &nodes,
1285            &edges,
1286            &BandedParams {
1287                bands: 2,
1288                ..Default::default()
1289            },
1290        );
1291        let cx: Vec<f64> = (1..=3).map(|i| l.nodes[i].cx).collect();
1292        assert!(cx[0] < cx[1] && cx[1] < cx[2], "fanned out: {cx:?}");
1293        for i in 0..3 {
1294            let (f, y) = (&l.nodes[1 + i], &l.nodes[4 + i]);
1295            assert_eq!(f.cx, y.cx, "y{i} under its factor");
1296            assert!(y.cy > f.cy);
1297        }
1298    }
1299}