#![allow(dead_code)]
use std::collections::{HashMap, HashSet};
use crate::errors::RenderError;
use crate::format::normalize_enum_token;
pub use crate::graph::attachment::EdgePort;
use crate::graph::projection::GridProjection;
pub use crate::graph::space::{FPoint, FRect};
use crate::graph::{Direction, Shape};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GeometryLevel {
#[default]
Layout,
Routed,
}
impl std::fmt::Display for GeometryLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GeometryLevel::Layout => write!(f, "layout"),
GeometryLevel::Routed => write!(f, "routed"),
}
}
}
impl GeometryLevel {
pub fn parse(s: &str) -> Result<Self, RenderError> {
match normalize_enum_token(s).as_str() {
"layout" => Ok(GeometryLevel::Layout),
"routed" => Ok(GeometryLevel::Routed),
_ => Err(RenderError {
message: format!("unknown geometry level: {s:?}"),
}),
}
}
}
impl std::str::FromStr for GeometryLevel {
type Err = RenderError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Debug, Clone)]
pub struct GraphGeometry {
pub nodes: HashMap<String, PositionedNode>,
pub edges: Vec<LayoutEdge>,
pub subgraphs: HashMap<String, SubgraphGeometry>,
pub self_edges: Vec<SelfEdgeGeometry>,
pub direction: Direction,
pub node_directions: HashMap<String, Direction>,
pub bounds: FRect,
pub reversed_edges: Vec<usize>,
pub engine_hints: Option<EngineHints>,
pub grid_projection: Option<GridProjection>,
pub rerouted_edges: HashSet<usize>,
pub enhanced_backward_routing: bool,
}
#[derive(Debug, Clone)]
pub struct PositionedNode {
pub id: String,
pub rect: FRect,
pub shape: Shape,
pub label: String,
pub parent: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LayoutEdge {
pub index: usize,
pub from: String,
pub to: String,
pub waypoints: Vec<FPoint>,
pub label_position: Option<FPoint>,
pub label_side: Option<EdgeLabelSide>,
pub from_subgraph: Option<String>,
pub to_subgraph: Option<String>,
pub layout_path_hint: Option<Vec<FPoint>>,
pub preserve_orthogonal_topology: bool,
pub label_geometry: Option<EdgeLabelGeometry>,
pub effective_wrapped_lines: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct SubgraphGeometry {
pub id: String,
pub rect: FRect,
pub title: String,
pub depth: usize,
}
#[derive(Debug, Clone)]
pub struct SelfEdgeGeometry {
pub node_id: String,
pub edge_index: usize,
pub points: Vec<FPoint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EdgeLabelSide {
Above,
Below,
#[default]
Center,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EdgeLabelGeometry {
pub center: FPoint,
pub rect: FRect,
pub padding: (f64, f64),
pub side: EdgeLabelSide,
pub track: i32,
pub compartment_size: usize,
}
impl Default for EdgeLabelGeometry {
fn default() -> Self {
Self {
center: FPoint::new(0.0, 0.0),
rect: FRect::new(0.0, 0.0, 0.0, 0.0),
padding: (0.0, 0.0),
side: EdgeLabelSide::Center,
track: 0,
compartment_size: 1,
}
}
}
#[derive(Debug, Clone)]
pub enum EngineHints {
Layered(LayeredHints),
}
#[derive(Debug, Clone)]
pub struct LayeredHints {
pub node_ranks: HashMap<String, i32>,
pub rank_to_position: HashMap<i32, (f64, f64)>,
pub edge_waypoints: HashMap<usize, Vec<(FPoint, i32)>>,
pub label_positions: HashMap<usize, (FPoint, i32)>,
}
#[derive(Debug, Clone)]
pub struct RoutedGraphGeometry {
pub nodes: HashMap<String, PositionedNode>,
pub edges: Vec<RoutedEdgeGeometry>,
pub subgraphs: HashMap<String, SubgraphGeometry>,
pub self_edges: Vec<RoutedSelfEdge>,
pub direction: Direction,
pub bounds: FRect,
pub unfit_label_overlaps: Vec<UnfitOverlap>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnfitOverlap {
pub edge_index: usize,
pub label: String,
pub gap_pixels: f64,
pub label_span_pixels: f64,
pub attempted_side: EdgeLabelSide,
}
#[derive(Debug, Clone)]
pub struct RoutedEdgeGeometry {
pub index: usize,
pub from: String,
pub to: String,
pub path: Vec<FPoint>,
pub label_position: Option<FPoint>,
pub label_side: Option<EdgeLabelSide>,
pub head_label_position: Option<FPoint>,
pub tail_label_position: Option<FPoint>,
pub is_backward: bool,
pub from_subgraph: Option<String>,
pub to_subgraph: Option<String>,
pub source_port: Option<EdgePort>,
pub target_port: Option<EdgePort>,
pub preserve_orthogonal_topology: bool,
pub label_geometry: Option<EdgeLabelGeometry>,
pub effective_wrapped_lines: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct RoutedSelfEdge {
pub node_id: String,
pub edge_index: usize,
pub path: Vec<FPoint>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graph_geometry_default_construction() {
let geo = GraphGeometry {
nodes: HashMap::new(),
edges: Vec::new(),
subgraphs: HashMap::new(),
self_edges: Vec::new(),
direction: Direction::TopDown,
node_directions: HashMap::new(),
bounds: FRect::new(0.0, 0.0, 0.0, 0.0),
reversed_edges: Vec::new(),
engine_hints: None,
grid_projection: None,
rerouted_edges: HashSet::new(),
enhanced_backward_routing: false,
};
assert!(geo.nodes.is_empty());
assert!(geo.edges.is_empty());
assert!(geo.engine_hints.is_none());
assert!(geo.grid_projection.is_none());
}
#[test]
fn engine_hints_layered_construction() {
let hints = EngineHints::Layered(LayeredHints {
node_ranks: HashMap::new(),
rank_to_position: HashMap::new(),
edge_waypoints: HashMap::new(),
label_positions: HashMap::new(),
});
let EngineHints::Layered(inner) = &hints;
assert!(inner.node_ranks.is_empty());
}
#[test]
fn layout_edge_path_hint_optional() {
let edge = LayoutEdge {
index: 0,
from: "A".into(),
to: "B".into(),
waypoints: vec![FPoint::new(1.0, 2.0)],
label_position: None,
label_side: None,
from_subgraph: None,
to_subgraph: None,
layout_path_hint: None,
preserve_orthogonal_topology: false,
label_geometry: None,
effective_wrapped_lines: None,
};
assert!(edge.layout_path_hint.is_none());
assert_eq!(edge.waypoints.len(), 1);
}
#[test]
fn edge_label_geometry_constructs_with_center_rect_padding_side_track() {
let g = EdgeLabelGeometry {
center: FPoint::new(10.0, 20.0),
rect: FRect::new(0.0, 10.0, 20.0, 20.0),
padding: (4.0, 2.0),
side: EdgeLabelSide::Above,
track: 1,
compartment_size: 1,
};
assert_eq!(g.center.x, 10.0);
assert_eq!(g.center.y, 20.0);
assert_eq!(g.rect.width, 20.0);
assert_eq!(g.track, 1);
}
#[test]
fn layout_edge_carries_label_geometry_none_by_default() {
let edge = LayoutEdge {
index: 0,
from: "A".into(),
to: "B".into(),
waypoints: vec![],
label_position: None,
label_side: None,
from_subgraph: None,
to_subgraph: None,
layout_path_hint: None,
preserve_orthogonal_topology: false,
label_geometry: None,
effective_wrapped_lines: None,
};
assert!(edge.label_geometry.is_none());
}
#[test]
fn routed_edge_geometry_carries_label_geometry_none_by_default() {
let edge = RoutedEdgeGeometry {
index: 0,
from: "A".into(),
to: "B".into(),
path: vec![],
label_position: None,
label_side: None,
head_label_position: None,
tail_label_position: None,
is_backward: false,
from_subgraph: None,
to_subgraph: None,
source_port: None,
target_port: None,
preserve_orthogonal_topology: false,
label_geometry: None,
effective_wrapped_lines: None,
};
assert!(edge.label_geometry.is_none());
}
#[test]
fn routed_edge_geometry_with_ports() {
let edge = RoutedEdgeGeometry {
index: 0,
from: "A".to_string(),
to: "B".to_string(),
path: vec![],
label_position: None,
label_side: None,
head_label_position: None,
tail_label_position: None,
is_backward: false,
from_subgraph: None,
to_subgraph: None,
source_port: Some(EdgePort {
face: crate::graph::attachment::PortFace::Bottom,
fraction: 0.5,
position: FPoint::new(50.0, 35.0),
group_size: 1,
}),
target_port: None,
preserve_orthogonal_topology: false,
label_geometry: None,
effective_wrapped_lines: None,
};
assert!(edge.source_port.is_some());
assert!(edge.target_port.is_none());
}
}