{"spec_id":"network-transport-static","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nnetwork-transport-static: Static Transport Network Diagram\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\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 palette for route types\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Seed for reproducibility\nnp.random.seed(42)\n\n# Station data - regional rail network with geographic-style positioning\nstations = [\n    {\"id\": 0, \"label\": \"Central\", \"x\": 0.5, \"y\": 0.5},\n    {\"id\": 1, \"label\": \"North\", \"x\": 0.5, \"y\": 0.92},\n    {\"id\": 2, \"label\": \"East\", \"x\": 0.92, \"y\": 0.5},\n    {\"id\": 3, \"label\": \"South\", \"x\": 0.5, \"y\": 0.08},\n    {\"id\": 4, \"label\": \"West\", \"x\": 0.08, \"y\": 0.5},\n    {\"id\": 5, \"label\": \"Airport\", \"x\": 0.88, \"y\": 0.88},\n    {\"id\": 6, \"label\": \"University\", \"x\": 0.12, \"y\": 0.88},\n    {\"id\": 7, \"label\": \"Harbor\", \"x\": 0.88, \"y\": 0.12},\n    {\"id\": 8, \"label\": \"Old Town\", \"x\": 0.12, \"y\": 0.12},\n]\n\n# Route data with manual label positioning for clarity\n# t_pos: position along edge (0=source, 1=target)\n# offset: perpendicular offset multiplier (positive = left of direction)\n# type: route type (0=RE, 1=AIR, 2=S) for Okabe-Ito color mapping\nroutes = [\n    # Main lines from Central (RE = Regional Express) - outbound\n    {\n        \"source\": 0,\n        \"target\": 1,\n        \"route\": \"RE1\",\n        \"dep\": \"06:00\",\n        \"arr\": \"06:18\",\n        \"t_pos\": 0.58,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 0,\n        \"target\": 2,\n        \"route\": \"RE2\",\n        \"dep\": \"06:10\",\n        \"arr\": \"06:28\",\n        \"t_pos\": 0.58,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 0,\n        \"target\": 3,\n        \"route\": \"RE3\",\n        \"dep\": \"06:20\",\n        \"arr\": \"06:38\",\n        \"t_pos\": 0.58,\n        \"offset\": -0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 0,\n        \"target\": 4,\n        \"route\": \"RE4\",\n        \"dep\": \"06:15\",\n        \"arr\": \"06:33\",\n        \"t_pos\": 0.58,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    # Main lines - return to Central\n    {\n        \"source\": 1,\n        \"target\": 0,\n        \"route\": \"RE1\",\n        \"dep\": \"06:30\",\n        \"arr\": \"06:48\",\n        \"t_pos\": 0.42,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 2,\n        \"target\": 0,\n        \"route\": \"RE2\",\n        \"dep\": \"06:40\",\n        \"arr\": \"06:58\",\n        \"t_pos\": 0.42,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 3,\n        \"target\": 0,\n        \"route\": \"RE3\",\n        \"dep\": \"06:50\",\n        \"arr\": \"07:08\",\n        \"t_pos\": 0.42,\n        \"offset\": -0.12,\n        \"type\": 0,\n    },\n    {\n        \"source\": 4,\n        \"target\": 0,\n        \"route\": \"RE4\",\n        \"dep\": \"06:45\",\n        \"arr\": \"07:03\",\n        \"t_pos\": 0.42,\n        \"offset\": 0.12,\n        \"type\": 0,\n    },\n    # Airport express (AIR)\n    {\n        \"source\": 0,\n        \"target\": 5,\n        \"route\": \"AIR\",\n        \"dep\": \"05:30\",\n        \"arr\": \"06:00\",\n        \"t_pos\": 0.40,\n        \"offset\": 0.12,\n        \"type\": 1,\n    },\n    {\n        \"source\": 5,\n        \"target\": 0,\n        \"route\": \"AIR\",\n        \"dep\": \"22:00\",\n        \"arr\": \"22:30\",\n        \"t_pos\": 0.60,\n        \"offset\": 0.12,\n        \"type\": 1,\n    },\n    # Suburban lines (S-Bahn) - single direction only\n    {\"source\": 1, \"target\": 6, \"route\": \"S1\", \"dep\": \"07:00\", \"arr\": \"07:15\", \"t_pos\": 0.5, \"offset\": 0.10, \"type\": 2},\n    {\"source\": 1, \"target\": 5, \"route\": \"S2\", \"dep\": \"07:05\", \"arr\": \"07:22\", \"t_pos\": 0.5, \"offset\": -0.10, \"type\": 2},\n    {\"source\": 2, \"target\": 7, \"route\": \"S3\", \"dep\": \"07:10\", \"arr\": \"07:25\", \"t_pos\": 0.5, \"offset\": 0.10, \"type\": 2},\n    {\"source\": 3, \"target\": 7, \"route\": \"S4\", \"dep\": \"07:20\", \"arr\": \"07:38\", \"t_pos\": 0.5, \"offset\": -0.10, \"type\": 2},\n    {\"source\": 3, \"target\": 8, \"route\": \"S5\", \"dep\": \"07:25\", \"arr\": \"07:43\", \"t_pos\": 0.5, \"offset\": 0.10, \"type\": 2},\n    {\"source\": 4, \"target\": 8, \"route\": \"S6\", \"dep\": \"07:15\", \"arr\": \"07:32\", \"t_pos\": 0.5, \"offset\": 0.10, \"type\": 2},\n    {\"source\": 4, \"target\": 6, \"route\": \"S7\", \"dep\": \"07:30\", \"arr\": \"07:48\", \"t_pos\": 0.5, \"offset\": -0.10, \"type\": 2},\n]\n\n# Create figure\nfig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Set axis limits with padding\nax.set_xlim(-0.05, 1.05)\nax.set_ylim(-0.05, 1.05)\n\n# Track route pairs for curve calculation\nroute_pairs = {}\nfor route in routes:\n    pair = (min(route[\"source\"], route[\"target\"]), max(route[\"source\"], route[\"target\"]))\n    if pair not in route_pairs:\n        route_pairs[pair] = []\n    route_pairs[pair].append(route)\n\n# Draw routes as curved arrows with labels\nfor route in routes:\n    src = stations[route[\"source\"]]\n    tgt = stations[route[\"target\"]]\n\n    # Get curve parameters based on multiple routes between same stations\n    pair = (min(route[\"source\"], route[\"target\"]), max(route[\"source\"], route[\"target\"]))\n    same_pair = route_pairs[pair]\n    n_routes = len(same_pair)\n\n    # Calculate curve direction and strength for bidirectional routes\n    if n_routes == 1:\n        curve = 0.0\n    else:\n        # Outbound from lower-id station gets positive curve\n        if route[\"source\"] < route[\"target\"]:\n            curve = 0.2\n        else:\n            curve = -0.2\n\n    # Get route color from Okabe-Ito palette based on type\n    color = IMPRINT[route[\"type\"]]\n\n    # Draw arrow\n    arrow = mpatches.FancyArrowPatch(\n        (src[\"x\"], src[\"y\"]),\n        (tgt[\"x\"], tgt[\"y\"]),\n        connectionstyle=f\"arc3,rad={curve}\",\n        arrowstyle=\"->,head_length=10,head_width=6\",\n        color=color,\n        linewidth=3,\n        alpha=0.8,\n        zorder=1,\n    )\n    ax.add_patch(arrow)\n\n    # Get label position from route data\n    t = route[\"t_pos\"]\n    base_offset = route[\"offset\"]\n\n    # Linear position along edge\n    base_x = src[\"x\"] + t * (tgt[\"x\"] - src[\"x\"])\n    base_y = src[\"y\"] + t * (tgt[\"y\"] - src[\"y\"])\n\n    # Calculate perpendicular direction\n    dx = tgt[\"x\"] - src[\"x\"]\n    dy = tgt[\"y\"] - src[\"y\"]\n    length = np.sqrt(dx**2 + dy**2)\n\n    if length > 0:\n        perp_x = -dy / length\n        perp_y = dx / length\n    else:\n        perp_x, perp_y = 0, 0\n\n    label_x = base_x + perp_x * base_offset\n    label_y = base_y + perp_y * base_offset\n\n    # Route label with times\n    label_text = f\"{route['route']} {route['dep']}→{route['arr']}\"\n    ax.annotate(\n        label_text,\n        (label_x, label_y),\n        fontsize=11,\n        ha=\"center\",\n        va=\"center\",\n        color=INK,\n        fontweight=\"bold\",\n        bbox={\n            \"boxstyle\": \"round,pad=0.2\",\n            \"facecolor\": ELEVATED_BG,\n            \"edgecolor\": color,\n            \"linewidth\": 1.5,\n            \"alpha\": 0.95,\n        },\n        zorder=3,\n    )\n\n# Draw station nodes\nnode_radius = 0.035\nfor station in stations:\n    # Node circle with fill\n    circle = plt.Circle(\n        (station[\"x\"], station[\"y\"]), node_radius, facecolor=PAGE_BG, edgecolor=INK_SOFT, linewidth=3.5, zorder=4\n    )\n    ax.add_patch(circle)\n\n    # Station label below node\n    ax.annotate(\n        station[\"label\"],\n        (station[\"x\"], station[\"y\"] - node_radius - 0.025),\n        fontsize=15,\n        ha=\"center\",\n        va=\"top\",\n        fontweight=\"bold\",\n        color=INK,\n        zorder=5,\n    )\n\n# Create legend\nlegend_elements = [\n    mpatches.Patch(facecolor=IMPRINT[0], edgecolor=IMPRINT[0], label=\"Regional Express (RE)\"),\n    mpatches.Patch(facecolor=IMPRINT[1], edgecolor=IMPRINT[1], label=\"Airport Service (AIR)\"),\n    mpatches.Patch(facecolor=IMPRINT[2], edgecolor=IMPRINT[2], label=\"S-Bahn (S)\"),\n]\nleg = ax.legend(handles=legend_elements, loc=\"upper right\", fontsize=16, framealpha=0.95)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    leg.get_frame().set_linewidth(1)\n\n# Styling\nax.set_title(\n    \"network-transport-static · python · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=20\n)\nax.set_xlabel(\"Relative Position (West → East)\", fontsize=20, color=INK)\nax.set_ylabel(\"Relative Position (South → North)\", fontsize=20, color=INK)\nax.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\nax.set_aspect(\"equal\")\n\n# Subtle grid\nax.grid(True, alpha=0.10, linewidth=0.8, color=INK_SOFT)\n\n# Remove spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor spine in [\"left\", \"bottom\"]:\n    ax.spines[spine].set_color(INK_SOFT)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}