{"spec_id":"network-weighted","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nnetwork-weighted: Weighted Network Graph with Edge Thickness\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 99/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito categorical palette (positions 1-4)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Trade network between countries (billions USD)\nnp.random.seed(42)\n\n# Define nodes (countries) with region groups\nnodes = {\n    \"USA\": {\"group\": 0},  # Americas\n    \"CAN\": {\"group\": 0},\n    \"MEX\": {\"group\": 0},\n    \"BRA\": {\"group\": 0},\n    \"DEU\": {\"group\": 1},  # Europe\n    \"FRA\": {\"group\": 1},\n    \"GBR\": {\"group\": 1},\n    \"ITA\": {\"group\": 1},\n    \"CHN\": {\"group\": 2},  # Asia\n    \"JPN\": {\"group\": 2},\n    \"KOR\": {\"group\": 2},\n    \"IND\": {\"group\": 2},\n    \"AUS\": {\"group\": 3},  # Oceania\n}\n\n# Define edges with trade volume weights (billions USD)\nedges = [\n    (\"USA\", \"CAN\", 650),\n    (\"USA\", \"MEX\", 580),\n    (\"USA\", \"CHN\", 520),\n    (\"USA\", \"JPN\", 180),\n    (\"USA\", \"DEU\", 200),\n    (\"USA\", \"GBR\", 130),\n    (\"USA\", \"KOR\", 140),\n    (\"USA\", \"BRA\", 80),\n    (\"CAN\", \"CHN\", 75),\n    (\"CAN\", \"MEX\", 40),\n    (\"MEX\", \"CHN\", 90),\n    (\"DEU\", \"FRA\", 170),\n    (\"DEU\", \"GBR\", 120),\n    (\"DEU\", \"ITA\", 130),\n    (\"DEU\", \"CHN\", 200),\n    (\"FRA\", \"GBR\", 90),\n    (\"FRA\", \"ITA\", 80),\n    (\"FRA\", \"CHN\", 65),\n    (\"GBR\", \"CHN\", 95),\n    (\"CHN\", \"JPN\", 280),\n    (\"CHN\", \"KOR\", 250),\n    (\"CHN\", \"AUS\", 180),\n    (\"CHN\", \"IND\", 100),\n    (\"JPN\", \"KOR\", 70),\n    (\"JPN\", \"AUS\", 55),\n    (\"IND\", \"AUS\", 30),\n    (\"BRA\", \"DEU\", 20),\n]\n\n# Force-directed layout computation\nnode_list = list(nodes.keys())\nn = len(node_list)\nnode_idx = {name: i for i, name in enumerate(node_list)}\n\n# Initialize positions randomly\npos = np.random.rand(n, 2) * 2 - 1\n\nk = 1.5 / np.sqrt(n)  # Optimal distance\nt = 0.5  # Temperature (step size)\n\nfor _ in range(300):\n    disp = np.zeros((n, 2))\n\n    # Repulsive forces between all pairs\n    for i in range(n):\n        for j in range(i + 1, n):\n            delta = pos[i] - pos[j]\n            dist = max(np.linalg.norm(delta), 0.01)\n            force = k * k / dist * 1.5\n            direction = delta / dist\n            disp[i] += direction * force\n            disp[j] -= direction * force\n\n    # Attractive forces along edges (weighted)\n    for src, tgt, weight in edges:\n        i, j = node_idx[src], node_idx[tgt]\n        delta = pos[i] - pos[j]\n        dist = max(np.linalg.norm(delta), 0.01)\n        force = dist * dist / k * (0.8 + weight / 400)\n        direction = delta / dist\n        disp[i] -= direction * force\n        disp[j] += direction * force\n\n    # Apply displacement with temperature limiting\n    for i in range(n):\n        disp_norm = max(np.linalg.norm(disp[i]), 0.01)\n        pos[i] += disp[i] / disp_norm * min(disp_norm, t)\n\n    t *= 0.97  # Cool down\n\n# Normalize positions to [-1, 1]\npos_min = pos.min(axis=0)\npos_max = pos.max(axis=0)\npos = 2 * (pos - pos_min) / (pos_max - pos_min + 0.001) - 1\npos *= 0.75\n\npositions = {name: pos[node_idx[name]] for name in node_list}\n\n# Compute weighted degree for node sizing\nweighted_degree = dict.fromkeys(nodes, 0)\nfor src, tgt, weight in edges:\n    weighted_degree[src] += weight\n    weighted_degree[tgt] += weight\n\n# Region names and group mapping\nregion_names = [\"Americas\", \"Europe\", \"Asia\", \"Oceania\"]\ngroup_colors = {i: IMPRINT[i] for i in range(4)}\n\n# Create plot\nfig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Edge width and alpha scaling\nedge_weights = [w for _, _, w in edges]\nmax_weight = max(edge_weights)\nmin_weight = min(edge_weights)\nweight_range = max_weight - min_weight\n\n# Draw edges with varying thickness\nfor src, tgt, weight in edges:\n    pos_src = positions[src]\n    pos_tgt = positions[tgt]\n    norm_w = (weight - min_weight) / weight_range if weight_range > 0 else 0.5\n    line_width = 1.5 + norm_w * 10\n    alpha = 0.3 + norm_w * 0.5\n    ax.plot(\n        [pos_src[0], pos_tgt[0]],\n        [pos_src[1], pos_tgt[1]],\n        color=INK_SOFT,\n        linewidth=line_width,\n        alpha=alpha,\n        solid_capstyle=\"round\",\n        zorder=1,\n    )\n\n# Node sizes based on weighted degree\nmax_degree = max(weighted_degree.values())\nnode_sizes = {name: 400 + (weighted_degree[name] / max_degree) * 2000 for name in nodes}\n\n# Draw nodes\nfor name, data in nodes.items():\n    color = group_colors[data[\"group\"]]\n    node_pos = positions[name]\n    ax.scatter(node_pos[0], node_pos[1], s=node_sizes[name], c=color, edgecolors=PAGE_BG, linewidths=2.5, zorder=2)\n\n# Draw node labels (above nodes)\nfor name in nodes:\n    node_pos = positions[name]\n    node_radius = np.sqrt(node_sizes[name]) / 100\n    ax.annotate(\n        name,\n        (node_pos[0], node_pos[1] + node_radius + 0.06),\n        fontsize=14,\n        fontweight=\"bold\",\n        ha=\"center\",\n        va=\"bottom\",\n        color=INK,\n        zorder=3,\n    )\n\n# Region legend\nlegend_handles = []\nfor i, region in enumerate(region_names):\n    handle = ax.scatter([], [], s=400, c=IMPRINT[i], edgecolors=PAGE_BG, linewidths=2, label=region)\n    legend_handles.append(handle)\n\nleg = ax.legend(\n    handles=legend_handles, loc=\"upper left\", fontsize=16, framealpha=0.95, title=\"Region\", title_fontsize=18\n)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n    plt.setp(leg.get_title(), color=INK_SOFT)\n\n# Edge thickness legend\nlegend_text = f\"Edge thickness: Trade volume\\n(${min_weight}B - ${max_weight}B USD)\"\nax.annotate(\n    legend_text,\n    xy=(0.02, 0.02),\n    xycoords=\"axes fraction\",\n    fontsize=14,\n    bbox={\"boxstyle\": \"round,pad=0.5\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.95},\n    verticalalignment=\"bottom\",\n    color=INK_SOFT,\n)\n\n# Style\nax.set_title(\"network-weighted · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=20)\nax.set_xlim(-1.15, 1.15)\nax.set_ylim(-1.15, 1.15)\nax.set_aspect(\"equal\")\nax.axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}