The layout of a tree: the place of each box and the path of each line
The layout is recursive. Each subtree is a box, and the layout of a node places the boxes of the subtrees under it with one of three layouts:
| Layout | The nodes under a node |
|---|---|
:top_down | Side by side in one row under the node. The node is at the center above the row. |
:left_right | One under the other in a column at the right of the node. The node is at the middle at the left. |
:stacked | One under the other below the node, with an indent. |
The layout does not move a subtree into the space next to a smaller
subtree, so the author can predict the place of each box. docs/tree.md
gives the reasons.
Each level has a size of its own, from the box option of the tree. In a
tree with one layout, the boxes of one level thus have their tops in one
row for :top_down, and their left edges in one column for :left_right.
A line goes from the edge of a box to the edge of the box of a child, with horizontal and vertical parts only. The lines of the children of one node share the part from the box of the node.
The layout is a function of the tree and the options only, so the same tree gives the same picture each time.
Summary
Types
One box: its node, its level from 0, its place and its size
A layout of the nodes under a node
The options of the layout: the layout of the tree, the first stacked level, and an offset that moves each box and each line
The width and the height of a box
The boxes in the order of the tree, the paths of the lines, the name of the node at the end of each line, and the size of the drawing
Functions
Return the layout of a tree from its root and the size of each level
Return the three layouts
Return a float with no fraction as an integer, so its text has no ".0"
Return the size of the boxes of a level, from 0 for the root
Return the layout of a tree in which each node has the layout :top_down
Types
@type box() :: %{ node: Expresso.Element.Node.t(), depth: non_neg_integer(), x: number(), y: number(), width: pos_integer(), height: pos_integer() }
One box: its node, its level from 0, its place and its size
@type layout() :: :top_down | :left_right | :stacked
A layout of the nodes under a node
@type options() :: [ layout: layout(), stack_from: pos_integer() | nil, offset: {number(), number()} ]
The options of the layout: the layout of the tree, the first stacked level, and an offset that moves each box and each line
@type size() :: %{width: pos_integer(), height: pos_integer()}
The width and the height of a box
@type t() :: %{ boxes: [box()], lines: [String.t()], targets: [String.t()], width: number(), height: number() }
The boxes in the order of the tree, the paths of the lines, the name of the node at the end of each line, and the size of the drawing
Functions
@spec layout(Expresso.Element.Node.t(), [size()], options()) :: t()
Return the layout of a tree from its root and the size of each level
The last size applies to each deeper level. The layout of the nodes under a node comes from the first of these:
- The
layoutoption of the node. - The option
stack_from: the nodes of that level and below are stacked. The root is level 1. - The
layoutoption of the nearest node above it with the option. - The
layoutoption of the tree, which is:top_downby default.
iex> alias Expresso.Element.Node
iex> root = %Node{name: "A", elements: [%Node{name: "B"}, %Node{name: "C"}]}
iex> layout = Expresso.Element.Hierarchy.Layout.layout(root, [%{width: 100, height: 40}], [])
iex> Enum.map(layout.boxes, &{&1.node.name, &1.x, &1.y})
[{"A", 62, 0}, {"B", 0, 88}, {"C", 124, 88}]
iex> {layout.width, layout.height}
{224, 128}
iex> layout = Expresso.Element.Hierarchy.Layout.layout(root, [%{width: 100, height: 40}], layout: :left_right)
iex> Enum.map(layout.boxes, &{&1.node.name, &1.x, &1.y})
[{"A", 0, 32}, {"B", 148, 0}, {"C", 148, 64}]
@spec layouts() :: [layout()]
Return the three layouts
Return a float with no fraction as an integer, so its text has no ".0"
iex> Expresso.Element.Hierarchy.Layout.number(62.0)
62
iex> Expresso.Element.Hierarchy.Layout.number(61.5)
61.5
@spec size([size()], non_neg_integer()) :: size()
Return the size of the boxes of a level, from 0 for the root
iex> sizes = [%{width: 280, height: 120}, %{width: 220, height: 96}]
iex> Expresso.Element.Hierarchy.Layout.size(sizes, 0)
%{width: 280, height: 120}
iex> Expresso.Element.Hierarchy.Layout.size(sizes, 5)
%{width: 220, height: 96}
@spec top_down(Expresso.Element.Node.t(), [size()]) :: t()
Return the layout of a tree in which each node has the layout :top_down