Getting Started
Plotting requires loading a Makie backend and GraphMakie.jl. Below we use CairoMakie, and Graphs.jl to build graphs:
using Sugiyama
using Graphs
using CairoMakie
using GraphMakieSugiyamaLayout follows the same "callable struct" convention as NetworkLayout.jl, so it can be passed directly as the layout keyword to graphplot.
Laying out an adjacency matrix
sugiyama (and the SugiyamaLayout callable) take the adjacency matrix of a directed graph and return one coordinate per vertex, as a Vector{Point{2,Float64}}:
adj = [0 1 1 0;
0 0 0 1;
0 0 0 1;
0 0 0 0]
positions = sugiyama(adj)4-element Vector{GeometryBasics.Point{2, Float64}}:
[0.0, 1.0]
[2.0, 0.0]
[2.0, 2.0]
[4.0, 1.0]Vertex i of adj corresponds to positions[i]. Here vertex 1 has two children (2 and 3), which both point to vertex 4, so vertices 2 and 3 land on the same rank, side by side, above vertex 4:
g = SimpleDiGraph(adj)
graphplot(g; layout = SugiyamaLayout(), ilabels = repr.(1:nv(g)))Using a Graphs.jl graph
An AbstractGraph can be passed directly instead of an adjacency matrix, to sugiyama/SugiyamaLayout as well as to graphplot:
sugiyama(g)4-element Vector{GeometryBasics.Point{2, Float64}}:
[0.0, 1.0]
[2.0, 0.0]
[2.0, 2.0]
[4.0, 1.0]Customizing the layout
Both sugiyama and SugiyamaLayout accept keyword arguments to control node spacing, direction, and the ranking/crossing-minimization heuristics used internally. For example, laying out top-to-bottom instead of left-to-right:
graphplot(g; layout = SugiyamaLayout(; direction = :down), ilabels = repr.(1:nv(g)))See the Reference for the full list of keyword arguments.
Cycles
Cycles are broken internally (by implicitly reversing edges) so that any directed graph can be laid out, not just DAGs:
cyclic = SimpleDiGraph(3)
add_edge!(cyclic, 1, 2)
add_edge!(cyclic, 2, 3)
add_edge!(cyclic, 3, 1)
graphplot(cyclic; layout = SugiyamaLayout(), ilabels = repr.(1:nv(cyclic)))Disconnected graphs
Weakly connected components are laid out independently and placed side by side:
disconnected = SimpleDiGraph(4)
add_edge!(disconnected, 1, 2)
add_edge!(disconnected, 3, 4)
graphplot(disconnected; layout = SugiyamaLayout(), ilabels = repr.(1:nv(disconnected)))Edge routing with CausalStructures
using CausalStructuresHere we use CausalStructures.jl, for generating and plotting a DAG:
dag = DAG("A --> X, A --> B, X --> Y, B --> Y, A --> Y")
ns = nodes(dag)
node_index = Dict(n => i for (i, n) in enumerate(ns))
adj = zeros(Int, length(ns), length(ns))
for e in CausalStructures.edges(dag)
adj[node_index[e.src], node_index[e.dst]] = 1
end
positions = sugiyama(adj)
plot(dag; layout = positions)CausalStructures does automatic edge routing via Bezier curves if needed, but instead of falling back to that, we can use sugiyama_paths for the routing sugiyama uses. Alongside the positions, it returns edge_paths, a Dict mapping each (i, j) edge to the polyline it should be drawn as: positions[i], then one bend point per rank the edge spans, then positions[j]:
positions, edge_paths = sugiyama_paths(adj)
edge_paths[(node_index[:A], node_index[:Y])]3-element Vector{GeometryBasics.Point{2, Float64}}:
[0.0, 2.0]
[2.0, 3.5]
[4.0, 2.0]plot accepts this directly as its own edge_paths keyword (keyed by node name rather than index here):
name_of = Dict(i => n for (n, i) in node_index)
named_paths = Dict((name_of[i], name_of[j]) => path for ((i, j), path) in edge_paths)
plot(dag; layout = positions, edge_paths = named_paths)