Weighted Network Graph with Edge Thickness — Matplotlib

A weighted network graph displays relationships between entities using edge thickness to represent connection strength or weight. Unlike basic network graphs where edges are uniform, the varying line widths immediately communicate the relative importance of each relationship. This visualization makes it easy to identify strong vs weak connections, central hubs with many heavy links, and structural patterns in weighted relational data.

Weighted Network Graph with Edge Thickness rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
network-weighted: Weighted Network Graph with Edge Thickness
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 99/100 | Updated: 2026-05-17
"""

import os

import matplotlib.pyplot as plt
import numpy as np


# 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 categorical palette (positions 1-4)
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data: Trade network between countries (billions USD)
np.random.seed(42)

# Define nodes (countries) with region groups
nodes = {
    "USA": {"group": 0},  # Americas
    "CAN": {"group": 0},
    "MEX": {"group": 0},
    "BRA": {"group": 0},
    "DEU": {"group": 1},  # Europe
    "FRA": {"group": 1},
    "GBR": {"group": 1},
    "ITA": {"group": 1},
    "CHN": {"group": 2},  # Asia
    "JPN": {"group": 2},
    "KOR": {"group": 2},
    "IND": {"group": 2},
    "AUS": {"group": 3},  # Oceania
}

# Define edges with trade volume weights (billions USD)
edges = [
    ("USA", "CAN", 650),
    ("USA", "MEX", 580),
    ("USA", "CHN", 520),
    ("USA", "JPN", 180),
    ("USA", "DEU", 200),
    ("USA", "GBR", 130),
    ("USA", "KOR", 140),
    ("USA", "BRA", 80),
    ("CAN", "CHN", 75),
    ("CAN", "MEX", 40),
    ("MEX", "CHN", 90),
    ("DEU", "FRA", 170),
    ("DEU", "GBR", 120),
    ("DEU", "ITA", 130),
    ("DEU", "CHN", 200),
    ("FRA", "GBR", 90),
    ("FRA", "ITA", 80),
    ("FRA", "CHN", 65),
    ("GBR", "CHN", 95),
    ("CHN", "JPN", 280),
    ("CHN", "KOR", 250),
    ("CHN", "AUS", 180),
    ("CHN", "IND", 100),
    ("JPN", "KOR", 70),
    ("JPN", "AUS", 55),
    ("IND", "AUS", 30),
    ("BRA", "DEU", 20),
]

# Force-directed layout computation
node_list = list(nodes.keys())
n = len(node_list)
node_idx = {name: i for i, name in enumerate(node_list)}

# Initialize positions randomly
pos = np.random.rand(n, 2) * 2 - 1

k = 1.5 / np.sqrt(n)  # Optimal distance
t = 0.5  # Temperature (step size)

for _ in range(300):
    disp = np.zeros((n, 2))

    # Repulsive forces between all pairs
    for i in range(n):
        for j in range(i + 1, n):
            delta = pos[i] - pos[j]
            dist = max(np.linalg.norm(delta), 0.01)
            force = k * k / dist * 1.5
            direction = delta / dist
            disp[i] += direction * force
            disp[j] -= direction * force

    # Attractive forces along edges (weighted)
    for src, tgt, weight in edges:
        i, j = node_idx[src], node_idx[tgt]
        delta = pos[i] - pos[j]
        dist = max(np.linalg.norm(delta), 0.01)
        force = dist * dist / k * (0.8 + weight / 400)
        direction = delta / dist
        disp[i] -= direction * force
        disp[j] += direction * force

    # Apply displacement with temperature limiting
    for i in range(n):
        disp_norm = max(np.linalg.norm(disp[i]), 0.01)
        pos[i] += disp[i] / disp_norm * min(disp_norm, t)

    t *= 0.97  # Cool down

# Normalize positions to [-1, 1]
pos_min = pos.min(axis=0)
pos_max = pos.max(axis=0)
pos = 2 * (pos - pos_min) / (pos_max - pos_min + 0.001) - 1
pos *= 0.75

positions = {name: pos[node_idx[name]] for name in node_list}

# Compute weighted degree for node sizing
weighted_degree = dict.fromkeys(nodes, 0)
for src, tgt, weight in edges:
    weighted_degree[src] += weight
    weighted_degree[tgt] += weight

# Region names and group mapping
region_names = ["Americas", "Europe", "Asia", "Oceania"]
group_colors = {i: IMPRINT[i] for i in range(4)}

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

# Edge width and alpha scaling
edge_weights = [w for _, _, w in edges]
max_weight = max(edge_weights)
min_weight = min(edge_weights)
weight_range = max_weight - min_weight

# Draw edges with varying thickness
for src, tgt, weight in edges:
    pos_src = positions[src]
    pos_tgt = positions[tgt]
    norm_w = (weight - min_weight) / weight_range if weight_range > 0 else 0.5
    line_width = 1.5 + norm_w * 10
    alpha = 0.3 + norm_w * 0.5
    ax.plot(
        [pos_src[0], pos_tgt[0]],
        [pos_src[1], pos_tgt[1]],
        color=INK_SOFT,
        linewidth=line_width,
        alpha=alpha,
        solid_capstyle="round",
        zorder=1,
    )

# Node sizes based on weighted degree
max_degree = max(weighted_degree.values())
node_sizes = {name: 400 + (weighted_degree[name] / max_degree) * 2000 for name in nodes}

# Draw nodes
for name, data in nodes.items():
    color = group_colors[data["group"]]
    node_pos = positions[name]
    ax.scatter(node_pos[0], node_pos[1], s=node_sizes[name], c=color, edgecolors=PAGE_BG, linewidths=2.5, zorder=2)

# Draw node labels (above nodes)
for name in nodes:
    node_pos = positions[name]
    node_radius = np.sqrt(node_sizes[name]) / 100
    ax.annotate(
        name,
        (node_pos[0], node_pos[1] + node_radius + 0.06),
        fontsize=14,
        fontweight="bold",
        ha="center",
        va="bottom",
        color=INK,
        zorder=3,
    )

# Region legend
legend_handles = []
for i, region in enumerate(region_names):
    handle = ax.scatter([], [], s=400, c=IMPRINT[i], edgecolors=PAGE_BG, linewidths=2, label=region)
    legend_handles.append(handle)

leg = ax.legend(
    handles=legend_handles, loc="upper left", fontsize=16, framealpha=0.95, title="Region", title_fontsize=18
)
if leg:
    leg.get_frame().set_facecolor(ELEVATED_BG)
    leg.get_frame().set_edgecolor(INK_SOFT)
    plt.setp(leg.get_texts(), color=INK_SOFT)
    plt.setp(leg.get_title(), color=INK_SOFT)

# Edge thickness legend
legend_text = f"Edge thickness: Trade volume\n(${min_weight}B - ${max_weight}B USD)"
ax.annotate(
    legend_text,
    xy=(0.02, 0.02),
    xycoords="axes fraction",
    fontsize=14,
    bbox={"boxstyle": "round,pad=0.5", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.95},
    verticalalignment="bottom",
    color=INK_SOFT,
)

# Style
ax.set_title("network-weighted · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK, pad=20)
ax.set_xlim(-1.15, 1.15)
ax.set_ylim(-1.15, 1.15)
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 Weighted Network Graph with Edge Thickness on anyplot.ai.

Other implementations