{"spec_id":"boxen-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# boxen-basic: Basic Boxen Plot (Letter-Value Plot)\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-09-01\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 INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n]\n\n# --- Data --------------------------------------------------------------------\n# Response-time distributions (ms) across server endpoints, right-skewed\n# (log-normal) as is typical for latency data at high request volumes.\n# Traffic volume varies sharply by endpoint (25k req for the public gateway\n# down to 1.5k for the low-traffic payment path) so letter-value depth below\n# actually spans its full range instead of clamping at the ceiling.\nendpoints = [\"API Gateway\", \"Auth Service\", \"Payment Service\", \"Search Service\"]\nn_points  = [25000, 8000, 1500, 12000]\nmu        = [3.9, 4.3, 4.6, 4.1]\nsigma     = [0.35, 0.45, 0.55, 0.40]\nresponse_times = [exp.(randn(n_points[j]) .* sigma[j] .+ mu[j]) for j in eachindex(endpoints)]\n\n# --- Letter values (Tukey) ---------------------------------------------------\n# Level i covers the central probability mass [2^-(i+1), 1 - 2^-(i+1)]:\n# i=1 -> fourths (quartiles), i=2 -> eighths, i=3 -> sixteenths, and so on.\n# Depth scales with sample size: Payment Service's ~1.5k points stop at 4\n# levels while API Gateway's 25k points reach the 8-level ceiling.\nletter_value_depth(n) = clamp(floor(Int, log2(n)) - 6, 3, 8)\nLEVEL_NAMES = [\"Fourths\", \"Eighths\", \"16ths\", \"32nds\", \"64ths\", \"128ths\", \"256ths\", \"512ths\"]\n\nfunction letter_values(values, k)\n    lo = Vector{Float64}(undef, k)\n    hi = Vector{Float64}(undef, k)\n    for i in 1:k\n        p = 2.0^(-(i + 1))\n        lo[i] = quantile(values, p)\n        hi[i] = quantile(values, 1 - p)\n    end\n    lo, hi\nend\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              = \"boxen-basic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Endpoint\",\n    ylabel             = \"Response Time (ms)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\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    xticks             = (1:length(endpoints), endpoints),\n)\n\nbase_half_width = 0.34\nshrink          = 0.78\noutlier_alpha   = 0.35\n\nfor (j, name) in enumerate(endpoints)\n    values = response_times[j]\n    color  = IMPRINT_PALETTE[j]\n    k      = letter_value_depth(length(values))\n    lo, hi = letter_values(values, k)\n\n    # Nested boxes, widest (i=1, fourths) drawn last so it sits on top of the\n    # taller-but-narrower outer boxes — the classic tapering boxen silhouette.\n    for i in k:-1:1\n        half_width = base_half_width * shrink^(i - 1)\n        shade_t    = (i - 1) / max(k - 1, 1) * 0.65\n        fill_color = weighted_color_mean(1 - shade_t, color, PAGE_BG)\n        poly!(\n            ax,\n            Rect2f(j - half_width, lo[i], 2 * half_width, hi[i] - lo[i]);\n            color       = fill_color,\n            strokewidth = 1,\n            strokecolor = PAGE_BG,\n        )\n    end\n\n    # Median line across the widest (fourths) box\n    median_val = median(values)\n    lines!(\n        ax,\n        [j - base_half_width, j + base_half_width], [median_val, median_val];\n        color     = INK,\n        linewidth = 3.0,\n    )\n\n    # Outliers beyond the deepest letter value\n    lo_deep, hi_deep = lo[k], hi[k]\n    outliers = filter(v -> v < lo_deep || v > hi_deep, values)\n    if !isempty(outliers)\n        jitter = (rand(length(outliers)) .- 0.5) .* (base_half_width * 0.5)\n        scatter!(\n            ax, fill(j, length(outliers)) .+ jitter, outliers;\n            color       = RGBAf(color.r, color.g, color.b, outlier_alpha),\n            markersize  = 5,\n            strokewidth = 0,\n        )\n    end\nend\n\n# Depth legend: swatch strip mapping shade darkness to letter-value level,\n# built with the same weighted_color_mean ramp used for the boxes so the\n# legend colors match the plot exactly. Uses the deepest category (API\n# Gateway, k = max_k) as the reference hue.\nmax_k = maximum(letter_value_depth(n) for n in n_points)\nlegend_elems = [\n    PolyElement(\n        color       = weighted_color_mean(1 - (i - 1) / max(max_k - 1, 1) * 0.65, IMPRINT_PALETTE[1], PAGE_BG),\n        strokecolor = PAGE_BG,\n        strokewidth = 1,\n    )\n    for i in 1:max_k\n]\nLegend(\n    fig[1, 2],\n    legend_elems,\n    LEVEL_NAMES[1:max_k],\n    \"Quantile Depth\";\n    labelcolor      = INK,\n    titlecolor      = INK,\n    labelsize       = 12,\n    titlesize       = 13,\n    framevisible    = false,\n    backgroundcolor = PAGE_BG,\n    tellheight      = false,\n)\n\nLabel(\n    fig[2, 1:2],\n    \"Nested boxes taper from the fourths (25–75%) out to the deepest letter value \" *\n    \"(scaled to sample size); dots mark outliers beyond the deepest box.\";\n    fontsize = 12,\n    color    = INK_SOFT,\n)\n\ncolsize!(fig.layout, 2, Relative(0.14))\nrowsize!(fig.layout, 2, Auto(0.08))\n\n# --- Save ----------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}