Basic Network Graph — Makie.jl

A network graph (node-link diagram) visualizes relationships between entities as nodes connected by edges. It reveals the structure of connections, clusters, and central nodes in relational data. Network graphs are essential for understanding complex systems where relationships matter as much as the entities themselves, making hidden patterns of connectivity visible at a glance.

Basic Network Graph rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# network-basic: Basic Network Graph
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 95/100 | Created: 2026-07-24

using CairoMakie
using Colors
using Random

Random.seed!(42)

# --- Theme tokens -----------------------------------------------------------
const THEME       = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

# Imprint categorical palette (first 4 positions — module layers)
const IMPRINT_PALETTE = [
    colorant"#009E73",  # 1 — Core
    colorant"#C475FD",  # 2 — Data
    colorant"#4467A3",  # 3 — API
    colorant"#BD8233",  # 4 — UI
]
const LAYER_NAMES = ["Core", "Data", "API", "UI"]

# --- Data: software module dependency graph ---------------------------------
module_names = [
    "Auth", "Config", "Logger", "Cache", "EventBus",
    "UserRepo", "OrderRepo", "PaymentRepo", "Schema", "Migrations",
    "UserAPI", "OrderAPI", "PaymentAPI", "SearchAPI", "NotifyAPI", "Gateway",
    "Dashboard", "Checkout", "Profile", "Admin", "Reports", "MobileApp",
]
layer = [
    1, 1, 1, 1, 1,
    2, 2, 2, 2, 2,
    3, 3, 3, 3, 3, 3,
    4, 4, 4, 4, 4, 4,
]
n = length(module_names)

# Third element is coupling strength: 1=light, 2=normal, 3=critical dependency
edges = [
    # Core internals
    (1, 2, 2), (1, 3, 2), (2, 4, 1), (1, 5, 2),
    # Data depends on Core
    (6, 1, 3), (6, 4, 1), (7, 1, 2), (8, 1, 3), (9, 4, 1), (10, 9, 2),
    # API depends on Data + Core
    (11, 6, 3), (11, 1, 2), (12, 7, 3), (13, 8, 3), (14, 6, 2), (14, 7, 1), (15, 5, 2),
    (16, 11, 2), (16, 12, 2), (16, 13, 2),
    # UI depends on API
    (17, 16, 3), (17, 11, 1), (18, 12, 3), (18, 13, 3), (19, 11, 2),
    (20, 16, 2), (21, 14, 1), (21, 12, 2), (22, 16, 2),
]

degrees = zeros(Int, n)
for (a, b, _) in edges
    degrees[a] += 1
    degrees[b] += 1
end

# --- Force-directed layout (Fruchterman-Reingold, hand-rolled — no
#     NetworkLayout.jl, which is not installed in the CI runtime) -----------
angles = range(0, 2π; length = n + 1)[1:n] .+ randn(n) .* 0.15
radius = 0.4
positions = hcat(0.5 .+ radius .* cos.(angles), 0.5 .+ radius .* sin.(angles))

k = sqrt(1.0 / n)
iterations = 200
for iter in 1:iterations
    disp = zeros(n, 2)
    for i in 1:n, j in 1:n
        i == j && continue
        dx, dy = positions[i, 1] - positions[j, 1], positions[i, 2] - positions[j, 2]
        dist = max(hypot(dx, dy), 0.01)
        force = k^2 / dist
        disp[i, 1] += (dx / dist) * force
        disp[i, 2] += (dy / dist) * force
    end
    for (a, b, _) in edges
        dx, dy = positions[a, 1] - positions[b, 1], positions[a, 2] - positions[b, 2]
        dist = max(hypot(dx, dy), 0.01)
        force = dist^2 / k
        disp[a, 1] -= (dx / dist) * force
        disp[a, 2] -= (dy / dist) * force
        disp[b, 1] += (dx / dist) * force
        disp[b, 2] += (dy / dist) * force
    end
    temperature = 0.1 * (1 - iter / iterations)
    for i in 1:n
        dn = hypot(disp[i, 1], disp[i, 2])
        if dn > 0
            step = min(dn, temperature) / dn
            positions[i, 1] += disp[i, 1] * step
            positions[i, 2] += disp[i, 2] * step
        end
    end
end

pos_min = vec(minimum(positions; dims = 1))
pos_max = vec(maximum(positions; dims = 1))
positions = (positions .- pos_min') ./ (pos_max' .- pos_min') .* 0.86 .+ 0.07

# --- Plot ---------------------------------------------------------------
title_text = "Software Module Dependencies · network-basic · julia · makie · anyplot.ai"

fig = Figure(
    resolution      = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title            = title_text,
    titlesize        = 18,
    titlecolor       = INK,
    backgroundcolor  = PAGE_BG,
)
hidedecorations!(ax)
hidespines!(ax)
xlims!(ax, -0.05, 1.05)
ylims!(ax, -0.11, 1.05)

node_colors = [IMPRINT_PALETTE[layer[i]] for i in 1:n]
node_sizes = [26.0 + degrees[i] * 5.0 for i in 1:n]

# Edges drawn per-segment so linewidth/alpha can be keyed to coupling strength
# (weight 1=light, 2=normal, 3=critical) — a Makie-distinctive per-edge encoding
# rather than a single batched style for every dependency.
edge_points = Vector{Point2f}(undef, 2 * length(edges))
edge_widths = Vector{Float32}(undef, 2 * length(edges))
edge_colors = Vector{RGBAf}(undef, 2 * length(edges))
for (idx, (a, b, w)) in enumerate(edges)
    edge_points[2idx - 1] = Point2f(positions[a, 1], positions[a, 2])
    edge_points[2idx]     = Point2f(positions[b, 1], positions[b, 2])
    width = 1.2 + w * 0.75
    alpha = 0.28 + w * 0.11
    edge_widths[2idx - 1] = width
    edge_widths[2idx]     = width
    edge_colors[2idx - 1] = RGBAf(INK_SOFT.r, INK_SOFT.g, INK_SOFT.b, alpha)
    edge_colors[2idx]     = RGBAf(INK_SOFT.r, INK_SOFT.g, INK_SOFT.b, alpha)
end
linesegments!(ax, edge_points; color = edge_colors, linewidth = edge_widths)

# Small arrowhead per edge, pulled back from the target node's rim, so the
# undirected line segments above read as directed "depends on" relationships.
px_per_data_unit = 1150.0
arrow_positions = Vector{Point2f}(undef, length(edges))
arrow_rotations = Vector{Float32}(undef, length(edges))
for (idx, (a, b, _)) in enumerate(edges)
    pa, pb = positions[a, :], positions[b, :]
    dvec = pb .- pa
    dist = max(hypot(dvec[1], dvec[2]), 1e-6)
    dir = dvec ./ dist
    pullback = node_sizes[b] / 2 / px_per_data_unit + 0.012
    arrow_positions[idx] = Point2f((pb .- dir .* pullback)...)
    arrow_rotations[idx] = atan(dir[2], dir[1]) - Float32(pi / 2)
end
scatter!(
    ax, arrow_positions;
    marker      = :utriangle,
    markersize  = 10,
    rotation    = arrow_rotations,
    color       = RGBAf(INK_SOFT.r, INK_SOFT.g, INK_SOFT.b, 0.7),
    strokewidth = 0,
)

scatter!(
    ax, positions[:, 1], positions[:, 2];
    color       = node_colors,
    markersize  = node_sizes,
    strokecolor = PAGE_BG,
    strokewidth = 2.5,
)

# Emphasize the top hub module(s) with an outer ring beyond size alone, giving
# the layout a clear architectural-bottleneck focal point.
max_degree = maximum(degrees)
hub_indices = findall(==(max_degree), degrees)
length(hub_indices) > 2 && (hub_indices = hub_indices[1:2])
scatter!(
    ax, positions[hub_indices, 1], positions[hub_indices, 2];
    marker      = :circle,
    markersize  = [node_sizes[i] + 16.0 for i in hub_indices],
    color       = :transparent,
    strokecolor = INK,
    strokewidth = 2.2,
)

hub_set = Set(hub_indices)
label_offsets = [(0.0f0, -(node_sizes[i] / 2 + (i in hub_set ? 8.0 : 0.0) + 9)) for i in 1:n]
text!(
    ax, positions[:, 1], positions[:, 2];
    text      = module_names,
    align     = (:center, :top),
    fontsize  = 12,
    color     = INK,
    offset    = label_offsets,
)

for (i, name) in enumerate(LAYER_NAMES)
    scatter!(ax, [NaN], [NaN]; color = IMPRINT_PALETTE[i], markersize = 20, label = name)
end
axislegend(ax, "Layer"; position = :lt, backgroundcolor = ELEVATED_BG, labelcolor = INK_SOFT, titlecolor = INK)

text!(
    ax, 0.5, -0.065;
    text     = "Edge width/opacity ∝ coupling strength · arrows point toward the depended-upon module",
    align    = (:center, :top),
    fontsize = 11,
    color    = INK_SOFT,
)

# --- 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-basic/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-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/network-basic/julia/makie",
  "hub": "https://anyplot.ai/network-basic",
  "code_json": "https://api.anyplot.ai/specs/network-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/network-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/network-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/network-basic/julia/makie/plot-dark.png",
  "quality_score": 95.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Network Graph on anyplot.ai.

Other implementations