use super::graph::IndexedGraph;
use super::layout::NodePosition;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Direction {
Up,
Down,
Left,
Right,
}
pub(crate) fn find_nearest_in_direction(
current_idx: usize,
positions: &[NodePosition],
direction: Direction,
) -> Option<usize> {
let current = positions.get(current_idx)?;
let cx = current.center_x();
let cy = current.center_y();
positions
.iter()
.enumerate()
.filter(|(idx, _)| *idx != current_idx)
.filter(|(_, pos)| is_in_direction(cx, cy, pos.center_x(), pos.center_y(), direction))
.min_by(|(_, a), (_, b)| {
let dist_a = squared_distance(cx, cy, a.center_x(), a.center_y());
let dist_b = squared_distance(cx, cy, b.center_x(), b.center_y());
dist_a
.partial_cmp(&dist_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(idx, _)| idx)
}
fn is_in_direction(cx: f64, cy: f64, tx: f64, ty: f64, direction: Direction) -> bool {
let dx = tx - cx;
let dy = ty - cy;
match direction {
Direction::Right => dx > 0.0 && dx.abs() >= dy.abs(),
Direction::Left => dx < 0.0 && dx.abs() >= dy.abs(),
Direction::Down => dy > 0.0 && dy.abs() >= dx.abs(),
Direction::Up => dy < 0.0 && dy.abs() >= dx.abs(),
}
}
fn squared_distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
(x2 - x1).powi(2) + (y2 - y1).powi(2)
}
pub(crate) fn outgoing_targets(node_idx: usize, graph: &IndexedGraph) -> Vec<usize> {
graph.successors(node_idx).to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
fn pos(id: &str, x: f64, y: f64) -> NodePosition {
NodePosition::new(id.to_string(), x, y, 12.0, 3.0)
}
#[test]
fn test_find_nearest_right() {
let positions = vec![pos("a", 0.0, 5.0), pos("b", 20.0, 5.0), pos("c", 40.0, 5.0)];
let result = find_nearest_in_direction(0, &positions, Direction::Right);
assert_eq!(result, Some(1)); }
#[test]
fn test_find_nearest_left() {
let positions = vec![pos("a", 0.0, 5.0), pos("b", 20.0, 5.0), pos("c", 40.0, 5.0)];
let result = find_nearest_in_direction(2, &positions, Direction::Left);
assert_eq!(result, Some(1)); }
#[test]
fn test_find_nearest_down() {
let positions = vec![pos("a", 5.0, 0.0), pos("b", 5.0, 10.0), pos("c", 5.0, 20.0)];
let result = find_nearest_in_direction(0, &positions, Direction::Down);
assert_eq!(result, Some(1)); }
#[test]
fn test_find_nearest_up() {
let positions = vec![pos("a", 5.0, 0.0), pos("b", 5.0, 10.0), pos("c", 5.0, 20.0)];
let result = find_nearest_in_direction(2, &positions, Direction::Up);
assert_eq!(result, Some(1)); }
#[test]
fn test_no_node_in_direction() {
let positions = vec![pos("a", 0.0, 5.0), pos("b", 20.0, 5.0)];
let result = find_nearest_in_direction(0, &positions, Direction::Left);
assert_eq!(result, None);
}
#[test]
fn test_diagonal_preference() {
let positions = vec![
pos("a", 0.0, 0.0),
pos("b", 30.0, 5.0), pos("c", 5.0, 30.0), ];
let result = find_nearest_in_direction(0, &positions, Direction::Right);
assert_eq!(result, Some(1));
let result = find_nearest_in_direction(0, &positions, Direction::Down);
assert_eq!(result, Some(2));
}
#[test]
fn test_single_node() {
let positions = vec![pos("a", 0.0, 0.0)];
let result = find_nearest_in_direction(0, &positions, Direction::Right);
assert_eq!(result, None);
}
#[test]
fn test_picks_closest() {
let positions = vec![
pos("a", 0.0, 5.0),
pos("b", 20.0, 5.0), pos("c", 50.0, 5.0), ];
let result = find_nearest_in_direction(0, &positions, Direction::Right);
assert_eq!(result, Some(1)); }
#[test]
fn test_outgoing_targets() {
use super::super::types::{DiagramEdge, DiagramNode};
let nodes = vec![
DiagramNode::new("a", "A"),
DiagramNode::new("b", "B"),
DiagramNode::new("c", "C"),
];
let edges = vec![DiagramEdge::new("a", "b"), DiagramEdge::new("a", "c")];
let graph = IndexedGraph::build(&nodes, &edges);
let targets = outgoing_targets(0, &graph);
assert_eq!(targets, vec![1, 2]);
}
#[test]
fn test_outgoing_targets_none() {
use super::super::types::DiagramNode;
let nodes = vec![DiagramNode::new("a", "A")];
let graph = IndexedGraph::build(&nodes, &[]);
let targets = outgoing_targets(0, &graph);
assert!(targets.is_empty());
}
}