radial sugiyama positioning integration

This commit is contained in:
Oxy8
2026-03-23 11:13:27 -03:00
parent 6b9115e43b
commit 696844f341
51 changed files with 10089 additions and 364 deletions

View File

@@ -0,0 +1,88 @@
use std::collections::{HashSet, VecDeque};
use crate::error::LayoutError;
use crate::model::Graph;
pub fn compute_hierarchy_levels(graph: &Graph) -> Result<Vec<usize>, LayoutError> {
validate_simple_dag(graph)?;
let node_count = graph.nodes.len();
if node_count == 0 {
return Ok(Vec::new());
}
let mut indegree = vec![0usize; node_count];
let mut outgoing = vec![Vec::new(); node_count];
for edge in &graph.edges {
indegree[edge.target] += 1;
outgoing[edge.source].push(edge.target);
}
let mut queue = VecDeque::new();
for (node_index, degree) in indegree.iter().enumerate() {
if *degree == 0 {
queue.push_back(node_index);
}
}
let mut levels = vec![0usize; node_count];
let mut visited = 0usize;
while let Some(node) = queue.pop_front() {
visited += 1;
let next_level = levels[node] + 1;
for &child in &outgoing[node] {
if levels[child] < next_level {
levels[child] = next_level;
}
indegree[child] -= 1;
if indegree[child] == 0 {
queue.push_back(child);
}
}
}
if visited != node_count {
return Err(LayoutError::CycleDetected);
}
Ok(levels)
}
pub(crate) fn validate_simple_dag(graph: &Graph) -> Result<(), LayoutError> {
let node_count = graph.nodes.len();
let mut seen_edges = HashSet::new();
for (edge_index, edge) in graph.edges.iter().enumerate() {
if edge.source >= node_count {
return Err(LayoutError::InvalidNodeIndex {
edge_index,
node_index: edge.source,
node_count,
});
}
if edge.target >= node_count {
return Err(LayoutError::InvalidNodeIndex {
edge_index,
node_index: edge.target,
node_count,
});
}
if edge.source == edge.target {
return Err(LayoutError::SelfLoop {
edge_index,
node: edge.source,
});
}
if !seen_edges.insert((edge.source, edge.target)) {
return Err(LayoutError::DuplicateEdge {
edge_index,
source: edge.source,
target: edge.target,
});
}
}
Ok(())
}