diff --git a/rust/crates/tidy-tree/src/layout/basic_layout.rs b/rust/crates/tidy-tree/src/layout/basic_layout.rs index 4649602..ddabb59 100644 --- a/rust/crates/tidy-tree/src/layout/basic_layout.rs +++ b/rust/crates/tidy-tree/src/layout/basic_layout.rs @@ -19,8 +19,12 @@ pub struct BasicLayout { pub struct BoundingBox { pub total_width: Coord, pub total_height: Coord, + /// node x position relative to its parent pub relative_x: Coord, + /// node y position relative to its parent pub relative_y: Coord, + /// bounding box shift relative to the node + pub shift_x: Coord, } impl Default for BoundingBox { @@ -30,6 +34,7 @@ impl Default for BoundingBox { total_width: 0., relative_x: 0., relative_y: 0., + shift_x: 0., } } } @@ -65,6 +70,7 @@ impl BasicLayout { total_width: node.width, relative_x: 0., relative_y: 0., + shift_x: -node.width / 2., }; let children: *mut _ = &mut node.children; let children = unsafe { &mut *children }; @@ -74,18 +80,34 @@ impl BasicLayout { for child in children.iter() { total_width += child.meta.total_width; } + total_width += (n - 1.) * self.peer_margin; - let mut relative_x = -total_width / 2.; + let mut relative_x = 0.; let mut max_height = 0.; - for child in children.iter_mut() { + let mut mid_x: Vec = vec![]; + let n = children.len(); + for (i, child) in children.iter_mut().enumerate() { child.meta.relative_y = node.height + self.parent_child_margin; - child.meta.relative_x = relative_x + child.meta.total_width / 2.; - relative_x += child.meta.total_width + self.peer_margin; + relative_x += -child.meta.shift_x; + child.meta.relative_x = relative_x; + if i == (n - 1) / 2 { + mid_x.push(relative_x); + } + if i == n / 2 { + mid_x.push(relative_x); + } + relative_x += child.meta.total_width + child.meta.shift_x + self.peer_margin; max_height = Float::max(child.meta.total_height, max_height); } + let shift_x = -(mid_x[0] + mid_x[1]) / 2.; + for child in children.iter_mut() { + child.meta.relative_x += shift_x; + } + node.meta.total_width = total_width; node.meta.total_height = node.height + self.parent_child_margin + max_height; + node.meta.shift_x = shift_x; } } } diff --git a/rust/crates/tidy-tree/tests/aesthetic_rules.rs b/rust/crates/tidy-tree/tests/aesthetic_rules.rs index 0b55339..df96154 100644 --- a/rust/crates/tidy-tree/tests/aesthetic_rules.rs +++ b/rust/crates/tidy-tree/tests/aesthetic_rules.rs @@ -71,20 +71,40 @@ pub fn assert_no_crossed_lines(root: &Node) { }); } +pub fn assert_parent_visually_centered(root: &Node) { + root.pre_order_traversal(|node| { + let n = node.children.len(); + if n == 0 { + return; + } + + let middle = if n % 2 == 0 { + let m = n / 2; + let a = &node.children[m - 1]; + let b = &node.children[m]; + (a.x + b.x) / 2. + } else { + node.children[n / 2].x + }; + assert!( + (node.x - middle).abs() < 1e-6, + "parent node is not centered {} {}", + node.x, + middle + ); + }); +} + pub fn assert_symmetric(root: &Node, layout: &mut dyn Layout) { let mut mirrored = mirror(root); layout.layout(&mut mirrored); let mut point_origin: Vec = vec![]; let mut point_mirrored: Vec = vec![]; root.pre_order_traversal(|node| { - if let Some(parent) = node.parent { - point_origin.push(node.x); - } + point_origin.push(node.x); }); pre_order_traversal_rev(&mirrored, |node| { - if let Some(parent) = node.parent { - point_mirrored.push(node.x); - } + point_mirrored.push(node.x); }); // println!("{:#?}", root); // println!("{:#?}", mirrored); diff --git a/rust/crates/tidy-tree/tests/layout_test.rs b/rust/crates/tidy-tree/tests/layout_test.rs index 1b126fd..2c6062c 100644 --- a/rust/crates/tidy-tree/tests/layout_test.rs +++ b/rust/crates/tidy-tree/tests/layout_test.rs @@ -13,6 +13,7 @@ pub fn test_layout(layout: &mut dyn Layout aesthetic_rules::assert_symmetric(&tree, layout); aesthetic_rules::check_nodes_order(&tree); aesthetic_rules::check_y_position_in_same_level(&tree); + aesthetic_rules::assert_parent_visually_centered(&tree); } pub fn gen_tree(rng: &mut StdRng, num: usize) -> Node {