reingold_tilford/
lib.rs

1mod tree;
2
3use std::collections::HashMap;
4
5pub type SmallVec<T> = smallvec::SmallVec<[T; 4]>;
6type MediumVec<T> = smallvec::SmallVec<[T; 16]>;
7
8#[derive(Clone, Copy, Debug)]
9pub struct Dimensions {
10	pub top: f64,
11	pub right: f64,
12	pub bottom: f64,
13	pub left: f64,
14}
15
16impl Dimensions {
17	pub fn all(size: f64) -> Dimensions {
18		Dimensions {
19			top: size,
20			right: size,
21			bottom: size,
22			left: size,
23		}
24	}
25}
26
27/// Methods that are needed to apply this algorithm to any type of tree.
28///
29/// This trait is designed to work with both "traditional" point-to-children style trees (PTC trees)
30/// and index trees (such as you would have if you wanted to use `petgraph::Graph` as a tree). To
31/// accomodate this however the usage of the trait is not necessarily obvious when working with
32/// "traditional" trees (there more examples in the examples folder at this crate's repo).
33///
34/// The TL;DR is that because a PTC tree contains all of it's information inside the nodes you don't
35/// need a seperate tree type. However to accomodate index trees you need a "tree" type that you
36/// implement the trait on plus a "node" type that gets returned from `children`. We can handle this
37/// in PTC trees by defining a unit struct that we implement the trait on, but just ignore it in the
38/// functions.
39///
40/// # Examples
41///
42/// PTC trees:
43///
44/// ```
45/// extern crate reingold_tilford;
46///
47/// struct Tree;
48///
49/// struct Node {
50/// 	id: usize,
51/// 	children: Vec<Node>,
52/// }
53///
54/// impl<'n> reingold_tilford::NodeInfo<&'n Node> for Tree {
55/// 	type Key = usize;
56///
57/// 	fn key(&self, node: &'n Node) -> Self::Key {
58/// 		node.id
59/// 	}
60///
61/// 	fn children(&self, node: &'n Node) -> reingold_tilford::SmallVec<&'n Node> {
62/// 		node.children.iter().collect()
63/// 	}
64/// }
65///
66/// fn main() {
67/// 	let root = Node {
68/// 		id: 0,
69/// 		children: vec![
70/// 			Node { id: 1, children: vec![] },
71/// 			Node { id: 2, children: vec![] },
72/// 		],
73/// 	};
74///
75/// 	let layout = reingold_tilford::layout(&Tree, &root);
76///
77/// 	assert!(layout.get(&0).is_some());
78/// 	let zero = layout.get(&0).unwrap();
79/// 	assert!(1.0 - 1e-12 < zero.x && zero.x < 1.0 + 1e-12);
80/// 	assert!(0.5 - 1e-12 < zero.y && zero.y < 0.5 + 1e-12);
81/// }
82/// ```
83///
84/// Index trees:
85///
86/// ```
87/// extern crate petgraph;
88/// extern crate reingold_tilford;
89///
90/// use petgraph::graph;
91///
92/// struct Graph(graph::Graph<usize, ()>);
93///
94/// impl reingold_tilford::NodeInfo<graph::NodeIndex> for Graph {
95/// 	type Key = graph::NodeIndex;
96///
97/// 	fn key(&self, node: graph::NodeIndex) -> Self::Key {
98/// 		node
99/// 	}
100///
101/// 	fn children(&self, node: graph::NodeIndex) -> reingold_tilford::SmallVec<graph::NodeIndex> {
102/// 		self.0.neighbors(node).collect()
103/// 	}
104/// }
105/// ```
106pub trait NodeInfo<N>
107where
108	Self::Key: std::cmp::Eq + std::hash::Hash,
109	N: Copy,
110{
111	type Key;
112
113	/// Returns a key that will be used to uniquely identify a given node.
114	fn key(&self, node: N) -> Self::Key;
115
116	/// Returns the children that a given node has.
117	fn children(&self, node: N) -> SmallVec<N>;
118
119	/// Returns the dimensions of a given node.
120	///
121	/// This is the padding that you want around the centre point of the node so that you can line
122	/// things up as you want to (e.g. nodes aligned by their top border vs being aligned by their
123	/// centres).
124	///
125	/// This value is generic over units (but all nodes must use the same unit) and the layout that
126	/// this crate calculates will be given in terms of this unit. For example if you give this
127	/// value in pixels then the layout will be given in terms of number of pixels from the left of
128	/// the tree. Alternatively you might want to give this value in terms of the proportion of the
129	/// width of your window (though note that this does not guarantee that the tree will fit in
130	/// your window).
131	///
132	/// # Default
133	///
134	/// By default the algorithm assumes that each node is point-like (i.e. has no width or height).
135	fn dimensions(&self, _node: N) -> Dimensions {
136		Dimensions::all(0.0)
137	}
138
139	/// Returns the desired border around a given node.
140	///
141	/// See the `dimensions` method for a description of what units this has.
142	///
143	/// # Default
144	///
145	/// By default the algorithm assumes that each node has a border of `0.5` on every side.
146	fn border(&self, _node: N) -> Dimensions {
147		Dimensions::all(0.5)
148	}
149}
150
151#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
152pub struct Coordinate {
153	/// The horizontal coordinate of a given point.
154	///
155	/// The origin of the coordinate system is at the top left of the tree so this value is
156	/// relative to the left-most border of the left-most node. This coordinate is given in terms
157	/// of the same units that `NodeInfo::dimensions` and `NodeInfo::border` use.
158	pub x: f64,
159	/// The vertical coordinate of a given point.
160	///
161	/// The origin of the coordinate system is at the top left of the tree so this value is
162	/// relative to the top-most border of the top-most node. This coordinate is given in terms
163	/// of the same units that `NodeInfo::dimensions` and `NodeInfo::border` use.
164	pub y: f64,
165}
166
167#[derive(Clone, Debug)]
168struct Data<K> {
169	key: K,
170
171	x: f64,
172	y: f64,
173	mod_: f64,
174
175	dimensions: Dimensions,
176	border: Dimensions,
177}
178
179impl<K> Data<K> {
180	fn top_space(&self) -> f64 {
181		self.dimensions.top + self.border.top
182	}
183
184	#[allow(dead_code)]
185	fn top(&self) -> f64 {
186		self.y - self.top_space()
187	}
188
189	fn bottom_space(&self) -> f64 {
190		self.dimensions.bottom + self.border.bottom
191	}
192
193	fn bottom(&self) -> f64 {
194		self.y + self.bottom_space()
195	}
196
197	fn left_space(&self) -> f64 {
198		self.dimensions.left + self.border.left
199	}
200
201	fn left(&self) -> f64 {
202		self.x - self.left_space()
203	}
204
205	fn right_space(&self) -> f64 {
206		self.dimensions.right + self.border.right
207	}
208
209	fn right(&self) -> f64 {
210		self.x + self.right_space()
211	}
212}
213
214/// Returns the coordinates for the _centre_ of each node.
215///
216/// The origin of the coordinate system will be at the top left of the tree. The coordinates take
217/// into account the width of the left-most node and shift everything so that the left-most border
218/// of the left-most node is at 0 on the x-axis.
219///
220/// # Important
221///
222/// This algorithm _does_ account for the height of nodes but this is only to allow each row of
223/// nodes to be aligned by their centre. If your tree has some nodes at a given depth which are
224/// significantly larger than others and you want to avoid large gaps between rows then a more
225/// general graph layout algorithm is required.
226pub fn layout<N, T>(tree: &T, root: N) -> HashMap<T::Key, Coordinate>
227where
228	N: Copy,
229	T: NodeInfo<N>,
230{
231	let mut tree = tree::Tree::new(tree, root, |t, n| Data {
232		key: t.key(n),
233
234		x: 0.0,
235		y: 0.0,
236		mod_: 0.0,
237
238		dimensions: t.dimensions(n),
239		border: t.border(n),
240	});
241
242	if let Some(root) = tree.root() {
243		initialise_y(&mut tree, root);
244
245		initialise_x(&mut tree, root);
246		ensure_positive_x(&mut tree, root);
247		finalise_x(&mut tree, root);
248
249		tree.0
250			.into_iter()
251			.map(|tree::Node { data: d, .. }| (d.key, Coordinate { x: d.x, y: d.y }))
252			.collect()
253	} else {
254		HashMap::new()
255	}
256}
257
258fn initialise_y<K>(tree: &mut tree::Tree<Data<K>>, root: usize) {
259	let mut next_row = MediumVec::from_elem(root, 1);
260
261	while !next_row.is_empty() {
262		let row = next_row;
263		next_row = MediumVec::new();
264
265		let mut max = -std::f64::INFINITY;
266		for node in &row {
267			let node = *node;
268
269			tree[node].data.y = if let Some(parent) = tree[node].parent {
270				tree[parent].data.bottom()
271			} else {
272				0.0
273			} + tree[node].data.top_space();
274
275			if tree[node].data.y > max {
276				max = tree[node].data.y;
277			}
278
279			next_row.extend_from_slice(&tree[node].children);
280		}
281
282		for node in &row {
283			tree[*node].data.y = max;
284		}
285	}
286}
287
288fn initialise_x<K>(tree: &mut tree::Tree<Data<K>>, root: usize) {
289	for node in tree.post_order(root) {
290		if tree[node].is_leaf() {
291			tree[node].data.x = if let Some(sibling) = tree.previous_sibling(node) {
292				tree[sibling].data.right()
293			} else {
294				0.0
295			} + tree[node].data.left_space();
296		} else {
297			let mid = {
298				let first = tree[*tree[node]
299					.children
300					.first()
301					.expect("Only leaf nodes have no children.")]
302				.data
303				.x;
304				let last = tree[*tree[node]
305					.children
306					.last()
307					.expect("Only leaf nodes have no children.")]
308				.data
309				.x;
310
311				(first + last) / 2.0
312			};
313
314			if let Some(sibling) = tree.previous_sibling(node) {
315				tree[node].data.x = tree[sibling].data.right() + tree[node].data.left_space();
316				tree[node].data.mod_ = tree[node].data.x - mid;
317			} else {
318				tree[node].data.x = mid;
319			}
320
321			fix_overlaps(tree, node);
322		}
323	}
324}
325
326fn fix_overlaps<K>(tree: &mut tree::Tree<Data<K>>, right: usize) {
327	fn max_depth(l: &HashMap<usize, f64>, r: &HashMap<usize, f64>) -> usize {
328		if let Some(l) = l.keys().max() {
329			if let Some(r) = r.keys().max() {
330				return std::cmp::min(*l, *r);
331			}
332		}
333
334		0
335	}
336
337	let right_node_contour = left_contour(tree, right);
338
339	for left in tree.left_siblings(right) {
340		let left_node_contour = right_contour(tree, left);
341		let mut shift = 0.0;
342
343		for depth in tree[right].depth..=max_depth(&right_node_contour, &left_node_contour) {
344			let gap = right_node_contour[&depth] - left_node_contour[&depth];
345			if gap + shift < 0.0 {
346				shift = -gap;
347			}
348		}
349
350		tree[right].data.x += shift;
351		tree[right].data.mod_ += shift;
352
353		centre_nodes_between(tree, left, right);
354	}
355}
356
357fn left_contour<K>(tree: &tree::Tree<Data<K>>, node: usize) -> HashMap<usize, f64> {
358	contour(tree, node, min, |n| n.data.left())
359}
360
361fn right_contour<K>(tree: &tree::Tree<Data<K>>, node: usize) -> HashMap<usize, f64> {
362	contour(tree, node, max, |n| n.data.right())
363}
364
365fn min<T: std::cmp::PartialOrd>(l: T, r: T) -> T {
366	if l < r {
367		l
368	} else {
369		r
370	}
371}
372
373fn max<T: std::cmp::PartialOrd>(l: T, r: T) -> T {
374	if l > r {
375		l
376	} else {
377		r
378	}
379}
380
381fn contour<C, E, K>(tree: &tree::Tree<Data<K>>, node: usize, cmp: C, edge: E) -> HashMap<usize, f64>
382where
383	C: Fn(f64, f64) -> f64,
384	E: Fn(&tree::Node<Data<K>>) -> f64,
385{
386	let mut stack = MediumVec::from_elem((0.0, node), 1);
387	let mut contour = HashMap::new();
388
389	while let Some((mod_, node)) = stack.pop() {
390		let depth = tree[node].depth;
391		let shifted = edge(&tree[node]) + mod_;
392		let new = if let Some(current) = contour.get(&depth) {
393			cmp(*current, shifted)
394		} else {
395			shifted
396		};
397		let mod_ = mod_ + tree[node].data.mod_;
398
399		contour.insert(depth, new);
400		stack.extend(tree[node].children.iter().map(|c| (mod_, *c)));
401	}
402
403	contour
404}
405
406fn centre_nodes_between<K>(tree: &mut tree::Tree<Data<K>>, left: usize, right: usize) {
407	let num_gaps = tree[right].order - tree[left].order;
408
409	let space_per_gap = (tree[right].data.left() - tree[left].data.right()) / (num_gaps as f64);
410
411	for (i, sibling) in tree.siblings_between(left, right).into_iter().enumerate() {
412		let i = i + 1;
413
414		let old_x = tree[sibling].data.x;
415		// HINT: We traverse the tree in post-order so we should never be moving anything to the
416		//       left.
417		// TODO: Have some kind of `move_node` method that checks things like this?
418		let new_x = max(old_x, tree[left].data.right() + space_per_gap * (i as f64));
419		let diff = new_x - old_x;
420
421		tree[sibling].data.x = new_x;
422		tree[sibling].data.mod_ += diff;
423	}
424}
425
426fn ensure_positive_x<K>(tree: &mut tree::Tree<Data<K>>, root: usize) {
427	let contour = left_contour(tree, root);
428	let shift = -contour
429		.values()
430		.fold(None, |acc, curr| {
431			let acc = acc.unwrap_or(std::f64::INFINITY);
432			let curr = *curr;
433			Some(if curr < acc { curr } else { acc })
434		})
435		.unwrap_or(0.0);
436
437	tree[root].data.x += shift;
438	tree[root].data.mod_ += shift;
439}
440
441fn finalise_x<K>(tree: &mut tree::Tree<Data<K>>, root: usize) {
442	for node in tree.breadth_first(root) {
443		let shift = if let Some(parent) = tree[node].parent {
444			tree[parent].data.mod_
445		} else {
446			0.0
447		};
448
449		tree[node].data.x += shift;
450		tree[node].data.mod_ += shift;
451	}
452}