radial sugiyama positioning integration
This commit is contained in:
88
radial_sugiyama/src/layering.rs
Normal file
88
radial_sugiyama/src/layering.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user