|
| 1 | +# anyplot.ai |
| 2 | +# hexbin-map-geographic: Hexagonal Binning Map |
| 3 | +# Library: makie 0.22.10 | Julia 1.11.9 |
| 4 | +# Quality: 85/100 | Created: 2026-05-27 |
| 5 | + |
| 6 | +using CairoMakie |
| 7 | +using Colors |
| 8 | +using Random |
| 9 | + |
| 10 | +Random.seed!(42) |
| 11 | + |
| 12 | +# Theme tokens |
| 13 | +const THEME = get(ENV, "ANYPLOT_THEME", "light") |
| 14 | +const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" |
| 15 | +const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420" |
| 16 | +const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" |
| 17 | +const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" |
| 18 | +const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" |
| 19 | +const GRID_COLOR = THEME == "light" ? |
| 20 | + RGBAf(26/255f0, 26/255f0, 23/255f0, 0.12f0) : |
| 21 | + RGBAf(240/255f0, 239/255f0, 232/255f0, 0.12f0) |
| 22 | + |
| 23 | +# Water fill and coastline colors for the geographic base map |
| 24 | +const WATER_FILL = THEME == "light" ? |
| 25 | + RGBAf(0.58f0, 0.75f0, 0.88f0, 0.32f0) : |
| 26 | + RGBAf(0.18f0, 0.35f0, 0.56f0, 0.45f0) |
| 27 | +const COAST_LINE = THEME == "light" ? |
| 28 | + RGBAf(0.27f0, 0.46f0, 0.63f0, 0.65f0) : |
| 29 | + RGBAf(0.42f0, 0.62f0, 0.80f0, 0.52f0) |
| 30 | + |
| 31 | +# Anyplot sequential colormap for density (single-polarity) |
| 32 | +const ANYPLOT_SEQ = cgrad([colorant"#009E73", colorant"#4467A3"]) |
| 33 | + |
| 34 | +# Data: synthetic bike-share ride starts in San Francisco |
| 35 | +cluster_centers = [ |
| 36 | + (-122.4057, 37.7871), # Union Square / Downtown |
| 37 | + (-122.4194, 37.7599), # Mission District |
| 38 | + (-122.3971, 37.7749), # SoMa |
| 39 | + (-122.4352, 37.8012), # Marina |
| 40 | + (-122.4447, 37.7698), # Haight-Ashbury |
| 41 | +] |
| 42 | +cluster_weights = [0.33, 0.22, 0.20, 0.13, 0.12] |
| 43 | +cluster_stds = [0.013, 0.012, 0.011, 0.010, 0.013] |
| 44 | + |
| 45 | +n_total = 14000 |
| 46 | +lons = Float64[] |
| 47 | +lats = Float64[] |
| 48 | + |
| 49 | +for ((cx, cy), w, σ) in zip(cluster_centers, cluster_weights, cluster_stds) |
| 50 | + n_i = round(Int, n_total * w) |
| 51 | + append!(lons, cx .+ σ .* randn(n_i)) |
| 52 | + append!(lats, cy .+ σ .* randn(n_i)) |
| 53 | +end |
| 54 | + |
| 55 | +# Map extent — tightened from original to reduce empty ocean space upper-left |
| 56 | +lon_lo, lon_hi = -122.515, -122.360 |
| 57 | +lat_lo, lat_hi = 37.700, 37.835 |
| 58 | + |
| 59 | +# Clip to bounds |
| 60 | +mask = (lons .>= lon_lo) .& (lons .<= lon_hi) .& (lats .>= lat_lo) .& (lats .<= lat_hi) |
| 61 | +lons = lons[mask] |
| 62 | +lats = lats[mask] |
| 63 | + |
| 64 | +# --- Simplified geographic base map ----------------------------------------- |
| 65 | +# Polygon vertices tracing simplified SF coastlines; Makie auto-closes last→first. |
| 66 | + |
| 67 | +# SF Bay water polygon: follows the eastern SF waterfront (south→north), |
| 68 | +# then closes via the box top-right corner and right edge. |
| 69 | +bay_pts = Point2f[ |
| 70 | + (-122.374, 37.700), (-122.376, 37.748), (-122.381, 37.762), |
| 71 | + (-122.386, 37.776), (-122.393, 37.790), (-122.400, 37.800), |
| 72 | + (-122.407, 37.805), (-122.414, 37.808), (-122.432, 37.804), |
| 73 | + (-122.443, 37.803), (-122.455, 37.803), (-122.464, 37.804), |
| 74 | + (-122.479, 37.808), # Golden Gate Bridge — SF side |
| 75 | + (-122.479, 37.835), (-122.360, 37.835), (-122.360, 37.700), |
| 76 | +] |
| 77 | + |
| 78 | +# Pacific Ocean polygon: follows the western SF coast (north→south), |
| 79 | +# then closes via the box bottom-left and left edge back to the top. |
| 80 | +pac_pts = Point2f[ |
| 81 | + (-122.479, 37.808), # Golden Gate Bridge — SF side |
| 82 | + (-122.484, 37.800), (-122.493, 37.798), |
| 83 | + (-122.499, 37.794), (-122.505, 37.789), |
| 84 | + (-122.509, 37.782), (-122.511, 37.770), |
| 85 | + (-122.512, 37.748), (-122.511, 37.710), |
| 86 | + (-122.515, 37.700), (-122.515, 37.835), |
| 87 | + (-122.479, 37.835), |
| 88 | +] |
| 89 | + |
| 90 | +# --- Figure ----------------------------------------------------------------- |
| 91 | +fig = Figure( |
| 92 | + size = (1600, 900), |
| 93 | + fontsize = 14, |
| 94 | + backgroundcolor = PAGE_BG, |
| 95 | +) |
| 96 | + |
| 97 | +ax = Axis( |
| 98 | + fig[1, 1]; |
| 99 | + title = "SF Bike-Share · hexbin-map-geographic · julia · makie · anyplot.ai", |
| 100 | + titlesize = 20, |
| 101 | + titlecolor = INK, |
| 102 | + xlabel = "Longitude", |
| 103 | + ylabel = "Latitude", |
| 104 | + xlabelsize = 14, |
| 105 | + ylabelsize = 14, |
| 106 | + xticklabelsize = 12, |
| 107 | + yticklabelsize = 12, |
| 108 | + xlabelcolor = INK, |
| 109 | + ylabelcolor = INK, |
| 110 | + xticklabelcolor = INK_SOFT, |
| 111 | + yticklabelcolor = INK_SOFT, |
| 112 | + xtickcolor = INK_SOFT, |
| 113 | + ytickcolor = INK_SOFT, |
| 114 | + backgroundcolor = PAGE_BG, |
| 115 | + topspinevisible = false, |
| 116 | + rightspinevisible = false, |
| 117 | + leftspinecolor = INK_SOFT, |
| 118 | + bottomspinecolor = INK_SOFT, |
| 119 | + xgridcolor = GRID_COLOR, |
| 120 | + ygridcolor = GRID_COLOR, |
| 121 | + xminorgridvisible = false, |
| 122 | + yminorgridvisible = false, |
| 123 | +) |
| 124 | + |
| 125 | +# Base map: water bodies rendered before hexbins so land clusters stay visible |
| 126 | +poly!(ax, bay_pts; color = WATER_FILL, strokecolor = COAST_LINE, strokewidth = 1.2) |
| 127 | +poly!(ax, pac_pts; color = WATER_FILL, strokecolor = COAST_LINE, strokewidth = 1.2) |
| 128 | + |
| 129 | +# Hexagonal density map overlaid on base map |
| 130 | +hb = hexbin!(ax, lons, lats; |
| 131 | + cellsize = 0.009, |
| 132 | + colormap = ANYPLOT_SEQ, |
| 133 | + threshold = 1, |
| 134 | + strokecolor = PAGE_BG, |
| 135 | + strokewidth = 0.5, |
| 136 | +) |
| 137 | + |
| 138 | +# Colorbar |
| 139 | +Colorbar(fig[1, 2], hb; |
| 140 | + label = "Ride Count", |
| 141 | + labelsize = 13, |
| 142 | + labelcolor = INK, |
| 143 | + ticklabelsize = 11, |
| 144 | + ticklabelcolor = INK_SOFT, |
| 145 | + tickcolor = INK_SOFT, |
| 146 | + width = 18, |
| 147 | +) |
| 148 | + |
| 149 | +limits!(ax, lon_lo, lon_hi, lat_lo, lat_hi) |
| 150 | +colgap!(fig.layout, 12) |
| 151 | + |
| 152 | +save("plot-$(THEME).png", fig; px_per_unit = 2) |
0 commit comments