Getting Started
Sugiyama.jl only computes layouts, so plotting is left to other packages. Below we use the Makie backend CairoMakie with GraphMakie.jl for plotting, and Graphs.jl to build graphs. Any plotting package that accepts vertex positions works; see Edge routing with CausalStructures for an example with CausalStructures.jl instead:
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)