{"spec_id":"box-notched","library":"makie","language":"julia","code":"# anyplot.ai\n# box-notched: Notched Box Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 93/100 | Created: 2026-08-18\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\"\n\n# Imprint categorical palette — 8 hues, theme-independent, hybrid-v3 sort\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green, ALWAYS first series\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#AE3030\",  # 5 — matte red\n]\n\n# --- Data ---------------------------------------------------------------\n# Reaction times (ms) across five cue conditions in a cognitive science\n# experiment. Sample sizes and spreads vary slightly, as they would in real\n# collected data, so some notches overlap (no significant median shift)\n# while others clearly separate.\nconditions = [\"Baseline\", \"Visual Cue\", \"Auditory Cue\", \"Combined Cue\", \"Distraction\"]\nmeans       = [450.0, 410.0, 440.0, 390.0, 480.0]\nstds        = [40.0, 35.0, 45.0, 30.0, 50.0]\nsample_size = 50\n\ngroup_index = Int[]\nreaction_ms = Float64[]\npoint_color = RGB{Float64}[]\nfor (i, (mu, sigma)) in enumerate(zip(means, stds))\n    append!(group_index, fill(i, sample_size))\n    append!(reaction_ms, randn(sample_size) .* sigma .+ mu)\n    append!(point_color, fill(IMPRINT_PALETTE[i], sample_size))\nend\n\n# Per-group median + notch band (±1.57 * IQR / sqrt(n), matching the spec's\n# 95%-CI notch formula) so the significance callout below is derived from the\n# actual sampled data rather than hard-coded.\ngroup_vals   = [reaction_ms[group_index .== i] for i in eachindex(conditions)]\ngroup_median = [median(v) for v in group_vals]\ngroup_notch  = [1.57 * (quantile(v, 0.75) - quantile(v, 0.25)) / sqrt(length(v)) for v in group_vals]\nbaseline_lo, baseline_hi = group_median[1] - group_notch[1], group_median[1] + group_notch[1]\n\n# Dense y-axis ticks: every 50ms across the full sampled range, so the lower\n# half of the data reads with the same reference density as the upper half.\ny_tick_lo = floor(minimum(reaction_ms) / 50) * 50\ny_tick_hi = ceil(maximum(reaction_ms) / 50) * 50\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              = \"box-notched · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Condition\",\n    ylabel             = \"Reaction Time (ms)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticks             = (1:length(conditions), conditions),\n    xticklabelsize     = 12,\n    yticks             = y_tick_lo:50:y_tick_hi,\n    yticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = 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)\n\nboxplot!(\n    ax, group_index, reaction_ms;\n    color               = point_color,\n    width               = 0.6,\n    show_notch          = true,\n    notchwidth          = 0.5,\n    strokecolor         = INK,\n    strokewidth         = 1.5,\n    mediancolor         = INK,\n    medianlinewidth     = 2.5,\n    whiskerwidth        = 0.4,\n    whiskercolor        = INK_SOFT,\n    whiskerlinewidth    = 2.0,\n    markersize          = 10,\n    outlierstrokecolor  = INK,\n    outlierstrokewidth  = 1.0,\n)\n\n# Significance callout: connect Baseline to whichever condition's notch band\n# clears Baseline's with a Makie `bracket!` annotation, making the \"quick\n# visual hypothesis testing\" story explicit rather than requiring the viewer\n# to compare notch bands by eye. Picks the condition with the largest median\n# gap among the non-overlapping ones so the callout matches the most visually\n# obvious separation.\nnon_overlapping = [i for i in 2:length(conditions)\n                    if group_median[i] + group_notch[i] < baseline_lo ||\n                       group_median[i] - group_notch[i] > baseline_hi]\nif !isempty(non_overlapping)\n    callout_idx = non_overlapping[argmax(abs.(group_median[non_overlapping] .- group_median[1]))]\n    bracket_y = maximum(vcat(group_vals[1], group_vals[callout_idx])) + 15\n    bracket!(\n        ax, 1, bracket_y, callout_idx, bracket_y;\n        text        = \"$(conditions[callout_idx]) notch clears $(conditions[1]) — medians differ\",\n        style       = :square,\n        orientation = :up,\n        width       = 12,\n        color       = INK_SOFT,\n        textcolor   = INK,\n        fontsize    = 13,\n        linewidth   = 1.5,\n    )\nend\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}