{"spec_id":"bar-spine","library":"makie","language":"julia","code":"# anyplot.ai\n# bar-spine: Spine Plot for Two-Variable Proportions\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\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\"\n\n# Imprint palette — semantic exception applies: retained/churned is a\n# good/bad pair the legend labels explicitly, so green/red replace the\n# ordinal 1st/2nd slots (see default-style-guide.md \"Semantic exception\").\nconst RETAINED_COLOR = colorant\"#009E73\"  # good outcome — brand green\nconst CHURNED_COLOR  = colorant\"#AE3030\"  # bad outcome — matte red anchor\n\n# --- Data -----------------------------------------------------------------\n# Customer churn by subscription tier: bar width = tier size (marginal\n# count), segment heights = conditional retained/churned proportions.\ntiers            = [\"Basic\", \"Standard\", \"Premium\", \"Enterprise\"]\nretained_counts  = [420, 650, 540, 310]\nchurned_counts   = [380, 250, 110, 40]\ntier_totals      = retained_counts .+ churned_counts\nretained_frac    = retained_counts ./ tier_totals\n\nedges   = vcat(0, cumsum(tier_totals))\nlefts   = edges[1:end-1]\nrights  = edges[2:end]\ncenters = (lefts .+ rights) ./ 2\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              = \"bar-spine · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Subscription Tier  (bar width ∝ customer count)\",\n    ylabel             = \"Share of Customers\",\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             = (centers, tiers),\n    yticks             = (0:0.25:1, [\"0%\", \"25%\", \"50%\", \"75%\", \"100%\"]),\n)\nxlims!(ax, 0, edges[end])\nylims!(ax, 0, 1)\n\nfor i in eachindex(tiers)\n    left, right, split = lefts[i], rights[i], retained_frac[i]\n\n    poly!(ax, Rect2f(left, 0, right - left, split);\n          color = RETAINED_COLOR, strokecolor = PAGE_BG, strokewidth = 3)\n    poly!(ax, Rect2f(left, split, right - left, 1 - split);\n          color = CHURNED_COLOR, strokecolor = PAGE_BG, strokewidth = 3)\n\n    text!(ax, (left + right) / 2, split / 2;\n          text = \"$(round(Int, split * 100))%\", align = (:center, :center),\n          color = :white, fontsize = 15)\n    text!(ax, (left + right) / 2, split + (1 - split) / 2;\n          text = \"$(round(Int, (1 - split) * 100))%\", align = (:center, :center),\n          color = :white, fontsize = 15)\nend\n\nLegend(\n    fig[1, 2],\n    [PolyElement(color = RETAINED_COLOR), PolyElement(color = CHURNED_COLOR)],\n    [\"Retained\", \"Churned\"];\n    labelcolor = INK_SOFT,\n    framevisible = false,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}