00:06 Today we're going to draw binary trees. We want to feature tree
diagrams in our slides
framework,
but what we build today can also be used independently.

00:26 For the illustrations in our Thinking in
SwiftUI book, we built tree
layouts using `VStack`

s and `HStack`

s. However, this produced trees with
suboptimal space usage because subtrees would never overlap in any way, leaving
more spaces empty than necessary. It worked fine for what we needed in the book,
but more complex trees can be laid out in a more optimal way.

01:19 There's a great article that discusses drawing neat trees in
Python: Drawing Presentable Trees by Bill
Mill. We'll use some of the principles and algorithms described in there.

01:35 We've prepared most of the stuff we need, so we don't have to waste
any time on the actual drawing of the nodes and the connecting lines. Each node
is presented as a circle with buttons for adding left and right subtrees. Only
the layout logic is still missing, so when we add a child node, it currently
gets drawn over the parent node:

## Tree Class

02:09 The data structure with which we build up the tree is a class,
`Tree`

. It holds a `value`

and optional `left`

and `right`

subtrees. It also has
a `point`

property that contains two integer coordinates, `x`

and `y`

:

```
struct Point: Hashable {
var x: Int
var y: Int
static let zero = Point(x: 0, y: 0)
}
final class Tree<A>: ObservableObject, Identifiable, CustomStringConvertible {
@Published var value: A
@Published var point: Point = .zero
@Published var left: Tree<A>? {
didSet { left?.parent = self }
}
@Published var right: Tree<A>? {
didSet { right?.parent = self }
}
weak var parent: Tree<A>? = nil
}
```

02:40 The `point`

value defines a node's position in the tree. We lay
out all nodes according to these values, and then we draw edges between each
node and its parent.

03:00 Most of the time in SwiftUI, we model data using structs and
enums, but a reference type makes demoing today's algorithms much easier, so
that's why we chose to implement `Tree`

as a class.

03:38 The first algorithm we'll implement is a straightforward one by
Donald Knuth. Starting with the root node, it lays out the left subtree, then
the node itself, and then the right subtree. Each node's `x`

coordinate comes
from a running counter, and its `y`

coordinate is equal to its depth in the
tree.

## Layout

04:36 But before we implement the algorithm, we have to implement the
`relayout`

method stub. This method gets called on the node whose subtrees have
changed. If this happens, we have to lay out the entire tree again. So we
traverse up to the root node and we call `layout`

on it:

```
final class Tree<A>: ObservableObject, Identifiable, CustomStringConvertible {
func relayout() {
var root = self
while let p = root.parent {
root = p
}
root.layout()
}
}
```

## Knuth

05:31 In an extension on `Tree`

, we can implement the algorithm itself
in the `layout`

method. We need a running `x`

coordinate, which we model as an
in-out parameter. And we add another parameter to know the depth of the node
we're laying out:

```
extension Tree {
func layout() {
var x = 0
knuth(depth: 0, x: &x)
}
func knuth(depth: Int, x: inout Int) {
}
}
```

06:35 We first lay out the left subtree recursively. Then we assign the
current node's coordinates, incrementing the running `x`

by `1`

. Finally, we lay
out the right subtree:

```
extension Tree {
func layout() {
var x: [Int:Int] = [:]
alt(depth: 0, x: &x)
}
func knuth(depth: Int, x: inout Int) {
left?.knuth(depth: depth + 1, x: &x)
point.x = x
point.y = depth
x += 1
right?.knuth(depth: depth + 1, x: &x)
}
```

07:44 This works well for simple trees. But because there will never be
nodes that share an `x`

coordinate, the algorithm can result in suboptimal
layouts depending on the tree structure:

In this case, it would be fine for the right subtree to move to the left, with
some nodes below each other sharing the same `x`

coordinate.

## Alternative

09:02 We can improve the algorithm by keeping a running `x`

for every
depth. So instead of a single `x`

value, we use a dictionary that maps depths to
available `x`

positions. When setting a node's `point`

, we use the `x`

entry for
the node's depth — defaulting to zero — and then we increment the available
position for that depth by `1`

:

```
extension Tree {
func layout() {
var x: [Int:Int] = [:]
alt(depth: 0, x: &x)
}
func alt(depth: Int, x: inout [Int:Int]) {
left?.alt(depth: depth+1, x: &x)
point.x = x[depth, default: 0]
point.y = depth
x[depth, default: 0] += 1
right?.alt(depth: depth+1, x: &x)
}
}
```

10:59 This produces extremely compact trees, but the layout actually
makes it hard to read the tree:

11:55 Both the article we're basing this work on and the papers it cites
offer a few principles for drawing aesthetically pleasing trees. We're already
adhering to some principles — e.g. nodes at the same depths are drawn on the
same horizontal line — but we should adopt another important one: each parent
node is centered above its children.

12:34 We went from a readable but space-wasting tree to a very compact
tree that's hard to read. We can try to get some readability back by adding more
space where needed. For one, we should move a node to the right if it has a left
subtree:

```
extension Tree {
func layout() {
var x: [Int:Int] = [:]
alt(depth: 0, x: &x)
}
func alt(depth: Int, x: inout [Int:Int]) {
left?.alt(depth: depth+1, x: &x)
point.x = x[depth, default: 0]
if let _ = left {
point.x += 1
}
}
```

14:28 And to balance the tree, we also have to move the right subtree to
the right by one position. We do so, after laying out the right subtree, by
using the `modifyAll`

method on `Tree`

to transform every node:

```
extension Tree {
func layout() {
var x: [Int:Int] = [:]
alt(depth: 0, x: &x)
}
func alt(depth: Int, x: inout [Int:Int]) {
left?.alt(depth: depth+1, x: &x)
point.x = x[depth, default: 0]
if let _ = left {
point.x += 1
}
point.y = depth
x[depth, default: 0] = point.x + 1
right?.alt(depth: depth+1, x: &x)
right?.moveRight(1)
}
func moveRight(_ amount: Int) {
modifyAll { $0.point.x += amount }
}
}
```

15:52 This works great for a simple tree:

16:05 But things get weird when we add more nodes. Here we can see that
the tree is left-biased and nodes can overlap each other:

## Adjustments

16:33 As we move the right subtree to the right, we should adjust the
dictionary with available `x`

positions accordingly:

```
extension Tree {
func alt(depth: Int, x: inout [Int:Int]) {
right?.moveRight(1)
for (d, v) in x {
if d > depth {
x[d] = v + 1
}
}
}
}
```

17:44 However, we might be updating some `x`

entries unnecessarily,
causing nodes to be placed too far to the right. In some cases, this makes it
hard to see whether a subtree is to the left or right of its parent node:

18:23 We can partially fix this by making sure each node is positioned
right of its left subtree:

```
extension Tree {
func alt(depth: Int, x: inout [Int:Int]) {
left?.alt(depth: depth+1, x: &x)
point.x = x[depth, default: 0]
if let l = left {
point.x = l.point.x + 1
}
point.y = depth
x[depth, default: 0] = point.x + 1
}
}
```

18:48 That fixes the configuration of parents and children, but we're
still seeing some gaps that could be smaller. Also, some nodes aren't centered
correctly above their children:

## Next Time

19:28 The overall result is a slightly more compact yet readable tree.
But it isn't perfect. The tree is left biased, which makes the right subtrees
look a little funny. And we're not sure if we can efficiently improve this
algorithm to yield better results.

20:31 In the next episode, we'll take a different approach and build
the tree from the bottom up. By laying out the left and right subtrees and then
figuring out how to position the subtrees so that they don't overlap, we can
hopefully draw a more symmetrical tree.