{"spec_id":"flowmap-origin-destination","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nflowmap-origin-destination: Origin-Destination Flow Map\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\n\nimport matplotlib.lines as mlines\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nLAND_COLOR = \"#D4E5D4\" if THEME == \"light\" else \"#2A3D2A\"\nOCEAN_COLOR = \"#C8DFF0\" if THEME == \"light\" else \"#1A2D3A\"\nBORDER_COLOR = \"#888888\" if THEME == \"light\" else \"#555555\"\n\n# Data - Trade flows between major world ports\nnp.random.seed(42)\n\n# Major port cities with coordinates (lon, lat)\nports = {\n    \"Shanghai\": (121.47, 31.23),\n    \"Singapore\": (103.82, 1.35),\n    \"Rotterdam\": (4.48, 51.92),\n    \"Los Angeles\": (-118.25, 33.75),\n    \"Dubai\": (55.27, 25.20),\n    \"Hong Kong\": (114.17, 22.32),\n    \"Busan\": (129.03, 35.10),\n    \"Hamburg\": (9.99, 53.55),\n    \"New York\": (-74.00, 40.71),\n    \"Santos\": (-46.33, -23.95),\n}\n\n# Define trade flows (origin, destination, flow volume in million TEUs)\nflows = [\n    (\"Shanghai\", \"Los Angeles\", 8.5),\n    (\"Shanghai\", \"Rotterdam\", 6.2),\n    (\"Shanghai\", \"Singapore\", 5.8),\n    (\"Singapore\", \"Rotterdam\", 4.5),\n    (\"Hong Kong\", \"Los Angeles\", 3.9),\n    (\"Busan\", \"Los Angeles\", 3.2),\n    (\"Dubai\", \"Rotterdam\", 2.8),\n    (\"Hamburg\", \"New York\", 2.5),\n    (\"Rotterdam\", \"New York\", 2.3),\n    (\"Santos\", \"Rotterdam\", 1.9),\n    (\"Shanghai\", \"Dubai\", 3.5),\n    (\"Singapore\", \"Dubai\", 2.7),\n    (\"Hong Kong\", \"Singapore\", 2.4),\n    (\"Shanghai\", \"Hamburg\", 4.1),\n    (\"Busan\", \"Shanghai\", 1.8),\n]\n\n# Label offsets to avoid overlap (lon_offset, lat_offset)\nlabel_offsets = {\n    \"Shanghai\": (4, 2),\n    \"Singapore\": (4, -4),\n    \"Rotterdam\": (-6, -5),\n    \"Los Angeles\": (-8, -5),\n    \"Dubai\": (4, -5),\n    \"Hong Kong\": (3, -5),\n    \"Busan\": (3, 2),\n    \"Hamburg\": (-8, 3),\n    \"New York\": (-10, 3),\n    \"Santos\": (-10, -4),\n}\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(OCEAN_COLOR)\nax.set_xlim(-180, 180)\nax.set_ylim(-60, 80)\n\n# Simplified continent outlines\ncontinent_coords = [\n    [\n        (-170, 60),\n        (-170, 25),\n        (-130, 25),\n        (-100, 20),\n        (-80, 25),\n        (-60, 45),\n        (-55, 50),\n        (-70, 70),\n        (-170, 70),\n        (-170, 60),\n    ],\n    [(-80, 10), (-60, 5), (-35, -5), (-35, -25), (-55, -55), (-75, -55), (-80, -20), (-80, 10)],\n    [(-10, 35), (0, 35), (30, 35), (40, 45), (30, 60), (30, 70), (10, 70), (-10, 60), (-10, 35)],\n    [(-20, 35), (35, 35), (50, 15), (50, -5), (35, -35), (20, -35), (10, -5), (-20, 5), (-20, 35)],\n    [\n        (30, 35),\n        (60, 25),\n        (70, 25),\n        (100, 20),\n        (120, 25),\n        (145, 45),\n        (145, 55),\n        (180, 65),\n        (180, 75),\n        (60, 75),\n        (30, 50),\n        (30, 35),\n    ],\n    [(110, -10), (155, -10), (155, -40), (130, -40), (110, -25), (110, -10)],\n]\n\nfor coords in continent_coords:\n    xs = [c[0] for c in coords]\n    ys = [c[1] for c in coords]\n    ax.fill(xs, ys, color=LAND_COLOR, edgecolor=BORDER_COLOR, linewidth=0.5, zorder=1)\n\n# Normalize flow values for line width scaling\nmax_flow = max(f[2] for f in flows)\nmin_flow = min(f[2] for f in flows)\n\n# Draw flows using quadratic Bezier curves\nt = np.linspace(0, 1, 80)\nheight_factor = 0.28\n\nfor origin_name, dest_name, flow in flows:\n    ox, oy = ports[origin_name]\n    dx, dy = ports[dest_name]\n\n    mx, my = (ox + dx) / 2, (oy + dy) / 2\n    distance = np.sqrt((dx - ox) ** 2 + (dy - oy) ** 2)\n\n    if distance > 0:\n        px, py = -(dy - oy) / distance, (dx - ox) / distance\n    else:\n        px, py = 0, 1\n\n    cx = mx + px * distance * height_factor\n    cy = my + py * distance * height_factor\n\n    x = (1 - t) ** 2 * ox + 2 * (1 - t) * t * cx + t**2 * dx\n    y = (1 - t) ** 2 * oy + 2 * (1 - t) * t * cy + t**2 * dy\n\n    normalized = (flow - min_flow) / (max_flow - min_flow) if max_flow > min_flow else 0.5\n    line_width = 1.5 + normalized * 6.5\n    alpha = 0.45 + normalized * 0.35\n\n    # Blue colormap works well in both light and dark themes\n    color = plt.cm.Blues(0.4 + normalized * 0.5)\n    ax.plot(x, y, color=color, linewidth=line_width, alpha=alpha, zorder=3, solid_capstyle=\"round\")\n\n# Draw port markers — brand green for single categorical series (Okabe-Ito position 1)\nfor port_name, (lon, lat) in ports.items():\n    ax.scatter(lon, lat, s=60, c=\"#009E73\", edgecolors=PAGE_BG, linewidths=1.2, zorder=4)\n    lon_off, lat_off = label_offsets.get(port_name, (4, 3))\n    ax.annotate(\n        port_name,\n        (lon, lat),\n        xytext=(lon_off, lat_off),\n        textcoords=\"offset points\",\n        fontsize=7,\n        fontweight=\"bold\",\n        color=INK,\n        zorder=5,\n    )\n\n# Legend with actual line samples to reflect line-width encoding\nlegend_levels = [(1.9, \"~2 M TEUs\"), (5.0, \"~5 M TEUs\"), (8.5, \"~8.5 M TEUs\")]\nlegend_handles = []\nfor lf, label in legend_levels:\n    normalized = (lf - min_flow) / (max_flow - min_flow)\n    lw = 1.5 + normalized * 6.5\n    color = plt.cm.Blues(0.4 + normalized * 0.5)\n    handle = mlines.Line2D([], [], color=color, linewidth=lw, label=label, alpha=0.8, solid_capstyle=\"round\")\n    legend_handles.append(handle)\n\nleg = ax.legend(\n    handles=legend_handles,\n    loc=\"lower left\",\n    fontsize=8,\n    title=\"Trade Volume\",\n    title_fontsize=9,\n    framealpha=0.9,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n)\nplt.setp(leg.get_title(), color=INK)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\n# Style\nax.set_xlabel(\"Longitude\", fontsize=10, color=INK)\nax.set_ylabel(\"Latitude\", fontsize=10, color=INK)\nax.set_title(\n    \"Global Port Trade Routes · flowmap-origin-destination · python · matplotlib · anyplot.ai\",\n    fontsize=10,\n    fontweight=\"medium\",\n    color=INK,\n)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.grid(True, alpha=0.10, linewidth=0.5, color=INK, zorder=0)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}