#![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,
}
#[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)]
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,
}
#[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,
}
#[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,
};
assert!(edge.layout_path_hint.is_none());
assert_eq!(edge.waypoints.len(), 1);
}
#[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,
};
assert!(edge.source_port.is_some());
assert!(edge.target_port.is_none());
}
}