|
| 1 | +# anyplot.ai |
| 2 | +# area-elevation-profile: Terrain Elevation Profile Along Transect |
| 3 | +# Library: makie 0.22.10 | Julia 1.11.9 |
| 4 | +# Quality: 90/100 | Created: 2026-06-10 |
| 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 FILL_ALPHA = THEME == "light" ? 0.28f0 : 0.42f0 |
| 20 | + |
| 21 | +const IMPRINT_PALETTE = [ |
| 22 | + colorant"#009E73", # 1 — brand green (first series, always) |
| 23 | + colorant"#C475FD", # 2 — lavender |
| 24 | + colorant"#4467A3", # 3 — blue |
| 25 | + colorant"#BD8233", # 4 — ochre |
| 26 | + colorant"#AE3030", # 5 — matte red (semantic: bad/loss) |
| 27 | + colorant"#2ABCCD", # 6 — cyan |
| 28 | + colorant"#954477", # 7 — rose |
| 29 | + colorant"#99B314", # 8 — lime |
| 30 | +] |
| 31 | + |
| 32 | +# Data: Fictional alpine traverse — 80 km route across passes and summits |
| 33 | +wp_dist = [ 0.0, 10.0, 18.0, 26.0, 35.0, 45.0, 60.0, 70.0, 80.0] |
| 34 | +wp_elev = [950.0, 1600.0, 2750.0, 2050.0, 3180.0, 2600.0, 2900.0, 2150.0, 1050.0] |
| 35 | + |
| 36 | +n = 320 |
| 37 | +distance = collect(range(0.0, 80.0, length=n)) |
| 38 | + |
| 39 | +function lerp_elev(d) |
| 40 | + for i in 2:length(wp_dist) |
| 41 | + if d <= wp_dist[i] |
| 42 | + t = (d - wp_dist[i-1]) / (wp_dist[i] - wp_dist[i-1]) |
| 43 | + return wp_elev[i-1] + t * (wp_elev[i] - wp_elev[i-1]) |
| 44 | + end |
| 45 | + end |
| 46 | + return wp_elev[end] |
| 47 | +end |
| 48 | + |
| 49 | +elev_base = lerp_elev.(distance) |
| 50 | + |
| 51 | +function gauss_smooth(v, sigma) |
| 52 | + nv = length(v) |
| 53 | + ks = round(Int, 3 * sigma) |
| 54 | + result = similar(v, Float64) |
| 55 | + for i in 1:nv |
| 56 | + s = 0.0; w = 0.0 |
| 57 | + for j in max(1, i - ks):min(nv, i + ks) |
| 58 | + wj = exp(-0.5 * ((j - i) / sigma)^2) |
| 59 | + s += wj * v[j]; w += wj |
| 60 | + end |
| 61 | + result[i] = s / w |
| 62 | + end |
| 63 | + return result |
| 64 | +end |
| 65 | + |
| 66 | +# Terrain micro-texture via superimposed sinusoids (no cumulative drift) |
| 67 | +terrain_noise = zeros(n) |
| 68 | +for freq in [4, 9, 17, 29] |
| 69 | + terrain_noise .+= randn() .* sin.(range(0.0, Float64(freq) * π, length=n) .+ rand() * 2π) |
| 70 | +end |
| 71 | +terrain_noise = gauss_smooth(terrain_noise, 3.0) .* 28.0 |
| 72 | + |
| 73 | +elevation = elev_base .+ terrain_noise |
| 74 | + |
| 75 | +# Key landmarks — pairs of (distance_km, label) |
| 76 | +lm_dists = [ 0.0, 18.0, 35.0, 45.0, 60.0, 80.0] |
| 77 | +lm_labels = ["Trailhead", "First Pass", "Summit Peak", "Col de Neige", "Grand Col", "Trail End"] |
| 78 | +lm_idx = [argmin(abs.(distance .- d)) for d in lm_dists] |
| 79 | +lm_elevs = elevation[lm_idx] |
| 80 | +lm_offsets = [180.0, 180.0, 200.0, -230.0, 180.0, 180.0] |
| 81 | +lm_valigns = [:bottom, :bottom, :bottom, :top, :bottom, :bottom] |
| 82 | +lm_haligns = [:left, :center, :center, :center, :center, :right ] |
| 83 | + |
| 84 | +const TITLE = "area-elevation-profile · julia · makie · anyplot.ai" |
| 85 | +title_fontsize = 20 # 51 chars < 67 baseline — no scaling needed |
| 86 | + |
| 87 | +# Figure |
| 88 | +fig = Figure( |
| 89 | + size = (1600, 900), |
| 90 | + fontsize = 14, |
| 91 | + backgroundcolor = PAGE_BG, |
| 92 | +) |
| 93 | + |
| 94 | +ax = Axis( |
| 95 | + fig[1, 1]; |
| 96 | + title = TITLE, |
| 97 | + titlesize = title_fontsize, |
| 98 | + titlecolor = INK, |
| 99 | + xlabel = "Distance (km)", |
| 100 | + ylabel = "Elevation (m)", |
| 101 | + xlabelsize = 14, |
| 102 | + ylabelsize = 14, |
| 103 | + xlabelcolor = INK, |
| 104 | + ylabelcolor = INK, |
| 105 | + xticklabelsize = 12, |
| 106 | + yticklabelsize = 12, |
| 107 | + xticklabelcolor = INK_SOFT, |
| 108 | + yticklabelcolor = INK_SOFT, |
| 109 | + xtickcolor = INK_SOFT, |
| 110 | + ytickcolor = INK_SOFT, |
| 111 | + backgroundcolor = PAGE_BG, |
| 112 | + topspinevisible = false, |
| 113 | + rightspinevisible = false, |
| 114 | + leftspinecolor = INK_SOFT, |
| 115 | + bottomspinecolor = INK_SOFT, |
| 116 | + xgridvisible = false, |
| 117 | + ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.12), |
| 118 | + yminorgridvisible = false, |
| 119 | + xminorgridvisible = false, |
| 120 | + limits = (0.0, 82.0, 500.0, 3700.0), |
| 121 | +) |
| 122 | + |
| 123 | +# Filled terrain silhouette (Imprint brand green; alpha higher on dark for equal visual weight) |
| 124 | +band!(ax, distance, fill(500.0, n), elevation; |
| 125 | + color = RGBAf(IMPRINT_PALETTE[1].r, IMPRINT_PALETTE[1].g, IMPRINT_PALETTE[1].b, FILL_ALPHA)) |
| 126 | + |
| 127 | +# Profile line |
| 128 | +lines!(ax, distance, elevation; |
| 129 | + color = IMPRINT_PALETTE[1], linewidth = 2.5) |
| 130 | + |
| 131 | +# Landmark annotations — Summit Peak gets extra emphasis as the highest point |
| 132 | +for i in 1:length(lm_dists) |
| 133 | + d = lm_dists[i] |
| 134 | + name = lm_labels[i] |
| 135 | + elev = lm_elevs[i] |
| 136 | + off = lm_offsets[i] |
| 137 | + va = lm_valigns[i] |
| 138 | + |
| 139 | + is_summit = (name == "Summit Peak") |
| 140 | + msize = is_summit ? 13 : 9 |
| 141 | + mstroke = is_summit ? 2.5f0 : 1.5f0 |
| 142 | + |
| 143 | + # Connector line from terrain point to label position |
| 144 | + lines!(ax, [d, d], [elev, elev + off]; |
| 145 | + color = RGBAf(INK_MUTED.r, INK_MUTED.g, INK_MUTED.b, 0.5), |
| 146 | + linewidth = 0.8) |
| 147 | + |
| 148 | + # Dot marker at elevation (Summit Peak larger for focal hierarchy) |
| 149 | + scatter!(ax, [d], [elev]; |
| 150 | + color = IMPRINT_PALETTE[1], markersize = msize, |
| 151 | + strokecolor = PAGE_BG, strokewidth = mstroke) |
| 152 | + |
| 153 | + # Label: name + elevation; 13 pt for mobile readability |
| 154 | + text!(ax, d, elev + off; |
| 155 | + text = "$(name)\n$(round(Int, elev)) m", |
| 156 | + align = (lm_haligns[i], va), |
| 157 | + fontsize = 13, |
| 158 | + color = INK_SOFT) |
| 159 | +end |
| 160 | + |
| 161 | +# Vertical exaggeration note (bottom-right) |
| 162 | +text!(ax, 80.0, 540.0; |
| 163 | + text = "VE ≈ 15×", |
| 164 | + align = (:right, :bottom), |
| 165 | + fontsize = 13, |
| 166 | + color = INK_MUTED) |
| 167 | + |
| 168 | +save("plot-$(THEME).png", fig; px_per_unit = 2) |
0 commit comments