flow_rs_core/layout/algorithms/
hierarchical.rs1use crate::error::{FlowError, Result};
4use crate::graph::Graph;
5use crate::layout::LayoutAlgorithm;
6use crate::types::{NodeId, Position, Size};
7
8#[derive(Debug, Clone)]
10pub struct HierarchicalLayout {
11 pub node_separation: f64,
12 pub level_separation: f64,
13 pub edge_routing: EdgeRouting,
14 pub direction: LayoutDirection,
15 pub root_node: Option<NodeId>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum EdgeRouting {
21 Straight,
22 Orthogonal,
23 Curved,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq)]
28pub enum LayoutDirection {
29 TopToBottom,
30 BottomToTop,
31 LeftToRight,
32 RightToLeft,
33}
34
35impl Default for HierarchicalLayout {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl HierarchicalLayout {
42 pub fn new() -> Self {
44 Self {
45 node_separation: 80.0,
46 level_separation: 100.0,
47 edge_routing: EdgeRouting::Straight,
48 direction: LayoutDirection::TopToBottom,
49 root_node: None,
50 }
51 }
52
53 pub fn builder() -> HierarchicalLayoutBuilder {
55 HierarchicalLayoutBuilder::default()
56 }
57
58 pub fn with_root(mut self, root_id: NodeId) -> Self {
60 self.root_node = Some(root_id);
61 self
62 }
63
64 fn build_tree<N, E>(&self, graph: &Graph<N, E>) -> Result<TreeNode> {
66 let root_id = if let Some(ref root) = self.root_node {
67 root.clone()
68 } else {
69 let root_nodes = self.find_root_nodes(graph)?;
70 root_nodes.into_iter().next()
71 .ok_or_else(|| FlowError::layout("No root node found for hierarchical layout"))?
72 };
73
74 self.build_tree_recursive(&root_id, graph, &mut std::collections::HashSet::new())
75 }
76
77 fn find_root_nodes<N, E>(&self, graph: &Graph<N, E>) -> Result<Vec<NodeId>> {
79 let mut root_nodes = Vec::new();
80
81 for node in graph.nodes() {
82 let incoming_edges = graph.get_incoming_edges(&node.id);
83 if incoming_edges.is_empty() {
84 root_nodes.push(node.id.clone());
85 }
86 }
87
88 if root_nodes.is_empty() {
89 return Err(FlowError::layout(
90 "No root nodes found - graph may be cyclic",
91 ));
92 }
93
94 Ok(root_nodes)
95 }
96
97 #[allow(clippy::only_used_in_recursion)]
99 fn build_tree_recursive<N, E>(
100 &self,
101 node_id: &NodeId,
102 graph: &Graph<N, E>,
103 visited: &mut std::collections::HashSet<NodeId>,
104 ) -> Result<TreeNode> {
105 if visited.contains(node_id) {
106 return Err(FlowError::layout("Cycle detected in graph"));
107 }
108
109 visited.insert(node_id.clone());
110
111 let node = graph
112 .get_node(node_id)
113 .ok_or_else(|| FlowError::node_not_found(node_id.as_str()))?;
114
115 let mut children = Vec::new();
116 let outgoing_edges = graph.get_outgoing_edges(node_id);
117
118 for edge in outgoing_edges {
119 let child_tree = self.build_tree_recursive(&edge.target, graph, visited)?;
120 children.push(child_tree);
121 }
122
123 visited.remove(node_id);
124
125 Ok(TreeNode {
126 id: node_id.clone(),
127 size: node.size,
128 children,
129 position: Position::zero(), width: 0.0, height: 0.0, })
133 }
134
135 fn calculate_positions(&self, tree: &mut TreeNode) {
137 self.first_walk(tree, 0);
139
140 self.second_walk(tree, 0.0, 0);
142 }
143
144 #[allow(clippy::only_used_in_recursion)]
146 fn first_walk(&self, node: &mut TreeNode, level: usize) {
147 if node.children.is_empty() {
148 node.width = node.size.width;
150 node.height = node.size.height;
151 } else {
152 for child in &mut node.children {
154 self.first_walk(child, level + 1);
155 }
156
157 let total_child_width: f64 = node.children.iter().map(|child| child.width).sum();
159 let separations = (node.children.len().saturating_sub(1)) as f64 * self.node_separation;
160
161 node.width = total_child_width + separations;
162 node.height = node.size.height;
163
164 let mut current_x = 0.0;
166 for child in &mut node.children {
167 child.position.x = current_x + child.width / 2.0;
168 current_x += child.width + self.node_separation;
169 }
170 }
171 }
172
173 fn second_walk(&self, node: &mut TreeNode, x_offset: f64, level: usize) {
175 let y_position = match self.direction {
176 LayoutDirection::TopToBottom => level as f64 * self.level_separation,
177 LayoutDirection::BottomToTop => -(level as f64 * self.level_separation),
178 LayoutDirection::LeftToRight => level as f64 * self.level_separation,
179 LayoutDirection::RightToLeft => -(level as f64 * self.level_separation),
180 };
181
182 match self.direction {
184 LayoutDirection::TopToBottom | LayoutDirection::BottomToTop => {
185 node.position.x += x_offset;
186 node.position.y = y_position;
187 }
188 LayoutDirection::LeftToRight | LayoutDirection::RightToLeft => {
189 node.position.x = y_position;
190 node.position.y += x_offset;
191 }
192 }
193
194 let children_start_x = node.position.x - node.width / 2.0;
196 for child in &mut node.children {
197 self.second_walk(child, children_start_x, level + 1);
198 }
199 }
200
201 #[allow(clippy::only_used_in_recursion)]
203 fn apply_tree_to_graph<N, E>(&self, tree: &TreeNode, graph: &mut Graph<N, E>) {
204 if let Some(node) = graph.get_node_mut(&tree.id) {
205 node.position = tree.position;
206 }
207
208 for child in &tree.children {
209 self.apply_tree_to_graph(child, graph);
210 }
211 }
212}
213
214impl<N, E> LayoutAlgorithm<N, E> for HierarchicalLayout {
215 fn name(&self) -> &str {
216 "Hierarchical"
217 }
218
219 fn apply(&mut self, graph: &mut Graph<N, E>) -> Result<()> {
220 if graph.is_empty() {
221 return Ok(());
222 }
223
224 let mut tree = self.build_tree(graph)?;
226
227 self.calculate_positions(&mut tree);
229
230 self.apply_tree_to_graph(&tree, graph);
232
233 Ok(())
234 }
235}
236
237#[derive(Debug, Clone)]
239struct TreeNode {
240 id: NodeId,
241 size: Size,
242 children: Vec<TreeNode>,
243 position: Position,
244 width: f64,
245 height: f64,
246}
247
248#[derive(Debug, Clone, Default)]
250pub struct HierarchicalLayoutBuilder {
251 layout: HierarchicalLayout,
252}
253
254impl HierarchicalLayoutBuilder {
255 pub fn node_separation(mut self, separation: f64) -> Self {
257 self.layout.node_separation = separation;
258 self
259 }
260
261 pub fn level_separation(mut self, separation: f64) -> Self {
263 self.layout.level_separation = separation;
264 self
265 }
266
267 pub fn edge_routing(mut self, routing: EdgeRouting) -> Self {
269 self.layout.edge_routing = routing;
270 self
271 }
272
273 pub fn direction(mut self, direction: LayoutDirection) -> Self {
275 self.layout.direction = direction;
276 self
277 }
278
279 pub fn root_node(mut self, root_id: impl Into<NodeId>) -> Self {
281 self.layout.root_node = Some(root_id.into());
282 self
283 }
284
285 pub fn build(self) -> HierarchicalLayout {
287 self.layout
288 }
289}