use std::collections::HashMap;
use unicode_width::UnicodeWidthStr;
use crate::types::{Graph, Subgraph};
pub const SG_BORDER_PAD: usize = 2;
#[derive(Debug, Clone)]
pub struct SubgraphBounds {
pub id: String,
pub label: String,
pub col: usize,
pub row: usize,
pub width: usize,
pub height: usize,
pub depth: usize,
}
fn node_draw_width(graph: &Graph, id: &str) -> usize {
if let Some(node) = graph.node(id) {
let label_w = node.label_width();
let inner = label_w + 4; match node.shape {
crate::types::NodeShape::Circle
| crate::types::NodeShape::Stadium
| crate::types::NodeShape::Hexagon
| crate::types::NodeShape::Asymmetric
| crate::types::NodeShape::Subroutine
| crate::types::NodeShape::Parallelogram
| crate::types::NodeShape::Trapezoid => inner + 2,
crate::types::NodeShape::DoubleCircle => inner + 4,
_ => inner,
}
} else {
6
}
}
fn node_draw_height(graph: &Graph, id: &str) -> usize {
if let Some(node) = graph.node(id) {
let extra = node.label_line_count().saturating_sub(1);
match node.shape {
crate::types::NodeShape::Cylinder => 4 + extra,
crate::types::NodeShape::DoubleCircle => 5 + extra,
_ => 3 + extra,
}
} else {
3
}
}
pub fn compute_subgraph_bounds(
graph: &Graph,
positions: &HashMap<String, (usize, usize)>,
) -> Vec<SubgraphBounds> {
let mut result: Vec<SubgraphBounds> = Vec::new();
for sg in &graph.subgraphs {
compute_bounds_recursive(graph, sg, positions, 0, &mut result);
}
result.sort_by_key(|b| b.depth);
result
}
fn compute_bounds_recursive(
graph: &Graph,
sg: &Subgraph,
positions: &HashMap<String, (usize, usize)>,
depth: usize,
out: &mut Vec<SubgraphBounds>,
) -> Option<SubgraphBounds> {
let mut child_bounds: Vec<SubgraphBounds> = Vec::new();
for child_id in &sg.subgraph_ids {
if let Some(child) = graph.find_subgraph(child_id)
&& let Some(cb) = compute_bounds_recursive(graph, child, positions, depth + 1, out)
{
child_bounds.push(cb);
}
}
let mut min_col = usize::MAX;
let mut min_row = usize::MAX;
let mut max_col = 0usize;
let mut max_row = 0usize;
let mut any = false;
for nid in &sg.node_ids {
if let Some(&(col, row)) = positions.get(nid) {
let w = node_draw_width(graph, nid);
let h = node_draw_height(graph, nid);
min_col = min_col.min(col);
min_row = min_row.min(row);
max_col = max_col.max(col + w);
max_row = max_row.max(row + h);
any = true;
}
}
for cb in &child_bounds {
min_col = min_col.min(cb.col);
min_row = min_row.min(cb.row);
max_col = max_col.max(cb.col + cb.width);
max_row = max_row.max(cb.row + cb.height);
any = true;
}
if !any {
return None; }
let border_col = min_col.saturating_sub(SG_BORDER_PAD);
let border_row = min_row.saturating_sub(SG_BORDER_PAD);
let content_width = (max_col - min_col) + SG_BORDER_PAD * 2;
let label_width = UnicodeWidthStr::width(sg.label.as_str()) + 4;
let border_width = content_width.max(label_width);
let border_height = (max_row - min_row) + SG_BORDER_PAD * 2;
let bounds = SubgraphBounds {
id: sg.id.clone(),
label: sg.label.clone(),
col: border_col,
row: border_row,
width: border_width,
height: border_height,
depth,
};
out.push(bounds.clone());
Some(bounds)
}