1use crate::geom::{BoundingBox, Point, Spline};
2use serde::{Deserialize, Serialize};
3use std::collections::{BTreeMap, BTreeSet};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6pub struct NodeId(pub u32);
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct EdgeId(pub u32);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
12pub struct SubgraphId(pub u32);
13
14pub type AttrMap = BTreeMap<String, String>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum GraphKind {
18 Undirected,
19 Directed,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
23pub struct Port {
24 pub name: Option<String>,
25 pub compass: Option<String>,
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct NodeData {
30 pub id: NodeId,
31 pub name: String,
32 pub attrs: AttrMap,
33 pub pos: Point,
35 pub width: f64,
37 pub height: f64,
39 pub rank: i32,
40 pub order: usize,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct EdgeData {
45 pub id: EdgeId,
46 pub tail: NodeId,
47 pub head: NodeId,
48 pub tail_port: Port,
49 pub head_port: Port,
50 pub attrs: AttrMap,
51 pub spline: Spline,
52 pub label_pos: Option<Point>,
53 #[serde(default)]
56 pub label_unplaced: bool,
57}
58
59pub fn has_cluster_prefix(name: &str) -> bool {
64 name.as_bytes()
65 .get(..7)
66 .is_some_and(|p| p.eq_ignore_ascii_case(b"cluster"))
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct SubgraphData {
71 pub id: SubgraphId,
72 pub name: String,
73 pub is_cluster: bool,
74 pub graph_attrs: AttrMap,
75 pub node_defaults: AttrMap,
76 pub edge_defaults: AttrMap,
77 pub nodes: Vec<NodeId>,
78 pub edges: Vec<EdgeId>,
79 pub subgraphs: Vec<SubgraphId>,
80 pub bb: BoundingBox,
81}
82
83pub const CLUST_NODE_KEY: &str = "\u{1}clustnode";
87
88pub const NO_GRAPH_LABEL_KEY: &str = "\u{1}nolabel";
91
92pub const NO_EDGE_INIT_KEY: &str = "\u{1}noedgeinit";
95
96pub const SEQ_KEY: &str = "\u{1}seq";
100
101pub const UNREGISTERED_CLUSTER_KEY: &str = "\u{1}unregclust";
105
106pub const REGISTERED_CLUSTER_KEY: &str = "\u{1}regclust";
109
110pub const CLUSTER_LIST_KEY: &str = "\u{1}clustlist";
116
117pub const UNPLACED_PORT_LABELS_KEY: &str = "\u{1}unplacedportlbl";
121pub const TAIL_PORT_NAME_KEY: &str = "\u{1}tailportname";
125pub const HEAD_PORT_NAME_KEY: &str = "\u{1}headportname";
126macro_rules! ignored_edge_key {
128 ($attr:literal) => {
129 concat!("\u{1}ignorededge", $attr)
130 };
131}
132pub const IGNORED_EDGE_KEY: &str = ignored_edge_key!("");
139pub const IGNORED_EDGE_INPUT_KEYS: [(&str, &str); 3] = [
142 ("head_lp", ignored_edge_key!("head_lp")),
143 ("tail_lp", ignored_edge_key!("tail_lp")),
144 ("xlp", ignored_edge_key!("xlp")),
145];
146
147pub const NODE_LW_RW_KEY: &str = "\u{1}lwrw";
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub struct Graph {
156 pub name: String,
157 pub strict: bool,
158 pub kind: GraphKind,
159 pub graph_attrs: AttrMap,
160 pub node_defaults: AttrMap,
161 pub edge_defaults: AttrMap,
162 pub nodes: Vec<NodeData>,
163 pub node_lookup: BTreeMap<String, NodeId>,
164 pub edges: Vec<EdgeData>,
165 pub subgraphs: Vec<SubgraphData>,
166 pub bb: BoundingBox,
167 #[serde(default)]
170 pub html_strings: BTreeSet<String>,
171 #[serde(default)]
175 pub anon_subgraph_ids: BTreeMap<u32, u64>,
176 #[serde(default)]
180 pub dicts: Vec<AttrDicts>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub render_view: Option<Box<Graph>>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
192pub struct AttrSym {
193 pub defval: String,
194 pub print: bool,
195 #[serde(default)]
198 pub id: usize,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
203pub struct AttrDicts {
204 pub graph: BTreeMap<String, AttrSym>,
205 pub node: BTreeMap<String, AttrSym>,
206 pub edge: BTreeMap<String, AttrSym>,
207}
208
209impl Graph {
210 pub fn new(name: impl Into<String>, kind: GraphKind, strict: bool) -> Self {
211 Self {
212 name: name.into(),
213 strict,
214 kind,
215 graph_attrs: AttrMap::new(),
216 node_defaults: AttrMap::new(),
217 edge_defaults: AttrMap::new(),
218 nodes: Vec::new(),
219 node_lookup: BTreeMap::new(),
220 edges: Vec::new(),
221 subgraphs: Vec::new(),
222 bb: BoundingBox::default(),
223 html_strings: BTreeSet::new(),
224 anon_subgraph_ids: BTreeMap::new(),
225 dicts: Vec::new(),
226 render_view: None,
227 }
228 }
229
230 pub fn ensure_node(&mut self, name: &str) -> NodeId {
231 if let Some(&id) = self.node_lookup.get(name) {
232 return id;
233 }
234 let id = NodeId(self.nodes.len() as u32);
235 let mut attrs = self.node_defaults.clone();
236 attrs
237 .entry("label".to_string())
238 .or_insert_with(|| "\\N".to_string());
239 self.nodes.push(NodeData {
240 id,
241 name: name.to_string(),
242 attrs,
243 pos: Point::default(),
244 width: 0.75,
245 height: 0.5,
246 rank: 0,
247 order: id.0 as usize,
248 });
249 self.node_lookup.insert(name.to_string(), id);
250 id
251 }
252
253 pub fn add_edge(&mut self, tail: NodeId, head: NodeId, attrs: AttrMap) -> EdgeId {
254 if self.strict
255 && let Some(existing) = self.edges.iter_mut().find(|e| {
256 (e.tail == tail && e.head == head)
257 || (self.kind == GraphKind::Undirected && e.tail == head && e.head == tail)
258 })
259 {
260 existing.attrs.extend(attrs);
261 return existing.id;
262 }
263 let id = EdgeId(self.edges.len() as u32);
264 let mut merged_attrs = self.edge_defaults.clone();
265 merged_attrs.extend(attrs);
266 self.edges.push(EdgeData {
267 id,
268 tail,
269 head,
270 tail_port: Port::default(),
271 head_port: Port::default(),
272 attrs: merged_attrs,
273 spline: Spline::default(),
274 label_pos: None,
275 label_unplaced: false,
276 });
277 id
278 }
279}
280
281#[cfg(test)]
282mod cluster_prefix_tests {
283 use super::has_cluster_prefix;
284
285 #[test]
286 fn compares_the_first_seven_bytes_case_insensitively() {
287 assert!(has_cluster_prefix("cluster"));
288 assert!(has_cluster_prefix("Cluster_a"));
289 assert!(has_cluster_prefix("CLUSTERé"));
290 assert!(!has_cluster_prefix("cluste"));
291 assert!(!has_cluster_prefix("aaaaaaé"));
292 assert!(!has_cluster_prefix("clusé_x"));
293 assert!(!has_cluster_prefix(""));
294 }
295}