{"spec_id":"map-marker-clustered","library":"makie","language":"julia","code":"# anyplot.ai\n# map-marker-clustered: Clustered Marker Map\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 87/100 | Created: 2026-05-23\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# Theme tokens\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Basemap palette\nconst LAND_COLOR   = THEME == \"light\" ? RGBAf(0.93f0, 0.91f0, 0.86f0, 1.0f0) : RGBAf(0.20f0, 0.20f0, 0.17f0, 1.0f0)\nconst WATER_COLOR  = THEME == \"light\" ? RGBAf(0.63f0, 0.77f0, 0.87f0, 0.95f0) : RGBAf(0.10f0, 0.18f0, 0.30f0, 0.95f0)\nconst STREET_COLOR = THEME == \"light\" ? RGBAf(1.0f0, 0.99f0, 0.97f0, 0.85f0) : RGBAf(0.10f0, 0.10f0, 0.08f0, 0.70f0)\nconst WATER_LABEL  = THEME == \"light\" ? RGBAf(0.25f0, 0.45f0, 0.65f0, 0.65f0) : RGBAf(0.50f0, 0.70f0, 0.90f0, 0.65f0)\n\nconst IMPRINT = [\n    colorant\"#009E73\",\n    colorant\"#C475FD\",\n    colorant\"#AE3030\",\n    colorant\"#4467A3\",\n    colorant\"#99B314\",\n    colorant\"#954477\",\n    colorant\"#BD8233\",\n]\n\n# Data: specialty venues (coffee shops, bakeries, tea houses) across lower Manhattan, NYC\nconst CAT_NAMES = [\"Coffee Shop\", \"Bakery\", \"Tea House\"]\n\ndistrict_centers = [\n    (40.7128, -74.0060),  # Financial District\n    (40.7200, -74.0010),  # Tribeca\n    (40.7262, -74.0018),  # SoHo West\n    (40.7248, -73.9978),  # SoHo East\n    (40.7300, -73.9938),  # NoHo\n    (40.7362, -73.9905),  # East Village\n    (40.7415, -73.9988),  # West Village\n    (40.7478, -73.9855),  # Gramercy / Union Square\n]\n\nlats = Float64[]\nlons = Float64[]\ncat_ids = Int[]\n\nfor (clat, clon) in district_centers\n    n = rand(22:42)\n    for _ in 1:n\n        push!(lats, clat + randn() * 0.0033)\n        push!(lons, clon + randn() * 0.0033)\n        push!(cat_ids, rand(1:3))\n    end\nend\n\n# Grid-based spatial clustering: 0.007° cells (~650 m)\nconst GRID_DEG      = 0.007\nconst MIN_CLUSTER_N = 4\n\ncell_map = Dict{Tuple{Int,Int}, Vector{Int}}()\nfor i in eachindex(lats)\n    key = (floor(Int, lats[i] / GRID_DEG), floor(Int, lons[i] / GRID_DEG))\n    push!(get!(cell_map, key, Int[]), i)\nend\n\nsolo_idx     = Int[]\ncluster_rows = Tuple{Float64,Float64,Int,Int}[]   # (lat, lon, npts, dom_cat)\n\nfor (_, idxs) in cell_map\n    if length(idxs) < MIN_CLUSTER_N\n        append!(solo_idx, idxs)\n    else\n        clat     = mean(lats[idxs])\n        clon     = mean(lons[idxs])\n        npts     = length(idxs)\n        cat_cnts = [sum(cat_ids[j] == c for j in idxs) for c in 1:3]\n        dom_cat  = argmax(cat_cnts)\n        push!(cluster_rows, (clat, clon, npts, dom_cat))\n    end\nend\n\n# Figure\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"map-marker-clustered · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Longitude\",\n    ylabel            = \"Latitude\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xticklabelsize    = 11,\n    yticklabelsize    = 11,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK_SOFT,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = LAND_COLOR,\n    topspinevisible   = false,\n    rightspinevisible = false,\n    leftspinecolor    = INK_SOFT,\n    bottomspinecolor  = INK_SOFT,\n    xgridcolor        = RGBAf(Float32(INK.r), Float32(INK.g), Float32(INK.b), 0.10f0),\n    ygridcolor        = RGBAf(Float32(INK.r), Float32(INK.g), Float32(INK.b), 0.10f0),\n    xminorgridvisible = false,\n    yminorgridvisible = false,\n    limits            = ((-74.017f0, -73.963f0), (40.702f0, 40.760f0)),\n)\n\n# Basemap layer 1: simplified Manhattan street grid (~block spacing)\nfor lat in 40.703:0.0025:40.759\n    hlines!(ax, [lat]; color = STREET_COLOR, linewidth = 0.6)\nend\nfor lon in -74.016:0.003:-73.964\n    vlines!(ax, [lon]; color = STREET_COLOR, linewidth = 0.6)\nend\n\n# Basemap layer 2: water bodies (drawn after streets so they cover the grid at the edges)\n# Hudson River (west) and East River (east) as filled rectangles\npoly!(ax, Rect2f(-74.017f0, 40.702f0, 0.010f0, 0.058f0); color = WATER_COLOR, strokewidth = 0)\npoly!(ax, Rect2f(-73.978f0, 40.702f0, 0.015f0, 0.058f0); color = WATER_COLOR, strokewidth = 0)\n\n# Water body labels\ntext!(ax, -74.012f0, 40.728f0;\n    text = \"Hudson\\nRiver\",\n    align = (:center, :center),\n    fontsize = 9,\n    color = WATER_LABEL,\n)\ntext!(ax, -73.970f0, 40.728f0;\n    text = \"East\\nRiver\",\n    align = (:center, :center),\n    fontsize = 9,\n    color = WATER_LABEL,\n)\n\n# Individual (unclustered) markers, colored by category\nfor (i, cat_name) in enumerate(CAT_NAMES)\n    idx = filter(j -> cat_ids[j] == i, solo_idx)\n    isempty(idx) && continue\n    scatter!(ax, lons[idx], lats[idx];\n        color       = IMPRINT[i],\n        markersize  = 15,\n        strokewidth = 1.5,\n        strokecolor = PAGE_BG,\n        label       = cat_name,\n    )\nend\n\n# Cluster bubbles: size proportional to point count, colored by dominant category\nfor (clat, clon, npts, dom_cat) in cluster_rows\n    bubble_sz = clamp(npts * 2.8 + 18, 28, 72)\n    scatter!(ax, [clon], [clat];\n        color       = (IMPRINT[dom_cat], 0.80),\n        markersize  = bubble_sz,\n        strokewidth = 2.5,\n        strokecolor = IMPRINT[dom_cat],\n    )\n    text!(ax, clon, clat;\n        text     = string(npts),\n        align    = (:center, :center),\n        fontsize = 14,\n        color    = colorant\"#FFFFFF\",\n    )\nend\n\n# Legend for individual-marker categories\naxislegend(ax;\n    position        = :rb,\n    labelsize       = 12,\n    framecolor      = INK_SOFT,\n    backgroundcolor = ELEVATED_BG,\n    labelcolor      = INK,\n    padding         = (10, 10, 10, 10),\n)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}