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 ¶
- type Edge
- type ForceOpts
- type ForcePosition
- type Graph
- func (g *Graph) AddEdge(e Edge)
- func (g *Graph) AddNode(n Node)
- func (g *Graph) BuildTree() (string, error)
- func (g *Graph) Children(id string) []string
- func (g *Graph) EdgeCount() int
- func (g *Graph) HasCycle() bool
- func (g *Graph) NodeCount() int
- func (g *Graph) Parents(id string) []string
- func (g *Graph) Roots() []string
- type Node
- type TreePosition
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Edge ¶
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 ¶
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 ¶
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 (*Graph) AddEdge ¶
AddEdge registers a directed edge. Both endpoints are ensured to exist as nodes (with empty labels) before the edge lands.
func (*Graph) BuildTree ¶
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 ¶
Children returns the IDs of every direct successor of id in declaration order.
func (*Graph) HasCycle ¶
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.
type Node ¶
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 ¶
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.