{"spec_id":"density-rug","library":"makie","language":"julia","code":"# anyplot.ai\n# density-rug: Density Plot with Rug Marks\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 82/100 | Created: 2026-09-02\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\"\nBRAND    = colorant\"#009E73\"  # Imprint palette position 1 — always first series\n\n# --- Data ---------------------------------------------------------------\n# Reaction times (ms) from a cognitive test battery: a large cluster of\n# typical trials plus a smaller cluster of attentional lapses (slow-response\n# outliers), giving the KDE a genuine second mode for the rug to corroborate.\nn_typical = 190\nn_lapses  = 30\ntypical_times_ms = exp.(0.18 .* randn(n_typical) .+ log(330))\nlapse_times_ms   = exp.(0.10 .* randn(n_lapses) .+ log(580))\nreaction_times_ms = vcat(typical_times_ms, lapse_times_ms)\n\n# Valley between the two modes — used to graduate emphasis toward the tail.\nmode_split = (median(typical_times_ms) + median(lapse_times_ms)) / 2\n\n# Manual Gaussian KDE (Silverman bandwidth) so the fill/stroke can be split\n# into a layered `band!` composition instead of one flat `density!` shape.\nfunction silverman_bandwidth(x)\n    n = length(x)\n    iqr = quantile(x, 0.75) - quantile(x, 0.25)\n    return 0.9 * min(std(x), iqr / 1.34) * n^(-1 / 5)\nend\n\nfunction gaussian_kde(x, grid, bandwidth)\n    y = zeros(length(grid))\n    for xi in x\n        @. y += exp(-0.5 * ((grid - xi) / bandwidth)^2)\n    end\n    return y ./ (length(x) * bandwidth * sqrt(2π))\nend\n\nbandwidth = silverman_bandwidth(reaction_times_ms)\ngrid_lo   = max(0.0, minimum(reaction_times_ms) - 3bandwidth)\ngrid_hi   = maximum(reaction_times_ms) + 3bandwidth\ngrid      = collect(range(grid_lo, grid_hi; length = 400))\ndensity_y = gaussian_kde(reaction_times_ms, grid, bandwidth)\n\ntypical_mask = grid .<= mode_split\nlapse_mask   = grid .>= mode_split\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              = \"density-rug · julia · makie · anyplot.ai\",\n    titlesize          = 23,\n    titlecolor         = INK,\n    xlabel             = \"Reaction Time (ms)\",\n    ylabel             = \"Density\",\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    yminorgridvisible  = false,\n)\n\n# Smoothed KDE fill, layered as two `band!` passes with graduated opacity —\n# lighter under the typical-trial mode, richer under the slow-response tail —\n# so the secondary mode reads as a deliberate focal point rather than raw\n# unemphasized shape. A single continuous stroke keeps the curve seamless,\n# with a bolder overlay reinforcing the emphasis over the tail.\nband!(ax, grid[typical_mask], 0, density_y[typical_mask]; color = (BRAND, 0.22))\nband!(ax, grid[lapse_mask], 0, density_y[lapse_mask]; color = (BRAND, 0.55))\nlines!(ax, grid, density_y; color = BRAND, linewidth = 2.5)\nlines!(ax, grid[lapse_mask], density_y[lapse_mask]; color = BRAND, linewidth = 3.5)\n\n# Rug marks — one tick per raw observation, drawn along the axis baseline in\n# axis-relative units so they sit clear of the density fill. Thin, faint\n# ticks keep the crowded typical-trial band legible; bolder, more opaque\n# ticks over the sparse lapse tail echo the fill's emphasis.\nvlines!(\n    ax, typical_times_ms;\n    ymin      = 0.0,\n    ymax      = 0.045,\n    color     = (BRAND, 0.28),\n    linewidth = 1.1,\n)\nvlines!(\n    ax, lapse_times_ms;\n    ymin      = 0.0,\n    ymax      = 0.045,\n    color     = (BRAND, 0.7),\n    linewidth = 1.8,\n)\n\nylims!(ax, low = 0)\n\n# --- Save -----------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}