diff --git a/plots/area-elevation-profile/implementations/julia/makie.jl b/plots/area-elevation-profile/implementations/julia/makie.jl new file mode 100644 index 0000000000..1eda409a0a --- /dev/null +++ b/plots/area-elevation-profile/implementations/julia/makie.jl @@ -0,0 +1,168 @@ +# anyplot.ai +# area-elevation-profile: Terrain Elevation Profile Along Transect +# Library: makie 0.22.10 | Julia 1.11.9 +# Quality: 90/100 | Created: 2026-06-10 + +using CairoMakie +using Colors +using Random + +Random.seed!(42) + +# Theme tokens +const THEME = get(ENV, "ANYPLOT_THEME", "light") +const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" +const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420" +const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" +const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" +const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" +const FILL_ALPHA = THEME == "light" ? 0.28f0 : 0.42f0 + +const IMPRINT_PALETTE = [ + colorant"#009E73", # 1 — brand green (first series, always) + colorant"#C475FD", # 2 — lavender + colorant"#4467A3", # 3 — blue + colorant"#BD8233", # 4 — ochre + colorant"#AE3030", # 5 — matte red (semantic: bad/loss) + colorant"#2ABCCD", # 6 — cyan + colorant"#954477", # 7 — rose + colorant"#99B314", # 8 — lime +] + +# Data: Fictional alpine traverse — 80 km route across passes and summits +wp_dist = [ 0.0, 10.0, 18.0, 26.0, 35.0, 45.0, 60.0, 70.0, 80.0] +wp_elev = [950.0, 1600.0, 2750.0, 2050.0, 3180.0, 2600.0, 2900.0, 2150.0, 1050.0] + +n = 320 +distance = collect(range(0.0, 80.0, length=n)) + +function lerp_elev(d) + for i in 2:length(wp_dist) + if d <= wp_dist[i] + t = (d - wp_dist[i-1]) / (wp_dist[i] - wp_dist[i-1]) + return wp_elev[i-1] + t * (wp_elev[i] - wp_elev[i-1]) + end + end + return wp_elev[end] +end + +elev_base = lerp_elev.(distance) + +function gauss_smooth(v, sigma) + nv = length(v) + ks = round(Int, 3 * sigma) + result = similar(v, Float64) + for i in 1:nv + s = 0.0; w = 0.0 + for j in max(1, i - ks):min(nv, i + ks) + wj = exp(-0.5 * ((j - i) / sigma)^2) + s += wj * v[j]; w += wj + end + result[i] = s / w + end + return result +end + +# Terrain micro-texture via superimposed sinusoids (no cumulative drift) +terrain_noise = zeros(n) +for freq in [4, 9, 17, 29] + terrain_noise .+= randn() .* sin.(range(0.0, Float64(freq) * π, length=n) .+ rand() * 2π) +end +terrain_noise = gauss_smooth(terrain_noise, 3.0) .* 28.0 + +elevation = elev_base .+ terrain_noise + +# Key landmarks — pairs of (distance_km, label) +lm_dists = [ 0.0, 18.0, 35.0, 45.0, 60.0, 80.0] +lm_labels = ["Trailhead", "First Pass", "Summit Peak", "Col de Neige", "Grand Col", "Trail End"] +lm_idx = [argmin(abs.(distance .- d)) for d in lm_dists] +lm_elevs = elevation[lm_idx] +lm_offsets = [180.0, 180.0, 200.0, -230.0, 180.0, 180.0] +lm_valigns = [:bottom, :bottom, :bottom, :top, :bottom, :bottom] +lm_haligns = [:left, :center, :center, :center, :center, :right ] + +const TITLE = "area-elevation-profile · julia · makie · anyplot.ai" +title_fontsize = 20 # 51 chars < 67 baseline — no scaling needed + +# Figure +fig = Figure( + size = (1600, 900), + fontsize = 14, + backgroundcolor = PAGE_BG, +) + +ax = Axis( + fig[1, 1]; + title = TITLE, + titlesize = title_fontsize, + titlecolor = INK, + xlabel = "Distance (km)", + ylabel = "Elevation (m)", + xlabelsize = 14, + ylabelsize = 14, + xlabelcolor = INK, + ylabelcolor = INK, + xticklabelsize = 12, + yticklabelsize = 12, + xticklabelcolor = INK_SOFT, + yticklabelcolor = INK_SOFT, + xtickcolor = INK_SOFT, + ytickcolor = INK_SOFT, + backgroundcolor = PAGE_BG, + topspinevisible = false, + rightspinevisible = false, + leftspinecolor = INK_SOFT, + bottomspinecolor = INK_SOFT, + xgridvisible = false, + ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.12), + yminorgridvisible = false, + xminorgridvisible = false, + limits = (0.0, 82.0, 500.0, 3700.0), +) + +# Filled terrain silhouette (Imprint brand green; alpha higher on dark for equal visual weight) +band!(ax, distance, fill(500.0, n), elevation; + color = RGBAf(IMPRINT_PALETTE[1].r, IMPRINT_PALETTE[1].g, IMPRINT_PALETTE[1].b, FILL_ALPHA)) + +# Profile line +lines!(ax, distance, elevation; + color = IMPRINT_PALETTE[1], linewidth = 2.5) + +# Landmark annotations — Summit Peak gets extra emphasis as the highest point +for i in 1:length(lm_dists) + d = lm_dists[i] + name = lm_labels[i] + elev = lm_elevs[i] + off = lm_offsets[i] + va = lm_valigns[i] + + is_summit = (name == "Summit Peak") + msize = is_summit ? 13 : 9 + mstroke = is_summit ? 2.5f0 : 1.5f0 + + # Connector line from terrain point to label position + lines!(ax, [d, d], [elev, elev + off]; + color = RGBAf(INK_MUTED.r, INK_MUTED.g, INK_MUTED.b, 0.5), + linewidth = 0.8) + + # Dot marker at elevation (Summit Peak larger for focal hierarchy) + scatter!(ax, [d], [elev]; + color = IMPRINT_PALETTE[1], markersize = msize, + strokecolor = PAGE_BG, strokewidth = mstroke) + + # Label: name + elevation; 13 pt for mobile readability + text!(ax, d, elev + off; + text = "$(name)\n$(round(Int, elev)) m", + align = (lm_haligns[i], va), + fontsize = 13, + color = INK_SOFT) +end + +# Vertical exaggeration note (bottom-right) +text!(ax, 80.0, 540.0; + text = "VE ≈ 15×", + align = (:right, :bottom), + fontsize = 13, + color = INK_MUTED) + +save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/area-elevation-profile/metadata/julia/makie.yaml b/plots/area-elevation-profile/metadata/julia/makie.yaml new file mode 100644 index 0000000000..d2417787ce --- /dev/null +++ b/plots/area-elevation-profile/metadata/julia/makie.yaml @@ -0,0 +1,244 @@ +library: makie +language: julia +specification_id: area-elevation-profile +created: '2026-06-10T06:06:38Z' +updated: '2026-06-10T06:23:23Z' +generated_by: claude-sonnet +workflow_run: 27256310300 +issue: 4578 +language_version: 1.11.9 +library_version: 0.22.10 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/julia/makie/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/julia/makie/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 90 +review: + strengths: + - 'Perfect spec compliance: filled silhouette, landmark annotations with connector + lines, VE note, and start/end labels all present' + - Both light and dark renders are fully theme-correct and readable; no dark-on-dark + failures detected + - Summit Peak receives intentional visual hierarchy via larger marker (size 13 vs + 9) and taller connector, creating a clear focal point + - Idiomatic use of band!() for terrain silhouette — the correct Makie primitive + for this plot type + - Realistic alpine traverse scenario with plausible French/English mountain naming; + neutral geography context + weaknesses: + - Helper functions lerp_elev and gauss_smooth break the flat KISS Imports→Data→Plot→Save + structure — consider inlining or using simpler parameterized noise without named + functions + - Trail End label at x=80 with right-alignment is tight against the right axis boundary + — extend x-axis limit from 82.0 to 84.0 to give the label more breathing room + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct, not pure white + Chrome: Title "area-elevation-profile · julia · makie · anyplot.ai" in dark ink, well-centered. Axis labels "Distance (km)" and "Elevation (m)" in dark INK, proportional at 14pt. Tick labels in INK_SOFT (dark-ish) at 12pt — all readable. Horizontal-only y-grid lines subtle. L-shaped spines (top/right removed). + Data: Semi-transparent brand-green (#009E73, ~28% alpha) filled area below the solid #009E73 profile line. Six landmark annotations (Trailhead 983m, First Pass 2753m, Summit Peak 3221m, Col de Neige 2597m, Grand Col 2888m, Trail End 1039m) with thin connector lines and INK_SOFT text. VE ≈ 15× note bottom-right in INK_MUTED. First (and only) series is #009E73. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct, not pure black + Chrome: Title and axis labels in light #F0EFE8 (INK on dark). Tick labels in #B8B7B0 (INK_SOFT on dark). Connector lines in muted light tone. All text clearly readable against dark background. No dark-on-dark failures. Grid lines appear as subtle lighter-toned horizontal rules. + Data: Same #009E73 profile line; fill uses higher alpha (~42%) for a richer deep-green silhouette. Data colors identical to light render — only chrome has flipped. Brand green #009E73 clearly visible against the dark background. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All sizes explicitly set; proportions balanced. Trail End label at + x=80 sits close to the right axis boundary but fully visible. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlap; Col de Neige floats cleanly below the terrain line; all + landmark labels clear. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Profile line (linewidth=2.5) and landmark markers (size 9–13) well-adapted + to 320-point density. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Single-series Imprint green; CVD-safe; full contrast via INK/INK_SOFT + tokens. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas gate passed (3200x1800). Chart fills ~70% of canvas height + with generous margins. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Distance (km) and Elevation (m) with correct units. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73; backgrounds #FAF8F1 (light) / #1A1A17 (dark); + fully theme-adaptive chrome in both renders.' + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: 'Strong design: theme-differentiated fill alpha, Summit Peak emphasis, + clean L-shaped frame — clearly above library defaults.' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Top/right spines removed; y-only grid; explicit spine colors; transparent + connector lines (0.5 alpha). Tick marks retained; minor spacing opportunities + remain. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: 'Visual hierarchy guides viewer: Summit Peak as focal apex; Trailhead/Trail + End as journey endpoints; terrain texture adds realism.' + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct filled elevation profile (band + line = terrain silhouette). + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Filled silhouette, start/end labels, landmark annotations with connector + lines, VE noted (VE ≈ 15×). + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Distance on x, Elevation on y; full 0–80 km range visible. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches required format. No legend (single series — correct). + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Multi-peak traverse with ascent, descent, col/saddles, terrain texture + — all elevation-profile aspects present. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Fictional alpine route with plausible French/English mountain names; + neutral hiking/geography context. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 80 km traverse, 983–3221 m elevation range — realistic for an alpine + traverse. + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: false + comment: Two helper functions (lerp_elev, gauss_smooth) break the flat Imports→Data→Plot→Save + pattern. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Random.seed!(42) set. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: CairoMakie, Colors, Random — all three used. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, well-commented code explaining design decisions. No fake UI. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot-$(THEME).png; uses current size= API. + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: band!(), lines!(), scatter!(), text!() composited on a single Axis + — textbook idiomatic CairoMakie. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: band!() for terrain silhouette is distinctively Makie; RGBAf() type-safe + alpha color construction leverages Makie's color system. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: + - interpolation + styling: + - alpha-blending + - edge-highlighting