Directed Network Graph — Matplotlib

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 Matplotlib

Python source (Matplotlib)

""" anyplot.ai
network-directed: Directed Network Graph
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 91/100 | Updated: 2026-05-14
"""

import os

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle, FancyArrowPatch


THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Okabe-Ito palette for group colors (first 4 positions)
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data: Software package dependencies (arrows show import direction)
np.random.seed(42)

# Define modules with groups (core, utils, api, tests)
nodes = {
    "main": {"group": "core", "pos": (0.45, 0.88)},
    "config": {"group": "core", "pos": (0.18, 0.68)},
    "database": {"group": "core", "pos": (0.45, 0.55)},
    "auth": {"group": "api", "pos": (0.72, 0.73)},
    "routes": {"group": "api", "pos": (0.88, 0.52)},
    "handlers": {"group": "api", "pos": (0.68, 0.32)},
    "validators": {"group": "utils", "pos": (0.32, 0.38)},
    "helpers": {"group": "utils", "pos": (0.48, 0.15)},
    "logger": {"group": "utils", "pos": (0.12, 0.38)},
    "cache": {"group": "utils", "pos": (0.05, 0.58)},
    "test_auth": {"group": "tests", "pos": (0.92, 0.88)},
    "test_routes": {"group": "tests", "pos": (0.95, 0.32)},
    "test_db": {"group": "tests", "pos": (0.25, 0.12)},
}

# Define dependencies (arrow from source to target means source imports target)
edges = [
    ("main", "config"),
    ("main", "database"),
    ("main", "routes"),
    ("main", "logger"),
    ("config", "validators"),
    ("database", "logger"),
    ("database", "config"),
    ("auth", "database"),
    ("auth", "validators"),
    ("auth", "logger"),
    ("routes", "auth"),
    ("routes", "handlers"),
    ("routes", "validators"),
    ("handlers", "database"),
    ("handlers", "helpers"),
    ("handlers", "logger"),
    ("validators", "helpers"),
    ("cache", "logger"),
    ("cache", "config"),
    ("test_auth", "auth"),
    ("test_routes", "routes"),
    ("test_db", "database"),
]

# Group colors (Okabe-Ito)
group_colors = {"core": IMPRINT[0], "api": IMPRINT[1], "utils": IMPRINT[2], "tests": IMPRINT[3]}

# Plot
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)

# Node radius for arrow endpoint calculations
node_radius = 0.055

# Draw edges with arrows
edge_color = INK_SOFT if THEME == "light" else "#999999"
for source, target in edges:
    pos1 = nodes[source]["pos"]
    pos2 = nodes[target]["pos"]

    # Calculate edge start/end points outside node circles
    dx = pos2[0] - pos1[0]
    dy = pos2[1] - pos1[1]
    dist = np.sqrt(dx**2 + dy**2)
    dx_norm, dy_norm = dx / dist, dy / dist
    start = (pos1[0] + dx_norm * node_radius, pos1[1] + dy_norm * node_radius)
    end = (pos2[0] - dx_norm * node_radius * 1.6, pos2[1] - dy_norm * node_radius * 1.6)

    # Create curved arrow
    arrow = FancyArrowPatch(
        start,
        end,
        arrowstyle="-|>",
        mutation_scale=25,
        color=edge_color,
        linewidth=2.5,
        alpha=0.6,
        connectionstyle="arc3,rad=0.12",
    )
    ax.add_patch(arrow)

# Draw nodes
for name, props in nodes.items():
    pos = props["pos"]
    color = group_colors[props["group"]]

    # Draw node circle
    circle = Circle(pos, radius=node_radius, facecolor=color, edgecolor=INK_SOFT, linewidth=2.5, alpha=0.9, zorder=10)
    ax.add_patch(circle)

    # Draw label
    ax.text(pos[0], pos[1], name, ha="center", va="center", fontsize=13, fontweight="bold", color=INK, zorder=11)

# Legend
legend_handles = [
    mpatches.Patch(color=color, label=group.capitalize(), alpha=0.9) for group, color in group_colors.items()
]
leg = ax.legend(
    handles=legend_handles, loc="upper left", fontsize=16, framealpha=0.95, title="Module Type", title_fontsize=18
)
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
for text in leg.get_texts():
    text.set_color(INK_SOFT)

# Style
ax.set_title("network-directed · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK, pad=20)
ax.set_xlim(-0.02, 1.02)
ax.set_ylim(0.0, 1.0)
ax.set_aspect("equal")
ax.axis("off")

plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)

Part of Directed Network Graph on anyplot.ai.

Other implementations