layout

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package layout holds the shared graph + tree layout helpers used by the encode/marks tree, dendrogram, and network encoders.

All algorithms are pure Go, deterministic for fixed seeds, and live behind this small interface so encoders depend on data shapes rather than algorithm internals.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Edge

type Edge struct {
	From   string
	To     string
	Weight float64
}

Edge is one directed connection. For trees, From is the parent and To is the child; for networks, the direction may be ignored.

type ForceOpts

type ForceOpts struct {
	// Iterations is the number of relaxation steps. Default 200.
	// Capped at 2000 to bound encode-time CPU.
	Iterations int
	// LinkDistance is the preferred edge length. Default 30.
	LinkDistance float64
	// Charge is the repulsion strength (negative magnitudes pull
	// nodes apart more strongly). Default -30.
	Charge float64
	// Width / Height bound the layout box. Default 400 × 300.
	Width, Height float64
	// Seed drives the deterministic initial position assignment.
	// Default 42.
	Seed int64
}

ForceOpts controls the Fruchterman-Reingold convergence loop. All fields take sane defaults when zero.

type ForcePosition

type ForcePosition struct {
	ID string
	X  float64
	Y  float64
}

ForcePosition is one node's resolved coordinates after a force-directed layout pass. X and Y land in a unit-ish coordinate space; the encoder scales / translates to the plot rect.

func ForceLayout

func ForceLayout(g *Graph, opts ForceOpts) ([]ForcePosition, error)

ForceLayout runs a Fruchterman-Reingold force-directed layout on g. Returns one ForcePosition per node in declaration order. The algorithm is deterministic for a fixed Seed so SVG goldens stay stable across runs.

Convergence: O(iterations × n²) pair-ops; the encoder bounds n via the dispatch's PRISM_ENCODE_NETWORK_BUDGET cap (out of scope here).

Returns PRISM_ENCODE_NETWORK_NONFINITE-equivalent error via the caller's prismerrors wrapper when any node position becomes non-finite (NaN / Inf) — guards against pathological inputs that blow up the gradient.

type Graph

type Graph struct {
	Nodes []Node
	Edges []Edge
	// contains filtered or unexported fields
}

Graph is an adjacency-list representation. Nodes are deduped by ID in insertion order; Edges keep their declaration order so layout output is deterministic.

func NewGraph

func NewGraph() *Graph

NewGraph returns an empty graph ready for AddNode / AddEdge.

func (*Graph) AddEdge

func (g *Graph) AddEdge(e Edge)

AddEdge registers a directed edge. Both endpoints are ensured to exist as nodes (with empty labels) before the edge lands.

func (*Graph) AddNode

func (g *Graph) AddNode(n Node)

AddNode registers a node; duplicate IDs update Label.

func (*Graph) BuildTree

func (g *Graph) BuildTree() (string, error)

BuildTree validates that g is a rooted forest with a single root and returns the root ID. Returns an error when:

  • the graph contains 0 or >1 roots,
  • the graph contains a cycle.

The validator-level rules (PRISM_SPEC_028/029) catch these earlier in the pipeline, but BuildTree is the defensive last line before the layout walks try to recurse on malformed inputs.

func (*Graph) Children

func (g *Graph) Children(id string) []string

Children returns the IDs of every direct successor of id in declaration order.

func (*Graph) EdgeCount

func (g *Graph) EdgeCount() int

EdgeCount returns the number of edges (no deduping).

func (*Graph) HasCycle

func (g *Graph) HasCycle() bool

HasCycle reports whether the graph contains a directed cycle. Uses a DFS with WHITE/GRAY/BLACK colors; safe on graphs with multiple roots or disconnected components.

func (*Graph) NodeCount

func (g *Graph) NodeCount() int

NodeCount returns the number of unique nodes.

func (*Graph) Parents

func (g *Graph) Parents(id string) []string

Parents returns the IDs of every direct predecessor of id.

func (*Graph) Roots

func (g *Graph) Roots() []string

Roots returns every node with zero incoming edges, in declaration order. A well-formed tree has exactly one root.

type Node

type Node struct {
	ID    string
	Label string
}

Node is one vertex in a graph or tree. ID is the user-supplied identity (e.g. the value of the spec's `target` channel); Label is optional and surfaces in the rendered mark.

type TreePosition

type TreePosition struct {
	ID    string
	X     float64
	Y     float64
	Depth int
}

TreePosition is one node's resolved coordinates after a tidy-tree layout pass. Y is the depth (0 at root) × verticalGap; X is the horizontal offset within the layout's local coordinate space.

func TidyTree

func TidyTree(g *Graph, rootID string, horizontalGap, verticalGap float64) ([]TreePosition, error)

TidyTree runs a tidy-tree layout on g rooted at rootID. Y is the depth × verticalGap; X is positioned so:

  • leaves are spaced horizontalGap apart in declaration order,
  • every internal node sits at the midpoint of its children's X coordinates (so the root centres over the leaf span).

The algorithm runs in O(n) via one post-order assignment of leaf X + one final pass that resolves internal nodes from their child positions. It's the "naïve tidy" variant of Reingold-Tilford; good enough for org charts, decision trees, and the gallery fixtures while staying easy to read and golden-stable.

BuildTree validation guarantees a single root + no cycle before this function runs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL