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