use crate::graph::{EdgeLabel, Graph, NodeLabel, Point};
use std::collections::HashMap;
pub const GRAPH_NODE: &str = "\x00";
use std::sync::atomic::{AtomicU64, Ordering};
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn unique_id(prefix: &str) -> String {
let id = ID_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
format!("{}{}", prefix, id)
}
pub fn reset_id_counter() {
ID_COUNTER.store(0, Ordering::Relaxed);
}
pub fn add_dummy_node(
graph: &mut Graph,
node_type: &str,
mut attrs: NodeLabel,
name: &str,
) -> String {
let mut v = name.to_string();
while graph.has_node(&v) {
v = unique_id(name);
}
attrs.dummy = Some(node_type.to_string());
graph.set_node(&v, attrs);
v
}
pub fn add_border_node(graph: &mut Graph, prefix: &str) -> String {
let node = NodeLabel {
width: 0.0,
height: 0.0,
..Default::default()
};
add_dummy_node(graph, "border", node, prefix)
}
pub fn simplify(graph: &Graph) -> Graph {
let mut simplified = Graph::with_options(graph.is_directed, false, false);
simplified.set_graph(graph.graph().clone());
for v in graph.nodes() {
simplified.set_node(&v, graph.node(&v).clone());
}
for e in graph.edges() {
let label = graph.edge(&e).unwrap();
let new_weight;
let new_minlen;
if let Some(existing) = simplified.edge_vw(&e.v, &e.w) {
new_weight = existing.weight.unwrap_or(0.0) + label.weight.unwrap_or(0.0);
new_minlen = existing.minlen.unwrap_or(1).max(label.minlen.unwrap_or(1));
} else {
new_weight = label.weight.unwrap_or(0.0);
new_minlen = label.minlen.unwrap_or(1);
}
simplified.set_edge(
&e.v,
&e.w,
EdgeLabel {
weight: Some(new_weight),
minlen: Some(new_minlen),
..Default::default()
},
None,
);
}
simplified
}
pub fn as_non_compound_graph(graph: &Graph) -> Graph {
let mut simplified = Graph::with_options(graph.is_directed, graph.is_multigraph, false);
simplified.set_graph(graph.graph().clone());
for v in graph.nodes() {
if graph.children(&v).is_empty() {
simplified.set_node(&v, graph.node(&v).clone());
}
}
for e in graph.edges() {
let label = graph.edge(&e).unwrap().clone();
simplified.set_edge_obj(&e, label);
}
simplified
}
pub fn build_layer_matrix(graph: &Graph) -> Vec<Vec<String>> {
let mr = max_rank(graph);
if mr < 0 {
return Vec::new();
}
let size = (mr + 1) as usize;
let mut layering: Vec<Vec<String>> = vec![Vec::new(); size];
for v in graph.nodes() {
let node = graph.node(&v);
if let Some(rank) = node.rank {
let rank_idx = rank as usize;
if rank_idx >= layering.len() {
layering.resize(rank_idx + 1, Vec::new());
}
let order = node.order.unwrap_or(0) as usize;
let layer = &mut layering[rank_idx];
if order >= layer.len() {
layer.resize(order + 1, String::new());
}
layer[order] = v;
}
}
layering
}
pub fn normalize_ranks(graph: &mut Graph) {
let min_rank = graph
.nodes()
.iter()
.filter_map(|v| graph.node(v).rank)
.min()
.unwrap_or(0);
let node_ids: Vec<String> = graph.nodes();
for v in node_ids {
let node = graph.node_mut(&v);
if let Some(r) = node.rank.as_mut() {
*r -= min_rank;
}
}
}
pub fn remove_empty_ranks(graph: &mut Graph) {
let node_ranks: Vec<i32> = graph
.nodes()
.iter()
.filter_map(|v| graph.node(v).rank)
.collect();
if node_ranks.is_empty() {
return;
}
let offset = *node_ranks.iter().min().unwrap();
let node_rank_factor = graph.graph().node_rank_factor.unwrap_or(1);
let max_adj = node_ranks.iter().map(|r| r - offset).max().unwrap_or(0) as usize;
let mut layers: Vec<Vec<String>> = vec![Vec::new(); max_adj + 1];
for v in graph.nodes() {
if let Some(rank) = graph.node(&v).rank {
let adj = (rank - offset) as usize;
layers[adj].push(v);
}
}
let mut delta: i32 = 0;
for (i, vs) in layers.iter().enumerate() {
if vs.is_empty() && (i as i32) % node_rank_factor != 0 {
delta -= 1;
} else if !vs.is_empty() && delta != 0 {
for v in vs {
if let Some(r) = graph.node_mut(v).rank.as_mut() {
*r += delta;
}
}
}
}
}
pub fn successor_weights(graph: &Graph) -> HashMap<String, HashMap<String, f64>> {
let mut result = HashMap::new();
for v in graph.nodes() {
let mut sucs: HashMap<String, f64> = HashMap::new();
if let Some(out_edges) = graph.out_edges(&v) {
for e in out_edges {
let w = graph.edge(&e).map_or(0.0, |l| l.weight.unwrap_or(0.0));
*sucs.entry(e.w.clone()).or_insert(0.0) += w;
}
}
result.insert(v, sucs);
}
result
}
pub fn predecessor_weights(graph: &Graph) -> HashMap<String, HashMap<String, f64>> {
let mut result = HashMap::new();
for v in graph.nodes() {
let mut preds: HashMap<String, f64> = HashMap::new();
if let Some(in_edges) = graph.in_edges(&v) {
for e in in_edges {
let w = graph.edge(&e).map_or(0.0, |l| l.weight.unwrap_or(0.0));
*preds.entry(e.v.clone()).or_insert(0.0) += w;
}
}
result.insert(v, preds);
}
result
}
pub fn intersect_node(node: &NodeLabel, point: &Point) -> Point {
match node.intersect_type {
Some("diamond") => intersect_diamond(node, point),
Some("circle") => intersect_ellipse(node, point),
_ => intersect_rect(node, point),
}
}
fn intersect_diamond(node: &NodeLabel, point: &Point) -> Point {
let x = node.x.unwrap_or(0.0);
let y = node.y.unwrap_or(0.0);
let dx = point.x - x;
let dy = point.y - y;
let hw = node.width / 2.0;
let hh = node.height / 2.0;
if dx == 0.0 && dy == 0.0 {
return Point { x, y };
}
let t = 1.0 / (dx.abs() / hw + dy.abs() / hh);
Point {
x: x + dx * t,
y: y + dy * t,
}
}
fn intersect_ellipse(node: &NodeLabel, point: &Point) -> Point {
let x = node.x.unwrap_or(0.0);
let y = node.y.unwrap_or(0.0);
let dx = point.x - x;
let dy = point.y - y;
let rx = node.width / 2.0;
let ry = node.height / 2.0;
if dx == 0.0 && dy == 0.0 {
return Point { x, y };
}
let t = 1.0 / ((dx / rx).powi(2) + (dy / ry).powi(2)).sqrt();
Point {
x: x + dx * t,
y: y + dy * t,
}
}
pub fn intersect_rect(rect: &NodeLabel, point: &Point) -> Point {
let x = rect.x.unwrap_or(0.0);
let y = rect.y.unwrap_or(0.0);
let dx = point.x - x;
let dy = point.y - y;
let mut w = rect.width / 2.0;
let mut h = rect.height / 2.0;
if dx == 0.0 && dy == 0.0 {
return Point { x, y };
}
let (sx, sy);
if dy.abs() * w > dx.abs() * h {
if dy < 0.0 {
h = -h;
}
sx = if dy != 0.0 { h * dx / dy } else { 0.0 };
sy = h;
} else {
if dx < 0.0 {
w = -w;
}
sx = w;
sy = if dx != 0.0 { w * dy / dx } else { 0.0 };
}
Point {
x: x + sx,
y: y + sy,
}
}
pub fn max_rank(graph: &Graph) -> i32 {
graph
.nodes()
.iter()
.filter_map(|v| graph.node(v).rank)
.max()
.unwrap_or(i32::MIN)
}
pub fn range(start: i32, limit: Option<i32>, step: Option<i32>) -> Vec<i32> {
let step = step.unwrap_or(1);
let (actual_start, actual_limit) = match limit {
Some(lim) => (start, lim),
None => (0, start),
};
let mut v = Vec::new();
if step > 0 {
let mut i = actual_start;
while i < actual_limit {
v.push(i);
i += step;
}
} else if step < 0 {
let mut i = actual_start;
while i > actual_limit {
v.push(i);
i += step;
}
}
v
}
pub struct PartitionResult<T> {
pub lhs: Vec<T>,
pub rhs: Vec<T>,
}
pub fn partition<T, F: Fn(&T) -> bool>(collection: Vec<T>, f: F) -> PartitionResult<T> {
let mut lhs = Vec::new();
let mut rhs = Vec::new();
for item in collection {
if f(&item) {
lhs.push(item);
} else {
rhs.push(item);
}
}
PartitionResult { lhs, rhs }
}
pub fn map_values<V, R, F: Fn(V, &str) -> R>(obj: HashMap<String, V>, f: F) -> HashMap<String, R> {
obj.into_iter()
.map(|(k, v)| {
let r = f(v, &k);
(k, r)
})
.collect()
}
pub fn map_values_ref<V: Clone, R, F: Fn(&V, &str) -> R>(
obj: &HashMap<String, V>,
f: F,
) -> HashMap<String, R> {
obj.iter()
.map(|(k, v)| {
let r = f(v, k);
(k.clone(), r)
})
.collect()
}
pub fn zip_object<V: Clone>(keys: &[String], values: &[V]) -> HashMap<String, V> {
keys.iter()
.enumerate()
.filter_map(|(i, k)| values.get(i).map(|v| (k.clone(), v.clone())))
.collect()
}
const CHUNKING_THRESHOLD: usize = 65535;
pub fn apply_min(arr: &[f64]) -> f64 {
if arr.is_empty() {
return f64::INFINITY;
}
if arr.len() > CHUNKING_THRESHOLD {
let chunks: Vec<f64> = arr.chunks(CHUNKING_THRESHOLD).map(apply_min).collect();
apply_min(&chunks)
} else {
arr.iter().cloned().fold(f64::INFINITY, f64::min)
}
}
pub fn apply_max(arr: &[f64]) -> f64 {
if arr.is_empty() {
return f64::NEG_INFINITY;
}
if arr.len() > CHUNKING_THRESHOLD {
let chunks: Vec<f64> = arr.chunks(CHUNKING_THRESHOLD).map(apply_max).collect();
apply_max(&chunks)
} else {
arr.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}
}
pub fn apply_min_i32(arr: &[i32]) -> i32 {
arr.iter().cloned().min().unwrap_or(i32::MAX)
}
pub fn apply_max_i32(arr: &[i32]) -> i32 {
arr.iter().cloned().max().unwrap_or(i32::MIN)
}
pub fn points_to_path(points: &[(f64, f64)]) -> String {
if points.is_empty() {
return String::new();
}
if points.len() == 1 {
return format!("M {:.1} {:.1}", points[0].0, points[0].1);
}
let mut path = format!("M {:.1} {:.1}", points[0].0, points[0].1);
if points.len() == 2 {
path.push_str(&format!(" L {:.1} {:.1}", points[1].0, points[1].1));
return path;
}
for i in 1..points.len() - 1 {
let p0 = points[i - 1];
let p1 = points[i];
let p2 = points[i + 1];
let cp1x = p1.0 - (p2.0 - p0.0) / 6.0;
let cp1y = p1.1 - (p2.1 - p0.1) / 6.0;
let cp2x = p1.0 + (p2.0 - p0.0) / 6.0;
let cp2y = p1.1 + (p2.1 - p0.1) / 6.0;
path.push_str(&format!(
" C {:.1} {:.1} {:.1} {:.1} {:.1} {:.1}",
cp1x, cp1y, cp2x, cp2y, p1.0, p1.1
));
}
let last = points.last().unwrap();
path.push_str(&format!(" L {:.1} {:.1}", last.0, last.1));
path
}
pub fn clamp(v: f64, min: f64, max: f64) -> f64 {
v.max(min).min(max)
}