Expresso.Element.Hierarchy.Layout (expresso v0.1.0)

Copy Markdown View Source

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:

LayoutThe nodes under a node
:top_downSide by side in one row under the node. The node is at the center above the row.
:left_rightOne under the other in a column at the right of the node. The node is at the middle at the left.
:stackedOne 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

t()

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

box()

@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

layout()

@type layout() :: :top_down | :left_right | :stacked

A layout of the nodes under a node

options()

@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

size()

@type size() :: %{width: pos_integer(), height: pos_integer()}

The width and the height of a box

t()

@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

layout(root, sizes, options)

@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:

  1. The layout option of the node.
  2. The option stack_from: the nodes of that level and below are stacked. The root is level 1.
  3. The layout option of the nearest node above it with the option.
  4. The layout option of the tree, which is :top_down by 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}]

layouts()

@spec layouts() :: [layout()]

Return the three layouts

number(value)

@spec number(number()) :: number()

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

size(sizes, depth)

@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}

top_down(root, sizes)

@spec top_down(Expresso.Element.Node.t(), [size()]) :: t()

Return the layout of a tree in which each node has the layout :top_down