{"spec_id":"map-marker-clustered","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' map-marker-clustered: Clustered Marker Map\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 85/100 | Created: 2026-05-23\n\nlibrary(ggplot2)\nlibrary(dplyr)\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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nWATER_BG    <- if (THEME == \"light\") \"#CDDFF0\" else \"#101E2A\"\nLAND_FILL   <- if (THEME == \"light\") \"#E0EBD5\" else \"#263322\"\n\nIMPRINT <- c(\n    \"#009E73\",  # 1: Music venues\n    \"#C475FD\",  # 2: Sports venues\n    \"#AE3030\"   # 3: Arts venues\n)\ncategories <- c(\"Music\", \"Sports\", \"Arts\")\n\n# --- Data -------------------------------------------------------------------\nmetro_areas <- data.frame(\n    lat = c(\n        40.71, 34.05, 41.88, 29.76, 33.45, 39.95, 29.42,\n        32.72, 32.78, 30.27, 47.61, 39.74, 42.36, 25.77,\n        45.52, 44.98, 33.75, 42.33, 36.17, 36.16\n    ),\n    lon = c(\n        -74.01, -118.24, -87.63, -95.37, -112.07, -75.17, -98.49,\n        -117.16, -96.80, -97.74, -122.33, -104.99, -71.06, -80.19,\n        -122.68, -93.27, -84.39, -83.05, -115.14, -86.78\n    ),\n    weight = c(15, 12, 10, 7, 7, 6, 5, 5, 5, 5, 4, 4, 4, 3, 3, 2, 2, 2, 2, 2)\n)\n\nn_venues <- 460\ncity_idx <- sample(\n    nrow(metro_areas), n_venues,\n    replace = TRUE,\n    prob    = metro_areas$weight / sum(metro_areas$weight)\n)\n\nvenues <- data.frame(\n    lat      = metro_areas$lat[city_idx] + rnorm(n_venues, 0, 0.55),\n    lon      = metro_areas$lon[city_idx] + rnorm(n_venues, 0, 0.75),\n    category = sample(categories, n_venues, replace = TRUE, prob = c(0.40, 0.35, 0.25))\n)\n\n# Grid-based pre-clustering — simulates a fixed zoom-level snapshot\ngrid_res <- 3.5\nclusters <- venues %>%\n    mutate(\n        clat = round(lat / grid_res) * grid_res,\n        clon = round(lon / grid_res) * grid_res\n    ) %>%\n    group_by(clat, clon) %>%\n    summarize(\n        count    = dplyr::n(),\n        category = names(sort(table(category), decreasing = TRUE))[1],\n        .groups  = \"drop\"\n    )\n\nus_states <- map_data(\"state\")\n\n# --- Plot -------------------------------------------------------------------\np <- ggplot() +\n    geom_polygon(\n        data      = us_states,\n        aes(x = long, y = lat, group = group),\n        fill      = LAND_FILL,\n        color     = INK_MUTED,\n        linewidth = 0.15\n    ) +\n    geom_point(\n        data   = clusters,\n        aes(x = clon, y = clat, size = count, fill = category),\n        shape  = 21,\n        color  = PAGE_BG,\n        alpha  = 0.90,\n        stroke = 0.5\n    ) +\n    geom_text(\n        data     = clusters,\n        aes(x = clon, y = clat, label = count),\n        size     = 2.5,\n        color    = \"white\",\n        fontface = \"bold\"\n    ) +\n    scale_fill_manual(\n        values = setNames(IMPRINT, categories),\n        name   = \"Venue Type\"\n    ) +\n    scale_size_area(\n        max_size = 18,\n        name     = \"Venues\",\n        breaks   = c(5, 20, 50, 80),\n        guide    = guide_legend(\n            override.aes = list(fill = INK_SOFT, color = PAGE_BG, stroke = 0.5)\n        )\n    ) +\n    coord_fixed(\n        ratio = 1.3,\n        xlim  = c(-126, -66),\n        ylim  = c(23.5, 50.5)\n    ) +\n    labs(\n        title = \"Venue Clusters · map-marker-clustered · r · ggplot2 · anyplot.ai\",\n        x     = \"Longitude\",\n        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 = WATER_BG, color = NA),\n        panel.grid.major  = element_line(color = INK_SOFT, linewidth = 0.15),\n        panel.grid.minor  = element_blank(),\n        panel.border      = element_rect(fill = NA, color = INK_SOFT, linewidth = 0.3),\n        axis.title        = element_text(color = INK, size = 10),\n        axis.text         = element_text(color = INK_SOFT, size = 8),\n        plot.title        = element_text(color = INK, size = 11, face = \"bold\"),\n        legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),\n        legend.text       = element_text(color = INK_SOFT, size = 8),\n        legend.title      = element_text(color = INK, size = 9),\n        legend.key        = element_rect(fill = NA, color = NA),\n        plot.margin       = margin(10, 15, 10, 10)\n    )\n\n# --- Save -------------------------------------------------------------------\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"}