{"spec_id":"network-transport-static","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nnetwork-transport-static: Static Transport Network Diagram\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    arrow,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_void,\n)\n\n\nnp.random.seed(42)\n\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# Station data - regional rail network\nstations = pd.DataFrame(\n    {\n        \"id\": [\"CTR\", \"NTH\", \"STH\", \"EST\", \"WST\", \"NE\", \"NW\", \"SE\", \"SW\", \"AIR\", \"UNI\", \"IND\"],\n        \"label\": [\n            \"Central\",\n            \"North\",\n            \"South\",\n            \"East\",\n            \"West\",\n            \"Northeast\",\n            \"Northwest\",\n            \"Southeast\",\n            \"Southwest\",\n            \"Airport\",\n            \"University\",\n            \"Industrial\",\n        ],\n        \"x\": [0.5, 0.5, 0.5, 0.85, 0.15, 0.75, 0.25, 0.75, 0.25, 0.95, 0.05, 0.5],\n        \"y\": [0.5, 0.85, 0.15, 0.5, 0.5, 0.75, 0.75, 0.25, 0.25, 0.65, 0.65, 0.0],\n    }\n)\n\n# Route data - train services with times\nroutes_data = [\n    # Express routes (RE)\n    (\"CTR\", \"NTH\", \"RE1\", \"06:00\", \"06:25\"),\n    (\"NTH\", \"CTR\", \"RE1\", \"06:30\", \"06:55\"),\n    (\"CTR\", \"STH\", \"RE2\", \"06:15\", \"06:40\"),\n    (\"STH\", \"CTR\", \"RE2\", \"06:45\", \"07:10\"),\n    (\"CTR\", \"EST\", \"RE3\", \"07:00\", \"07:20\"),\n    (\"EST\", \"CTR\", \"RE3\", \"07:30\", \"07:50\"),\n    (\"CTR\", \"WST\", \"RE4\", \"07:15\", \"07:35\"),\n    (\"WST\", \"CTR\", \"RE4\", \"07:45\", \"08:05\"),\n    # Regional routes (RB)\n    (\"NTH\", \"NE\", \"RB1\", \"08:00\", \"08:15\"),\n    (\"NE\", \"EST\", \"RB1\", \"08:20\", \"08:40\"),\n    (\"NTH\", \"NW\", \"RB2\", \"08:00\", \"08:15\"),\n    (\"NW\", \"WST\", \"RB2\", \"08:20\", \"08:40\"),\n    (\"STH\", \"SE\", \"RB3\", \"08:00\", \"08:15\"),\n    (\"SE\", \"EST\", \"RB3\", \"08:20\", \"08:40\"),\n    (\"STH\", \"SW\", \"RB4\", \"08:00\", \"08:15\"),\n    (\"SW\", \"WST\", \"RB4\", \"08:20\", \"08:40\"),\n    # Airport Express (AE)\n    (\"CTR\", \"EST\", \"AE1\", \"09:00\", \"09:15\"),\n    (\"EST\", \"AIR\", \"AE1\", \"09:20\", \"09:35\"),\n    (\"AIR\", \"EST\", \"AE1\", \"10:00\", \"10:15\"),\n    (\"EST\", \"CTR\", \"AE1\", \"10:20\", \"10:35\"),\n    # Local routes (S)\n    (\"CTR\", \"UNI\", \"S1\", \"08:30\", \"08:50\"),\n    (\"UNI\", \"NW\", \"S1\", \"08:55\", \"09:10\"),\n    (\"CTR\", \"IND\", \"S2\", \"09:00\", \"09:25\"),\n    (\"IND\", \"STH\", \"S2\", \"09:30\", \"09:45\"),\n]\n\nroutes = pd.DataFrame(routes_data, columns=[\"source\", \"target\", \"route_id\", \"dep\", \"arr\"])\n\n# Create station lookup\nstation_coords = stations.set_index(\"id\")[[\"x\", \"y\"]]\n\n# Add coordinates to routes\nroutes[\"x\"] = routes[\"source\"].map(station_coords[\"x\"])\nroutes[\"y\"] = routes[\"source\"].map(station_coords[\"y\"])\nroutes[\"xend\"] = routes[\"target\"].map(station_coords[\"x\"])\nroutes[\"yend\"] = routes[\"target\"].map(station_coords[\"y\"])\n\n# Route type for coloring\nroutes[\"route_type\"] = routes[\"route_id\"].str.extract(r\"([A-Z]+)\")[0]\n\n# Offset overlapping routes\nroute_pairs = routes.groupby([\"source\", \"target\"]).cumcount()\noffset_amount = 0.025\n\n# Calculate perpendicular offset for multiple routes\ndx = routes[\"xend\"] - routes[\"x\"]\ndy = routes[\"yend\"] - routes[\"y\"]\nlength = np.sqrt(dx**2 + dy**2)\nperpx = -dy / length * offset_amount * route_pairs\nperpy = dx / length * offset_amount * route_pairs\n\nroutes[\"x\"] = routes[\"x\"] + perpx\nroutes[\"y\"] = routes[\"y\"] + perpy\nroutes[\"xend\"] = routes[\"xend\"] + perpx\nroutes[\"yend\"] = routes[\"yend\"] + perpy\n\n# Shorten edges so arrows don't overlap with nodes\nshorten = 0.04\ndx = routes[\"xend\"] - routes[\"x\"]\ndy = routes[\"yend\"] - routes[\"y\"]\nlength = np.sqrt(dx**2 + dy**2)\nroutes[\"x\"] = routes[\"x\"] + dx / length * shorten\nroutes[\"y\"] = routes[\"y\"] + dy / length * shorten\nroutes[\"xend\"] = routes[\"xend\"] - dx / length * shorten\nroutes[\"yend\"] = routes[\"yend\"] - dy / length * shorten\n\n# Calculate edge label positions - stagger along edge to reduce overlaps\nlabel_offset = np.where(routes.index % 2 == 0, 0.4, 0.6)\nroutes[\"label_x\"] = routes[\"x\"] + (routes[\"xend\"] - routes[\"x\"]) * label_offset\nroutes[\"label_y\"] = routes[\"y\"] + (routes[\"yend\"] - routes[\"y\"]) * label_offset\nroutes[\"edge_label\"] = routes[\"route_id\"] + \" | \" + routes[\"dep\"] + \"→\" + routes[\"arr\"]\n\n# Color palette for route types\nroute_colors = {\n    \"RE\": \"#4467A3\",  # Blue - Express\n    \"RB\": \"#AE3030\",  # Orange - Regional\n    \"AE\": \"#C475FD\",  # Vermillion - Airport\n    \"S\": \"#009E73\",  # Green - Local (first series brand color)\n}\n\n# Create the plot\nplot = (\n    ggplot()\n    # Draw route edges with arrows\n    + geom_segment(\n        data=routes,\n        mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"route_type\"),\n        size=1.5,\n        arrow=arrow(length=0.15, type=\"closed\", angle=25),\n    )\n    # Draw station nodes\n    + geom_point(data=stations, mapping=aes(x=\"x\", y=\"y\"), size=12, fill=PAGE_BG, color=INK, stroke=2)\n    # Station labels\n    + geom_text(\n        data=stations, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), size=12, fontweight=\"bold\", color=INK, nudge_y=0.06\n    )\n    # Edge labels (route and times) - improved size and positioning\n    + geom_text(\n        data=routes,\n        mapping=aes(x=\"label_x\", y=\"label_y\", label=\"edge_label\", color=\"route_type\"),\n        size=9,\n        nudge_y=0.04,\n        fontweight=\"bold\",\n        show_legend=False,\n    )\n    # Color scale\n    + scale_color_manual(\n        values=route_colors,\n        name=\"Route Type\",\n        labels={\"RE\": \"RE (Express)\", \"RB\": \"RB (Regional)\", \"AE\": \"AE (Airport)\", \"S\": \"S (Local)\"},\n        limits=[\"RE\", \"RB\", \"AE\", \"S\"],\n    )\n    # Labels and theme\n    + labs(title=\"network-transport-static · python · plotnine · anyplot.ai\")\n    + theme_void()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(size=24, ha=\"center\", weight=\"bold\", color=INK),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=16, weight=\"bold\", color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}