abstracttui_graph/layout/
layered.rs1use std::collections::HashMap;
11
12use crate::desc::{Direction, GraphDesc};
13
14use super::coords::assign;
15use super::geom::{cross_extent, flow_extent, map_point, map_rect, self_loop};
16use super::grid::grid_with_notes;
17use super::ordering::RankStructure;
18use super::resolve::Resolved;
19use super::{assemble, EdgeLayout, Layout, NodeLayout};
20
21#[derive(Clone, Debug, PartialEq)]
33pub struct LayeredOpts {
34 pub direction: Direction,
36 pub node_gap: i32,
39 pub rank_gap: i32,
42 pub sweeps: u32,
46 pub node_cap: usize,
50}
51
52impl Default for LayeredOpts {
53 fn default() -> Self {
54 LayeredOpts {
55 direction: Direction::TopDown,
56 node_gap: 3,
57 rank_gap: 2,
58 sweeps: 4,
59 node_cap: 512,
60 }
61 }
62}
63
64pub fn layered(desc: &GraphDesc, opts: &LayeredOpts) -> Layout {
72 let mut resolved = Resolved::new(desc);
73 let notes = resolved.notes();
74 let n = resolved.len();
75
76 if n > opts.node_cap {
77 let mut all = vec![format!(
78 "node cap exceeded ({n} > {}); grid placement fallback",
79 opts.node_cap
80 )];
81 all.extend(notes);
82 return grid_with_notes(desc, all);
83 }
84 if n == 0 {
85 return assemble(Vec::new(), Vec::new(), notes);
86 }
87
88 let dir = opts.direction;
89 let node_gap = f64::from(opts.node_gap.max(1));
90 let rank_gap = f64::from(opts.rank_gap.max(1));
91
92 resolved.break_cycles();
93 let rank = longest_path_ranks(&resolved);
94 let comp_of = resolved.components();
95 let comp_count = comp_of.iter().copied().max().map_or(0, |c| c + 1);
96
97 let mut node_out: Vec<Option<NodeLayout>> = (0..n).map(|_| None).collect();
98 let mut edge_out: Vec<(usize, EdgeLayout)> = Vec::new();
99 let mut running_cross = 0.0f64;
100 let comp_gap = node_gap * 2.0;
101
102 for comp in 0..comp_count {
103 let locals: Vec<usize> = (0..n).filter(|&g| comp_of[g] == comp).collect();
104 let piece = layout_component(
105 &resolved,
106 &rank,
107 &locals,
108 dir,
109 node_gap,
110 rank_gap,
111 opts.sweeps,
112 );
113
114 let offset = running_cross - piece.min_cross;
116 running_cross += piece.cross_span() + comp_gap;
117
118 for (g, cross, flow) in piece.node_places {
119 let size = resolved.sizes[g];
120 let rect = map_rect(dir, cross + offset, flow, size);
121 node_out[g] = Some(NodeLayout::new(resolved.id(desc, g), rect, rank[g]));
122 }
123 for poly in piece.edge_polylines {
124 let waypoints = poly
125 .points
126 .into_iter()
127 .map(|(c, f)| map_point(dir, c + offset, f))
128 .collect();
129 let e = &desc.edges[poly.desc_index];
130 let mut el = EdgeLayout::new(e.from.clone(), e.to.clone(), poly.desc_index, waypoints);
131 el.broken = poly.broken;
132 edge_out.push((poly.desc_index, el));
133 }
134 }
135
136 let nodes: Vec<NodeLayout> = node_out.into_iter().flatten().collect();
137
138 for &(g, desc_index) in &resolved.self_edges {
141 let rect = nodes[g].rect;
142 let e = &desc.edges[desc_index];
143 edge_out.push((
144 desc_index,
145 EdgeLayout::new(e.from.clone(), e.to.clone(), desc_index, self_loop(rect)),
146 ));
147 }
148
149 edge_out.sort_by_key(|(i, _)| *i);
150 let edges = edge_out.into_iter().map(|(_, e)| e).collect();
151 assemble(nodes, edges, notes)
152}
153
154fn longest_path_ranks(resolved: &Resolved) -> Vec<usize> {
158 let n = resolved.len();
159 let mut out: Vec<Vec<usize>> = vec![Vec::new(); n];
160 let mut indeg = vec![0usize; n];
161 for e in &resolved.edges {
162 let (u, v) = oriented(e.from, e.to, e.broken);
163 out[u].push(v);
164 indeg[v] += 1;
165 }
166 let mut rank = vec![0usize; n];
167 let mut queue: std::collections::VecDeque<usize> = (0..n).filter(|&i| indeg[i] == 0).collect();
168 while let Some(u) = queue.pop_front() {
169 for &v in &out[u] {
170 rank[v] = rank[v].max(rank[u] + 1);
171 indeg[v] -= 1;
172 if indeg[v] == 0 {
173 queue.push_back(v);
174 }
175 }
176 }
177 rank
178}
179
180const fn oriented(from: usize, to: usize, broken: bool) -> (usize, usize) {
181 if broken {
182 (to, from)
183 } else {
184 (from, to)
185 }
186}
187
188struct EdgePolyline {
190 desc_index: usize,
191 broken: bool,
192 points: Vec<(f64, f64)>,
193}
194
195struct ComponentPiece {
197 node_places: Vec<(usize, f64, f64)>,
199 edge_polylines: Vec<EdgePolyline>,
200 min_cross: f64,
201 max_cross: f64,
202}
203
204impl ComponentPiece {
205 fn cross_span(&self) -> f64 {
206 (self.max_cross - self.min_cross).max(0.0)
207 }
208}
209
210#[allow(clippy::too_many_arguments)]
211fn layout_component(
212 resolved: &Resolved,
213 rank: &[usize],
214 locals: &[usize],
215 dir: Direction,
216 node_gap: f64,
217 rank_gap: f64,
218 sweeps: u32,
219) -> ComponentPiece {
220 let mut member_of = HashMap::with_capacity(locals.len());
223 for (m, &g) in locals.iter().enumerate() {
224 member_of.insert(g, m);
225 }
226 let rank_count = locals.iter().map(|&g| rank[g] + 1).max().unwrap_or(1);
227
228 let mut cross_ext: Vec<f64> = locals
229 .iter()
230 .map(|&g| cross_extent(dir, resolved.sizes[g]))
231 .collect();
232 let mut flow_ext: Vec<f64> = locals
233 .iter()
234 .map(|&g| flow_extent(dir, resolved.sizes[g]))
235 .collect();
236
237 struct Chain {
240 desc_index: usize,
241 broken: bool,
242 members: Vec<usize>,
243 u: usize,
244 v: usize,
245 }
246 let mut chains: Vec<Chain> = Vec::new();
247 let mut dummy_ranks: Vec<usize> = Vec::new();
248 for e in &resolved.edges {
249 let (gu, gv) = oriented(e.from, e.to, e.broken);
250 let (Some(&mu), Some(&mv)) = (member_of.get(&gu), member_of.get(&gv)) else {
251 continue; };
253 let mut members = vec![mu];
254 for r in (rank[gu] + 1)..rank[gv] {
255 let d = locals.len() + dummy_ranks.len();
256 dummy_ranks.push(r);
257 cross_ext.push(1.0);
258 flow_ext.push(1.0);
259 members.push(d);
260 }
261 members.push(mv);
262 chains.push(Chain {
263 desc_index: e.desc_index,
264 broken: e.broken,
265 members,
266 u: mu,
267 v: mv,
268 });
269 }
270
271 let member_count = cross_ext.len();
274 let mut ranks: Vec<Vec<usize>> = vec![Vec::new(); rank_count];
275 for (m, &g) in locals.iter().enumerate() {
276 ranks[rank[g]].push(m);
277 }
278 for (i, &r) in dummy_ranks.iter().enumerate() {
279 ranks[r].push(locals.len() + i);
280 }
281 let mut up: Vec<Vec<usize>> = vec![Vec::new(); member_count];
282 let mut down: Vec<Vec<usize>> = vec![Vec::new(); member_count];
283 for chain in &chains {
284 for pair in chain.members.windows(2) {
285 down[pair[0]].push(pair[1]);
286 up[pair[1]].push(pair[0]);
287 }
288 }
289 let mut rs = RankStructure { ranks, up, down };
290 rs.reduce_crossings(sweeps);
291 let coords = assign(&rs, &cross_ext, &flow_ext, node_gap, rank_gap);
292
293 let member_rank = {
294 let mut mr = vec![0usize; member_count];
295 for (r, members) in rs.ranks.iter().enumerate() {
296 for &m in members {
297 mr[m] = r;
298 }
299 }
300 mr
301 };
302 let center = |m: usize| coords.cross[m] + cross_ext[m] / 2.0;
303
304 let mut pair_total: HashMap<(usize, usize), usize> = HashMap::new();
308 for chain in &chains {
309 *pair_total.entry((chain.u, chain.v)).or_insert(0) += 1;
310 }
311 let mut pair_seen: HashMap<(usize, usize), usize> = HashMap::new();
312
313 let mut node_places = Vec::with_capacity(locals.len());
314 for (m, &g) in locals.iter().enumerate() {
315 node_places.push((g, coords.cross[m], coords.flow_start[member_rank[m]]));
316 }
317
318 let mut edge_polylines = Vec::with_capacity(chains.len());
319 for chain in &chains {
320 let total = pair_total[&(chain.u, chain.v)];
321 let ordinal = pair_seen.entry((chain.u, chain.v)).or_insert(0);
322 let k = *ordinal;
323 *ordinal += 1;
324 let spread = if total > 1 {
325 let raw = (2 * k) as f64 - (total - 1) as f64;
326 let limit = ((cross_ext[chain.u].min(cross_ext[chain.v])) / 2.0 - 0.5).max(0.0);
327 raw.clamp(-limit, limit)
328 } else {
329 0.0
330 };
331
332 let mut points = Vec::with_capacity(chain.members.len());
333 let ru = member_rank[chain.u];
334 points.push((
335 center(chain.u) + spread,
336 coords.flow_start[ru] + flow_ext[chain.u],
337 ));
338 for &d in &chain.members[1..chain.members.len() - 1] {
339 let rd = member_rank[d];
340 points.push((center(d), coords.flow_start[rd] + coords.band_ext[rd] / 2.0));
341 }
342 let rv = member_rank[chain.v];
343 points.push((center(chain.v) + spread, coords.flow_start[rv] - 1.0));
344
345 if chain.broken {
346 points.reverse();
349 }
350 edge_polylines.push(EdgePolyline {
351 desc_index: chain.desc_index,
352 broken: chain.broken,
353 points,
354 });
355 }
356
357 let min_cross = (0..member_count)
358 .map(|m| coords.cross[m])
359 .fold(f64::INFINITY, f64::min);
360 let max_cross = (0..member_count)
361 .map(|m| coords.cross[m] + cross_ext[m])
362 .fold(f64::NEG_INFINITY, f64::max);
363 ComponentPiece {
364 node_places,
365 edge_polylines,
366 min_cross,
367 max_cross,
368 }
369}