Tree

:= []

Tree layouts for input already known to be a hierarchy. Two readings of the same input: the level-by-level one (the default layout), and the radial one (layout_radial), which puts the root at the center and each generation on its own ring. A caller with a hierarchy provides it directly as a Spec, the root node of the tree; every other node is a descendant.

layout is the usual entry point. For repeated layouts of unchanged input, call prepare once and pass the result to layout_prepared.

default_input : Input

A single node with no children. Replace by record update.

default_settings : Settings

Settings that produce a readable top-to-bottom tree in common cases.

default_radial_settings : RadialSettings

Radial settings that produce a readable drawing in common cases: the first subtree starts at twelve o'clock and siblings follow clockwise.

prepare : Input, Settings -> [Ok(Prepared), Err(List(Problem))]

Check the input and settings, then cache the layout it determines.

layout_prepared : Prepared -> Result

Return the previously computed layout. This operation cannot fail.

layout : Input, Settings -> [Ok(Result), Err(List(Problem))]

Check and lay out input in one call. This is the usual entry point.

prepare_radial : Input, RadialSettings -> [Ok(RadialPrepared), Err(List(Problem))]

Check the input and radial settings, then cache the layout they determine.

layout_radial_prepared : RadialPrepared -> Result

Return the previously computed radial layout. This operation cannot fail.

layout_radial : Input, RadialSettings -> [Ok(Result), Err(List(Problem))]

Check and lay out input in one call, read radially: the root at the center, each generation on its own ring, subtrees in nested wedges in sibling order.

Spec

:= { width : F64, height : F64, children : List(Spec) }

A hierarchy given as one: the spec is its own root node, and every value of this type is a tree by construction — there is no separate "is it a tree" check. Nodes are numbered in depth-first order (this root is 0), and every other output list aligns to that order.

Input : Spec

Tidy input for the current exemplar: the root of the hierarchy.

Settings : { sibling_gap : F64, subtree_gap : F64, level_gap : F64, direction : [Down, Up, Left, Right] }

Space between adjacent siblings, between adjacent subtrees that do not share a parent, and between levels; the axis the tree grows along.

RadialSettings : {
    sibling_gap : F64,
    subtree_gap : F64,
    ring_gap : F64,
    start_angle : F64,
    winding : [Clockwise, CounterClockwise],
}

Spacing for the radial reading. sibling_gap and subtree_gap keep the same meaning as in Settings and set each node's share of its ring; ring_gap separates consecutive rings; start_angle (radians) is where the first subtree begins, with Clockwise or CounterClockwise deciding which way around the rings siblings follow.

Problem : [
    InvalidWidth(U64),
    InvalidHeight(U64),
    InvalidSiblingGap,
    InvalidSubtreeGap,
    InvalidLevelGap,
    InvalidRingGap,
    InvalidStartAngle,
]

Invalid sizes or settings found while checking input, indexed by each node's depth-first position.

Result : {
    layout : {
        positions : List({ x : F64, y : F64 }),
        bounds : { x : F64, y : F64, width : F64, height : F64 },
        routes : List(Route),
    },
    depths : List(U64),
}

Geometry plus each node's depth, both in depth-first order. positions are node centers; route endpoints lie on the connected nodes' bounding rectangles (a zero-size node's endpoint is its center), so an arrowhead drawn at a route's end lands on the box.