use super::graph_prep::{lay_out_by_piece, PreparedGraph};
use super::Mat;
use crate::matrix::graph::{bfs, far_end, AdjListGraph};
use petgraph::unionfind::UnionFind;
use std::f64::consts::TAU;
#[must_use]
pub fn radial_tree_layout_2d_on_graph(
n: usize,
edges: &[(usize, usize, f32)],
root: Option<usize>,
) -> Mat {
let graph = PreparedGraph::new(n, edges);
let first = root.or((n > 0).then_some(0));
lay_out_by_piece(
&graph,
|piece, at_first| {
let tree = maximum_spanning_tree(piece);
let tree_root = match (root, at_first) {
(Some(_), Some(r)) => r,
_ => tree_centre(&tree),
};
place_tree(&tree, tree_root)
},
first,
)
}
fn maximum_spanning_tree(graph: &PreparedGraph) -> AdjListGraph {
let edges = graph.edges();
let mut by_weight: Vec<usize> = (0..edges.len()).collect();
by_weight.sort_by(|&a, &b| edges[b].2.total_cmp(&edges[a].2));
let mut sets = UnionFind::<usize>::new(graph.n());
let tree: Vec<(usize, usize, f32)> = by_weight
.into_iter()
.map(|e| edges[e])
.filter(|&(i, j, _)| sets.union(i, j))
.collect();
AdjListGraph::from_edges(graph.n(), &tree)
}
fn tree_centre(tree: &AdjListGraph) -> usize {
let a = far_end(tree, 0);
let (order, parent, depth) = bfs(tree, a);
let mut c = *order.last().expect("a is reached");
for _ in 0..depth[c] / 2 {
c = parent[c];
}
c
}
fn place_tree(tree: &AdjListGraph, root: usize) -> Mat {
let (order, parent, depth) = bfs(tree, root);
let n = order.len();
let mut out = Mat::zeros(n, 2);
let mut children: Vec<Vec<usize>> = vec![vec![]; n];
for &v in &order[1..] {
children[parent[v]].push(v);
}
let mut leaves = vec![0usize; n];
for &v in order.iter().rev() {
leaves[v] = if children[v].is_empty() {
1
} else {
children[v].iter().map(|&c| leaves[c]).sum()
};
}
let mut lo = vec![0.0f64; n];
let mut width = vec![0.0f64; n];
width[root] = TAU;
for &v in &order {
let mut at = lo[v];
for &c in &children[v] {
lo[c] = at;
width[c] = width[v] * leaves[c] as f64 / leaves[v] as f64;
at += width[c];
}
if v != root {
let theta = lo[v] + 0.5 * width[v];
let r = depth[v] as f64;
out[(v, 0)] = (r * theta.cos()) as f32;
out[(v, 1)] = (r * theta.sin()) as f32;
}
}
out
}
#[cfg(test)]
mod tests;