{"spec_id":"violin-split","library":"makie","language":"julia","code":"# anyplot.ai\n# violin-split: Split Violin Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 95/100 | Created: 2026-09-09\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nINK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint categorical palette — theme-independent, first series always brand green\nIMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data ---------------------------------------------------------------\n# Crop yields under two irrigation regimes, compared across four crop types.\ncrops = [\"Wheat\", \"Corn\", \"Soybean\", \"Rice\"]\nn_per_group = 220\n\nirrigated_mean = [4.3, 6.1, 3.2, 6.6]\nirrigated_std = [0.55, 0.75, 0.45, 0.85]\nrainfed_mean = [2.7, 4.0, 2.2, 3.9]\nrainfed_std = [0.70, 0.90, 0.55, 1.00]\n\nx_irrigated = Int[]\ny_irrigated = Float64[]\nx_rainfed = Int[]\ny_rainfed = Float64[]\n\nfor i in 1:length(crops)\n    append!(x_irrigated, fill(i, n_per_group))\n    append!(y_irrigated, irrigated_mean[i] .+ irrigated_std[i] .* randn(n_per_group))\n\n    append!(x_rainfed, fill(i, n_per_group))\n    if crops[i] == \"Rice\"\n        # Rain-fed rice is drought-sensitive: yield clusters into a low mode in\n        # dry years and a high mode in favorable-rainfall years, unlike the\n        # single-mode variability of the other crop/regime combinations.\n        n_drought = round(Int, 0.35 * n_per_group)\n        n_favorable = n_per_group - n_drought\n        append!(\n            y_rainfed,\n            vcat(\n                2.3 .+ 0.35 .* randn(n_drought),\n                4.6 .+ 0.55 .* randn(n_favorable),\n            ),\n        )\n    else\n        append!(y_rainfed, rainfed_mean[i] .+ rainfed_std[i] .* randn(n_per_group))\n    end\nend\n\n# Median yield per crop/regime drives the callout below (computed from the\n# generated samples so it matches whatever the violins actually show).\nirrigated_medians = [median(y_irrigated[x_irrigated .== i]) for i in 1:length(crops)]\nrainfed_medians = [median(y_rainfed[x_rainfed .== i]) for i in 1:length(crops)]\ngains = irrigated_medians .- rainfed_medians\ngain_idx = argmax(gains)\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"violin-split · julia · makie · anyplot.ai\",\n    titlesize         = 28,\n    titlecolor        = INK,\n    xlabel            = \"Crop\",\n    ylabel            = \"Yield (tons per hectare)\",\n    xlabelsize        = 16,\n    ylabelsize        = 16,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 14,\n    yticklabelsize    = 14,\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(crops), crops),\n)\n\nviolin!(\n    ax, x_irrigated, y_irrigated;\n    side = :left,\n    color = (IMPRINT_PALETTE[1], 0.85),\n    strokewidth = 1.5,\n    strokecolor = IMPRINT_PALETTE[1],\n    width = 0.85,\n    show_median = true,\n    mediancolor = INK,\n    datalimits = (0, Inf), # yield cannot be negative; trim the KDE tail at zero\n)\n\nviolin!(\n    ax, x_rainfed, y_rainfed;\n    side = :right,\n    color = (IMPRINT_PALETTE[2], 0.85),\n    strokewidth = 1.5,\n    strokecolor = IMPRINT_PALETTE[2],\n    width = 0.85,\n    show_median = true,\n    mediancolor = INK,\n    datalimits = (0, Inf), # yield cannot be negative; trim the KDE tail at zero\n)\n\n# Storytelling callout: bracket the crop with the largest irrigated-vs-rain-fed\n# median gain, offset (in screen space, toward whichever neighboring gap has\n# room) so it reads next to the violins without clipping against the axis edge.\ncallout_side = gain_idx <= length(crops) / 2 ? 1 : -1\nbracket!(\n    ax,\n    gain_idx, irrigated_medians[gain_idx],\n    gain_idx, rainfed_medians[gain_idx];\n    text = \"Largest irrigation gain: +$(round(gains[gain_idx], digits = 1)) t/ha ($(crops[gain_idx]))\",\n    offset = callout_side * 70,\n    width = 12,\n    orientation = :up,\n    style = :square,\n    color = INK_SOFT,\n    textcolor = INK,\n    fontsize = 13,\n    rotation = 0,\n    align = (callout_side > 0 ? :left : :right, :center),\n)\n\nlegend_elements = [\n    PolyElement(color = (IMPRINT_PALETTE[1], 0.85), strokecolor = IMPRINT_PALETTE[1], strokewidth = 1.5),\n    PolyElement(color = (IMPRINT_PALETTE[2], 0.85), strokecolor = IMPRINT_PALETTE[2], strokewidth = 1.5),\n]\nLegend(\n    fig[1, 1], legend_elements, [\"Irrigated\", \"Rain-fed\"];\n    tellwidth = false, tellheight = false,\n    halign = :left, valign = :top,\n    framevisible = false, labelcolor = INK, labelsize = 14,\n    margin = (20, 20, 20, 20),\n)\n\n# --- Save -----------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}