use std::collections::{HashMap, HashSet};
use super::kernel::types::EdgeLabelInfo;
use super::kernel::{
self as layered, Direction as LayeredDirection, LayoutConfig as LayeredConfig, Ranker,
};
use crate::graph::grid::{GridLayoutConfig, GridRanker};
use crate::graph::{Direction, Edge, Graph, Node, Stroke};
pub(crate) fn to_layered_direction(dir: Direction) -> LayeredDirection {
match dir {
Direction::TopDown => LayeredDirection::TopBottom,
Direction::BottomTop => LayeredDirection::BottomTop,
Direction::LeftRight => LayeredDirection::LeftRight,
Direction::RightLeft => LayeredDirection::RightLeft,
}
}
pub(crate) struct SubLayoutResult {
pub(crate) result: layered::LayoutResult,
pub(crate) edge_index_map: Vec<usize>,
}
fn subgraph_has_tainting_cross_boundary_edges(diagram: &Graph, sg_id: &str) -> bool {
let Some(sg) = diagram.subgraphs.get(sg_id) else {
return false;
};
let sg_nodes: HashSet<&str> = sg.nodes.iter().map(|s| s.as_str()).collect();
diagram.edges.iter().any(|edge| {
let from_in = sg_nodes.contains(edge.from.as_str());
let to_in = sg_nodes.contains(edge.to.as_str());
if from_in == to_in {
return false;
}
let via_sg_endpoint = edge.to_subgraph.as_deref() == Some(sg_id)
|| edge.from_subgraph.as_deref() == Some(sg_id);
!via_sg_endpoint
})
}
pub(crate) fn compute_sublayouts<FN, FE>(
diagram: &Graph,
parent_layered_config: &LayeredConfig,
node_dims: FN,
edge_label_dims: FE,
skip_non_isolated_overrides: bool,
) -> HashMap<String, SubLayoutResult>
where
FN: Fn(&Node) -> (f64, f64),
FE: Fn(&Edge) -> Option<(f64, f64)>,
{
let mut sublayouts = HashMap::new();
for (sg_id, sg) in &diagram.subgraphs {
let sub_dir = match sg.dir {
Some(d) => d,
None => continue,
};
if skip_non_isolated_overrides && subgraph_has_tainting_cross_boundary_edges(diagram, sg_id)
{
continue;
}
let layered_direction = to_layered_direction(sub_dir);
let mut sub_graph: layered::DiGraph<(f64, f64)> = layered::DiGraph::new();
for node_id in &sg.nodes {
if !diagram.is_subgraph(node_id)
&& let Some(node) = diagram.nodes.get(node_id)
{
let (w, h) = node_dims(node);
sub_graph.add_node(node_id.as_str(), (w, h));
}
}
let sg_node_set: HashSet<&str> = sg.nodes.iter().map(|s| s.as_str()).collect();
let mut edge_labels: HashMap<usize, EdgeLabelInfo> = HashMap::new();
let mut edge_index_map = Vec::new();
for (edge_idx, edge) in diagram.edges.iter().enumerate() {
if sg_node_set.contains(edge.from.as_str()) && sg_node_set.contains(edge.to.as_str()) {
let sub_edge_idx = edge_index_map.len();
edge_index_map.push(edge_idx);
sub_graph.add_edge(edge.from.as_str(), edge.to.as_str());
if let Some((label_width, label_height)) = edge_label_dims(edge) {
let mut info = EdgeLabelInfo::new(label_width, label_height);
info.thickness = match edge.stroke {
Stroke::Thick => 3.0,
_ => 1.0,
};
edge_labels.insert(sub_edge_idx, info);
}
}
}
let leaf_ids: Vec<&str> = sg
.nodes
.iter()
.filter(|n| !diagram.is_subgraph(n) && diagram.nodes.contains_key(n.as_str()))
.map(|s| s.as_str())
.collect();
if leaf_ids.len() > 1 {
let id_to_idx: HashMap<&str, usize> = leaf_ids
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect();
let mut parent: Vec<usize> = (0..leaf_ids.len()).collect();
let find = |parent: &mut Vec<usize>, mut x: usize| -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]];
x = parent[x];
}
x
};
for edge in &diagram.edges {
if let (Some(&a), Some(&b)) = (
id_to_idx.get(edge.from.as_str()),
id_to_idx.get(edge.to.as_str()),
) {
let ra = find(&mut parent, a);
let rb = find(&mut parent, b);
if ra != rb {
parent[ra] = rb;
}
}
}
let mut prev_component = find(&mut parent, 0);
for i in 1..leaf_ids.len() {
let comp = find(&mut parent, i);
if comp != prev_component {
sub_graph.add_edge(leaf_ids[i - 1], leaf_ids[i]);
let rc = find(&mut parent, comp);
let rp = find(&mut parent, prev_component);
parent[rc] = rp;
}
prev_component = find(&mut parent, i);
}
}
let sub_config = LayeredConfig {
direction: layered_direction,
..parent_layered_config.clone()
};
let result =
layered::layout_with_labels(&sub_graph, &sub_config, |_, dims| *dims, &edge_labels);
sublayouts.insert(
sg_id.clone(),
SubLayoutResult {
result,
edge_index_map,
},
);
}
sublayouts
}
pub(crate) fn layered_config_for_layout(
diagram: &Graph,
config: &GridLayoutConfig,
) -> LayeredConfig {
let layered_direction = to_layered_direction(diagram.direction);
let node_sep = config.node_sep;
let edge_sep = config.edge_sep;
let mut rank_sep = config.rank_sep;
if diagram.has_subgraphs() && config.cluster_rank_sep > 0.0 {
rank_sep += config.cluster_rank_sep;
}
LayeredConfig {
direction: layered_direction,
node_sep,
edge_sep,
rank_sep,
margin: config.margin,
acyclic: true,
ranker: match config.ranker.unwrap_or_default() {
GridRanker::NetworkSimplex => Ranker::NetworkSimplex,
GridRanker::LongestPath => Ranker::LongestPath,
},
..Default::default()
}
}
pub(crate) fn build_layered_layout_with_config<FN, FE>(
diagram: &Graph,
layered_config: &LayeredConfig,
node_dims: FN,
edge_label_dims: FE,
) -> layered::LayoutResult
where
FN: Fn(&Node) -> (f64, f64),
FE: Fn(&Edge) -> Option<(f64, f64)>,
{
let mut digraph = layered::DiGraph::new();
let mut seen = std::collections::HashSet::new();
let mut ordered_node_ids = Vec::new();
for edge in &diagram.edges {
for node_id in [&edge.from, &edge.to] {
if seen.insert(node_id.clone()) {
ordered_node_ids.push(node_id.clone());
}
}
}
let mut node_keys: Vec<&String> = diagram.nodes.keys().collect();
node_keys.sort();
for id in node_keys {
if seen.insert(id.clone()) {
ordered_node_ids.push(id.clone());
}
}
for id in &ordered_node_ids {
if let Some(node) = diagram.nodes.get(id) {
let dims = node_dims(node);
digraph.add_node(id.as_str(), dims);
}
}
let subgraph_keys: Vec<&String> = if !diagram.subgraph_order.is_empty() {
diagram.subgraph_order.iter().rev().collect()
} else {
let mut keys: Vec<&String> = diagram.subgraphs.keys().collect();
keys.sort();
keys
};
let semantic_subgraph_keys: Vec<&String> = if !diagram.subgraph_order.is_empty() {
diagram.subgraph_order.iter().collect()
} else {
let mut keys: Vec<&String> = diagram.subgraphs.keys().collect();
keys.sort();
keys
};
for (order, sg_id) in semantic_subgraph_keys.iter().enumerate() {
digraph.set_compound_semantic_order(sg_id.as_str(), order);
}
for sg_id in &subgraph_keys {
let sg = &diagram.subgraphs[*sg_id];
digraph.add_node(sg_id.as_str(), (0.0, 0.0));
if !sg.title.trim().is_empty() {
digraph.set_has_title(sg_id.as_str());
}
}
let mut node_parent_keys: Vec<&String> = diagram.nodes.keys().collect();
node_parent_keys.sort();
for node_id in node_parent_keys {
let node = &diagram.nodes[node_id];
if let Some(ref parent) = node.parent {
digraph.set_parent(node_id.as_str(), parent.as_str());
}
}
for sg_id in &subgraph_keys {
let sg = &diagram.subgraphs[*sg_id];
if let Some(ref parent_id) = sg.parent {
digraph.set_parent(sg_id.as_str(), parent_id.as_str());
}
}
let dir_override_internal: HashSet<usize> = {
fn effective_parent_direction(diagram: &Graph, sg: &crate::graph::Subgraph) -> Direction {
let mut current = sg.parent.as_deref();
while let Some(parent_id) = current {
let Some(parent) = diagram.subgraphs.get(parent_id) else {
break;
};
if let Some(dir) = parent.dir {
return dir;
}
current = parent.parent.as_deref();
}
diagram.direction
}
let mut set = HashSet::new();
for sg in diagram.subgraphs.values() {
let Some(sub_dir) = sg.dir else {
continue;
};
if sub_dir == effective_parent_direction(diagram, sg) {
continue;
}
let members: HashSet<&str> = sg.nodes.iter().map(|s| s.as_str()).collect();
for (idx, edge) in diagram.edges.iter().enumerate() {
if members.contains(edge.from.as_str()) && members.contains(edge.to.as_str()) {
set.insert(idx);
}
}
}
set
};
let mut edge_labels: HashMap<usize, EdgeLabelInfo> = HashMap::new();
for (edge_idx, edge) in diagram.edges.iter().enumerate() {
let weight = if edge.stroke == Stroke::Invisible {
0.0
} else {
1.0
};
if dir_override_internal.contains(&edge_idx) {
digraph.add_edge_full(edge.from.as_str(), edge.to.as_str(), weight, 0);
} else {
digraph.add_edge_full(edge.from.as_str(), edge.to.as_str(), weight, edge.minlen);
}
if let Some((label_width, label_height)) = edge_label_dims(edge) {
let mut info = EdgeLabelInfo::new(label_width, label_height);
info.thickness = match edge.stroke {
Stroke::Thick => 3.0,
_ => 1.0,
};
edge_labels.insert(edge_idx, info);
}
}
layered::layout_with_labels(&digraph, layered_config, |_, dims| *dims, &edge_labels)
}
#[cfg(test)]
pub(crate) fn build_layered_layout<FN, FE>(
diagram: &Graph,
config: &GridLayoutConfig,
node_dims: FN,
edge_label_dims: FE,
) -> layered::LayoutResult
where
FN: Fn(&Node) -> (f64, f64),
FE: Fn(&Edge) -> Option<(f64, f64)>,
{
let layered_config = layered_config_for_layout(diagram, config);
build_layered_layout_with_config(diagram, &layered_config, node_dims, edge_label_dims)
}