{"spec_id":"raincloud-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# raincloud-basic: Basic Raincloud Plot\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-05-27\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\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 ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green (Baseline)\n    colorant\"#C475FD\",  # 2 — lavender    (Variant A)\n    colorant\"#4467A3\",  # 3 — blue        (Variant B)\n    colorant\"#BD8233\",  # 4 — ochre       (Variant C)\n]\n\n# --- Data: Page load times (ms) across 4 A/B test variants ------------------\nconst N_PER_CAT  = 110\nconst N_HALF     = N_PER_CAT ÷ 2\n\n# Distributions differ in mean, spread, and shape:\n#   Baseline:  slow control, right-skewed tail\n#   Variant A: bimodal — cache hit vs cache miss\n#   Variant B: fastest, tighter spread\n#   Variant C: moderate mean with a heavier right tail\nconst RAW_CATEGORIES = [\"Baseline\", \"Variant A\", \"Variant B\", \"Variant C\"]\nconst RAW_DATA = [\n    720.0 .+ 110.0 .* randn(N_PER_CAT) .+ 60.0 .* abs.(randn(N_PER_CAT)),\n    vcat(380.0 .+  60.0 .* randn(N_HALF),\n         620.0 .+  70.0 .* randn(N_PER_CAT - N_HALF)),\n    460.0 .+  75.0 .* randn(N_PER_CAT) .+ 25.0 .* abs.(randn(N_PER_CAT)),\n    590.0 .+  95.0 .* randn(N_PER_CAT) .+ 90.0 .* abs.(randn(N_PER_CAT)),\n]\n\n# Sort categories by median ascending — fastest at bottom, slowest at top —\n# so the eye reads the A/B-test winners from the baseline upward.\nconst _ORDER     = sortperm(median.(RAW_DATA))\nconst CATEGORIES = RAW_CATEGORIES[_ORDER]\nconst DATA_BY_CAT = RAW_DATA[_ORDER]\nconst PALETTE_BY_CAT = [IMPRINT_PALETTE[findfirst(==(c), RAW_CATEGORIES)] for c in CATEGORIES]\n\n# --- Figure -----------------------------------------------------------------\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"raincloud-basic · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Page Load Time (ms)\",\n    ylabel            = \"\",\n    xlabelcolor       = INK,\n    xlabelsize        = 14,\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = PAGE_BG,\n    topspinevisible   = false,\n    rightspinevisible = false,\n    leftspinevisible  = false,\n    bottomspinecolor  = INK_SOFT,\n    yticks            = (1:length(CATEGORIES), CATEGORIES),\n    xgridvisible      = true,\n    ygridvisible      = false,\n    xgridcolor        = RGBAf(red(INK), green(INK), blue(INK), 0.12),\n    yminorgridvisible = false,\n    xminorgridvisible = false,\n)\n\n# --- Raincloud layout knobs (y-units, category spacing = 1) -----------------\ncloud_gap    = 0.05   # gap between baseline and start of cloud\ncloud_height = 0.45   # max height of cloud above baseline\nbox_height   = 0.12   # full vertical extent of the box (centered on baseline)\nrain_gap     = 0.14   # gap between baseline and start of rain\nrain_spread  = 0.18   # vertical spread of jittered rain points\n\n# --- Plot cloud + box + rain for each category ------------------------------\nfor (i, vals) in enumerate(DATA_BY_CAT)\n    baseline = Float64(i)\n    color    = PALETTE_BY_CAT[i]\n    n        = length(vals)\n\n    # Summary statistics — drive both boxplot and KDE bandwidth\n    q1, med, q3 = quantile(vals, [0.25, 0.5, 0.75])\n    iqr_v       = q3 - q1\n    sd_v        = std(vals)\n\n    # Inline Gaussian KDE on a 256-point grid (Silverman + IQR fallback)\n    vmin, vmax = extrema(vals)\n    pad        = 0.06 * (vmax - vmin)\n    grid       = collect(range(vmin - pad, vmax + pad; length = 256))\n    bw         = 0.9 * min(sd_v, iqr_v / 1.34) * n^(-1/5)\n    sqdiffs    = (grid' .- vals) .^ 2 ./ (2 * bw^2)\n    dens       = vec(sum(exp.(-sqdiffs); dims = 1)) ./ (n * bw * sqrt(2π))\n    dens_h     = dens ./ maximum(dens) .* cloud_height\n\n    # Cloud — half-violin rising ABOVE the baseline\n    base_y = fill(baseline + cloud_gap, length(grid))\n    top_y  = baseline .+ cloud_gap .+ dens_h\n    band!(ax, grid, base_y, top_y; color = (color, 0.55))\n    lines!(ax, grid, top_y; color = color, linewidth = 1.6)\n\n    # Boxplot — sits ON the baseline (whiskers, box, median)\n    lo_w = max(minimum(vals), q1 - 1.5 * iqr_v)\n    hi_w = min(maximum(vals), q3 + 1.5 * iqr_v)\n    lines!(ax, [lo_w, hi_w], [baseline, baseline];\n        color = INK_SOFT, linewidth = 1.2)\n    cap_h = box_height * 0.35\n    lines!(ax, [lo_w, lo_w], [baseline - cap_h, baseline + cap_h];\n        color = INK_SOFT, linewidth = 1.2)\n    lines!(ax, [hi_w, hi_w], [baseline - cap_h, baseline + cap_h];\n        color = INK_SOFT, linewidth = 1.2)\n    poly!(ax, Rect2f(q1, baseline - box_height/2, q3 - q1, box_height);\n        color = ELEVATED_BG, strokecolor = INK_SOFT, strokewidth = 1.3)\n    lines!(ax, [med, med], [baseline - box_height/2, baseline + box_height/2];\n        color = INK, linewidth = 2.4)\n\n    # Rain — jittered points falling BELOW the baseline\n    y_rain = baseline .- rain_gap .- rand(n) .* rain_spread\n    scatter!(ax, vals, y_rain;\n        color       = (color, 0.6),\n        markersize  = 6,\n        strokewidth = 0)\nend\n\n# --- Axis limits ------------------------------------------------------------\nall_vals = vcat(DATA_BY_CAT...)\nxlims!(ax, minimum(all_vals) - 60, maximum(all_vals) + 60)\nylims!(ax, 0.45, length(CATEGORIES) + 0.65)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}