Basic Plotting

Core plotting entry points and customisation options.

using Graphs, DAGMakie, CairoMakie

# Classic confounding triangle: Z → X → Y, Z → Y
g, labels = confounding_graph(["Z", "X", "Y"])

The dagplot Function

The main entry point is dagplot, which creates a new figure:

fig, ax, p = dagplot(g; kwargs...)

For plotting into an existing axis, use dagplot!:

p = dagplot!(ax, g; kwargs...)

Node Customisation

Size and Colour

Default plots use steel-blue fills with white in-node labels. When you choose light fills, set nlabels_color accordingly (and keep nodes large enough for the label):

fig, ax, p = dagplot(g,
    nlabels = labels,
    node_size = 34,
    node_color = [:goldenrod, :seagreen, :steelblue],
    nlabels_color = :white,
)
fig
Example block output

Outline Styling

fig, ax, p = dagplot(g,
    nlabels = labels,
    node_strokewidth = 2.0,
    node_strokecolor = :black,
)
fig
Example block output

Edge Customisation

fig, ax, p = dagplot(g,
    nlabels = labels,
    edge_color = :gray,
    edge_width = 1.5,
    arrow_size = 12,
    arrow_shift = :end,
)
fig
Example block output

Label Customisation

Basic Labels

In-node labels (default) want a contrasting colour on the fill. External labels need a positive nlabels_distance and a non-centred nlabels_align: GraphMakie offsets along the align direction, so (:center, :center) leaves the pixel offset at zero and the text stays inside the marker. Prefer black text off the fill:

fig = Figure(size = (720, 280))
ax1 = Axis(fig[1, 1], title = "In-node (default)")
ax2 = Axis(fig[1, 2], title = "Outside the node")
dagplot!(ax1, g;
    nlabels = labels,
    nlabels_fontsize = 16,
    nlabels_color = :white,
    nlabels_distance = 0,
)
dagplot!(ax2, g;
    nlabels = labels,
    nlabels_color = :black,
    nlabels_distance = 12,
    # (:center, :bottom) anchors the bottom of the text, so labels sit above
    nlabels_align = (:center, :bottom),
)
fig
Example block output

Label Alignment

nlabels_align is the Makie text-box anchor (passed through GraphMakie), not “put the label on this side of the node”. The named side is which edge of the label sits on the anchor point, so the node ends up on that side of the label:

nlabels_alignAnchor meaningLabel ends up…
(:left, :center)left edge of text on pointright of the node
(:right, :center)right edge of text on pointleft of the node
(:center, :bottom)bottom of text on pointabove the node
(:center, :top)top of text on pointbelow the node

With a positive nlabels_distance, GraphMakie also shifts the anchor along the same direction (so (:left, :center) both left-anchors and nudges east).

By default labels sit inside the node (nlabels_distance = 0, centred). Pass auto_align_labels = true to place labels outside in the largest angular gap between incident edges (dagitty-style external labels). That mode automatically uses a positive distance and dark text unless you override them:

fig, ax, p = dagplot(g,
    nlabels = labels,
    auto_align_labels = true,
)
fig
Example block output

Manual anchors (still typically with a positive nlabels_distance when labels sit outside the fill). Panel titles show where the label appears relative to the node:

fig = Figure(size = (720, 280))
ax1 = Axis(fig[1, 1], title = "(:right, :bottom) → label NW of node")
ax2 = Axis(fig[1, 2], title = "Per-node anchors")
dagplot!(ax1, g;
    nlabels = labels,
    auto_align_labels = false,
    nlabels_distance = 12,
    nlabels_color = :black,
    nlabels_align = (:right, :bottom),
)
dagplot!(ax2, g;
    nlabels = labels,
    auto_align_labels = false,
    nlabels_distance = 12,
    nlabels_color = :black,
    # Z right-anchored → label west; X top-anchored → label south; Y left-anchored → label east
    nlabels_align = [(:right, :center), (:center, :top), (:left, :center)],
)
fig
Example block output

Layout

DAGMakie now defaults to an automatic layout strategy:

  • layout_mode = :auto chooses a deterministic layered layout for acyclic graphs
  • layout_mode = :cyclic uses an SCC-aware layout with curved feedback edges
  • layout_mode = :spring falls back to a force-directed layout
  • layout = ... still accepts explicit node positions or a NetworkLayout.jl layout object

For CPDAG skeletons and occasion×variable grids, see Skeletons & Time (dagplot_skeleton, dagplot_time_indexed).

You can still use NetworkLayout.jl directly when you want a force-directed layout:

fig, ax, p = dagplot(g, nlabels = labels, layout_mode = :spring)
fig
Example block output
using NetworkLayout

fig = Figure(size = (720, 280))
ax1 = Axis(fig[1, 1], title = "Spring()")
ax2 = Axis(fig[1, 2], title = "Stress()")
dagplot!(ax1, g; nlabels = labels, layout = Spring())
dagplot!(ax2, g; nlabels = labels, layout = Stress())
fig
Example block output

Figure Size

fig, ax, p = dagplot(g,
    nlabels = labels,
    figure_size = (480, 320),    # Width × Height in pixels
)
fig
Example block output

Structural edge labels (LaTeX)

GraphMakie elabels pass through dagplot / dagplot!. Use structural_edge_labels to annotate edges with linear-SCM structural weights from a matrix $B$ (with $B_{ij}$ the parameter on $j\to i$), or with short TeX fragments of a mechanism, optionally as Makie LaTeXStrings. A non-zero diagonal is treated as a self-loop: by default structural_edge_labels adds any missing i → i edges on g before labelling (layout still treats the loopless core as a DAG). Prefer a dedicated copy of the graph if later chunks must keep the original edge set:

B = [
    0.0  0.0  0.0;
    0.8  0.0  0.0;
    0.5  1.2  3.0;
]
g_loop, labels_loop = confounding_graph(["Z", "X", "Y"])
fig, ax, p = dagplot(g_loop;
    nlabels = labels_loop,
    elabels = structural_edge_labels(g_loop, B; digits = 1),  # adds Y → Y
    elabels_fontsize = 14,
    elabels_distance = 12,
    elabels_rotation = 0,   # keep maths upright
)
fig
Example block output

Custom TeX on edges (still one label per Graphs.edges(g) entry):

fig, ax, p = dagplot(g;
    nlabels = labels,
    elabels = structural_edge_labels(
        g,
        ["\\beta_{ZX}", "\\beta_{ZY}", "\\beta_{XY}"];
        latex = true,
    ),
    elabels_fontsize = 14,
    elabels_distance = 12,
    elabels_rotation = 0,
    padding = 0.45,
)
fig
Example block output

Padding

Control spacing around the graph:

fig, ax, p = dagplot(g,
    nlabels = labels,
    padding = 0.15,              # 15% padding (fraction of range)
)
fig
Example block output