{"spec_id":"network-weighted","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nnetwork-weighted: Weighted Network Graph with Edge Thickness\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\nnp.random.seed(42)\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Create trade network: 15 countries with weighted trade relationships\ncountries = [\n    \"USA\",\n    \"China\",\n    \"Germany\",\n    \"Japan\",\n    \"UK\",\n    \"France\",\n    \"Canada\",\n    \"Mexico\",\n    \"Brazil\",\n    \"India\",\n    \"S.Korea\",\n    \"Italy\",\n    \"Australia\",\n    \"Spain\",\n    \"Netherlands\",\n]\nn_nodes = len(countries)\n\n# Generate edges with weights (bilateral trade in billions USD)\nedges_data = []\nedge_pairs = [\n    (0, 1, 550),\n    (0, 2, 180),\n    (0, 3, 220),\n    (0, 4, 140),\n    (0, 6, 380),\n    (0, 7, 420),\n    (0, 9, 95),\n    (0, 10, 130),\n    (1, 2, 200),\n    (1, 3, 340),\n    (1, 4, 85),\n    (1, 10, 290),\n    (1, 12, 160),\n    (2, 3, 55),\n    (2, 4, 150),\n    (2, 5, 180),\n    (2, 8, 45),\n    (2, 13, 65),\n    (2, 14, 220),\n    (3, 4, 35),\n    (3, 10, 85),\n    (3, 12, 70),\n    (4, 5, 95),\n    (4, 11, 55),\n    (4, 14, 80),\n    (5, 11, 85),\n    (5, 13, 95),\n    (6, 7, 75),\n    (7, 8, 40),\n    (8, 9, 30),\n    (9, 12, 25),\n    (11, 13, 45),\n    (12, 14, 35),\n]\n\nfor src, tgt, weight in edge_pairs:\n    edges_data.append({\"source\": src, \"target\": tgt, \"weight\": weight})\n\n# Calculate positions using spring layout (simple force-directed simulation)\npos = np.zeros((n_nodes, 2))\n# Initial positions in a circle\nangles = np.linspace(0, 2 * np.pi, n_nodes, endpoint=False)\npos[:, 0] = np.cos(angles)\npos[:, 1] = np.sin(angles)\n\n# Force-directed iterations with weight-aware attraction\nfor _ in range(150):\n    forces = np.zeros_like(pos)\n\n    # Repulsion between all nodes\n    for i in range(n_nodes):\n        for j in range(i + 1, n_nodes):\n            diff = pos[i] - pos[j]\n            dist = max(np.linalg.norm(diff), 0.1)\n            force = diff / (dist**2) * 0.5\n            forces[i] += force\n            forces[j] -= force\n\n    # Attraction along edges (weighted)\n    for edge in edges_data:\n        i, j = edge[\"source\"], edge[\"target\"]\n        diff = pos[j] - pos[i]\n        dist = max(np.linalg.norm(diff), 0.01)\n        # Weight influences attraction strength more strongly\n        weight_factor = (np.log1p(edge[\"weight\"]) ** 1.2) * 0.002\n        force = diff / dist * weight_factor\n        forces[i] += force\n        forces[j] -= force\n\n    # Apply forces with damping\n    pos += forces * 0.08\n\n    # Center\n    pos -= pos.mean(axis=0)\n\n# Scale positions with margin for labels\npos = pos / np.abs(pos).max() * 3.2\n\n# Calculate weighted degree for node sizing\nweighted_degree = np.zeros(n_nodes)\nfor edge in edges_data:\n    weighted_degree[edge[\"source\"]] += edge[\"weight\"]\n    weighted_degree[edge[\"target\"]] += edge[\"weight\"]\n\n# Create node dataframe\nnodes_df = pd.DataFrame({\"x\": pos[:, 0], \"y\": pos[:, 1], \"label\": countries, \"weighted_degree\": weighted_degree})\n\n# Create edges dataframe with line segments\nedges_list = []\nfor edge in edges_data:\n    src, tgt = edge[\"source\"], edge[\"target\"]\n    edges_list.append(\n        {\"x\": pos[src, 0], \"y\": pos[src, 1], \"xend\": pos[tgt, 0], \"yend\": pos[tgt, 1], \"weight\": edge[\"weight\"]}\n    )\n\nedges_df = pd.DataFrame(edges_list)\n\n# Normalize edge weights for line width (1 to 8 range)\nmin_w, max_w = edges_df[\"weight\"].min(), edges_df[\"weight\"].max()\nedges_df[\"line_width\"] = 1 + (edges_df[\"weight\"] - min_w) / (max_w - min_w) * 7\n\n# Normalize node sizes (6 to 18 range for better visibility)\nmin_d, max_d = nodes_df[\"weighted_degree\"].min(), nodes_df[\"weighted_degree\"].max()\nnodes_df[\"node_size\"] = 6 + (nodes_df[\"weighted_degree\"] - min_d) / (max_d - min_d) * 12\n\n# Create the plot\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid=element_blank(),\n    axis_title=element_blank(),\n    axis_text=element_blank(),\n    axis_ticks=element_blank(),\n    plot_title=element_text(size=28, color=INK),\n    plot_subtitle=element_text(size=18, color=INK_SOFT),\n    legend_title=element_text(size=16, color=INK),\n    legend_text=element_text(size=14, color=INK_SOFT),\n    legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT),\n    legend_position=[0.95, 0.25],\n    legend_justification=[1, 0.5],\n)\n\nplot = (\n    ggplot()\n    # Edges as segments with varying width based on weight\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", size=\"weight\"), data=edges_df, color=INK_SOFT, alpha=0.5)\n    # Scale for edge thickness - use Okabe-Ito color for legend\n    + scale_size(range=[1, 10], name=\"Trade Volume\\n(Billions USD)\")\n    # Nodes as points sized by weighted degree\n    + geom_point(aes(x=\"x\", y=\"y\", size=\"node_size\"), data=nodes_df, color=BRAND, alpha=0.9, show_legend=False)\n    # Node labels\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=nodes_df, size=14, color=INK, nudge_y=0.4, fontface=\"bold\")\n    # Styling\n    + labs(\n        title=\"network-weighted · letsplot · anyplot.ai\",\n        subtitle=\"International Trade Network (Edge Thickness = Trade Volume)\",\n    )\n    # Explicit axis limits to prevent label clipping\n    + scale_x_continuous(limits=[-5, 5])\n    + scale_y_continuous(limits=[-5, 5])\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Save PNG (scaled 3x for 4800 × 2700 px)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\n\n# Save HTML for interactive version\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}