Frequency Polygon for Distribution Comparison — Makie.jl

A frequency polygon connects the midpoints of histogram bins with straight line segments, creating a smooth outline of the distribution shape. This visualization excels at comparing multiple distributions simultaneously since lines overlap without obscuring each other, unlike stacked or overlapping histogram bars. Frequency polygons reveal differences in central tendency, spread, skewness, and modality across groups with minimal visual clutter.

Frequency Polygon for Distribution Comparison rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# frequency-polygon-basic: Frequency Polygon for Distribution Comparison
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 90/100 | Created: 2026-09-02

using CairoMakie
using Colors
using Random

Random.seed!(42)

# --- Theme tokens -----------------------------------------------------------
const THEME    = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG  = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK      = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const IMPRINT_PALETTE = [
    colorant"#009E73", colorant"#C475FD", colorant"#4467A3",
]

# --- Data ---------------------------------------------------------------
# Reaction times (ms) across three experimental conditions in a psychology study
n_per_group = 300
conditions = ["Control", "Caffeine", "Sleep-deprived"]
reaction_times = [
    280 .+ 45 .* randn(n_per_group),
    250 .+ 38 .* randn(n_per_group),
    340 .+ 60 .* randn(n_per_group),
]

# Shared bin edges across all groups for accurate comparison
all_values = vcat(reaction_times...)
n_bins = 24
lo, hi = minimum(all_values), maximum(all_values)
edges = range(lo, hi; length = n_bins + 1)
bin_width = step(edges)
midpoints = [(edges[i] + edges[i + 1]) / 2 for i in 1:n_bins]

# Bin each group onto the shared edges, closing the polygon to zero at both ends
polygon_xs = Vector{Vector{Float64}}()
polygon_ys = Vector{Vector{Int}}()
for values in reaction_times
    counts = zeros(Int, n_bins)
    for v in values
        idx = clamp(searchsortedlast(edges, v), 1, n_bins)
        counts[idx] += 1
    end
    push!(polygon_xs, vcat(midpoints[1] - bin_width, midpoints, midpoints[end] + bin_width))
    push!(polygon_ys, vcat(0, counts, 0))
end

# --- Plot -----------------------------------------------------------------
fig = Figure(
    resolution      = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = "frequency-polygon-basic · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Reaction Time (ms)",
    ylabel             = "Frequency",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridvisible       = false,
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
)

# Sleep-deprived carries the widest, longest-tailed distribution — the story
# of this chart — so it gets a heavier line to draw the eye toward it.
linestyles = [:solid, :dash, :solid]
linewidths = [3, 3, 4]

for (i, label) in enumerate(conditions)
    xs, ys = polygon_xs[i], polygon_ys[i]
    color = IMPRINT_PALETTE[i]
    band!(ax, xs, zeros(length(ys)), ys; color = RGBAf(color.r, color.g, color.b, 0.15))
    lines!(ax, xs, ys; color = color, linewidth = linewidths[i], linestyle = linestyles[i], label = label)
end

axislegend(ax; position = :rt, framevisible = false, labelcolor = INK_SOFT)

# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/frequency-polygon-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "frequency-polygon-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/frequency-polygon-basic/julia/makie",
  "hub": "https://anyplot.ai/frequency-polygon-basic",
  "code_json": "https://api.anyplot.ai/specs/frequency-polygon-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/frequency-polygon-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/frequency-polygon-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/frequency-polygon-basic/julia/makie/plot-dark.png",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Frequency Polygon for Distribution Comparison on anyplot.ai.

Other implementations