Graph

:= []

General-graph layouts for data whose meaning comes from connectivity: any sized graph, no structure claimed beyond nodes and edges. Four readings of the same input:

  • layout_circular — all nodes on one ring, neighbors seated together.
  • layout_force — organic clusters from a seeded physical simulation (one-call form here; the ForceLayout module has the reusable prepare/place/run surface, position hints, and pins).
  • layout_stress — drawn distances tracking graph distances (one-call form here; the reusable surface is in StressLayout).
  • layout_radial — concentric rings by depth from a root (one-call form here; the reusable surface is in RadialLayout).

For repeated circular layouts of unchanged input, call prepare_circular once and pass the result to layout_circular_prepared.

default_spec : Spec

The empty graph. Replace by record update.

default_input : Input

General-graph input defaults to the empty spec.

default_circular_settings : CircularSettings

Circular settings that produce a readable ring in common cases: the first node sits at twelve o'clock and seats follow clockwise.

prepare_circular : Input, CircularSettings -> [Ok(CircularPrepared), Err(List(Problem))]

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

layout_circular_prepared : CircularPrepared -> Result

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

layout_circular : Input, CircularSettings -> [Ok(Result), Err(List(Problem))]

Check and lay out input in one call, read as a ring: every node on one circle, neighbors seated together, crossings reduced.

default_force_settings : {
    node_gap : F64,
    repulsion : F64,
    gravity : F64,
    opening_angle : F64,
    max_iterations : U64,
    tolerance : F64,
    pins : List({ node : U64, x : F64, y : F64 }),
}

Force settings that produce a readable organic drawing in common cases; see the ForceLayout module for what each dial changes.

layout_force : Input, {
    node_gap : F64,
    repulsion : F64,
    gravity : F64,
    opening_angle : F64,
    max_iterations : U64,
    tolerance : F64,
    pins : List({ node : U64, x : F64, y : F64 }),
}, { seed : U32, hints : List({ x : F64, y : F64 }) } -> [
    Ok(
        {
            layout : {
                positions : List({ x : F64, y : F64 }),
                bounds : { x : F64, y : F64, width : F64, height : F64 },
                routes : List(Route),
            },
            components : List(U64),
            convergence : { iterations : U64, converged : Bool },
        },
    ),
    Err(
        List(
            [
                InvalidNodeWidth(U64),
                InvalidNodeHeight(U64),
                MissingEdgeStart(U64, U64),
                MissingEdgeEnd(U64, U64),
                InvalidNodeGap,
                InvalidRepulsion,
                InvalidGravity,
                InvalidOpeningAngle,
                InvalidTolerance,
                MissingPin(U64),
                InvalidPin(U64),
            ],
        ),
    ),
]

Check and lay out input in one call, read organically: connected nodes pull together, everything else pushes apart, and clusters emerge from the balance. The seed selects among equally valid drawings. For repeated layouts, position hints, or pinned nodes, use the ForceLayout module's prepare/place/run surface.

default_stress_settings : {
    node_gap : F64,
    mode : [Exact, Pivots(U64)],
    max_iterations : U64,
    tolerance : F64,
    pins : List({ node : U64, x : F64, y : F64 }),
}

Stress settings that produce a readable distance-faithful drawing in common cases; see the StressLayout module for what each dial changes.

layout_stress : Input, {
    node_gap : F64,
    mode : [Exact, Pivots(U64)],
    max_iterations : U64,
    tolerance : F64,
    pins : List({ node : U64, x : F64, y : F64 }),
}, { seed : U32, hints : List({ x : F64, y : F64 }) } -> [
    Ok(
        {
            layout : {
                positions : List({ x : F64, y : F64 }),
                bounds : { x : F64, y : F64, width : F64, height : F64 },
                routes : List(Route),
            },
            components : List(U64),
            convergence : { iterations : U64, converged : Bool },
        },
    ),
    Err(
        List(
            [
                InvalidNodeWidth(U64),
                InvalidNodeHeight(U64),
                MissingEdgeStart(U64, U64),
                MissingEdgeEnd(U64, U64),
                InvalidNodeGap,
                InvalidPivotCount,
                InvalidTolerance,
                MissingPin(U64),
                InvalidPin(U64),
            ],
        ),
    ),
]

Check and lay out input in one call, read metrically: how far apart two nodes are drawn tracks how far apart they are in the graph. For repeated layouts, position hints, or pinned nodes, use the StressLayout module's prepare/place/run surface.

default_radial_settings : {
    root : [Auto, Node(U64)],
    ring_gap : F64,
    node_gap : F64,
    start_angle : F64,
    winding : [Clockwise, CounterClockwise],
}

Radial settings that produce a readable ringed drawing in common cases; see the RadialLayout module for what each dial changes.

layout_radial : Input, {
    root : [Auto, Node(U64)],
    ring_gap : F64,
    node_gap : F64,
    start_angle : F64,
    winding : [Clockwise, CounterClockwise],
} -> [
    Ok(
        {
            layout : {
                positions : List({ x : F64, y : F64 }),
                bounds : { x : F64, y : F64, width : F64, height : F64 },
                routes : List(Route),
            },
            rings : List(U64),
            components : List(U64),
        },
    ),
    Err(
        List(
            [
                InvalidNodeWidth(U64),
                InvalidNodeHeight(U64),
                MissingEdgeStart(U64, U64),
                MissingEdgeEnd(U64, U64),
                InvalidRingGap,
                InvalidNodeGap,
                InvalidStartAngle,
                InvalidRoot(U64),
            ],
        ),
    ),
]

Check and lay out input in one call, read as centrality: concentric rings by depth from a root, everything oriented around one thing. For the reusable prepare/run surface, use the RadialLayout module.

Spec : {
    nodes : List({ width : F64, height : F64 }),
    edges : List({ from : U64, to : U64 }),
}

The shared input every layout family consumes: sized nodes and the edges between them. A node is identified by its index in nodes and an edge by its index in edges; every output list aligns to those orders.

Input : Spec

General-graph input is the shared spec itself.

CircularSettings : {
    node_gap : F64,
    start_angle : F64,
    winding : [Clockwise, CounterClockwise],
}

Space between neighbors on the ring, where the first node sits (radians), and which way around the ring the order runs. The radius is derived from node extents and the gap, not configured.

Problem : [
    InvalidNodeWidth(U64),
    InvalidNodeHeight(U64),
    MissingEdgeStart(U64, U64),
    MissingEdgeEnd(U64, U64),
    InvalidNodeGap,
    InvalidStartAngle,
]

Invalid sizes, spacing, or edge endpoints found while checking input.

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

Geometry plus each node's seat around the ring, counted from the start angle in winding order.