Documentation
¶
Overview ¶
Package sink provides output format renderers for tower visualizations.
Overview ¶
A "sink" transforms a computed layout.Layout into a final output format. This package provides renderers for:
- SVG: Scalable vector graphics with interactivity
- PDF: Print-ready output (requires rsvg-convert)
- PNG: Raster image output (requires rsvg-convert)
SVG Output ¶
RenderSVG produces interactive SVG with:
- Visual styles (hand-drawn XKCD-style or clean simple style)
- Hover highlighting of related blocks
- Optional popups showing package metadata
- Optional "Nebraska guy" ranking panel
- Optional dependency edge visualization
Basic usage:
svg := sink.RenderSVG(layout,
sink.WithGraph(g),
sink.WithStyle(handdrawn.New(seed)),
sink.WithPopups(),
)
SVG Options ¶
- WithGraph: Required for edge rendering and metadata access
- WithStyle: Visual style (styles.Simple or [handdrawn.New])
- WithEdges: Show dependency edges as dashed lines
- WithMerged: Merge subdivider blocks into continuous columns
- WithPopups: Enable hover popups with package metadata
- WithNebraska: Add maintainer ranking panel
PDF and PNG Output ¶
RenderPDF and RenderPNG render the layout as PDF/PNG by first generating SVG, then converting via render.ToPDF and render.ToPNG:
pdf, err := sink.RenderPDF(layout, opts...) png, err := sink.RenderPNG(layout, sink.WithScale(2), opts...)
These require librsvg to be installed:
- macOS: brew install librsvg
- Linux: apt install librsvg2-bin
The conversion functions are shared with [nodelink] so both visualization types can export to PDF/PNG.
render.ToPDF: github.com/matzehuels/stacktower/pkg/core/render.ToPDF render.ToPNG: github.com/matzehuels/stacktower/pkg/core/render.ToPNG [nodelink]: github.com/matzehuels/stacktower/pkg/core/render/nodelink
Adding New Formats ¶
To add a new output format:
- Create a renderer function: func RenderFoo(l layout.Layout, opts ...FooOption) ([]byte, error)
- Define option types for configuration
- Access l.Blocks for positioned blocks, l.RowOrders for orderings
- Register in internal/cli/render.go for CLI support
The existing sinks provide examples: svg.go for full-featured output, pdf.go/png.go for format conversion wrappers.
layout.Layout: github.com/matzehuels/stacktower/pkg/core/render/tower/layout.Layout styles.Simple: github.com/matzehuels/stacktower/pkg/core/render/tower/styles.Simple [handdrawn.New]: github.com/matzehuels/stacktower/pkg/core/render/tower/styles/handdrawn.New
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderPDF ¶
RenderPDF renders the layout as PDF via SVG conversion. Requires librsvg: brew install librsvg (macOS), apt install librsvg2-bin (Linux).
func RenderPNG ¶
RenderPNG renders the layout as PNG via SVG conversion. Requires librsvg: brew install librsvg (macOS), apt install librsvg2-bin (Linux).
func RenderSVG ¶
Example ¶
package main
import (
"fmt"
"strings"
"github.com/matzehuels/stacktower/pkg/core/dag"
"github.com/matzehuels/stacktower/pkg/core/render/tower/layout"
"github.com/matzehuels/stacktower/pkg/core/render/tower/sink"
)
func main() {
// Build a simple dependency graph
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "app", Row: 0})
_ = g.AddNode(dag.Node{ID: "lib", Row: 1})
_ = g.AddNode(dag.Node{ID: "core", Row: 2})
_ = g.AddEdge(dag.Edge{From: "app", To: "lib"})
_ = g.AddEdge(dag.Edge{From: "lib", To: "core"})
// Compute layout
l := layout.Build(g, 400, 300)
// Render to SVG
svg := sink.RenderSVG(l, sink.WithGraph(g))
fmt.Println("SVG starts with:", string(svg[:4]))
fmt.Println("Contains viewBox:", strings.Contains(string(svg), "viewBox"))
}
Output: SVG starts with: <svg Contains viewBox: true
Example (WithEdges) ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/core/dag"
"github.com/matzehuels/stacktower/pkg/core/render/tower/layout"
"github.com/matzehuels/stacktower/pkg/core/render/tower/sink"
)
func main() {
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "app", Row: 0})
_ = g.AddNode(dag.Node{ID: "auth", Row: 1})
_ = g.AddNode(dag.Node{ID: "cache", Row: 1})
_ = g.AddEdge(dag.Edge{From: "app", To: "auth"})
_ = g.AddEdge(dag.Edge{From: "app", To: "cache"})
l := layout.Build(g, 400, 300)
// Enable edge rendering to show dependency lines
svg := sink.RenderSVG(l,
sink.WithGraph(g),
sink.WithEdges(),
)
// Edges are rendered as dashed lines
fmt.Println("Has content:", len(svg) > 100)
}
Output: Has content: true
Example (WithPopups) ¶
package main
import (
"fmt"
"strings"
"github.com/matzehuels/stacktower/pkg/core/dag"
"github.com/matzehuels/stacktower/pkg/core/render/tower/layout"
"github.com/matzehuels/stacktower/pkg/core/render/tower/sink"
)
func main() {
g := dag.New(nil)
_ = g.AddNode(dag.Node{
ID: "fastapi",
Row: 0,
Meta: dag.Metadata{
"version": "0.100.0",
"repo_description": "FastAPI framework",
"repo_stars": 70000,
},
})
_ = g.AddNode(dag.Node{ID: "starlette", Row: 1})
_ = g.AddEdge(dag.Edge{From: "fastapi", To: "starlette"})
l := layout.Build(g, 400, 300)
// Enable hover popups showing package metadata
svg := sink.RenderSVG(l,
sink.WithGraph(g),
sink.WithPopups(),
)
// Popups include metadata from nodes
fmt.Println("Has popup content:", strings.Contains(string(svg), "popup"))
}
Output: Has popup content: true
Example (WithStyle) ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/core/dag"
"github.com/matzehuels/stacktower/pkg/core/render/tower/layout"
"github.com/matzehuels/stacktower/pkg/core/render/tower/sink"
"github.com/matzehuels/stacktower/pkg/core/render/tower/styles"
)
func main() {
g := dag.New(nil)
_ = g.AddNode(dag.Node{ID: "app", Row: 0})
_ = g.AddNode(dag.Node{ID: "lib", Row: 1})
_ = g.AddEdge(dag.Edge{From: "app", To: "lib"})
l := layout.Build(g, 400, 300)
// Use simple style (clean, minimal appearance)
svg := sink.RenderSVG(l,
sink.WithGraph(g),
sink.WithStyle(styles.Simple{}),
)
fmt.Println("Generated SVG length:", len(svg) > 0)
}
Output: Generated SVG length: true
Types ¶
type PDFOption ¶
type PDFOption func(*pdfRenderer)
PDFOption configures PDF rendering.
func WithPDFSVGOptions ¶
WithPDFSVGOptions passes options through to the underlying SVG renderer.
type PNGOption ¶
type PNGOption func(*pngRenderer)
PNGOption configures PNG rendering.
func WithPNGSVGOptions ¶
WithPNGSVGOptions passes options through to the underlying SVG renderer.
type SVGOption ¶
type SVGOption func(*svgRenderer)
func WithMerged ¶
func WithMerged() SVGOption
func WithNebraska ¶
func WithNebraska(rankings []feature.NebraskaRanking) SVGOption
func WithPopups ¶
func WithPopups() SVGOption