Directed Network Graph — Makie.jl

A directed network graph visualizes relationships between entities using nodes connected by edges with arrows, indicating the direction of relationships or flow. Unlike undirected graphs, directed graphs reveal asymmetric relationships such as dependencies, hierarchies, or information flow. The arrows clearly communicate which entity points to which, making cause-and-effect relationships and directional dependencies immediately visible.

Directed Network Graph rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# network-directed: Directed Network Graph
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 91/100 | Created: 2026-09-05

using CairoMakie
using Colors
using Random

Random.seed!(42)

# --- Theme tokens ------------------------------------------------------------
THEME      = get(ENV, "ANYPLOT_THEME", "light")
PAGE_BG    = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
INK        = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
INK_SOFT   = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
EDGE_COLOR = RGBAf(INK_SOFT.r, INK_SOFT.g, INK_SOFT.b, 0.55)
IMPRINT_PALETTE = [
    colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
    colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314",
]

# --- Data: a software package dependency graph --------------------------------
# Arrows point from a consumer to what it depends on / imports, exactly the
# "import direction" application called out in the specification.
nodes = [
    "webapp", "cli",
    "api-client", "auth", "renderer",
    "http", "config", "crypto", "cache", "svg-utils",
    "json",
    "logging",
]

edges = [
    ("webapp", "api-client"), ("webapp", "auth"), ("webapp", "renderer"),
    ("cli", "api-client"), ("cli", "auth"), ("cli", "logging"),
    ("api-client", "http"), ("api-client", "config"),
    ("auth", "crypto"), ("auth", "config"), ("auth", "cache"),
    ("renderer", "svg-utils"), ("renderer", "config"),
    ("http", "logging"), ("crypto", "logging"), ("cache", "logging"),
    ("svg-utils", "json"), ("config", "logging"), ("json", "logging"),
]

# --- Hierarchical layout ------------------------------------------------------
# NetworkLayout.jl is not part of this catalog's Julia environment, so the
# layer assignment is computed directly: each node's layer is the length of
# the longest dependency chain reaching it, found by relaxing edges to a
# fixpoint (a tiny Bellman-Ford variant — the dependency graph is a DAG, so
# this always converges). Nodes with no incoming edges anchor layer 0.
layer = Dict(n => 0 for n in nodes)
changed = true
while changed
    global changed = false
    for (src, dst) in edges
        if layer[dst] < layer[src] + 1
            layer[dst] = layer[src] + 1
            global changed = true
        end
    end
end

n_layers = maximum(values(layer)) + 1
layer_nodes = [String[] for _ in 1:n_layers]
for n in nodes
    push!(layer_nodes[layer[n]+1], n)
end

indegree = Dict(n => 0 for n in nodes)
for (_, dst) in edges
    indegree[dst] += 1
end

# Barycenter crossing-minimization: repeatedly reorder each layer by the mean
# position of its neighbors, alternating downward/upward sweeps (Sugiyama-style).
# This is what pulls "auth"/"renderer" and their fan-out into straighter columns
# instead of the crossing tangle the review flagged.
neighbors = Dict(n => String[] for n in nodes)
for (src, dst) in edges
    push!(neighbors[src], dst)
    push!(neighbors[dst], src)
end

order_x = Dict{String,Float64}(n => Float64(j) for ns in layer_nodes for (j, n) in enumerate(ns))
for iter in 1:6
    layer_order = isodd(iter) ? (1:n_layers) : reverse(1:n_layers)
    for li in layer_order
        ns = layer_nodes[li]
        length(ns) <= 1 && continue
        bary = Dict(n => begin
            xs = [order_x[m] for m in neighbors[n]]
            isempty(xs) ? order_x[n] : sum(xs) / length(xs)
        end for n in ns)
        sort!(ns, by=n -> bary[n])
        for (j, n) in enumerate(ns)
            order_x[n] = Float64(j)
        end
    end
end

layer_spacing = 2.4
node_spacing = 2.2
pos = Dict{String,Point2f}()
for (li, ns) in enumerate(layer_nodes)
    k = length(ns)
    y = (n_layers - li) * layer_spacing
    for (j, n) in enumerate(ns)
        x = (j - (k + 1) / 2) * node_spacing
        pos[n] = Point2f(x, y)
    end
end

marker_size(n) = 26.0f0 + 6.0f0 * indegree[n]
node_radius(n) = 0.16 + 0.006 * marker_size(n)  # data-space clearance so arrows stop at the node edge

tier_labels = ["Applications", "Services", "Infrastructure/utilities", "Data format", "Core"]
node_color(n) = IMPRINT_PALETTE[min(layer[n] + 1, length(IMPRINT_PALETTE))]

# --- Plot ----------------------------------------------------------------------
fig = Figure(
    size=(1200, 1200),
    fontsize=14,
    backgroundcolor=PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title="network-directed · julia · makie · anyplot.ai",
    titlesize=20,
    titlecolor=INK,
    backgroundcolor=PAGE_BG,
    aspect=DataAspect(),
)
hidedecorations!(ax)
hidespines!(ax)
limits!(ax, -5.2, 5.2, -1.0, 10.2)

# Move `from` toward `to` by clearance `r` (data units) — keeps arrow shafts
# and heads from disappearing under the node markers they connect.
function pull_in(from::Point2f, to::Point2f, r)
    d = to - from
    u = d / hypot(d[1], d[2])
    from + u * Float32(r)
end

function draw_arrow!(ax, src::String, dst::String; waypoint::Union{Point2f,Nothing}=nothing)
    if waypoint === nothing
        tail = pull_in(pos[src], pos[dst], node_radius(src))
    else
        tail = waypoint
        start = pull_in(pos[src], waypoint, node_radius(src))
        lines!(ax, [start, waypoint]; color=EDGE_COLOR, linewidth=2.0)
    end
    head = pull_in(pos[dst], tail, node_radius(dst))
    arrows!(ax, [tail], [head - tail]; color=EDGE_COLOR, linewidth=2.0, arrowsize=15)
end

# The one long-range dependency (cli → logging) is routed around the middle
# tiers with a dog-leg instead of a straight line, so it doesn't cut through
# unrelated nodes — the "curved edges to avoid overlap" case from the spec.
skip_edge = ("cli", "logging")
waypoint = Point2f(5.0, 4.8)

for (src, dst) in edges
    if (src, dst) == skip_edge
        draw_arrow!(ax, src, dst; waypoint=waypoint)
    else
        draw_arrow!(ax, src, dst)
    end
end

for n in nodes
    scatter!(ax, [pos[n]]; color=node_color(n), markersize=marker_size(n), strokewidth=0)
end

# Labels sit below each node rather than inside it — several ids ("api-client",
# "svg-utils") are wider than even the largest marker and would get clipped.
for n in nodes
    label_pos = pos[n] - Point2f(0, node_radius(n) + 0.16)
    text!(ax, label_pos; text=n, color=INK, fontsize=13, align=(:center, :top))
end

legend_elements = [MarkerElement(color=IMPRINT_PALETTE[i], marker=:circle, markersize=14) for i in 1:n_layers]
Legend(fig[2, 1], legend_elements, tier_labels[1:n_layers];
    orientation=:horizontal, framevisible=false, labelcolor=INK, nbanks=2)
rowgap!(fig.layout, 0)
rowsize!(fig.layout, 2, Auto(0.05))

# --- Save ------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit=2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/network-directed/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "network-directed",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/network-directed/julia/makie",
  "hub": "https://anyplot.ai/network-directed",
  "code_json": "https://api.anyplot.ai/specs/network-directed/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/network-directed",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/network-directed/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/network-directed/julia/makie/plot-dark.png",
  "quality_score": 91.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Directed Network Graph on anyplot.ai.

Other implementations