{"spec_id":"network-weighted","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nnetwork-weighted: Weighted Network Graph with Edge Thickness\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (see prompts/default-style-guide.md)\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Data - Trade network between countries (billions USD)\nnp.random.seed(42)\n\n# Define nodes (countries)\ncountries = [\n    \"USA\",\n    \"China\",\n    \"Germany\",\n    \"Japan\",\n    \"UK\",\n    \"France\",\n    \"Canada\",\n    \"Mexico\",\n    \"Brazil\",\n    \"India\",\n    \"Australia\",\n    \"S. Korea\",\n    \"Netherlands\",\n    \"Italy\",\n    \"Spain\",\n]\nn_nodes = len(countries)\nnode_idx = {name: i for i, name in enumerate(countries)}\n\n# Create weighted edges (trade relationships)\nedges = [\n    # Major trade routes (high weight)\n    (\"USA\", \"China\", 580),\n    (\"USA\", \"Canada\", 620),\n    (\"USA\", \"Mexico\", 550),\n    (\"China\", \"Japan\", 320),\n    (\"China\", \"S. Korea\", 280),\n    (\"China\", \"Germany\", 190),\n    (\"Germany\", \"France\", 180),\n    (\"Germany\", \"Netherlands\", 210),\n    (\"Germany\", \"Italy\", 140),\n    (\"UK\", \"Germany\", 130),\n    (\"UK\", \"USA\", 140),\n    (\"UK\", \"Netherlands\", 90),\n    (\"Japan\", \"USA\", 200),\n    (\"Japan\", \"S. Korea\", 85),\n    # Medium trade routes\n    (\"France\", \"Italy\", 95),\n    (\"France\", \"Spain\", 110),\n    (\"Spain\", \"Italy\", 50),\n    (\"Canada\", \"Mexico\", 40),\n    (\"Brazil\", \"USA\", 75),\n    (\"Brazil\", \"China\", 100),\n    (\"India\", \"USA\", 90),\n    (\"India\", \"China\", 115),\n    (\"India\", \"UK\", 35),\n    (\"Australia\", \"China\", 145),\n    (\"Australia\", \"Japan\", 55),\n    (\"Australia\", \"S. Korea\", 45),\n    # Lower trade routes\n    (\"Netherlands\", \"UK\", 65),\n    (\"S. Korea\", \"USA\", 120),\n    (\"Mexico\", \"China\", 70),\n]\n\n# Compute force-directed layout (Fruchterman-Reingold algorithm)\npos = np.random.rand(n_nodes, 2) * 2 - 1\nk = 0.5\nfor _ in range(200):\n    displacement = np.zeros((n_nodes, 2))\n    # Repulsive forces\n    for i in range(n_nodes):\n        diff = pos[i] - pos\n        dist = np.sqrt((diff**2).sum(axis=1))\n        dist = np.where(dist < 0.01, 0.01, dist)\n        rep_force = k**2 / dist\n        rep_force[i] = 0\n        displacement[i] += (diff * rep_force[:, np.newaxis]).sum(axis=0)\n    # Attractive forces along edges\n    for source, target, weight in edges:\n        i, j = node_idx[source], node_idx[target]\n        diff = pos[j] - pos[i]\n        dist = np.sqrt((diff**2).sum())\n        if dist > 0.01:\n            attr_force = dist**2 / k * (1 + weight / 200)\n            displacement[i] += diff / dist * attr_force\n            displacement[j] -= diff / dist * attr_force\n    # Update positions\n    length = np.sqrt((displacement**2).sum(axis=1))\n    length = np.where(length < 0.01, 0.01, length)\n    pos += displacement / length[:, np.newaxis] * min(0.1, k)\n\n# Normalize positions\npos = (pos - pos.min(axis=0)) / (pos.max(axis=0) - pos.min(axis=0))\npos = pos * 1.6 - 0.8\npos = pos - pos.mean(axis=0)\nnode_positions = {countries[i]: pos[i] for i in range(n_nodes)}\n\n# Calculate weighted degree for node sizing\nweighted_degree = dict.fromkeys(countries, 0)\nfor source, target, weight in edges:\n    weighted_degree[source] += weight\n    weighted_degree[target] += weight\n\nnode_sizes = [20 + (weighted_degree[node] / 40) for node in countries]\n\n# Create edge traces with varying thickness\nedge_traces = []\nmin_weight = min(w for _, _, w in edges)\nmax_weight = max(w for _, _, w in edges)\n\nfor source, target, weight in edges:\n    x0, y0 = node_positions[source]\n    x1, y1 = node_positions[target]\n    # Scale width: 2 to 18 based on weight\n    normalized = (weight - min_weight) / (max_weight - min_weight)\n    line_width = 2 + normalized * 16\n    # Edge color from Okabe-Ito palette (use BRAND with alpha for weight-based opacity)\n    alpha = 0.4 + normalized * 0.5\n    # Parse BRAND hex and create rgba\n    edge_color = f\"rgba(0, 158, 115, {alpha})\"\n    edge_traces.append(\n        go.Scatter(\n            x=[x0, x1, None],\n            y=[y0, y1, None],\n            mode=\"lines\",\n            line={\"width\": line_width, \"color\": edge_color},\n            hoverinfo=\"text\",\n            text=f\"{source} ↔ {target}: ${weight}B\",\n            showlegend=False,\n        )\n    )\n\n# Create node trace\nnode_x = [node_positions[node][0] for node in countries]\nnode_y = [node_positions[node][1] for node in countries]\n\n# Calculate smart label positions to avoid overlap\nlabel_positions = []\n\nfor i, node in enumerate(countries):\n    x, y = node_positions[node]\n    # Find nearby nodes and adjust position\n    nearby_above = 0\n    nearby_below = 0\n    nearby_left = 0\n    nearby_right = 0\n    for j, other in enumerate(countries):\n        if i != j:\n            ox, oy = node_positions[other]\n            dx, dy = x - ox, y - oy\n            dist = np.sqrt(dx**2 + dy**2)\n            if dist < 0.35:\n                if dy > 0:\n                    nearby_below += 1\n                else:\n                    nearby_above += 1\n                if dx > 0:\n                    nearby_left += 1\n                else:\n                    nearby_right += 1\n\n    # Handle specific known close pairs to avoid overlap\n    if node == \"Japan\":\n        pos_choice = \"top right\"\n    elif node == \"S. Korea\":\n        pos_choice = \"bottom left\"\n    elif node == \"Italy\":\n        pos_choice = \"top left\"\n    elif node == \"France\":\n        pos_choice = \"bottom right\"\n    elif nearby_above > nearby_below:\n        pos_choice = \"bottom center\"\n    elif nearby_left > nearby_right:\n        pos_choice = \"middle right\"\n    elif nearby_right > nearby_left:\n        pos_choice = \"middle left\"\n    else:\n        pos_choice = \"top center\"\n    label_positions.append(pos_choice)\n\nnode_trace = go.Scatter(\n    x=node_x,\n    y=node_y,\n    mode=\"markers+text\",\n    marker={\"size\": node_sizes, \"color\": BRAND, \"line\": {\"width\": 2, \"color\": INK_SOFT}},\n    text=countries,\n    textposition=label_positions,\n    textfont={\"size\": 16, \"color\": INK},\n    hoverinfo=\"text\",\n    hovertext=[f\"{c}<br>Trade Volume: ${weighted_degree[c]}B\" for c in countries],\n    showlegend=False,\n)\n\n# Create figure\nfig = go.Figure()\n\n# Add edges first (behind nodes)\nfor trace in edge_traces:\n    fig.add_trace(trace)\n\n# Add nodes\nfig.add_trace(node_trace)\n\n# Add weight scale annotation\nfig.add_annotation(\n    x=0.01,\n    y=0.99,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=\"Edge Thickness = Trade Volume<br>35B USD (thin) to 620B USD (thick)\",\n    showarrow=False,\n    font={\"size\": 18, \"color\": INK, \"family\": \"Arial\"},\n    align=\"left\",\n    xanchor=\"left\",\n    yanchor=\"top\",\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    borderpad=10,\n)\n\n# Update layout with theme-adaptive colors\nfig.update_layout(\n    title={\n        \"text\": \"network-weighted · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False, \"title\": \"\", \"linecolor\": INK_SOFT},\n    yaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False, \"title\": \"\", \"linecolor\": INK_SOFT},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    showlegend=False,\n    margin={\"l\": 80, \"r\": 80, \"t\": 100, \"b\": 80},\n)\n\n# Save outputs\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}