{"spec_id":"map-connection-lines","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nmap-connection-lines: Connection Lines Map (Origin-Destination)\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-28\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_rect,\n    element_text,\n    geom_curve,\n    geom_point,\n    geom_polygon,\n    geom_text,\n    geom_text_repel,\n    ggplot,\n    ggsave,\n    ggsize,\n    guide_legend,\n    labs,\n    scale_color_gradient,\n    scale_size,\n    theme,\n    theme_void,\n    xlim,\n    ylim,\n)\n\n\nLetsPlot.setup_html()\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\"\nLAND_FILL = \"#E8E4DF\" if THEME == \"light\" else \"#2D2D29\"\nLAND_BORDER = \"#C5C0BA\" if THEME == \"light\" else \"#404040\"\n\n# Data: Major flight routes between world cities\nnp.random.seed(42)\n\nairports = {\n    \"JFK\": (-73.78, 40.64, \"New York\"),\n    \"LAX\": (-118.41, 33.94, \"Los Angeles\"),\n    \"LHR\": (-0.45, 51.47, \"London\"),\n    \"CDG\": (2.55, 49.01, \"Paris\"),\n    \"DXB\": (55.37, 25.25, \"Dubai\"),\n    \"HND\": (139.78, 35.55, \"Tokyo\"),\n    \"SIN\": (103.99, 1.36, \"Singapore\"),\n    \"SYD\": (151.18, -33.94, \"Sydney\"),\n    \"GRU\": (-46.47, -23.44, \"São Paulo\"),\n    \"JNB\": (28.24, -26.14, \"Johannesburg\"),\n}\n\nroutes = [\n    (\"JFK\", \"LHR\", 4.2),\n    (\"JFK\", \"CDG\", 2.8),\n    (\"JFK\", \"LAX\", 3.5),\n    (\"LAX\", \"HND\", 2.1),\n    (\"LAX\", \"SYD\", 1.5),\n    (\"LHR\", \"DXB\", 3.8),\n    (\"LHR\", \"SIN\", 2.4),\n    (\"LHR\", \"JNB\", 1.8),\n    (\"CDG\", \"DXB\", 2.2),\n    (\"DXB\", \"SIN\", 3.1),\n    (\"DXB\", \"HND\", 1.9),\n    (\"SIN\", \"SYD\", 2.6),\n    (\"SIN\", \"HND\", 2.3),\n    (\"GRU\", \"LHR\", 1.4),\n    (\"GRU\", \"JFK\", 1.6),\n    (\"JNB\", \"DXB\", 1.2),\n]\n\nroute_data = []\nfor origin, dest, passengers in routes:\n    o_lon, o_lat, _ = airports[origin]\n    d_lon, d_lat, _ = airports[dest]\n    route_data.append(\n        {\"origin_lon\": o_lon, \"origin_lat\": o_lat, \"dest_lon\": d_lon, \"dest_lat\": d_lat, \"passengers\": passengers}\n    )\ndf_routes = pd.DataFrame(route_data)\n\nairport_data = [{\"name\": name, \"lon\": lon, \"lat\": lat} for _, (lon, lat, name) in airports.items()]\ndf_airports = pd.DataFrame(airport_data)\n\n# Annotation: highlight busiest route JFK-LHR (4.2M passengers)\njfk_lon, jfk_lat, _ = airports[\"JFK\"]\nlhr_lon, lhr_lat, _ = airports[\"LHR\"]\ndf_callout = pd.DataFrame(\n    [{\"x\": (jfk_lon + lhr_lon) / 2, \"y\": (jfk_lat + lhr_lat) / 2 + 13, \"label\": \"Busiest route\\nJFK–LHR · 4.2M pax\"}]\n)\n\n# Simplified world coastline polygons\nworld_coords = [\n    # North America\n    (-170, 70),\n    (-140, 70),\n    (-120, 60),\n    (-100, 50),\n    (-80, 45),\n    (-70, 45),\n    (-60, 50),\n    (-55, 50),\n    (-55, 45),\n    (-80, 25),\n    (-100, 20),\n    (-120, 30),\n    (-130, 50),\n    (-170, 60),\n    (-170, 70),\n    (None, None),\n    # South America\n    (-80, 10),\n    (-60, 5),\n    (-35, -5),\n    (-40, -20),\n    (-55, -25),\n    (-70, -55),\n    (-75, -45),\n    (-80, -5),\n    (-80, 10),\n    (None, None),\n    # Europe/Africa\n    (-10, 60),\n    (30, 70),\n    (40, 65),\n    (30, 45),\n    (10, 35),\n    (-10, 35),\n    (-20, 15),\n    (50, 10),\n    (45, -35),\n    (20, -35),\n    (10, 5),\n    (-20, 10),\n    (-10, 60),\n    (None, None),\n    # Asia\n    (30, 70),\n    (70, 75),\n    (180, 70),\n    (160, 60),\n    (140, 50),\n    (130, 45),\n    (120, 30),\n    (105, 20),\n    (90, 25),\n    (70, 25),\n    (55, 25),\n    (45, 30),\n    (35, 35),\n    (30, 45),\n    (30, 70),\n    (None, None),\n    # Australia\n    (115, -20),\n    (150, -10),\n    (155, -25),\n    (150, -40),\n    (135, -35),\n    (115, -35),\n    (115, -20),\n]\n\npolygons = []\ncurrent_poly = []\nfor lon, lat in world_coords:\n    if lon is None:\n        if current_poly:\n            polygons.append(current_poly)\n            current_poly = []\n    else:\n        current_poly.append((lon, lat))\nif current_poly:\n    polygons.append(current_poly)\n\nworld_data = []\nfor i, poly in enumerate(polygons):\n    for lon, lat in poly:\n        world_data.append({\"x\": lon, \"y\": lat, \"group\": i})\ndf_world = pd.DataFrame(world_data)\n\n# Title font size scaled to length\ntitle = \"Global Flight Routes · map-connection-lines · python · letsplot · anyplot.ai\"\nn = len(title)\nratio = 67 / n if n > 67 else 1.0\ntitle_fontsize = max(11, round(16 * ratio))\n\n# Plot\nplot = (\n    ggplot()\n    + geom_polygon(data=df_world, mapping=aes(x=\"x\", y=\"y\", group=\"group\"), fill=LAND_FILL, color=LAND_BORDER, size=0.3)\n    + geom_curve(\n        data=df_routes,\n        mapping=aes(\n            x=\"origin_lon\", y=\"origin_lat\", xend=\"dest_lon\", yend=\"dest_lat\", size=\"passengers\", color=\"passengers\"\n        ),\n        curvature=-0.3,\n        alpha=0.5,\n    )\n    + geom_point(\n        data=df_airports, mapping=aes(x=\"lon\", y=\"lat\"), size=6, color=PAGE_BG, fill=\"#009E73\", shape=21, stroke=2\n    )\n    + geom_text_repel(\n        data=df_airports,\n        mapping=aes(x=\"lon\", y=\"lat\", label=\"name\"),\n        size=3,\n        color=INK,\n        seed=42,\n        point_padding=5,\n        box_padding=3,\n        max_overlaps=20,\n    )\n    + geom_text(\n        data=df_callout, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), size=3.5, color=INK, hjust=0.5, fontface=\"bold\"\n    )\n    + scale_size(range=[0.5, 6], name=\"Passengers (millions)\", guide=guide_legend())\n    + scale_color_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Passengers (millions)\", guide=guide_legend())\n    + labs(title=title)\n    + theme_void()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=title_fontsize, hjust=0.5, color=INK),\n        legend_title=element_text(size=12, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    )\n    + ggsize(800, 450)\n    + xlim(-180, 180)\n    + ylim(-60, 85)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}