Documentation
¶
Overview ¶
Package nodelink provides node-and-edge graph visualization using Graphviz.
This package implements a traditional graph visualization where nodes are represented as boxes and dependencies are shown as directed edges between them.
Architecture ¶
Unlike the tower visualization which separates layout computation from rendering, nodelink uses Graphviz which handles both in a single step:
Tower: DAG → layout.Build() → Layout → sink.RenderSVG() → SVG Nodelink: DAG → ToDOT() → DOT → RenderSVG() → SVG
The DOT format serves as the intermediate representation (similar to tower's layout.json), enabling re-rendering without re-parsing the dependency graph.
Pipeline Integration ¶
In the job system, nodelink follows the same three-stage pattern:
Parse: manifest/package → graph.json (DAG) Layout: graph.json → layout.dot (Graphviz DOT format) Export: layout.dot → svg/png/pdf
Layout Engines ¶
Graphviz provides several layout engines via the Engine option:
- dot: Hierarchical (default) - best for dependency graphs
- neato: Spring model - for undirected graphs
- fdp: Force-directed - for clustering
- circo: Circular - for cyclic structures
- twopi: Radial - for tree-like graphs
Usage ¶
Direct usage (bypassing the job system):
g, _ := resolver.Resolve(ctx, "requests", opts)
dot := nodelink.ToDOT(g, nodelink.Options{})
svg, _ := nodelink.RenderSVG(dot)
Via the API:
// Step 1: Parse
POST /api/v1/parse {language: "python", package: "requests"}
// → graph.json
// Step 2: Layout
POST /api/v1/layout {graph_path: "job-123/graph.json", viz_type: "nodelink"}
// → layout.dot
// Step 3: Export
POST /api/v1/export {layout_path: "job-456/layout.dot", formats: ["svg"]}
// → nodelink.svg
Subdividers ¶
Subdivider nodes (created by dag/transform.Subdivide) are rendered with dashed outlines and grey fill to visually distinguish them from regular dependency nodes.
Index ¶
- func Export(dot string, g *dag.DAG, opts Options, width, height float64, style string) (graph.Layout, error)
- func Parse(layout graph.Layout) (string, error)
- func RenderPDF(dot string) ([]byte, error)
- func RenderPNG(dot string, scale float64) ([]byte, error)
- func RenderSVG(dot string) ([]byte, error)
- func ToDOT(g *dag.DAG, opts Options) string
- type Options
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Export ¶
func Export(dot string, g *dag.DAG, opts Options, width, height float64, style string) (graph.Layout, error)
Export creates a serializable nodelink layout from a DOT string.
Unlike tower layouts, nodelink layouts don't compute positions internally—Graphviz does that during rendering. This function packages the DOT string and graph metadata into the unified serialization format.
Use this when you need to:
- Export nodelink layout to JSON file
- Cache the layout for later rendering
- Return layout data from an API
func Parse ¶
Parse extracts the DOT string from a serialized nodelink layout.
Returns an error if the layout is not a nodelink type or is missing the DOT string.
func RenderPDF ¶
RenderPDF renders a DOT graph as PDF via SVG conversion. This is a convenience wrapper around RenderSVG and render.ToPDF.
Requires librsvg: brew install librsvg (macOS), apt install librsvg2-bin (Linux).
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/dag"
"github.com/stacktower-io/stacktower/pkg/core/render/nodelink"
)
func main() {
// Create a dependency graph
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "frontend", Row: 0})
_ = g.AddNode(dag.Node{ID: "backend", Row: 1})
_ = g.AddEdge(dag.Edge{From: "frontend", To: "backend"})
// Convert to DOT
dot := nodelink.ToDOT(g, nodelink.Options{})
// Render to PDF (requires Graphviz and librsvg)
pdf, err := nodelink.RenderPDF(dot)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Generated PDF (%d bytes)\n", len(pdf))
// Output varies based on tool installation
}
Output:
func RenderPNG ¶
RenderPNG renders a DOT graph as PNG via SVG conversion. This is a convenience wrapper around RenderSVG and render.ToPNG.
A scale of 2.0 produces a 2x resolution image suitable for high-DPI displays.
Requires librsvg: brew install librsvg (macOS), apt install librsvg2-bin (Linux).
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/dag"
"github.com/stacktower-io/stacktower/pkg/core/render/nodelink"
)
func main() {
// Create a graph
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "service", Row: 0})
_ = g.AddNode(dag.Node{ID: "cache", Row: 1})
_ = g.AddEdge(dag.Edge{From: "service", To: "cache"})
// Convert to DOT
dot := nodelink.ToDOT(g, nodelink.Options{})
// Render to high-resolution PNG (requires Graphviz and librsvg)
png, err := nodelink.RenderPNG(dot, 2.0) // 2x scale for retina displays
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Generated PNG (%d bytes)\n", len(png))
// Output varies based on tool installation
}
Output:
func RenderSVG ¶
RenderSVG renders a DOT graph to SVG using Graphviz. Returns the SVG bytes ready for display or further conversion with render.ToPDF or render.ToPNG.
This function is serialized with a mutex because the go-graphviz WASM backend is not thread-safe and can produce memory access errors under concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/dag"
"github.com/stacktower-io/stacktower/pkg/core/render/nodelink"
)
func main() {
// Create a simple graph
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "web", Row: 0})
_ = g.AddNode(dag.Node{ID: "api", Row: 1})
_ = g.AddNode(dag.Node{ID: "db", Row: 2})
_ = g.AddEdge(dag.Edge{From: "web", To: "api"})
_ = g.AddEdge(dag.Edge{From: "api", To: "db"})
// Convert to DOT
dot := nodelink.ToDOT(g, nodelink.Options{})
// Render to SVG (requires Graphviz)
svg, err := nodelink.RenderSVG(dot)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Generated SVG (%d bytes)\n", len(svg))
// Output varies based on Graphviz installation
}
Output:
func ToDOT ¶
ToDOT converts a DAG to Graphviz DOT format for node-link visualization. The resulting DOT string can be rendered using RenderSVG, RenderPDF, or RenderPNG.
Subdivider nodes (created by dag/transform.Subdivide) are rendered with dashed outlines and grey fill to distinguish them from regular nodes.
Example ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/dag"
"github.com/stacktower-io/stacktower/pkg/core/render/nodelink"
)
func main() {
// Create a simple dependency graph
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "app", Row: 0})
_ = g.AddNode(dag.Node{ID: "database", Row: 1})
_ = g.AddNode(dag.Node{ID: "auth", Row: 1})
_ = g.AddEdge(dag.Edge{From: "app", To: "database"})
_ = g.AddEdge(dag.Edge{From: "app", To: "auth"})
// Convert to DOT format
_ = nodelink.ToDOT(g, nodelink.Options{})
// The DOT output can be rendered with Graphviz
fmt.Println("Generated DOT format for visualization")
}
Output: Generated DOT format for visualization
Example (Detailed) ¶
package main
import (
"fmt"
"github.com/stacktower-io/stacktower/pkg/core/dag"
"github.com/stacktower-io/stacktower/pkg/core/render/nodelink"
)
func main() {
// Create a graph with metadata
g := dag.New(nil)
_ = g.AddNode(dag.Node{
ID: "fastapi",
Row: 0,
Meta: dag.Metadata{"version": "0.100.0"},
})
_ = g.AddNode(dag.Node{
ID: "pydantic",
Row: 1,
Meta: dag.Metadata{"version": "2.0.0"},
})
_ = g.AddEdge(dag.Edge{From: "fastapi", To: "pydantic"})
// Use detailed mode to include metadata in labels
_ = nodelink.ToDOT(g, nodelink.Options{Detailed: true})
// The detailed DOT includes row numbers and metadata
fmt.Println("Generated detailed DOT with metadata")
}
Output: Generated detailed DOT with metadata