Graph Visualization

This example shows common ways to customize graphFigure and saveGraphFigure. The same visualization options work for Gaussian factor graphs, discrete factor graphs, and tree views.


Base Graph

Start with a small Gaussian factor graph that has three variables, two pairwise factors, and two unary factors:

using FactorGraph

variables = [
    GaussianVariable(:x1, 1; label = "x1"),
    GaussianVariable(:x2, 1; label = "x2"),
    GaussianVariable(:x3, 1; label = "x3")
]

factors = [
    GaussianFactor(:x1, 0.0, 1.0, 0.5; label = "f1", initialize = true),
    GaussianFactor(:x1, :x2, 0.0, [1.0 -1.0], 0.3; label = "f2"),
    GaussianFactor(:x2, :x3, 0.0, [1.0 -1.0], 0.3; label = "f3"),
    GaussianFactor(:x3, 2.0, 1.0, 0.8; label = "f4")
]

graph = factorGraph(variables, factors)

Default Figure

The default figure draws variable labels, factor labels, SVG hover tooltips, and curved edges. This is a compact way to check the graph structure while building a model:

saveGraphFigure("../gv_default.svg", graph)

Hover over a node or edge in the rendered SVG to inspect its tooltip. Summary tooltips show identity metadata, while tooltipDetail = :full includes more node or edge details.


Layout and Labels

Layout options control orientation, spacing, and edge geometry. Label options control node labels, edge identifiers, tooltip detail, and font size. The next figure uses a vertical layout, straight edges, outside labels, and visible edge identifiers:

saveGraphFigure(
    "../gv_vertical.svg",
    graph;
    layout = (
        orientation = :vertical,
        rowSpacing = (90, 150),
        columnSpacing = 120,
        curvedEdges = false
    ),
    label = (
        placement = :outside,
        outsideGap = 8,
        showEdgeIds = true,
        tooltipDetail = :full,
        fontSize = 13
    )
)

Spacing options can be scalars for uniform gaps, or tuples and vectors for per-gap spacing. In the default horizontal layout, columnSpacing = (90, 210) sets the gap from unary factors to variables first, then the gap from variables to multi-variable factors. In vertical layout, the same idea applies through rowSpacing.


Focused Views

Use view to draw a focused part of the graph. The hops keyword expands from the selected variables or factors through the bipartite graph. With hops = 2, the figure includes x1, its neighboring factors, and the variables connected to those factors:

saveGraphFigure(
    "../gv_focus.svg",
    graph;
    view = (variables = [:x1], hops = 2),
    label = (showEdgeIds = true,)
)

Here, hops = 0 draws only the selected seed nodes, while hops = :all expands through the connected component. Focus nodes keep the normal style, and expanded context nodes use the context style.


Style and Highlights

Style options set the default colors and stroke widths. Highlight entries can select variables, factors, edges, or a variable-factor edge pair. This is useful when a figure needs to call attention to one relationship inside a larger model:

saveGraphFigure(
    "../gv_highlight.svg",
    graph;
    style = (
        backgroundFill = "#f8fafc",
        variableFill = "#e0f2fe",
        variableStroke = "#0369a1",
        variableStrokeWidth = 2.2,
        factorFill = "#fee2e2",
        factorStroke = "#b91c1c",
        factorStrokeWidth = 2.2,
        edgeStroke = "#64748b",
        edgeStrokeWidth = 1.8,
        edgeOpacity = 0.9,
        labelFill = "#0f172a"
    ),
    highlight = [
        (variable = :x2, stroke = "#16a34a", fill = "#dcfce7", strokeWidth = 4),
        (factor = "f2", stroke = "#f59e0b", fill = "#fef3c7", strokeWidth = 4),
        (variable = :x3, factor = "f4", stroke = "#7c3aed", strokeWidth = 4)
    ]
)

Variable and factor highlights can include their incident edges. Edge-specific highlights can be selected either by edge identifier or by a (variable, factor) pair.


Tree Views

Tree figures place nodes by graph depth. The same label, style, highlight, and view options are available. Here, the graph is converted to a tree view and drawn horizontally from root variable x1:

tree = treeFactorGraph(graph; root = :x1)

saveGraphFigure(
    "../gv_tree.svg",
    tree;
    layout = (orientation = :horizontal, rowSpacing = 70, columnSpacing = (105, 120, 145)),
    view = (variables = [:x1], hops = :all),
    label = (showEdgeIds = true, tooltipDetail = :full),
    highlight = [(variable = :x1, stroke = "#16a34a", fill = "#dcfce7", strokeWidth = 4)]
)

In horizontal tree layout, columnSpacing separates depth levels and rowSpacing separates nodes within the same level. Vertical tree layout swaps that interpretation. Tuple or vector spacing sets the gaps one by one, and the last value is reused when the graph has more gaps.


Inference Diagnostics

Passing an existing inference object adds diagnostic values to graph tooltips without running inference. Residual shading highlights the largest message residuals, while variance shading highlights Gaussian variable nodes by marginal variance:

inference = moment(graph)

for _ in 1:4
    messages!(graph, inference; schedule = :flooding)
end

saveGraphFigure(
    "../gv_inference.svg",
    graph,
    inference;
    residual = 4,
    variance = :all,
    label = (tooltipDetail = :full, showEdgeIds = true)
)

Variable tooltips include current marginal values when available. Edge tooltips include residual values when residual is requested.


Full Option Sketch

This final call collects the option groups in one place. Treat it as a reference pattern rather than as a recommended visual style:

graphFigure(
    graph;
    canvas = (
        width = 700,
        height = nothing,
        padding = 24,
        zoom = 1.0
    ),
    layout = (
        orientation = :horizontal,
        rowSpacing = 90,
        columnSpacing = (90, 210),
        curvedEdges = true
    ),
    node = (
        variableRadius = 24,
        factorSize = 22
    ),
    label = (
        placement = :outside,
        outsideGap = 6,
        showVariables = true,
        showFactors = true,
        showTooltips = true,
        showEdgeIds = false,
        tooltipDetail = :summary,
        fontSize = 14
    ),
    view = (
        variables = [:x1],
        factors = nothing,
        hops = :all
    ),
    style = (
        backgroundFill = "#ffffff",
        variableFill = "#e0f2fe",
        variableStroke = "#0369a1",
        variableStrokeWidth = 1.8,
        factorFill = "#dc2626",
        factorStroke = "#991b1b",
        factorStrokeWidth = 1.8,
        edgeStroke = "#64748b",
        edgeStrokeWidth = 1.6,
        edgeOpacity = 1.0,
        labelFill = "#111827"
    ),
    highlight = [
        (variable = :x1, stroke = "#16a34a", fill = "#dcfce7", strokeWidth = 4),
        (factor = "f2", stroke = "#f59e0b", fill = "#fef3c7", strokeWidth = 4),
        (edge = 1, stroke = "#7c3aed", strokeWidth = 4)
    ]
)

When an inference object is available, pass it as the second positional argument to add inference-specific diagnostics. The graph-only options above are still available, and inference adds residual and variance:

graphFigure(
    graph,
    inference;
    residual = 4,
    variance = :all
)