{"spec_id":"flowmap-origin-destination","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' flowmap-origin-destination: Origin-Destination Flow Map\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 81/100 | Created: 2026-05-20\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(maps)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT   <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                 \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# World basemap polygons for geographic context\nworld <- map_data(\"world\")\n\n# Major global air hub coordinates and IATA codes\nairports <- data.frame(\n  name   = c(\"New York\", \"London\", \"Paris\", \"Dubai\", \"Tokyo\",\n             \"Singapore\", \"Sydney\", \"Hong Kong\", \"Amsterdam\", \"Frankfurt\"),\n  code   = c(\"NYC\", \"LHR\", \"CDG\", \"DXB\", \"TYO\",\n             \"SIN\", \"SYD\", \"HKG\", \"AMS\", \"FRA\"),\n  lat    = c(40.64, 51.47, 49.00, 25.25, 35.55,\n             1.35, -33.95, 22.31, 52.31, 50.03),\n  lon    = c(-73.78, -0.45, 2.55, 55.36, 139.78,\n             103.99, 151.18, 113.92, 4.76, 8.57),\n  region = c(\"Americas\", \"Europe\", \"Europe\", \"Middle East\", \"Asia Pacific\",\n             \"Asia Pacific\", \"Asia Pacific\", \"Asia Pacific\", \"Europe\", \"Europe\"),\n  stringsAsFactors = FALSE\n)\n\n# Synthetic international air passenger flows (millions per year)\nflows_raw <- data.frame(\n  origin = c(\"New York\", \"New York\", \"New York\", \"London\", \"London\",\n             \"London\", \"Dubai\", \"Dubai\", \"Dubai\", \"Singapore\",\n             \"Singapore\", \"Singapore\", \"Tokyo\", \"Frankfurt\", \"Amsterdam\"),\n  dest   = c(\"London\", \"Paris\", \"Dubai\", \"Dubai\", \"Tokyo\",\n             \"Amsterdam\", \"Singapore\", \"Frankfurt\", \"Tokyo\", \"Hong Kong\",\n             \"Sydney\", \"Tokyo\", \"Hong Kong\", \"Amsterdam\", \"Paris\"),\n  flow   = c(4.2, 2.8, 3.6, 6.5, 3.1, 2.5, 5.8, 3.4, 2.6, 4.7,\n             2.3, 3.2, 5.1, 3.8, 2.9),\n  stringsAsFactors = FALSE\n)\n\n# Join origin and destination coordinates\nflows <- flows_raw |>\n  left_join(airports[, c(\"name\", \"lat\", \"lon\", \"region\")],\n            by = c(\"origin\" = \"name\")) |>\n  rename(origin_lat = lat, origin_lon = lon, origin_region = region) |>\n  left_join(airports[, c(\"name\", \"lat\", \"lon\")],\n            by = c(\"dest\" = \"name\")) |>\n  rename(dest_lat = lat, dest_lon = lon)\n\nregion_colors <- c(\n  \"Americas\"     = IMPRINT[1],\n  \"Europe\"       = IMPRINT[2],\n  \"Middle East\"  = IMPRINT[3],\n  \"Asia Pacific\" = IMPRINT[4]\n)\n\n# Manual label nudges to spread the dense European cluster\nairports$nudge_x <- c(-8, -11, -11,  4,  4,  4,  4,  4, -11,  3)\nairports$nudge_y <- c( 2,   4,  -3,  2,  2,  2, -3,  2,   1, -4)\n\np <- ggplot() +\n  geom_polygon(\n    data = world,\n    aes(x = long, y = lat, group = group),\n    fill      = NA,\n    color     = INK_SOFT,\n    linewidth = 0.15\n  ) +\n  geom_curve(\n    data = flows,\n    aes(\n      x         = origin_lon, y         = origin_lat,\n      xend      = dest_lon,   yend      = dest_lat,\n      color     = origin_region,\n      linewidth = flow\n    ),\n    curvature = -0.3,\n    alpha     = 0.65,\n    arrow     = arrow(length = unit(0.006, \"npc\"), type = \"open\")\n  ) +\n  geom_point(\n    data = airports,\n    aes(x = lon, y = lat, fill = region),\n    shape  = 21,\n    color  = PAGE_BG,\n    size   = 3.5,\n    stroke = 0.8\n  ) +\n  geom_text(\n    data = airports,\n    aes(x = lon + nudge_x, y = lat + nudge_y, label = code),\n    color    = INK,\n    size     = 2.5,\n    fontface = \"bold\"\n  ) +\n  scale_color_manual(values = region_colors, name = \"Origin Region\") +\n  scale_fill_manual(values = region_colors, name = \"Origin Region\") +\n  scale_linewidth_continuous(range = c(0.4, 2.8), name = \"Flow (M pax/yr)\") +\n  coord_cartesian(xlim = c(-100, 165), ylim = c(-40, 65)) +\n  labs(\n    title    = \"Global Air Passenger Flows · flowmap-origin-destination · r · ggplot2 · anyplot.ai\",\n    subtitle = \"LHR–DXB is the busiest corridor at 6.5M pax/yr\",\n    x = \"Longitude\", y = \"Latitude\"\n  ) +\n  theme_minimal(base_size = 8) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major  = element_line(color = INK_SOFT, linewidth = 0.1),\n    panel.grid.minor  = element_blank(),\n    axis.text         = element_text(color = INK_SOFT, size = 7),\n    axis.title        = element_text(color = INK_SOFT, size = 8),\n    axis.ticks        = element_blank(),\n    plot.title        = element_text(color = INK, size = 12, face = \"bold\",\n                                     margin = margin(b = 4)),\n    plot.subtitle     = element_text(color = INK_SOFT, size = 8,\n                                     margin = margin(b = 6)),\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT,\n                                     linewidth = 0.3),\n    legend.text       = element_text(color = INK_SOFT, size = 7),\n    legend.title      = element_text(color = INK, size = 8),\n    legend.key        = element_rect(fill = NA, color = NA),\n    legend.position   = \"right\",\n    plot.margin       = margin(t = 8, r = 8, b = 8, l = 8)\n  ) +\n  guides(\n    fill  = guide_legend(order = 1),\n    color = guide_legend(order = 1),\n    linewidth = guide_legend(order = 2)\n  )\n\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}