{"spec_id":"map-connection-lines","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' map-connection-lines: Connection Lines Map (Origin-Destination)\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 85/100 | Created: 2026-05-28\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(ragg)\nif (!requireNamespace(\"maps\", quietly = TRUE)) install.packages(\"maps\", repos = \"https://cran.r-project.org\")\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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Helper: spherical linear interpolation along a great circle arc\ngreat_circle_arc <- function(lon1, lat1, lon2, lat2, n = 80L) {\n  to_rad <- function(x) x * pi / 180\n  to_deg <- function(x) x * 180 / pi\n\n  lon1r <- to_rad(lon1); lat1r <- to_rad(lat1)\n  lon2r <- to_rad(lon2); lat2r <- to_rad(lat2)\n\n  x1 <- cos(lat1r) * cos(lon1r); y1 <- cos(lat1r) * sin(lon1r); z1 <- sin(lat1r)\n  x2 <- cos(lat2r) * cos(lon2r); y2 <- cos(lat2r) * sin(lon2r); z2 <- sin(lat2r)\n\n  d <- acos(pmax(pmin(x1 * x2 + y1 * y2 + z1 * z2, 1.0), -1.0))\n  if (d < 1e-9) return(data.frame(lon = lon1, lat = lat1))\n\n  t  <- seq(0, 1, length.out = n)\n  xa <- (sin((1 - t) * d) * x1 + sin(t * d) * x2) / sin(d)\n  ya <- (sin((1 - t) * d) * y1 + sin(t * d) * y2) / sin(d)\n  za <- (sin((1 - t) * d) * z1 + sin(t * d) * z2) / sin(d)\n\n  data.frame(\n    lon = to_deg(atan2(ya, xa)),\n    lat = to_deg(atan2(za, sqrt(xa^2 + ya^2)))\n  )\n}\n\n# Data: major international hub airports\nairports <- data.frame(\n  city = c(\"New York\", \"London\",    \"Tokyo\",      \"Dubai\",\n           \"Sydney\",   \"Sao Paulo\", \"Singapore\",  \"Cape Town\",\n           \"Mumbai\",   \"Los Angeles\",\"Frankfurt\", \"Hong Kong\"),\n  lat  = c( 40.64,  51.48,  35.55,  25.25,\n            -33.95, -23.43,   1.36, -33.96,\n             19.09,  33.94,  50.03,  22.31),\n  lon  = c(-73.78,  -0.45, 139.78,  55.36,\n           151.17, -46.47, 103.99,  18.60,\n            72.87, -118.41,  8.57, 113.91),\n  stringsAsFactors = FALSE\n)\n\n# Label nudge direction (avoid overlaps at map boundaries)\nairports$hjust <- c(1.1, 1.1, -0.1, -0.1,\n                    -0.1,  1.1, -0.1,  1.1,\n                    -0.1,  1.1,  1.1, -0.1)\nairports$vjust <- c(0.5, 1.5, 0.5, 0.5,\n                    0.5, 0.5, 1.5, 0.5,\n                    0.5, 1.5, -0.5, 1.5)\n\n# Routes: international flight connections with annual passengers (millions)\nroutes_raw <- data.frame(\n  origin       = c(\"New York\",  \"London\",    \"Dubai\",     \"Singapore\", \"Los Angeles\",\n                   \"Frankfurt\", \"New York\",  \"London\",    \"Dubai\",     \"Sao Paulo\",\n                   \"Tokyo\",     \"Hong Kong\", \"Mumbai\",    \"Sydney\",    \"Cape Town\"),\n  dest         = c(\"London\",    \"Dubai\",     \"Mumbai\",    \"Tokyo\",     \"Tokyo\",\n                   \"Dubai\",     \"Los Angeles\",\"Frankfurt\", \"Cape Town\", \"London\",\n                   \"Los Angeles\",\"Singapore\",\"Dubai\",     \"Los Angeles\",\"Dubai\"),\n  passengers_m = c(3.2, 4.8, 3.1, 2.9, 5.1,\n                   3.6, 4.3, 2.1, 1.9, 1.4,\n                   4.7, 3.8, 2.6, 2.3, 1.1),\n  stringsAsFactors = FALSE\n)\n\n# Join airport coordinates\nroutes <- routes_raw |>\n  left_join(airports[, c(\"city\", \"lat\", \"lon\")], by = c(\"origin\" = \"city\")) |>\n  rename(origin_lat = lat, origin_lon = lon) |>\n  left_join(airports[, c(\"city\", \"lat\", \"lon\")], by = c(\"dest\" = \"city\")) |>\n  rename(dest_lat = lat, dest_lon = lon)\n\n# Build arc segments — split at antimeridian to avoid horizontal line artifacts\nall_segs    <- list()\nseg_counter <- 1L\n\nfor (i in seq_len(nrow(routes))) {\n  row <- routes[i, ]\n  pts <- great_circle_arc(row$origin_lon, row$origin_lat,\n                          row$dest_lon,   row$dest_lat)\n  pts$passengers <- row$passengers_m\n\n  jump_pos <- which(abs(diff(pts$lon)) > 180)\n\n  if (length(jump_pos) == 0L) {\n    pts$seg_id      <- seg_counter\n    seg_counter     <- seg_counter + 1L\n    all_segs[[length(all_segs) + 1]] <- pts\n  } else {\n    bounds <- c(0L, jump_pos, nrow(pts))\n    for (j in seq_len(length(bounds) - 1L)) {\n      seg        <- pts[(bounds[j] + 1L):bounds[j + 1L], ]\n      seg$seg_id <- seg_counter\n      seg_counter <- seg_counter + 1L\n      all_segs[[length(all_segs) + 1]] <- seg\n    }\n  }\n}\n\narc_df <- do.call(rbind, all_segs)\n\n# Graticule grid lines (every 30°)\ngraticule_h <- data.frame(lat = seq(-60, 60, by = 30))\ngraticule_v <- data.frame(lon = seq(-180, 180, by = 60))\n\n# Title with font-size scaling\ntitle_str  <- \"Global Flight Routes · map-connection-lines · r · ggplot2 · anyplot.ai\"\ntitle_size <- max(round(12 * 67 / nchar(title_str)), 8L)\n\nworld_map <- map_data(\"world\")\n\np <- ggplot() +\n  # Base map: country borders for geographic context\n  geom_polygon(\n    data    = world_map,\n    mapping = aes(x = long, y = lat, group = group),\n    fill    = INK_MUTED,\n    color   = NA,\n    alpha   = 0.15\n  ) +\n  # Horizontal graticule lines\n  geom_hline(\n    data    = graticule_h,\n    mapping = aes(yintercept = lat),\n    color   = INK_SOFT,\n    linewidth = 0.1,\n    alpha   = 0.25\n  ) +\n  # Vertical graticule lines\n  geom_vline(\n    data    = graticule_v,\n    mapping = aes(xintercept = lon),\n    color   = INK_SOFT,\n    linewidth = 0.1,\n    alpha   = 0.25\n  ) +\n  # Flight route arcs colored and weighted by passenger volume\n  geom_path(\n    data    = arc_df,\n    mapping = aes(x = lon, y = lat, group = seg_id,\n                  color = passengers, linewidth = passengers),\n    alpha   = 0.60,\n    lineend = \"round\"\n  ) +\n  # Airport markers (hollow circles, brand green)\n  geom_point(\n    data    = airports,\n    mapping = aes(x = lon, y = lat),\n    shape   = 21,\n    size    = 2.5,\n    fill    = IMPRINT_PALETTE[1],\n    color   = PAGE_BG,\n    stroke  = 0.9\n  ) +\n  # City labels for geographic context\n  geom_text(\n    data    = airports,\n    mapping = aes(x = lon, y = lat, label = city,\n                  hjust = hjust, vjust = vjust),\n    color   = INK_SOFT,\n    size    = 2.4,\n    fontface = \"plain\"\n  ) +\n  scale_color_gradient(\n    low   = \"#009E73\",\n    high  = \"#4467A3\",\n    name  = \"Passengers\\n(millions)\",\n    guide = guide_colorbar(\n      barwidth       = 0.7,\n      barheight      = 5.0,\n      title.position = \"top\",\n      ticks.colour   = INK_SOFT\n    )\n  ) +\n  scale_linewidth_continuous(range = c(0.5, 2.6), guide = \"none\") +\n  scale_x_continuous(\n    limits = c(-180, 180),\n    breaks = seq(-120, 120, by = 60),\n    labels = function(x) paste0(abs(x), ifelse(x < 0, \"°W\", ifelse(x > 0, \"°E\", \"°\")))\n  ) +\n  scale_y_continuous(\n    limits = c(-65, 80),\n    breaks = seq(-60, 60, by = 30),\n    labels = function(y) paste0(abs(y), ifelse(y < 0, \"°S\", ifelse(y > 0, \"°N\", \"°\")))\n  ) +\n  labs(title = title_str, x = NULL, y = NULL) +\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_blank(),\n    panel.grid.minor = element_blank(),\n    panel.border     = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.3),\n    axis.text        = element_text(color = INK_SOFT, size = 7),\n    axis.title       = element_blank(),\n    axis.ticks       = element_blank(),\n    plot.title       = element_text(\n      color  = INK,\n      size   = title_size,\n      hjust  = 0.5,\n      margin = margin(b = 10)\n    ),\n    legend.background = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK, size = 9),\n    legend.position   = \"right\",\n    plot.margin       = margin(8, 12, 8, 8)\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"}