API Reference
Layout utilities
DAGMakie.time_indexed_layout — Function
time_indexed_layout(n_variables, n_times; dx=2.0, dy=1.5, origin=(0.0, 0.0))Grid layout for time-unrolled causal graphs.
Node order must be outer loop over occasions t = 1:n_times and inner loop over variables v = 1:n_variables (the indexing used by CausalDynamics unroll_temporal_dag). Column t is at x = origin[1] + (t - 1) * dx; variable row v is at y = origin[2] - (v - 1) * dy.
Returns
Vector{Point2f}of lengthn_variables * n_times
DAGMakie.dagplot_time_indexed — Function
dagplot_time_indexed(g, n_variables, n_times; kwargs...)dagplot with time_indexed_layout for graphs unrolled over time.
Pass nlabels with one label per node in the same (t, variable) order as time_indexed_layout. When color_by is set, exposure / outcome / ancestor roles propagate across each variable row (see propagate_temporal_smart_roles). exposure and outcome may be node indices or (variable_index, time_index) tuples. Remaining keywords go to dagplot.
DAGMakie.temporal_layout — Function
temporal_layout(node_keys; onset_times, dx, dy, origin)Compute positions for a temporal graph. node_keys contains (variable, time) pairs. A pointwise key (v, t::Integer) is placed at column t; a single-node key (v, nothing) is placed at its entry in onset_times (default 0). Rows follow the order in which variable names first occur. Placement is a display fact only: it does not encode temporal support, endurance, or ontology.
DAGMakie.dagplot_temporal — Function
dagplot_temporal(g, node_keys; onset_times,
temporal_supports, value_representations, graph_kind, kwargs...)Plot a temporal graph whose keys mix pointwise (v, t) nodes and single-node (v, nothing) keys placed at onset_times. Unlike dagplot_time_indexed, this API does not require a complete rectangular time grid. Markers follow value_representations (see marker_for_value_representation); temporal_supports is accepted for callers that carry it but does not change the glyph.
DAGMakie.interval_summary_node_marker — Function
interval_summary_node_marker()Return the Makie marker used when value_representation = :interval_summary under the package visual convention. Labels or support bars identify the interval summarised; shape alone does not establish support or ontology.
DAGMakie.marker_for_value_representation — Function
marker_for_value_representation(repr)Glyph from declared value representation. Only :interval_summary selects the rounded rectangle under the package convention. Attributes, trajectories, states, and events use a circle. Temporal support and referent identity never select the shape.
DAGMakie.digraph_skeleton — Function
digraph_skeleton(g::AbstractGraph) -> SimpleGraphUndirected skeleton of a directed graph: keep an undirected edge {i,j} whenever g has i → j, j → i, or both.
Useful for CPDAG-style PC output that encodes an undirected edge as a pair of opposing directed edges. Prefer this over plotting both arrows. See dagplot_skeleton for a ready-made figure with undirected stroke styling.
DAGMakie.dagplot_skeleton — Function
dagplot_skeleton(g; kwargs...)Plot the undirected skeleton of g (digraph_skeleton).
Arrowheads are suppressed and edges use UNDIRECTED_EDGE_COLOR unless overridden. Remaining keywords go to dagplot.
DAGMakie.graph_from_structural_matrix — Function
graph_from_structural_matrix(B; atol=0.0)Build a SimpleDiGraph from a structural parameter matrix B (linear SEM / linear SCM weights), including self-loops for non-zero diagonal entries.
Convention matches structural_edge_labels: B[i, j] is the weight of node j in the assignment for node i, so a non-zero entry yields the directed edge j → i. In particular, B[i, i] ≠ 0 yields a self-loop at i, which GraphMakie draws as a self-pointing arc.
Entries with absolute value at most atol are treated as absent.
Arguments
B: Square structural weight matrixatol: Absolute tolerance for treating entries as zero
Returns
SimpleDiGraphwith one edge per non-zero structural weight
Example
B = [0.0 0.0 0.0; 0.8 0.0 0.0; 0.5 1.2 3.0]
g = graph_from_structural_matrix(B) # edges 1→2, 1→3, 2→3, and self-loop 3→3DAGMakie.ensure_structural_self_loops! — Function
ensure_structural_self_loops!(g, B; atol=0.0)Add missing self-loops i → i whenever |B[i, i]| > atol. Mutates g and returns it. Off-diagonal structure is left unchanged.
Used by structural_edge_labels when ensure_self_loops=true (the default) so a non-zero diagonal is enough to draw self-pointing arcs.
DAGMakie.structural_edge_labels — Function
structural_edge_labels(g, B; latex=true, digits=2, ensure_self_loops=true)Build GraphMakie elabels for g from a structural parameter matrix B (linear SEM / linear SCM weights).
Convention: B[i, j] is the structural weight of node j in the assignment for node i, i.e. the parameter on the directed edge j → i (including self-loops when i == j). Labels are returned in Graphs.edges(g) order (the order GraphMakie expects for elabels).
When ensure_self_loops=true (default), non-zero diagonal entries that are missing from g are added in place via ensure_structural_self_loops! before labels are built. Off-diagonal non-zeros without a matching edge still warn (and are omitted from labels). You can also build the full edge set with graph_from_structural_matrix.
This is deliberately not named “effects”: in the Pearl ladder an effect is usually an interventional or counterfactual estimand, whereas these labels are structural parameters (or other short mechanism annotations) on the graph.
When latex=true (default), each entry is a Makie LaTeXString so parameters render as maths on the edge. Set latex=false for plain String labels.
Examples
g, labels = confounding_graph(["Z", "X", "Y"])
B = [0 0 0; 0.8 0 0; 0.5 1.2 3.0] # diagonal 3.0 → self-loop on Y
fig, ax, p = dagplot(g;
labels = labels,
elabels = structural_edge_labels(g, B), # adds Y → Y automatically
elabels_fontsize = 14,
elabels_distance = 12,
elabels_rotation = 0,
)structural_edge_labels(g, labels; latex=false)Pass edge annotations already ordered as Graphs.edges(g). With latex=true, wrap plain strings (or numbers) as LaTeXString via Makie.latexstring so short TeX such as "\\beta_{ZX}" or a fragment of a structural assignment renders on the edge.
DAGMakie.edge_coefficient_labels — Function
edge_coefficient_labels(args...; kwargs...)Deprecated alias for structural_edge_labels.
DAGMakie.edge_terminal_markers — Function
edge_terminal_markers(g, weights; atol=0.0)Return GraphMakie arrow markers in Graphs.edges(g) order. Negative entries in weights use INHIBITORY_EDGE_MARKER, producing an inhibitory -| terminal; non-negative entries use the ordinary arrowhead.
weights may be a vector with one entry per edge, or a square structural matrix B whose B[i, j] entry is the weight on j → i. This is display metadata only: DAGMakie does not infer a mechanism sign from graph topology. Values with absolute magnitude at most atol use an ordinary arrowhead.
DAGMakie.INHIBITORY_EDGE_MARKER — Constant
Marker for an inhibitory edge terminal (a bar perpendicular to the edge).
Main Plotting Functions
DAGMakie.dagplot — Function
dagplot(spec::DAGSpec; kwargs...)Plot a DAG from a DAGSpec specification.
Arguments
spec::DAGSpec: A DAG specification with graph, nodes, and edges
Keyword Arguments
Returns
- Tuple
(fig, ax, p)
dagplot(g; kwargs...)Create a clean DAG visualisation with publication-ready defaults.
This is a convenience wrapper that creates a new figure and axis, then delegates to dagplot!. All keyword arguments are passed through.
Arguments
g::AbstractGraph: A graph from Graphs.jl to plot
Keyword Arguments
figure_size::Tuple{Int, Int} = (600, 400): Figure dimensions- Preferred plot kwargs (full list on
dagplot!):labels,label_position(:inner/:outer),color_by,exposure,outcome,label_obstacle_graph, plus GraphMakie pass-throughs (nlabels_*,elabels_*, …).
Returns
- Tuple
(fig, ax, p)where:fig: The Figure objectax: The Axis objectp: The GraphPlot object (for accessing node positions, etc.)
Examples
using Graphs, DAGMakie, CairoMakie
# Simple chain graph
g = SimpleDiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
fig, ax, p = dagplot(g; labels=["X", "Y", "Z"])
save("dag.png", fig)
# Outer labels
fig, ax, p = dagplot(g; labels=["X", "Y", "Z"], label_position=:outer)
# Access node positions
positions = p[:node_pos][]dagplot(mg::MixedGraph; kwargs...)Create a DAG visualisation with both directed and bidirected edges.
Bidirected edges (↔) are rendered as curved arcs with double arrowheads, representing unmeasured common causes (latent confounders).
Additional Keyword Arguments
bidirected_color = :gray: Colour for bidirected edgesbidirected_width = 1.0: Line width for bidirected edgesbidirected_style = :dash: Line style for bidirected edgesbidirected_curvature = 0.3: Curvature of bidirected arcsbidirected_arrow_size = 8: Size of arrowheads on bidirected edges
Examples
using DAGMakie, CairoMakie
# Confounded treatment-outcome
mg = mixed_graph(2, [(1, 2)], [(1, 2)]) # X → Y with X ↔ Y
fig, ax, p = dagplot(mg, labels=["X", "Y"])
# Instrumental variable with confounding
mg = mixed_graph(3, [(1, 2), (2, 3)], [(2, 3)]) # Z → X → Y, X ↔ Y
fig, ax, p = dagplot(mg, labels=["Z", "X", "Y"])DAGMakie.dagplot! — Function
dagplot!(ax, spec::DAGSpec; kwargs...)Plot a DAG from a DAGSpec specification into an existing axis.
Kwargs match dagplot! on AbstractGraph.
dagplot!(ax, g; kwargs...)Plot a DAG into an existing axis with clean, publication-ready styling.
Automatically hides axis decorations, applies DataAspect, and sets appropriate limits to prevent clipping of nodes and labels.
Arguments
ax: A Makie Axis objectg::AbstractGraph: A graph from Graphs.jl to plot
Layout Keyword Arguments
layout_mode = :auto::autouses a deterministic layered layout for DAGs and an SCC-aware layout for cyclic digraphs;:acyclic/:cyclicforce those modes;:springuses NetworkLayoutSpring(). Pass explicitlayout = …(positions or a NetworkLayout algorithm) to bypass the strategypadding::Float64 = 0.1: Padding around graph as fraction of rangelayer_gap = 2.6: Spacing between layered columns (or rows)node_gap = nothing: Within-layer spacing. Defaults toDEFAULT_NODE_GAP_INNER(2.6) whenlabel_position=:inner, andDEFAULT_NODE_GAP_OUTER(1.8) whenlabel_position=:outer- Comparison / intervention helpers reuse a shared
DAGLayoutResultso panels keep the same node positions
Node Keyword Arguments
node_size = DEFAULT_NODE_SIZE: Node marker size in pixels (theme default 34; outer labels useOUTER_LABEL_NODE_SIZEunless set explicitly)node_color = DEFAULT_NODE_COLOR: Node fill colour (single value or vector; steel-blue by default)node_strokewidth = 1.0: Node outline widthnode_strokecolor = :black: Node outline colournode_marker = :circle: Node marker shape
Edge Keyword Arguments
edge_color = :black: Edge colouredge_width = 1.0: Edge line widthstraight_edges = nothing: Shorthand foredge_routing = Dict(e => :straight for e in edges)edge_routing = nothing:Dict((src, dst) => spec)per-edge routing — straight by default; curve with:curved,CurvedEdge(default bowDEFAULT_EDGE_BOW), a bow fraction (Real), orCurvedEdge(distance=…)for GraphMakie bend angle $γ = 2\operatorname{atan}(2d/L)$long_edge_routing = :quadratic: Geometry for bowed edges (:none,:natural_cubic, …)arrow_size = 10: Arrowhead sizearrow_shift = :end: Arrow position (:endor Float64 0-1)edge_weights = nothing: Optional signed edge values. A negative value uses an inhibitory-|terminal; pass either one value perGraphs.edges(g)entry or a structural matrixBwithB[i, j]the value onj → i.elabels = nothing: Edge labels inGraphs.edges(g)order. Accepts plainStrings or MakieLaTeXStrings (e.g. fromstructural_edge_labelswithlatex=true) for structural parameters or short mechanism TeX on edgeselabels_fontsize,elabels_distance,elabels_rotation,elabels_side, … : forwarded to GraphMakie (useelabels_rotation = 0to keep maths upright)selfedge_size,selfedge_direction,selfedge_width: GraphMakie self-loop geometry. When the graph has self-loops andselfedge_sizeis omitted, DAGMakie defaults toDEFAULT_SELFEDGE_SIZE(compact beside the node)
Label Keyword Arguments
labels = nothing: Node labels (preferred DAGMakie name).nlabels=is the GraphMakie-compatible aliasnlabels_align = (:center, :center): Makie text-box anchor (or vector of anchors). Named sides are edges of the label, not “label goes this side of the node”:(:left, :center)left-anchors the text so it sits to the right of the node. See the Label Alignment guide.label_position = :outer::outer(default) places labels outside in the largest angular gap (compactOUTER_LABEL_NODE_SIZEmarkers, dark text);:innercentres labels in the node fill (white text; passfit_node_size_to_labels=truefor oval markers on long names)label_position = :outer.truestill enables outer placement whenlabel_positionis left at:innerlabel_obstacle_graph = nothing: Optional graph used only for outer-label angle gaps (defaults to the plotted graph). Intervention plots pass the factual DAG so grey removed parent edges still count as obstacles.fit_node_size_to_labels = false: When true andlabel_position=:inner, size in-node markers from each label (short labels stay round; wider labels become ovals). Skipped for outer labels or an explicitnode_size. Passtruewithlabel_position=:innerfor fitted in-node markers.nlabels_distance = 0: Label distance from node in pixels alongnlabels_align(0 centres labels in nodes; with(:center, :center)the offset is zero regardless of distance—use a non-centred align orlabel_position = :outerfor outside labels)nlabels_fontsize = 16: Label font sizenlabels_color = :white: Label colour (white on dark node fills; dark viaOUTER_LABEL_COLORwhenlabel_position = :outer)
Colouring / dagitty roles
color_by = nothing: Set:ancestors(ortrue) for dagitty-style ancestor colours, or:adjustmentto also emphasise a backdoor adjustment set (needs CausalInference oradjustment=). Deprecated alias:smartexposure,outcome: Exposure and outcome node indices (required whencolor_byis on). -adjustment: OptionalSet{Int}forcolor_by=:adjustment
Additional Arguments
- Additional keyword arguments are passed to
GraphMakie.graphplot!
Returns
- The GraphPlot object
Examples
using Graphs, DAGMakie, CairoMakie
g = SimpleDiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 1, 3)
add_edge!(g, 2, 3)
# Simple usage
fig = Figure()
ax = Axis(fig[1, 1])
dagplot!(ax, g, labels=["Z", "X", "Y"])
# With node colours indicating roles
dagplot!(ax, g,
labels=["Confounder", "Treatment", "Outcome"],
node_color=[NODE_COLOR_CONFOUNDER, DEFAULT_NODE_COLOR, DEFAULT_NODE_COLOR]
)
# Multiple DAGs in one figure
fig = Figure(size=(1200, 400))
ax1, ax2, ax3 = Axis(fig[1, 1]), Axis(fig[1, 2]), Axis(fig[1, 3])
dagplot!(ax1, g1, labels=["A", "B", "C"])
dagplot!(ax2, g2, labels=["X", "Y", "Z"])
dagplot!(ax3, g3, labels=["P", "Q", "R"])dagplot!(ax, mg::MixedGraph; kwargs...)Plot a MixedGraph (with bidirected edges) into an existing axis.
Accepts the same preferred label kwargs as dagplot! on AbstractGraph (labels, label_position, …), plus bidirected-edge styling (bidirected_color, bidirected_width, …).
Convenience Pattern Functions
DAGMakie.dagplot_chain — Function
dagplot_chain(labels; kwargs...)Plot a simple chain DAG: X₁ → X₂ → ... → Xₙ
Arguments
labels::Vector{String}: Labels for each node in the chainkwargs...: Additional arguments passed todagplot
Returns
- Tuple
(fig, ax, p)
DAGMakie.dagplot_fork — Function
dagplot_fork(labels; kwargs...)Plot a fork DAG: X₁ ← X₂ → X₃
Arguments
labels::Vector{String}: Labels [left, fork, right]kwargs...: Additional arguments passed todagplot
DAGMakie.dagplot_collider — Function
dagplot_collider(labels; kwargs...)Plot a collider DAG: X₁ → X₂ ← X₃
Arguments
labels::Vector{String}: Labels [left, collider, right]kwargs...: Additional arguments passed todagplot
DAGMakie.dagplot_confounding — Function
dagplot_confounding(labels; kwargs...)Plot a confounding DAG: Z → X → Y, Z → Y.
Uses a triangle layout by default (confounder on top, treatment and outcome below). Layered left-to-right layout places one node per layer and draws the backdoor edge through the treatment node; pass layout to override.
Arguments
labels::Vector{String}: Labels [confounder, treatment, outcome]kwargs...: Additional arguments passed todagplot
DAGMakie.dagplot_mediation — Function
dagplot_mediation(labels; kwargs...)Plot a mediation DAG: X → M → Y, X → Y.
Uses a triangle layout by default (mediator on top, treatment and outcome below). Layered left-to-right layout places one node per layer and draws the direct effect through the mediator; pass layout to override.
Arguments
labels::Vector{String}: Labels [treatment, mediator, outcome]kwargs...: Additional arguments passed todagplot
DAGMakie.dagplot_confounded — Function
dagplot_confounded(labels; kwargs...)Plot a simple confounded graph: X → Y with X ↔ Y.
Uses a horizontal layout by default so the bidirected arc is visible above the directed edge; pass layout to override.
Arguments
labels::Vector{String}: Labels [treatment, outcome]
DAGMakie.dagplot_frontdoor — Function
dagplot_frontdoor(labels; kwargs...)Plot a frontdoor criterion graph: X → M → Y with X ↔ Y.
Uses a triangle layout by default (mediator on top). Layered layout collinearises the three nodes and obscures the bidirected confounding arc; pass layout to override.
Arguments
labels::Vector{String}: Labels [treatment, mediator, outcome]
DAGMakie.dagplot_iv_confounded — Function
dagplot_iv_confounded(labels; kwargs...)Plot an IV graph with confounding: Z → X → Y, X ↔ Y.
Uses a triangle layout by default (instrument on top). Layered layout collinearises the chain and hides the bidirected confounding arc; pass layout to override.
Arguments
labels::Vector{String}: Labels [instrument, treatment, outcome]
DAGMakie.dagplot_m_bias — Function
dagplot_m_bias(labels; kwargs...)Plot the classic five-node M-bias DAG with explicit latents:
U₁ → X, U₁ → M, U₂ → M, U₂ → YDefault layout forms the letter M (latents on top, $X$–$M$–$Y$ on the base). Pass layout to override.
Arguments
labels::Vector{<:AbstractString}: Labels $[U₁, U₂, X, M, Y]$
Types
Node Types
DAGMakie.NodeType — Type
NodeTypeEnumeration of node types in causal diagrams.
Values
Observed: Measured variable (filled circle)Latent: Unmeasured variable (hollow circle or dashed outline)Treatment: Treatment/exposure variable (thick stroke)Outcome: Outcome variable (emphasised stroke)Instrument: Instrumental variableConfounder: Confounding variableMediator: Mediating variableCollider: Collider variableEffectMeasure: Causal-effect node on an IDAG (not a factual outcome)SwigFixed: Fixed half of a SWIG split node (intervened value)
DAGMakie.NodeSpec — Type
NodeSpecSpecification for a single node in a DAG.
Fields
label::String: Display label for the nodetype::NodeType: Type of node (determines styling)color: Override colour (nothing= use type default)size::Union{Real, Tuple, Nothing}: Override size (nothing = use default; a(width, height)tuple is passed through to Makie for non-square markers)
Edge Types
DAGMakie.EdgeType — Type
EdgeTypeEnumeration of edge types in causal diagrams.
Values
Directed: Standard directed edge (→)Bidirected: Bidirected edge for unmeasured confounding (↔)Undirected: Undirected edge (—)Modifier: Pedagogical annotation that one variable modifies an effect (dash-dot; not an input to d-separation)
DAGMakie.EdgeSpec — Type
EdgeSpecSpecification for a single edge in a DAG.
Fields
src::Int: Source node indexdst::Int: Destination node indextype::EdgeType: Type of edgecolor: Override colourwidth::Union{Real, Nothing}: Override line widthstyle::Union{Symbol, Nothing}: Line style (:solid, :dash, :dot, etc.)label::Union{String, Nothing}: Optional edge label
DAG Specification
DAGMakie.DAGSpec — Type
DAGSpecComplete specification for a causal DAG visualisation.
Fields
graph::AbstractGraph: The underlying graph structurenodes::Vector{NodeSpec}: Node specifications (one per vertex)edges::Vector{EdgeSpec}: Edge specifications (overrides for specific edges)title::Union{String, Nothing}: Optional title for the DAG
Mixed Graphs
DAGMakie.MixedGraph — Type
MixedGraphA graph that supports both directed edges (→) and bidirected edges (↔).
Bidirected edges represent unmeasured common causes (latent confounders) in causal diagrams. Internally, bidirected edges are stored separately from the directed graph structure.
Fields
directed::SimpleDiGraph: The directed edgesbidirected::Set{Tuple{Int, Int}}: Set of bidirected edge pairs (unordered)
Examples
# Create a mixed graph with confounding
mg = MixedGraph(3)
add_directed_edge!(mg, 1, 3) # X → Y
add_directed_edge!(mg, 2, 3) # Z → Y
add_bidirected_edge!(mg, 1, 2) # X ↔ Z (unmeasured confounder)
# Or from existing directed graph
g = SimpleDiGraph(3)
add_edge!(g, 1, 3)
add_edge!(g, 2, 3)
mg = MixedGraph(g, [(1, 2)]) # Add bidirected edgePaths
DAGMakie.CausalPath — Type
CausalPathA path through a DAG, tracking nodes and edge directions (forward or backward along graph edges).
Fields
nodes::Vector{Int}: Sequence of nodesdirections::Vector{Symbol}: Direction of each edge (:forwardor:backward)
DAGMakie.PathSegment — Type
PathSegmentA segment of a path, tracking the direction of traversal.
Fields
node::Int: The node in this segmentdirection::Symbol::forward(→),:backward(←), or:start
DAGMakie.path_edges — Function
path_edges(path::CausalPath)Return the edges in a path as (src, dst) tuples in graph orientation.
DAGMakie.is_directed_path — Function
is_directed_path(path::CausalPath)Return true if every edge in the path is forward (a directed causal path).
Interventions
DAGMakie.Intervention — Type
InterventionSpecification for an intervention (do(·)) on one or more variables.
Fields
nodes::Vector{Int}: Nodes being intervened onvalues::Vector{String}: Display values for the interventions (e.g., "x=1")label::String: Overall intervention label (e.g., "do(X=1)")
DAGMakie.CausalQuery — Type
CausalQueryA query about a causal effect.
Fields
treatment::Int: Treatment variableoutcome::Int: Outcome variableintervention::Union{Intervention, Nothing}: Intervention (if specified)conditioning::Set{Int}: Variables to condition on
Graph Construction
DAGMakie.chain_graph — Function
chain_graph(labels::Vector{String})Create a simple chain DAG: X₁ → X₂ → ... → Xₙ
Arguments
labels: Vector of node labels
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.fork_graph — Function
fork_graph(labels::Vector{String})Create a fork DAG: X₁ ← X₂ → X₃ (with X₂ as the fork point)
Assumes 3 nodes: labels[1] ← labels[2] → labels[3]
Arguments
labels: Vector of 3 node labels
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.collider_graph — Function
collider_graph(labels::Vector{String})Create a collider DAG: X₁ → X₂ ← X₃ (with X₂ as the collider)
Assumes 3 nodes: labels[1] → labels[2] ← labels[3]
Arguments
labels: Vector of 3 node labels
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.confounding_graph — Function
confounding_graph(labels::Vector{String})Create a classic confounding DAG: Z → X → Y, Z → Y
Assumes 3 nodes: Z (confounder), X (treatment), Y (outcome)
Arguments
labels: Vector of 3 node labels [Z, X, Y]
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.mediation_graph — Function
mediation_graph(labels::Vector{String})Create a mediation DAG: X → M → Y, X → Y
Assumes 3 nodes: X (treatment), M (mediator), Y (outcome)
Arguments
labels: Vector of 3 node labels [X, M, Y]
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.instrumental_graph — Function
instrumental_graph(labels::Vector{String})Create an instrumental variable DAG: Z → X → Y, U → X, U → Y
Assumes 4 nodes: Z (instrument), X (treatment), Y (outcome), U (unobserved confounder)
Arguments
labels: Vector of 4 node labels [Z, X, Y, U]
Returns
- Tuple
(g, labels)wheregis the graph
DAGMakie.mixed_graph — Function
mixed_graph(n::Int, directed_edges, bidirected_edges)Create a MixedGraph with specified directed and bidirected edges.
Arguments
n::Int: Number of verticesdirected_edges: Iterable of(src, dst)pairs for directed edgesbidirected_edges: Iterable of(i, j)pairs for bidirected edges
Examples
# Confounded treatment-outcome graph
mg = mixed_graph(2, [(1, 2)], [(1, 2)]) # X → Y with X ↔ Y
# Instrumental variable setup
mg = mixed_graph(4,
[(1, 2), (2, 3)], # Z → X → Y
[(2, 3)] # X ↔ Y (unmeasured confounding)
)DAGMakie.confounded_graph — Function
confounded_graph(labels::Vector{String})Create a simple confounded graph: X → Y with X ↔ Y (unmeasured confounder).
Arguments
labels: Vector of 2 labels [treatment, outcome]
Returns
- Tuple
(mg, labels)wheremgis a MixedGraph
DAGMakie.frontdoor_graph — Function
frontdoor_graph(labels::Vector{String})Create a frontdoor criterion graph: X → M → Y with X ↔ Y.
Arguments
labels: Vector of 3 labels [treatment, mediator, outcome]
Returns
- Tuple
(mg, labels)wheremgis a MixedGraph
DAGMakie.iv_confounded_graph — Function
iv_confounded_graph(labels::Vector{String})Create an instrumental variable graph with confounding: Z → X → Y, X ↔ Y.
Arguments
labels: Vector of 3 labels [instrument, treatment, outcome]
Returns
- Tuple
(mg, labels)wheremgis a MixedGraph
DAGMakie.m_bias_graph — Function
m_bias_graph(labels)Create the classic five-node M-bias DAG with explicit latents:
U₁ → X, U₁ → M, U₂ → M, U₂ → YNo directed edge $X → Y$ (pure M-bias). Conditioning on the collider $M$ opens the non-causal path $X ← U₁ → M ← U₂ → Y$.
Arguments
labels: Vector of 5 labels $[U₁, U₂, X, M, Y]$ (defaults to those symbols)
Returns
- Tuple
(mg, labels)wheremgis aMixedGraphwith no bidirected edges (latents are drawn explicitly rather than as $X ↔ M ↔ Y$)
DAGMakie.m_bias_spec — Function
m_bias_spec(labels)DAGSpec for the five-node M-bias DAG with latent / collider styling.
Same edges as m_bias_graph: $U₁ → X$, $U₁ → M$, $U₂ → M$, $U₂ → Y$.
Highlighting
DAGMakie.HighlightSpec — Type
HighlightSpecSpecification for highlighting elements in a DAG plot.
Fields
nodes::Vector{Int}: Nodes to highlightnode_colors::Vector: Colors for highlighted nodesedges::Vector{Tuple{Int,Int}}: Edges to highlight (as src, dst pairs)edge_colors::Vector: Colors for highlighted edgeslabels::Vector{String}: Optional labels for highlighted nodes
Precedence
When the same node or edge is listed more than once, the first listing wins (exposure / outcome / adjustment colours should therefore precede path colours in merged specs). Default theme colours sit underneath.
DAGMakie.highlight_from_path — Function
highlight_from_path(path::CausalPath; color=:red)Create a HighlightSpec from a causal path.
DAGMakie.highlight_from_paths — Function
highlight_from_paths(paths::Vector{CausalPath}; colors=nothing)Create a HighlightSpec from multiple paths with different colors.
DAGMakie.highlight_adjustment_set — Function
highlight_adjustment_set(g::AbstractGraph, treatment::Int, outcome::Int,
adjustment::Set{Int}; treatment_color=:green,
outcome_color=:blue, adjustment_color=:orange)Create a HighlightSpec for visualising an adjustment set.
DAGMakie.highlight_backdoor_paths — Function
highlight_backdoor_paths(paths::Vector{CausalPath};
blocked_color=:gray, open_color=:red,
blocked::AbstractVector{Bool}=Bool[])Create a HighlightSpec from precomputed backdoor paths.
Pass blocked of the same length as paths to colour blocked vs open paths. If blocked is empty, all paths use open_color.
Path finding belongs in CausalInference.jl / CausalDynamics.jl — not DAGMakie.
DAGMakie.dagplot_highlighted — Function
dagplot_highlighted(g::AbstractGraph, highlight::HighlightSpec; kwargs...)Create a DAG plot with highlighted elements.
Arguments
g: The graphhighlight: HighlightSpec defining what to highlightkwargs...: Additional arguments passed to dagplot
Examples
g = confounding_graph(["Z", "X", "Y"])[1]
path = CausalPath([1, 2, 3]; directions = [:forward, :forward])
highlight = highlight_from_path(path, color=:red)
fig, ax, p = dagplot_highlighted(g, highlight, labels=["Z", "X", "Y"])DAGMakie.dagplot_highlighted! — Function
dagplot_highlighted!(ax, g::AbstractGraph, highlight::HighlightSpec; kwargs...)Plot a highlighted DAG into an existing axis.
Prefer labels= and label_position= (see dagplot!). Alias nlabels= / auto_align_labels= still work.
DAGMakie.dagplot_backdoor — Function
dagplot_backdoor(g, treatment, outcome; adjustment=Set{Int}(), paths=CausalPath[], kwargs...)Plot a DAG highlighting treatment, outcome, an adjustment set, and optional precomputed backdoor paths.
For automatic adjustment / d-separation from CausalInference.jl, load that package so DAGMakieCausalInferenceExt activates, or pass adjustment / paths explicitly from CausalDynamics.jl.
DAGMakie.dagplot_dsep — Function
dagplot_dsep(g, x, y, z; separated, kwargs...)Plot a DAG with X, Y, and conditioning set Z highlighted.
Pass separated::Bool for the axis title (from CausalInference.jl dsep or CausalDynamics.jl d_separated). With using CausalInference, separated defaults via the package extension when omitted.
DAGMakie.dagplot_causal_paths — Function
dagplot_causal_paths(g, treatment, outcome; paths, kwargs...)Highlight precomputed directed paths from treatment to outcome.
DAGMakie.dagplot_adjustment — Function
dagplot_adjustment(g, treatment, outcome; adjustment=nothing, kwargs...)Plot a DAG with an adjustment set. Pass adjustment::Set{Int} explicitly, or using CausalInference so the extension can compute a minimal backdoor set.
Interventions
DAGMakie.do_surgery — Function
do_surgery(g::AbstractGraph, intervention_nodes::Vector{Int})Perform display-only graph surgery for do(·): remove all incoming edges to intervention nodes. Use this to draw mutilated DAGs in Makie; it is not an identification API.
For CausalDynamics identification and CDM interventions, use DoIntervention / apply_intervention (and related helpers) in CausalDynamics.jl.
Returns a graph where intervened nodes no longer have parents (for plotting).
Arguments
g: Original DAGintervention_nodes: Nodes being intervened on (set to fixed values)
Returns
- New SimpleDiGraph with incoming edges to intervention nodes removed
Examples
# Confounding: Z → X → Y, Z → Y
g = SimpleDiGraph(3)
add_edge!(g, 1, 2) # Z → X
add_edge!(g, 2, 3) # X → Y
add_edge!(g, 1, 3) # Z → Y
# do(X) - intervene on X
g_do = do_surgery(g, [2])
# Result: X → Y, Z → Y (Z → X edge removed)do_surgery(g::AbstractGraph, intervention_node::Int; kwargs...)Single-node intervention convenience method.
DAGMakie.do_surgery! — Function
do_surgery!(g::SimpleDiGraph, intervention_nodes::Vector{Int})In-place display-only graph surgery — modifies the original graph. Same boundary as do_surgery: for plotting mutilated DAGs, not CausalDynamics ID.
DAGMakie.dagplot_intervention — Function
dagplot_intervention(g::AbstractGraph, intervention::Intervention;
show_removed_edges::Bool=true, kwargs...)Plot a DAG with intervention applied, showing removed edges.
Arguments
g: Original DAGintervention: Intervention specificationshow_removed_edges: If true, overlay severed parent edges as dashed grey (deprecated alias:show_original)kwargs...: Additional arguments for dagplot
Examples
g, labels = confounding_graph(["Z", "X", "Y"])
int = Intervention(2) # do(X)
fig, ax, p = dagplot_intervention(g, int, labels=labels)DAGMakie.dagplot_intervention! — Function
dagplot_intervention!(ax, g::AbstractGraph, intervention::Intervention; kwargs...)Plot intervention into an existing axis.
Preferred kwargs (see also dagplot!): labels, label_position, show_removed_edges (alias show_original), do_node_labels (alias relabel_nodes). Outer labels use the factual DAG as label_obstacle_graph when removed edges are overlaid.
DAGMakie.dagplot_do — Function
dagplot_do(g::AbstractGraph, intervention_node::Int; labels=nothing, kwargs...)Convenience function for single-node intervention visualisation.
Prefer labels= (alias nlabels=). Other kwargs match dagplot_intervention / dagplot!.
DAGMakie.dagplot_comparison — Function
dagplot_comparison(g::AbstractGraph, intervention::Intervention;
labels=nothing, kwargs...)Create a side-by-side comparison of original and post-intervention DAGs.
Prefer labels= (alias nlabels=). Shared layout and remaining kwargs are passed to dagplot! / dagplot_intervention!.
DAGMakie.dagplot_do_comparison — Function
dagplot_do_comparison(g::AbstractGraph, intervention_node::Int;
labels=nothing, kwargs...)Convenience function for side-by-side comparison with single intervention.
Prefer labels= (alias nlabels=). See dagplot_comparison.
DAGMakie.intervention_label — Function
intervention_label(var_name::String; value=nothing)Create a formatted intervention label: do(X) or do(X=x).
DAGMakie.format_intervention_labels — Function
format_intervention_labels(nlabels::Vector{String}, intervention::Intervention)Update node labels to show intervention notation.
DAGMakie.query_to_string — Function
query_to_string(query::CausalQuery, nlabels::Vector{String})Convert a causal query to readable notation for plot titles.
Visual grammar (interactions / DiD)
DAGMakie.modifier_edge — Function
modifier_edge(src, dst; kwargs...)Create a pedagogical modifier EdgeSpec (dash-dot, dark gray).
Modifier edges annotate that one variable modifies an effect; they are not inputs to d-separation. Prefer an IDAG (DAGMakie.vaccine_nutrition_idag_spec) when the claim is about effect variation.
DAGMakie.dagplot_side_by_side — Function
dagplot_side_by_side(left, right; titles, figure_size, kwargs...)Plot two DAGSpec (or graph) panels with shared keyword overrides.
Used for outcome-DAG | IDAG and factual-DAG | SWIG companion figures.
DAGMakie.did_2x2_factual_spec — Function
did_2x2_factual_spec(; labels)Time-expanded factual DAG for a two-group, two-period DiD design.
Nodes (default order): $G$, $U$, $Y₀$, $Y₁$, $A₁$.
DAGMakie.did_2x2_swig_spec — Function
did_2x2_swig_spec(; labels)SWIG under $do(A₁ = 0)$ for the untreated world used by parallel trends.
Nodes (default order): $G$, $U$, $Y₀$, $Y₁(0)$, $A₁$, $a=0$. Incoming edges stay on the random half $A₁$; outflows leave from the fixed half $a=0$.
DAGMakie.did_2x2_factual_layout — Function
did_2x2_factual_layout()Manual positions for the factual 2×2 DiD DAG (time left→right).
DAGMakie.did_2x2_swig_layout — Function
did_2x2_swig_layout()Manual positions for the DiD SWIG with a split treatment node.
DAGMakie.dagplot_did_swig — Function
dagplot_did_swig(; kwargs...)Side-by-side factual 2×2 DiD DAG and untreated-world SWIG.
Smart / dagitty colouring
DAGMakie.SmartNodeRole — Type
SmartNodeRoleStructural role of a node relative to a chosen exposure and outcome (dagitty- style ancestor highlighting).
DAGMakie.classify_smart_roles — Function
classify_smart_roles(g, exposure, outcome; anc_treatment=nothing, anc_outcome=nothing)Classify each vertex into a SmartNodeRole given exposure and outcome (1-based indices). Argument names treatment / anc_treatment remain for compatibility with older call sites.
DAGMakie.smart_style_for_graph — Function
smart_style_for_graph(g, exposure, outcome; mode=:ancestors, adjustment=nothing)Return a named tuple of per-node styling for dagitty-like colouring (color_by modes).
Modes
:ancestors— exposure / outcome / ancestor-of-X / ancestor-of-Y / both / gray:adjustment— same, plus thicker stroke on a backdoor adjustment set (requires CausalInference, or passadjustment::Set{Int})
DAGMakie.apply_smart_kwargs — Function
apply_smart_kwargs(g; color_by, exposure, outcome, adjustment, kwargs...)If color_by is enabled, merge dagitty-style colours into keyword args for dagplot!. Explicit node_color from the caller wins.
Deprecated aliases: smart= for color_by=, treatment= for exposure=.
DAGMakie.dagplot_smart — Function
dagplot_smart(g, treatment, outcome; color_by=:ancestors, kwargs...)Convenience wrapper: dagplot with dagitty-style ancestor colours. Prefer color_by=; smart= remains an alias.
DAGMakie.propagate_temporal_smart_roles — Function
propagate_temporal_smart_roles(
roles, n_variables, n_times; exposure, outcome
) -> Vector{SmartNodeRole}Propagate per-node SmartNodeRoles across each variable row so Treatment[t] / Outcome[t] styling applies at every occasion on the estimand path (see issue #5).
- The exposure variable row → all
SmartExposure - The outcome variable row → all
SmartOutcome - Other rows → strongest ancestral role among occasions (both > X-only > Y-only > gray)
DAGMakie.smart_style_for_temporal_graph — Function
smart_style_for_temporal_graph(
g, n_variables, n_times, exposure, outcome; mode=:ancestors, adjustment=nothing
) -> NamedTupleDagitty-style colours on a time-unrolled graph, with roles propagated across variable rows (see propagate_temporal_smart_roles).
DAGMakie.temporal_role_styling — Function
temporal_role_styling(
g, n_variables, n_times; exposure, outcome, color_by=:ancestors, adjustment=nothing
) -> NamedTupleConvenience wrapper returning (node_color, nlabels_color, node_strokewidth, node_strokecolor, roles, adjustment) for manual dagplot / dagplot_time_indexed calls (#5).
exposure and outcome may be node indices or (variable_index, time_index) pairs.
DAGMakie.apply_temporal_smart_kwargs — Function
apply_temporal_smart_kwargs(
g, n_variables, n_times; color_by, exposure, outcome, adjustment, kwargs...
) -> NamedTupleLike apply_smart_kwargs, but propagates exposure / outcome / ancestor roles across variable rows on time-unrolled graphs (#5).
DAGMakie.temporal_node_index — Function
temporal_node_index(n_variables, variable, time) -> IntLinear node index for (variable, time) in a time-unrolled graph.
DAGMakie.temporal_variable_time — Function
temporal_variable_time(node, n_variables) -> (variable, time)Inverse of temporal_node_index for 1-based node indices.
DAGMakie.ancestor_sets — Function
ancestor_sets(g, exposure, outcome) -> (anc_exposure, anc_outcome)Return ancestor sets of exposure and outcome. Uses CausalInference.jl when the extension is loaded; otherwise Graphs reverse-BFS.
DAGMakie.ancestors_via_graphs — Function
ancestors_via_graphs(g, seeds) -> Set{Int}Ancestors of seeds including the seeds themselves, via reverse BFS on inneighbors (Graphs only; no CausalInference).
DAGMakie.smart_node_color — Function
smart_node_color(role::SmartNodeRole)Return the default fill colour for a smart node role.
DAGMakie.smart_label_color — Function
smart_label_color(role::SmartNodeRole)Return in-node label colour for a smart role (white on role fills; black on light gray irrelevant nodes). With label_position=:outer (or legacy auto_align_labels=true), apply_smart_kwargs replaces these with OUTER_LABEL_COLOR.
DAGMakie.SMART_COLOR_EXPOSURE — Constant
Fill for the exposure / treatment node under smart colouring.
DAGMakie.SMART_COLOR_OUTCOME — Constant
Fill for the outcome node under smart colouring.
DAGMakie.SMART_COLOR_ANC_EXPOSURE — Constant
Dagitty-like fill for ancestors of the exposure only.
DAGMakie.SMART_COLOR_ANC_OUTCOME — Constant
Dagitty-like fill for ancestors of the outcome only.
DAGMakie.SMART_COLOR_ANC_BOTH — Constant
Dagitty-like fill for ancestors of both (often backdoor-relevant).
DAGMakie.SMART_COLOR_IRRELEVANT — Constant
Fill for nodes outside the ancestral closure of exposure ∪ outcome.
Preferred keyword names
Public plotting prefers discoverable names; legacy aliases still work:
| Prefer | Alias (removed from public API) |
|---|---|
labels | nlabels (GraphMakie pass-through still accepted) |
label_position = :outer | former auto_align_labels=true |
label_obstacle_graph | former auto_align_graph |
color_by | former smart |
exposure | former treatment (colouring kwargs) |
show_removed_edges | former show_original |
do_node_labels | former relabel_nodes |
OUTER_LABEL_* | former AUTO_ALIGN_* |
GraphMakie pass-throughs keep the nlabels_* / elabels_* prefixes.
Label Alignment
DAGMakie.compute_auto_label_aligns — Function
compute_auto_label_aligns(g::AbstractGraph, node_positions::AbstractVector)Compute optimal label alignment for each node to avoid edge overlaps.
For each node, finds the largest angular gap between incident edges and places the label in the middle of that gap. This ensures labels do not overlap with edges, improving readability of causal diagrams.
Arguments
g::AbstractGraph: A graph from Graphs.jl (directed or undirected)node_positions::AbstractVector: Vector of node positions (Point2f or similar)
Returns
Vector{Tuple{Symbol, Symbol}}: Alignment tuples(:horizontal, :vertical)for each node, suitable for use withnlabels_alignin GraphMakie.
Algorithm
- For each node, collect angles of all incident edges (both incoming and outgoing)
- Normalise angles to [0, 2π] and sort
- Find the largest angular gap between adjacent edges (including wrap-around)
- Place label in the middle of the largest gap
- Map the gap midpoint angle to one of 8 alignment directions
Edge Cases
- Isolated nodes (no incident edges): Returns
(:right, :bottom)as default - Single edge: Places label opposite the edge direction
- All edges in one direction: Places label in the opposite hemisphere
Examples
using Graphs, DAGMakie
g = SimpleDiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
# Compute positions (e.g., from a layout algorithm)
positions = [Point2f(0, 0), Point2f(1, 0), Point2f(2, 0)]
# Get optimal alignments
aligns = compute_auto_label_aligns(g, positions)
# aligns[1] might be (:center, :top) since edge goes right
# aligns[2] might be (:center, :top) since edges go left and right
# aligns[3] might be (:center, :top) since edge goes leftNotes
- Works with any layout algorithm (uses computed node positions)
- Handles both directed and undirected graphs
- Time complexity: O(V × E) where V is vertices and E is edges
- Intended for outside-node labels: pair with a positive
nlabels_distance(seeDAGMakie.resolve_auto_align_label_settings). With distance 0 the non-centred alignments only shift text inside the marker and look broken.
DAGMakie.align_to_direction — Function
align_to_direction(align::Tuple{Symbol, Symbol})Convert a Makie text-box align to the offset direction GraphMakie uses with nlabels_distance.
Makie align names the edge of the label that sits on the anchor. Combined with a positive distance, that places the label on the opposite side of the node (e.g. (:left, :center) → offset east → label to the right of the node).
Arguments
align: Tuple of(:horizontal, :vertical)Makie text-align symbols
Returns
- Direction from node toward the label (for use as
distance .* dir)
Examples
align_to_direction((:left, :center)) # (1, 0) — label ends up east of node
align_to_direction((:right, :center)) # (-1, 0) — label ends up west of node
align_to_direction((:center, :bottom)) # (0, 1) — label ends up north of nodeLayout Utilities
DAGMakie.estimate_label_extent — Function
estimate_label_extent(label, align, fontsize, distance)Estimate the extent of a label in pixel coordinates.
Returns a named tuple (dx_min, dx_max, dy_min, dy_max) representing the approximate bounding box offset from the node position.
Arguments
label::AbstractString: The label textalign::Tuple{Symbol, Symbol}: Alignment tuple(:horizontal, :vertical)fontsize::Real: Font size in pixelsdistance::Real: Label distance from node in pixels
Returns
- Named tuple with
dx_min,dx_max,dy_min,dy_maxin pixels
Notes
- Uses approximate character width of 0.6 × fontsize for typical fonts
- Returns pixel-space estimates (caller must convert to data coordinates)
DAGMakie.estimate_label_pixel_size — Function
estimate_label_pixel_size(label, fontsize) -> (width, height)Approximate pixel width and height of label at fontsize (multiline aware). Uses the same 0.6 × fontsize character-width heuristic as estimate_label_extent.
DAGMakie.node_size_for_inner_label — Function
node_size_for_inner_label(label; fontsize, padding, min_size, marker=:auto)Choose a GraphMakie node_size (and marker) that encloses an in-node label.
Short labels keep a circle whose diameter covers the text; wider labels keep FIT_NODE_MARKER (Makie.Circle) with a (width, height) size (an oval / ellipse). Pass marker = :circle or Circle to force a round disk; pass marker = :rect to force a rectangular box.
Returns
(size, marker)wheresizeis aRealor(width, height)tuple
DAGMakie.fit_node_sizes_to_labels — Function
fit_node_sizes_to_labels(nlabels; fontsize, padding, min_size, markers=nothing)Fit a size (and marker) for each in-node label.
When markers === nothing, markers are FIT_NODE_MARKER (round or oval). When markers is a vector, sizes respect those shapes (:circle is normalised to FIT_NODE_MARKER so sizing matches the drawn glyph).
DAGMakie.compute_label_bounds — Function
compute_label_bounds(node_positions, nlabels, nlabels_align, nlabels_distance, nlabels_fontsize)Compute the bounding box that encompasses all nodes and their labels.
Arguments
node_positions::AbstractVector: Vector of node positions (Point2f or similar)nlabels::AbstractVector{<:AbstractString}: Vector of label stringsnlabels_align: Vector of alignment tuples or single tuple for allnlabels_distance::Real: Label distance from node in pixelsnlabels_fontsize::Real: Font size for labels
Returns
- Tuple
(x_min, x_max, y_min, y_max)in data coordinates
Notes
- Estimates pixel-to-data conversion based on current data range
- Assumes typical figure width of ~500 pixels for the data range
DAGMakie.compute_padded_limits — Function
compute_padded_limits(node_positions, nlabels, nlabels_align, nlabels_distance, nlabels_fontsize; padding=0.1, node_sizes=nothing)Compute axis limits with padding that includes all nodes, labels, and marker extents.
Arguments
node_positions: Vector of node positionsnlabels: Vector of label strings (ornothingfor no labels)nlabels_align: Alignment specificationnlabels_distance: Label distance in pixelsnlabels_fontsize: Font sizepadding::Float64 = 0.1: Padding as fraction of range (0.1 = 10%)node_sizes: Optional node marker size(s) in pixels; used so flat chains/columns are not cropped underDataAspect
Returns
- Tuple of tuples
((x_min, x_max), (y_min, y_max))for axis limits
DAGMakie.get_node_positions — Function
get_node_positions(p)Extract node positions from a GraphPlot object.
Arguments
p: A GraphMakie GraphPlot object
Returns
- Vector of node positions (Point2f or Point3f)
DAGMakie.graph_extent — Function
graph_extent(node_positions)Compute the spatial extent of a graph from node positions.
Arguments
node_positions: Vector of node positions
Returns
- Named tuple
(x_min, x_max, y_min, y_max, x_range, y_range)
DAGMakie.compute_graph_layout — Function
compute_graph_layout(g; kwargs...)Resolve deterministic positions and feedback-edge routing metadata for a graph.
If layout is provided, those positions are respected, but the graph is still classified so cyclic feedback routing can be applied consistently.
When layout is omitted and the graph is a 3-node transitive triangle (unique source with edges to both other nodes plus an edge between them), an apex-top pedagogical layout is used so the shortcut edge remains visible.
DAGMakie.DAGLayoutResult — Type
DAGLayoutResultResolved node positions and routing metadata for a graph visualisation.
Fields
positions::Vector{Point2f}: Node positions in data coordinateskind::Symbol: One of:acyclic,:cyclic,:mixed_acyclic, or:mixed_cyclicnode_layers::Vector{Int}: Layer index for each nodecomponent_index::Vector{Int}: Strongly connected component index for each nodecomponents::Vector{Vector{Int}}: Node membership for each componentcomponent_layers::Vector{Int}: Layer index for each componentfeedback_edges::Vector{Tuple{Int, Int}}: Directed edges routed as feedback edgesedge_waypoints::Dict{Tuple{Int, Int}, Vector{Point2f}}: Extra waypoints for curved edgesedge_curve_distances::Dict{Tuple{Int, Int}, Float64}: GraphMakiecurve_distanceper edge
DAGMakie.classify_graph_kind — Function
classify_graph_kind(g)Classify a graph by directed cyclicity and mixed-edge support.
Self-loops are ignored for this classification: they are drawn by GraphMakie as local self-arcs and should not force the multi-node cyclic / feedback layout.
DAGMakie.feedback_edge_mask — Function
feedback_edge_mask(g, layout_result)Return a Boolean mask, aligned with edges(g), indicating which directed edges should be rendered as curved feedback overlays.
DAGMakie.edge_waypoint_vector — Function
edge_waypoint_vector(g, layout_result)Materialise a per-edge waypoint vector aligned with edges(g) for GraphMakie.graphplot!.
DAGMakie.count_layered_crossings — Function
count_layered_crossings(g, layers)Count pairwise edge crossings between consecutive layers of a layered digraph. Used for layout quality checks; not a public drawing API.
DAGMakie.node — Function
node(label; kwargs...)Shorthand for creating a NodeSpec.
DAGMakie.edge — Function
edge(src, dst; kwargs...)Shorthand for creating an EdgeSpec.
Edge routing
Edges are straight by default. Per-edge bows use edge_routing on dagplot / dagplot! (see Basic Plotting and Output Quality).
DAGMakie.CurvedEdge — Type
CurvedEdgePer-edge bow for dagplot. Unspecified edges are straight.
Fields
bow::Union{Real, Nothing}: Lateral quadratic control offset as a fraction of chord length (typical 0.12–0.35). Defaults toDEFAULT_EDGE_BOW. Ignored whendistanceis set.distance::Union{Real, Nothing}: GraphMakiecurve_distancein data units (perpendicular sagitta). The bend angle along the chord satisfies $γ = 2 \operatorname{atan}(2d / L)$ for chord length $L$.side::Symbol::autobows away from the nearest non-endpoint node on the chord;:left/:rightare relative to the forward chord direction.
Examples
edge_routing = Dict(
(1, 4) => CurvedEdge(), # default bow
(1, 5) => CurvedEdge(bow = 0.28), # deeper arc
(2, 4) => CurvedEdge(distance = 0.35),
(3, 5) => 0.18, # bow fraction shorthand
)DAGMakie.DEFAULT_EDGE_BOW — Constant
Default lateral bow as a fraction of chord length for CurvedEdge.
Themes and Styling
DAGMakie.dag_theme — Function
dag_theme()Return a clean theme for DAG visualisation.
This theme removes axes, grids, and frames to create publication-ready causal diagrams with focus on the graph structure.
Returns
Theme: A Makie theme with clean DAG styling
Usage
using CairoMakie, DAGMakie
# Apply theme globally
set_theme!(dag_theme())
# Or use with_theme for a single plot
with_theme(dag_theme()) do
dagplot(g, labels=["X", "Y", "Z"])
endDAGMakie.apply_dag_theme! — Function
apply_dag_theme!(ax)Apply clean DAG theme to an existing axis.
Hides all axis decorations (spines, ticks, labels, grid) and sets DataAspect for proper graph proportions.
Arguments
ax: A Makie Axis object
Example
fig = Figure()
ax = Axis(fig[1, 1])
apply_dag_theme!(ax)
graphplot!(ax, g)DAGMakie.DAGStyle — Type
DAGStylePreset styling configuration for DAG visualisation.
Fields
node_size::Real: Node size in pixelsnode_color::Symbol: Default node colournode_strokewidth::Real: Node outline widthnode_strokecolor::Symbol: Node outline colouredge_color::Symbol: Edge colouredge_width::Real: Edge line widtharrow_size::Real: Arrowhead sizearrow_shift: Arrow position (:endor Float64)label_fontsize::Real: Label font sizelabel_color::Symbol: Label colourlabel_distance::Real: Label distance from nodepadding::Float64: Padding fraction
DAGMakie.default_style — Function
default_style()Return the default DAG styling configuration.
DAGMakie.minimal_style — Function
minimal_style()Return a minimal DAG style with smaller nodes and thinner edges (in-node labels).
DAGMakie.bold_style — Function
bold_style()Return a bold DAG style with larger nodes and thicker edges.
DAGMakie.presentation_style — Function
presentation_style()Return a style optimised for presentations (large, high contrast, in-node labels).
DAGMakie.UNDIRECTED_EDGE_COLOR — Constant
Default colour for undirected (CPDAG skeleton) edges.
DAGMakie.OUTER_LABEL_DISTANCE — Constant
Pixel offset used when label_position=:outer places labels outside nodes.
DAGMakie.OUTER_LABEL_COLOR — Constant
Label colour for outside-node labels (label_position=:outer).
DAGMakie.OUTER_LABEL_NODE_SIZE — Constant
Default node size when label_position=:outer (compact markers; labels sit outside).
DAGMakie.DEFAULT_NODE_GAP_INNER — Constant
Within-layer node spacing when label_position=:inner (room for fitted markers / chords).
DAGMakie.DEFAULT_NODE_GAP_OUTER — Constant
Within-layer node spacing when label_position=:outer (compact markers).
DAGMakie.FIT_NODE_LABEL_PADDING — Constant
Total padding (px) around text when fitting in-node markers to labels.
DAGMakie.FIT_NODE_MIN_SIZE — Constant
Minimum marker size (px) when fitting in-node markers to labels.
DAGMakie.FIT_NODE_RECT_ASPECT — Constant
Width/height ratio above which fitted in-node markers become ovals (not disks).
DAGMakie.FIT_NODE_MARKER — Type
Marker used when fitting node size to in-node labels.
Prefer geometry Makie.Circle over the :circle BezierPath: Makie draws :circle at roughly 70% of markersize, so label-based sizes look too small and text overflows. Circle matches markersize one-to-one (including ovals via (width, height)).
DAGMakie.DEFAULT_NODE_COLOR — Constant
Default node colour (steel-blue; white labels read clearly).
DAGMakie.DEFAULT_NODE_SIZE — Constant
Default node size in pixels (large enough for in-node labels).
DAGMakie.DEFAULT_EDGE_COLOR — Constant
Default edge colour.
DAGMakie.DEFAULT_LABEL_FONTSIZE — Constant
Default label font size (compact outer labels).
DAGMakie.DEFAULT_LABEL_DISTANCE — Constant
Default label distance from node (pixels); 0 centres labels in nodes.
DAGMakie.DEFAULT_LABEL_POSITION — Constant
Default label placement (:outer = compact nodes + external labels).
DAGMakie.DEFAULT_SELFEDGE_SIZE — Constant
Default GraphMakie selfedge_size (data units) for self-loops.
GraphMakie's automatic size is half the nearest-neighbour distance, which is oversized on typical DAG layouts; keep loops compact beside the marker.
DAGMakie.MODIFIER_EDGE_COLOR — Constant
Colour for pedagogical modifier annotations.
DAGMakie.MODIFIER_EDGE_STYLE — Constant
Line style for pedagogical modifier annotations.
DAGMakie.MODIFIER_EDGE_WIDTH — Constant
Line width for pedagogical modifier annotations.
DAGMakie.NODE_COLOR_CONFOUNDER — Constant
Highlight colour for confounders / exogenous noise / instruments.
DAGMakie.NODE_COLOR_MEDIATOR — Constant
Highlight colour for mediators or other focal nodes.
DAGMakie.NODE_COLOR_EFFECT — Constant
Fill for IDAG effect-measure nodes (same family as mediators).
DAGMakie.NODE_COLOR_SWIG_FIXED — Constant
Fill for the fixed half of a SWIG split node.
DAGMakie.TREATMENT_STROKEWIDTH — Constant
Stroke width for treatment / exposure nodes.
DAGMakie.OUTCOME_STROKEWIDTH — Constant
Stroke width for outcome nodes (emphasised outline).
Node Type Styling
DAGMakie.default_node_color — Function
default_node_color(type::NodeType)Return the default colour for a given node type.
DAGMakie.default_node_marker — Function
default_node_marker(type::NodeType)Return the default marker for a given node type.
DAGMakie.default_node_strokewidth — Function
default_node_strokewidth(type::NodeType)Return the default stroke width for a given node type.
DAGMakie.default_node_strokecolor — Function
default_node_strokecolor(type::NodeType)Return the default outline colour for a given node type.
DAGMakie.default_node_label_color — Function
default_node_label_color(type::NodeType)Return the default in-node label colour for a given node type.
DAGMakie.node_type_marker — Function
node_type_marker(type::NodeType)Return the marker shape for a given node type (delegates to default_node_marker).
DAGMakie.node_type_color — Function
node_type_color(type::NodeType)Return the default fill colour for a given node type.
DAGMakie.node_type_strokewidth — Function
node_type_strokewidth(type::NodeType)Return the stroke width for a given node type.
DAGMakie.node_type_strokecolor — Function
node_type_strokecolor(type::NodeType)Return the stroke colour for a given node type.
DAGMakie.node_type_label_color — Function
node_type_label_color(type::NodeType)Return the in-node label colour for a given node type.
DAGMakie.apply_node_type_styling — Function
apply_node_type_styling(node_types::Vector{NodeType})Generate styling arrays for a list of node types.
Arguments
node_types: Vector of NodeType for each node
Returns
- Named tuple with:
colors: Vector of fill coloursmarkers: Vector of marker shapesstrokewidths: Vector of stroke widthsstrokecolors: Vector of stroke colours
label_colors: Vector of in-node label colours
apply_node_type_styling(spec::DAGSpec)Generate styling arrays from a DAGSpec.
DAGMakie.typed_confounding_graph — Function
typed_confounding_graph()Create a confounding graph with proper node types.
Returns DAGSpec with: Confounder → Treatment → Outcome, Confounder → Outcome
DAGMakie.typed_mediation_graph — Function
typed_mediation_graph()Create a mediation graph with proper node types.
Returns DAGSpec with: Treatment → Mediator → Outcome, Treatment → Outcome
DAGMakie.typed_instrumental_graph — Function
typed_instrumental_graph()Create an instrumental variable graph with proper node types.
Returns DAGSpec with: Instrument → Treatment → Outcome, Confounder → Treatment, Confounder → Outcome
DAGMakie.typed_collider_graph — Function
typed_collider_graph()Create a collider graph with proper node types.
Returns DAGSpec with: Cause₁ → Collider ← Cause₂
Mixed Graph Operations
DAGMakie.add_directed_edge! — Function
add_directed_edge!(mg::MixedGraph, i::Int, j::Int)Add a directed edge i → j to the mixed graph.
DAGMakie.add_bidirected_edge! — Function
add_bidirected_edge!(mg::MixedGraph, i::Int, j::Int)Add a bidirected edge i ↔ j to the mixed graph.
DAGMakie.has_bidirected_edge — Function
has_bidirected_edge(mg::MixedGraph, i::Int, j::Int)Check if there is a bidirected edge between nodes i and j.
DAGMakie.bidirected_edges — Function
bidirected_edges(mg::MixedGraph)Return an iterator over all bidirected edge pairs.
DAGMakie.num_bidirected_edges — Function
num_bidirected_edges(mg::MixedGraph)Return the number of bidirected edges.
DAGMakie.compute_bidirected_path — Function
compute_bidirected_path(p1::Point2f, p2::Point2f; curvature=0.3)Compute a curved path for a bidirected edge between two points.
Returns a vector of points representing a quadratic Bézier curve that arcs above the straight line connecting the two points.
Arguments
p1: Start pointp2: End pointcurvature::Float64 = 0.3: How much the arc curves (fraction of distance)
Returns
- Vector of Point2f representing the curved path
DAGMakie.compute_all_bidirected_paths — Function
compute_all_bidirected_paths(mg::MixedGraph, positions::AbstractVector; curvature=0.3)Compute curved paths for all bidirected edges in a mixed graph.
Arguments
mg: The mixed graphpositions: Vector of node positionscurvature: Arc curvature parameter
Returns
- Vector of path vectors, one per bidirected edge
DAGMakie.bidirected_arrow_positions — Function
bidirected_arrow_positions(mg::MixedGraph, positions::AbstractVector; curvature=0.3, arrow_offset=0.1)Compute positions and rotations for arrowheads on bidirected edges.
Returns positions and rotation angles for arrows at both ends of each bidirected edge.
Arguments
mg: The mixed graphpositions: Vector of node positionscurvature: Arc curvature parameterarrow_offset: How far from the node centre to place arrows (0-0.5)
Returns
- Named tuple with:
positions: Vector of Point2f for arrow positionsrotations: Vector of Float64 rotation anglesedge_indices: Which bidirected edge each arrow belongs to
Utilities
DAGMakie.is_dag — Function
is_dag(g::AbstractGraph)Check if a directed graph is acyclic (a valid DAG).
Arguments
g: A directed graph
Returns
trueif the graph has no cycles,falseotherwise
DAGMakie.edge_list — Function
edge_list(g::AbstractGraph)Return a vector of (src, dst) tuples for all edges in the graph.
DAGMakie.adjacency_to_graph — Function
adjacency_to_graph(adj::AbstractMatrix)Create a DiGraph from an adjacency matrix.
Arguments
adj: Square adjacency matrix whereadj[i,j] != 0indicates edge i → j
Returns
SimpleDiGraphwith edges from the adjacency matrix
DAGMakie.graph_from_edges — Function
graph_from_edges(n::Int, edge_pairs::Vector{Tuple{Int, Int}})Create a DiGraph from a list of edge pairs.
Arguments
n: Number of verticesedge_pairs: Vector of (src, dst) tuples
Returns
SimpleDiGraphwith the specified edges
Example
g = graph_from_edges(3, [(1, 2), (2, 3), (1, 3)])DAGMakie.resolve_temporal_exposure_outcome — Function
resolve_temporal_exposure_outcome(exposure, outcome, n_variables) -> (Int, Int)Accept node indices (Int) or (variable, time) tuples for exposure / outcome.