Scatter Map with Geographic Points — Makie.jl

A geographic scatter plot that displays data points on a world or regional map, with each point positioned by its latitude and longitude coordinates. This visualization is ideal for showing spatial distributions of events, locations, or measurements across geographic areas. Points can optionally encode additional variables through size and color, enabling multi-dimensional geographic analysis at a glance.

Scatter Map with Geographic Points rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# scatter-map-geographic: Scatter Map with Geographic Points
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 91/100 | Created: 2026-09-02

using CairoMakie
using Colors
using Random

Random.seed!(42)

# --- Theme tokens -------------------------------------------------------
THEME       = get(ENV, "ANYPLOT_THEME", "light")
PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
ELEVATED_BG = THEME == "light" ? colorant"#F2ECDD" : colorant"#2F2F29"
INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
LAND_FILL   = THEME == "light" ? colorant"#EFEADA" : colorant"#332C1F"

IMPRINT_SEQ = cgrad([colorant"#009E73", colorant"#4467A3"])

# --- Basemap: simplified continent silhouettes (equirectangular) -------
# CairoMakie has no bundled world-map dataset (GeoMakie / NaturalEarth are
# not installed), so the coastlines below are hand-digitized low-resolution
# outlines — enough vertices to read as continents, not a survey product.
north_america = Point2f.(
    [-165, -165, -155, -130, -125, -124, -117, -110, -105, -97, -90, -84,
     -97, -90, -81, -75, -70, -65, -60, -75, -95, -110, -140, -165],
    [65, 55, 58, 55, 48, 40, 33, 24, 20, 16, 14, 9,
     26, 29, 31, 35, 42, 45, 50, 60, 62, 68, 70, 65],
)
south_america = Point2f.(
    [-77, -72, -60, -50, -35, -38, -48, -57, -62, -65, -68, -72, -71, -70,
     -81, -79, -77],
    [8, 11, 8, 0, -5, -13, -25, -35, -40, -50, -55, -52, -40, -18,
     -5, 1, 8],
)
africa = Point2f.(
    [-17, -17, -10, 3, 9, 12, 12, 18, 26, 33, 40, 42, 51, 43, 38, 33, 25,
     10, 0, -6, -17],
    [21, 14, 6, 6, 4, -6, -18, -34, -33, -25, -15, 0, 12, 12, 15, 27, 32,
     37, 35, 35, 21],
)
# Europe and Asia are one contiguous landmass at this simplification level —
# a single Eurasia ring avoids a spurious seam where two separate rings
# would otherwise leave a gap near the Urals.
eurasia = Point2f.(
    [-9, -9, -2, 5, 20, 30, 60, 90, 140, 180, 160, 140, 130, 122, 110, 100,
     95, 90, 80, 70, 60, 50, 45, 40, 35, 27, 19, 15, 12, 7, 3, -9],
    [43, 53, 58, 62, 71, 70, 70, 75, 73, 66, 60, 45, 35, 30, 20, 8,
     5, 22, 8, 20, 25, 25, 15, 15, 30, 41, 40, 38, 45, 43, 39, 43],
)
australia = Point2f.(
    [113, 122, 129, 137, 142, 145, 153, 150, 140, 135, 131, 129, 122, 115, 113],
    [-22, -18, -14, -12, -11, -17, -28, -37, -38, -35, -32, -32, -34, -34, -22],
)
continents = (north_america, south_america, africa, eurasia, australia)

# --- Data: global earthquake epicenters (magnitude + depth) ------------
# Loosely follows real seismic belts (Ring of Fire, Alpide belt, mid-ocean
# ridges) so the spatial pattern reads as plausible rather than uniform noise.
clusters = [
    (-72, -20, 4, 8, 25),    # Peru-Chile subduction zone
    (-100, 17, 5, 5, 15),    # Mexico / Central America
    (-122, 38, 4, 6, 12),    # California / Pacific Northwest
    (-155, 57, 8, 3, 12),    # Alaska / Aleutians
    (140, 37, 4, 5, 20),     # Japan
    (118, -3, 8, 8, 25),     # Indonesia / Philippines
    (175, -20, 6, 10, 15),   # Tonga / New Zealand
    (35, 37, 10, 5, 15),     # Mediterranean / Anatolia
    (85, 30, 10, 4, 12),     # Himalayan front
    (-25, 5, 5, 30, 10),     # Mid-Atlantic ridge
]

longitude = Float64[]
latitude  = Float64[]
for (clon, clat, lon_spread, lat_spread, n) in clusters
    append!(longitude, clon .+ randn(n) .* lon_spread)
    append!(latitude, clat .+ randn(n) .* lat_spread)
end
latitude = clamp.(latitude, -70, 78)

n_points  = length(longitude)
magnitude = clamp.(4.3 .+ abs.(randn(n_points)) .* 0.9, 4.0, 8.3)
depth_km  = clamp.(30 .+ abs.(randn(n_points)) .* 180, 5, 700)

mag_to_size(m) = 6 + (m - 4.0) * 6.0

# --- Plot ----------------------------------------------------------------
fig = Figure(
    resolution      = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = "scatter-map-geographic · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Longitude (°)",
    ylabel             = "Latitude (°)",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = INK_SOFT,
    xticks             = -180:60:180,
    yticks             = -60:20:80,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    xminorgridvisible  = false,
    yminorgridvisible  = false,
    # Plate-carree/equirectangular (raw lon/lat with DataAspect()), not a true
    # Natural-Earth/Robinson projection — CairoMakie has no GeoMakie projection
    # support installed, so this is an intentional, documented simplification.
    aspect             = DataAspect(),
)
xlims!(ax, -180, 180)
ylims!(ax, -60, 80)

for outline in continents
    poly!(ax, outline; color = LAND_FILL, strokecolor = INK_SOFT, strokewidth = 1)
end

sc = scatter!(
    ax, longitude, latitude;
    markersize  = mag_to_size.(magnitude),
    color       = depth_km,
    colormap    = IMPRINT_SEQ,
    colorrange  = (0, 700),
    alpha       = 0.75,
    strokewidth = 0.75,
    strokecolor = PAGE_BG,
)

Colorbar(
    fig[1, 2], sc;
    label          = "Depth (km)",
    labelcolor     = INK,
    labelsize      = 13,
    ticklabelsize  = 11,
    ticklabelcolor = INK_SOFT,
    tickcolor      = INK_SOFT,
)

legend_magnitudes = [4.5, 6.0, 7.5]
legend_elements = [
    MarkerElement(marker = :circle, color = INK_SOFT, markersize = mag_to_size(m))
    for m in legend_magnitudes
]
Legend(
    fig[1, 1], legend_elements, ["M $(m)" for m in legend_magnitudes], "Magnitude";
    tellwidth       = false,
    tellheight      = false,
    halign          = :left,
    valign          = :bottom,
    margin          = (10, 10, 10, 10),
    backgroundcolor = ELEVATED_BG,
    framevisible    = true,
    framecolor      = RGBAf(INK_SOFT.r, INK_SOFT.g, INK_SOFT.b, 0.35),
    framewidth      = 1,
    labelcolor      = INK_SOFT,
    titlecolor      = INK,
    labelsize       = 12,
    titlesize       = 13,
    patchsize       = (20, 20),
)

# --- Save ------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/scatter-map-geographic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "scatter-map-geographic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/scatter-map-geographic/julia/makie",
  "hub": "https://anyplot.ai/scatter-map-geographic",
  "code_json": "https://api.anyplot.ai/specs/scatter-map-geographic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/scatter-map-geographic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/julia/makie/plot-dark.png",
  "quality_score": 91.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Scatter Map with Geographic Points on anyplot.ai.

Other implementations