{"spec_id":"frequency-polygon-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# frequency-polygon-basic: Frequency Polygon for Distribution Comparison\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Random\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\",\n]\n\n# --- Data ---------------------------------------------------------------\n# Reaction times (ms) across three experimental conditions in a psychology study\nn_per_group = 300\nconditions = [\"Control\", \"Caffeine\", \"Sleep-deprived\"]\nreaction_times = [\n    280 .+ 45 .* randn(n_per_group),\n    250 .+ 38 .* randn(n_per_group),\n    340 .+ 60 .* randn(n_per_group),\n]\n\n# Shared bin edges across all groups for accurate comparison\nall_values = vcat(reaction_times...)\nn_bins = 24\nlo, hi = minimum(all_values), maximum(all_values)\nedges = range(lo, hi; length = n_bins + 1)\nbin_width = step(edges)\nmidpoints = [(edges[i] + edges[i + 1]) / 2 for i in 1:n_bins]\n\n# Bin each group onto the shared edges, closing the polygon to zero at both ends\npolygon_xs = Vector{Vector{Float64}}()\npolygon_ys = Vector{Vector{Int}}()\nfor values in reaction_times\n    counts = zeros(Int, n_bins)\n    for v in values\n        idx = clamp(searchsortedlast(edges, v), 1, n_bins)\n        counts[idx] += 1\n    end\n    push!(polygon_xs, vcat(midpoints[1] - bin_width, midpoints, midpoints[end] + bin_width))\n    push!(polygon_ys, vcat(0, counts, 0))\nend\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              = \"frequency-polygon-basic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Reaction Time (ms)\",\n    ylabel             = \"Frequency\",\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    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\n# Sleep-deprived carries the widest, longest-tailed distribution — the story\n# of this chart — so it gets a heavier line to draw the eye toward it.\nlinestyles = [:solid, :dash, :solid]\nlinewidths = [3, 3, 4]\n\nfor (i, label) in enumerate(conditions)\n    xs, ys = polygon_xs[i], polygon_ys[i]\n    color = IMPRINT_PALETTE[i]\n    band!(ax, xs, zeros(length(ys)), ys; color = RGBAf(color.r, color.g, color.b, 0.15))\n    lines!(ax, xs, ys; color = color, linewidth = linewidths[i], linestyle = linestyles[i], label = label)\nend\n\naxislegend(ax; position = :rt, framevisible = false, labelcolor = INK_SOFT)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}