{"spec_id":"errorbar-asymmetric","library":"makie","language":"julia","code":"# anyplot.ai\n# errorbar-asymmetric: Asymmetric Error Bars Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Random\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 INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\nconst BRAND    = colorant\"#009E73\"  # Imprint palette position 1\nconst ANYPLOT_AMBER = colorant\"#DDCC77\"  # semantic anchor: draws the eye to the callout station\n\n# --- Data ---------------------------------------------------------------\n# Airborne particulate matter (PM2.5, μg/m3) reported as station medians. The\n# underlying measurement noise is log-normal, so back-transforming the\n# uncertainty to the linear concentration scale yields a larger upper bound\n# than lower bound at every station.\nstations = [\"Riverside\", \"Lakeview\", \"Hillcrest\", \"Downtown\",\n            \"Parkside\", \"Eastgate\", \"Westbrook\", \"Northfield\"]\nn = length(stations)\npositions = 1:n\n\nmedian_conc = [34.2, 28.7, 24.1, 21.5, 17.8, 14.4, 11.2, 8.6]\nlog_sigma = 0.14 .+ 0.22 .* rand(n)\n\nerror_lower = median_conc .* (1 .- exp.(-log_sigma))\nerror_upper = median_conc .* (exp.(log_sigma) .- 1)\n\n# Station with the widest asymmetric spread gets a callout accent below.\nspread = error_upper .- error_lower\nwidest = argmax(spread)\n\n# --- Plot ---------------------------------------------------------------\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"errorbar-asymmetric · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    subtitle           = \"Bars mark the 10th–90th percentile range (log-normal back-transform)\",\n    subtitlesize       = 13,\n    subtitlecolor      = INK_MUTED,\n    xlabel             = \"Monitoring Station\",\n    ylabel             = \"PM2.5 Concentration (μg/m³)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xticks             = (positions, stations),\n    xticklabelrotation = π / 8,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible     = false,\n    rightspinevisible   = false,\n    leftspinecolor      = INK_SOFT,\n    bottomspinecolor    = INK_SOFT,\n    xgridvisible        = false,\n    ygridcolor          = RGBAf(INK.r, INK.g, INK.b, 0.15),\n)\n\nerrorbars!(ax, positions, median_conc, error_lower, error_upper;\n           color = BRAND, whiskerwidth = 18, linewidth = 2.5)\nscatter!(ax, positions, median_conc; color = BRAND, markersize = 20,\n         strokewidth = 1.4, strokecolor = PAGE_BG)\n# Redrawing just the widest-spread point on top (own scatter! call, not a\n# per-point color/size vector) avoids relying on Makie's Colorant-vector\n# color path, which is why the earlier attempt's callout failed to render.\nscatter!(ax, [positions[widest]], [median_conc[widest]]; color = ANYPLOT_AMBER,\n         markersize = 26, strokewidth = 1.4, strokecolor = PAGE_BG)\n\nhalign = widest <= 2 ? :left : widest >= n - 1 ? :right : :center\ntext!(ax, positions[widest], median_conc[widest] + error_upper[widest];\n      text = \"widest spread: +$(round(error_upper[widest], digits = 1)) / −$(round(error_lower[widest], digits = 1)) μg/m³\",\n      align = (halign, :bottom), offset = (0, 10), fontsize = 12, color = INK_MUTED)\n\nylims!(ax, 0, maximum(median_conc .+ error_upper) * 1.18)\n\n# --- Save -----------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}