Hierarchical Network Graph with Tree Layout — plotnine

A hierarchical network graph organizes nodes in distinct levels from root to leaves, with edges showing parent-child relationships between levels. Unlike force-directed layouts that optimize for aesthetic spacing, hierarchical layouts explicitly encode tree structure by positioning nodes at fixed vertical or horizontal levels. This visualization reveals organizational depth, branching patterns, and the overall tree structure at a glance.

Hierarchical Network Graph with Tree Layout rendered with plotnine

Python source (plotnine)

""" anyplot.ai
network-hierarchical: Hierarchical Network Graph with Tree Layout
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 88/100 | Updated: 2026-05-17
"""

import os

import pandas as pd
from plotnine import (
    aes,
    element_blank,
    element_rect,
    element_text,
    geom_point,
    geom_segment,
    geom_text,
    ggplot,
    labs,
    scale_color_manual,
    scale_size_identity,
    theme,
    xlim,
    ylim,
)


# Theme tokens
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 - first series always #009E73
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data: Software company organizational hierarchy (22 employees, 4 levels)
nodes = [
    # Level 0 - CEO
    {"id": 0, "label": "CEO", "level": 0},
    # Level 1 - VPs (3 people)
    {"id": 1, "label": "VP Engineering", "level": 1},
    {"id": 2, "label": "VP Product", "level": 1},
    {"id": 3, "label": "VP Operations", "level": 1},
    # Level 2 - Directors/Managers (6 people)
    {"id": 4, "label": "Frontend Dir", "level": 2},
    {"id": 5, "label": "Backend Dir", "level": 2},
    {"id": 6, "label": "PM Lead", "level": 2},
    {"id": 7, "label": "UX Lead", "level": 2},
    {"id": 8, "label": "IT Manager", "level": 2},
    {"id": 9, "label": "HR Manager", "level": 2},
    # Level 3 - Team Members (12 people)
    {"id": 10, "label": "FE Dev 1", "level": 3},
    {"id": 11, "label": "FE Dev 2", "level": 3},
    {"id": 12, "label": "BE Dev 1", "level": 3},
    {"id": 13, "label": "BE Dev 2", "level": 3},
    {"id": 14, "label": "PM 1", "level": 3},
    {"id": 15, "label": "Designer", "level": 3},
    {"id": 16, "label": "UX Rsrch", "level": 3},
    {"id": 17, "label": "SysAdmin", "level": 3},
    {"id": 18, "label": "DevOps", "level": 3},
    {"id": 19, "label": "Recruiter", "level": 3},
    {"id": 20, "label": "Payroll", "level": 3},
    {"id": 21, "label": "Benefits", "level": 3},
]

edges = [
    # CEO to VPs
    (0, 1),
    (0, 2),
    (0, 3),
    # VP Engineering to Directors
    (1, 4),
    (1, 5),
    # VP Product to Leads
    (2, 6),
    (2, 7),
    # VP Operations to Managers
    (3, 8),
    (3, 9),
    # Directors to Team Members
    (4, 10),
    (4, 11),
    (5, 12),
    (5, 13),
    (6, 14),
    (7, 15),
    (7, 16),
    (8, 17),
    (8, 18),
    (9, 19),
    (9, 20),
    (9, 21),
]

# Compute hierarchical layout positions
levels = {}
for node in nodes:
    lvl = node["level"]
    if lvl not in levels:
        levels[lvl] = []
    levels[lvl].append(node)

positions = {}
y_spacing = 0.22
for lvl in sorted(levels.keys()):
    nodes_at_level = levels[lvl]
    n = len(nodes_at_level)
    if n > 1:
        x_positions = [0.05 + i * (0.90 / (n - 1)) for i in range(n)]
    else:
        x_positions = [0.5]
    y_pos = 0.90 - lvl * y_spacing
    for i, node in enumerate(nodes_at_level):
        positions[node["id"]] = (x_positions[i], y_pos)

# Level names for legend
level_names = {0: "Level 0: CEO", 1: "Level 1: VPs", 2: "Level 2: Directors", 3: "Level 3: Team"}

# Map level names to Okabe-Ito colors
level_colors = {
    "Level 0: CEO": IMPRINT[0],
    "Level 1: VPs": IMPRINT[1],
    "Level 2: Directors": IMPRINT[2],
    "Level 3: Team": IMPRINT[3],
}

# Node sizes by level
size_map = {0: 16, 1: 12, 2: 9, 3: 6}

# Create node dataframe
node_df = pd.DataFrame(
    {
        "x": [positions[node["id"]][0] for node in nodes],
        "y": [positions[node["id"]][1] for node in nodes],
        "label": [node["label"] for node in nodes],
        "level": [level_names[node["level"]] for node in nodes],
        "size": [size_map[node["level"]] for node in nodes],
    }
)

# Create edge dataframe
edge_data = []
for parent, child in edges:
    edge_data.append(
        {"x": positions[parent][0], "y": positions[parent][1], "xend": positions[child][0], "yend": positions[child][1]}
    )
edge_df = pd.DataFrame(edge_data)

# Create the plot
plot = (
    ggplot()
    # Draw edges first (underneath nodes)
    + geom_segment(
        data=edge_df, mapping=aes(x="x", y="y", xend="xend", yend="yend"), color=INK_SOFT, size=1.2, alpha=0.7
    )
    # Draw nodes colored by level using Okabe-Ito palette
    + geom_point(data=node_df, mapping=aes(x="x", y="y", color="level", size="size"), alpha=0.95, stroke=0.5)
    # Add node labels with offset
    + geom_text(data=node_df, mapping=aes(x="x", y="y", label="label"), size=9, va="bottom", nudge_y=0.025, color=INK)
    + scale_color_manual(values=level_colors)
    + scale_size_identity()
    + labs(title="network-hierarchical · plotnine · anyplot.ai", color="Hierarchy Level")
    + xlim(-0.02, 1.02)
    + ylim(0.18, 1.0)
    + theme(
        figure_size=(16, 9),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        plot_title=element_text(size=24, ha="center", color=INK),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
        legend_text=element_text(size=16, color=INK_SOFT),
        legend_title=element_text(size=18, color=INK),
        legend_position="bottom",
        legend_box_margin=10,
        legend_margin=5,
        legend_key=element_rect(fill=ELEVATED_BG),
        # Remove axis elements for network graph
        axis_title=element_blank(),
        axis_text=element_blank(),
        axis_ticks=element_blank(),
        panel_grid=element_blank(),
    )
)

# Save the plot
plot.save(f"plot-{THEME}.png", dpi=300)

Part of Hierarchical Network Graph with Tree Layout on anyplot.ai.

Other implementations