Skip to content

Strategies

ireko has four strategies: grid, radial, tree and layers. A strategy arranges the children of one node. It gets the size of each child and the extent of its subtree, and it gives an offset for each child. The engine does all other work: the recursion, the padding, the fit and the absolute positions. Thus a strategy is a small function, and you can write your own.

The controls come from the option schema of each strategy. The engine uses the same schema to examine the options of a node.

The options of radial
The strategy radial in the linked moderootabb1b2b3cdee1e2e3f
root radial {
  a
  b { b1 b2 b3 }
  c
  d
  e { e1 e2 e3 }
  f
}
import { grid, layout, radial } from "ireko";

const root = radial("root",
  "a", grid("b", "b1", "b2", "b3"), "c", "d", grid("e", "e1", "e2", "e3"), "f",
);
const result = layout({ root });

13 boxes. The audit finds no defect.Open in the playground →

The grid strategy puts the children in rows and columns. Each column is as wide as its widest child, and each row is as tall as its tallest child. A child counts with its whole subtree, so a large subtree gets a large cell.

row {
  small
  large { a b c d e }
  medium { x y }
}
A linked grid: the column of the subtree large is wide. The other columns stay narrow.rowsmalllargeabcdemediumxy
A linked grid: the column of the subtree large is wide. The other columns stay narrow.Open in the playground →

The number of columns depends on the mode:

  • In the linked mode, the default is one row. Then no edge crosses a sibling box. With side: left or side: right, the default is one column.
  • In the nested mode, the default is an approximately square grid.
  • Set columns to select a number.

In the linked mode, side selects where the children go:

root { a b c }
The side of a linked grid: bottomrootabc
side: bottom, the default.Open in the playground →
The options of the strategy grid
OptionTypeDefaultDescription
columnsinteger (0 or more)0The number of columns. With 0, the nested mode makes an approximately square grid, and the linked mode makes one row.
justify"start" | "center" | "end""center"The horizontal place of a child in its cell.
align"auto" | "start" | "center" | "end""auto"The vertical place of a child in its cell. With auto, linked children align at the top, and nested children at the center.
uniformbooleanfalseIf true, all cells have the size of the largest cell.
side"bottom" | "top" | "left" | "right""bottom"In the linked mode, the side of the node where the children go.
distancenumber (0 or more)0In the linked mode, the distance between the node and its children. The value 0 means 1.5 times the gap.

The radial strategy puts the children on a ring. Each child subtree fits in a circle around the center of its box. Two neighbors need an angle that keeps their circles apart by the gap. On a full ring, the strategy finds the smallest radius at which these angles fill the ring. Thus a small child takes less of the ring than a large child.

hub radial nested {
  tiny
  big radial nested { a b c d e f }
  mid nested { x y z }
  tiny2
  tiny3
}
A ring with children of different sizes. The large child gets a large part of the ring.hubbigmidtinyabcdefxyztiny2tiny3
A ring with children of different sizes. The large child gets a large part of the ring.Open in the playground →

With sweep less than 360, the children go on an arc. Each child is at the middle of an equal part of the arc, and the largest circle sets the radius.

root radial { a b c d e }
The sweep of a ring: 360°rootabcde
sweep: 360, a full ring. The first child is at the top (startAngle: -90).Open in the playground →
The options of the strategy radial
OptionTypeDefaultDescription
startAnglenumber (-360 to 360)-90The angle of the first child, in degrees. The value 0 is to the right, and -90 is at the top.
sweepnumber (1 to 360)360The angle of the arc that holds the children, in degrees. The value 360 is a full ring.
clockwisebooleantrueIf true, the children follow the clockwise direction on the screen.
distancenumber (0 or more)0In the linked mode, the smallest distance between the node and the circle of a child. The value 0 means the gap.

The tree strategy puts the children in one row, on one side of the node, like the linked grid. The grid keeps the extents of the subtrees apart: the rectangles that hold each subtree. The tree strategy keeps their shapes apart. Thus a subtree that is narrow at the top fits under the edge of its neighbor. This layout is a tidy tree.

ceo {
  cto { platform { infra { network storage compute } } web { ui api } }
  cfo { finance }
  coo { ops { warehouse { north south east west } } support }
}
An organization chart: grid or tree: gridceoctoplatforminfranetworkstoragecomputewebuiapicfofinancecooopswarehousenorthsoutheastwestsupport
Linked grids: each column is as wide as the extent of its subtree.Open in the playground →

The strategy packs the row two times: from the first child to the last, and from the last to the first. Then it takes the mean of the two places of each child. Both packings keep each pair of subtrees apart, so the mean keeps them apart too. The small subtrees go to the middle of their space.

The tree strategy gives the direction of its edges to the router. With the orthogonal router, the edges make the buses of an organization chart. The gallery has this chart with labels.

The options of the strategy tree
OptionTypeDefaultDescription
side"bottom" | "top" | "left" | "right""bottom"The side of the node where the children go.
distancenumber (0 or more)0The distance between the node and its children. The value 0 means 1.5 times the gap.
align"center" | "start" | "end""center"The place of the node along the row of its children: over the middle, the first child or the last child.

The layers strategy puts the children in layers, in the direction of the edges between them: a flowchart. The edges come from the input. An edge between two subtrees counts for the two children that hold them.

ci "CI" nested layers {
  push "git push"
  lint test build
  pack "package"
  review nested layers(side: right) { approve stage }
  deploy
}
push -> lint
push -> test
push -> build
lint -> pack
test -> pack
build -> pack
pack -> approve
approve -> stage
stage -> deploy
deploy -> push "next"
A CI pipeline. The edge back to git push makes a cycle, so the strategy turns it around. The review stage has its own layers.CIreviewgit pushlinttestbuildpackageapprovestagedeploynext
A CI pipeline. The edge back to git push makes a cycle, so the strategy turns it around. The review stage has its own layers.Open in the playground →

The strategy has three steps:

  1. It turns around the edges that close a cycle.
  2. It gives each child the layer of its longest path from a child with no edge into it.
  3. It orders each layer by the mean place of the neighbors of each child, so fewer edges cross.

Each layer is as deep as its deepest subtree, and the subtrees in a layer keep the gap. The strategy gives the direction of the flow to the router. Thus the forward edges leave the bottom of a box and enter the top of the next box.

For a large directed graph with many crossings, use dagre or ELK: they have better crossing reduction. The layers strategy is for the flow inside one container.

The options of the strategy layers
OptionTypeDefaultDescription
side"bottom" | "top" | "left" | "right""bottom"The side where the edges go: the first layer is on the other side.
distancenumber (0 or more)0The distance between two layers. The value 0 means 2 times the gap.
align"center" | "start" | "end""center"The place of a layer across the direction of the edges: at the start, at the middle or at the end.

Each strategy also has gap, padding and header. The engine reads them, not the strategy. Modes and fit explains them.

The text language puts the options in parentheses after the name of the strategy. The builder takes them in an object. Plain objects use layout: { type, ...options }.

team grid(columns: 3, uniform: true) nested { a b c d e }
grid.nested("team", { columns: 3, uniform: true }, "a", "b", "c", "d", "e");
const same = { id: "team", mode: "nested", layout: { type: "grid", columns: 3, uniform: true }, children: [/* ... */] };

An option with a wrong type or out of its range gives the finding INVALID_OPTION, and the strategy uses the default.