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