Pareto Chart with Cumulative Line — Makie.jl

A Pareto chart combining descending-sorted bars (by frequency or count) with a cumulative percentage line overlay on a secondary y-axis. This visualization helps identify the most significant factors in a dataset by applying the Pareto principle (80/20 rule), making it one of the "7 Basic Tools of Quality" in Six Sigma and quality management. It reveals which categories contribute the most to an overall effect, enabling data-driven prioritization.

Pareto Chart with Cumulative Line rendered with Makie.jl

Julia source (Makie.jl)

# anyplot.ai
# bar-pareto: Pareto Chart with Cumulative Line
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 88/100 | Created: 2026-06-20

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 ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

const IMPRINT_PALETTE = [
    colorant"#009E73",  # 1 — brand green (first series)
    colorant"#C475FD",  # 2 — lavender
    colorant"#4467A3",  # 3 — blue
    colorant"#BD8233",  # 4 — ochre
    colorant"#AE3030",  # 5 — matte red (semantic: threshold / error)
    colorant"#2ABCCD",  # 6 — cyan
    colorant"#954477",  # 7 — rose
    colorant"#99B314",  # 8 — lime
]

# Data — e-commerce support ticket categories, sorted descending by volume
categories = ["Billing Issues", "Login Problems", "Delivery Delays",
              "Product Damage", "Wrong Item", "App Crashes", "Other"]
counts     = [342, 289, 213, 178, 145, 98, 65]

n       = length(categories)
total   = sum(counts)
cum_pct = cumsum(counts) ./ total .* 100
xs      = collect(1:n)

# Interpolated x where cumulative line crosses 80% (for crossing annotation)
cross_idx = findfirst(cum_pct .>= 80.0)
cross_x   = xs[cross_idx - 1] +
            (80.0 - cum_pct[cross_idx - 1]) /
            (cum_pct[cross_idx] - cum_pct[cross_idx - 1])

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

# Primary axis: bars (left y = ticket counts)
ax1 = Axis(
    fig[1, 1];
    title              = "bar-pareto · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Issue Category",
    ylabel             = "Ticket Count",
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = INK_SOFT,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    topspinevisible    = false,
    rightspinevisible  = false,
    backgroundcolor    = PAGE_BG,
    xticks             = (xs, categories),
    xticklabelrotation = π / 4,
    xticklabelalign    = (:right, :top),
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xlabelsize         = 14,
    ylabelsize         = 14,
    xgridvisible       = false,
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15f0),
    yminorgridvisible  = false,
    xminorgridvisible  = false,
)

# Secondary axis: cumulative % line (right y = 0–100%)
ax2 = Axis(
    fig[1, 1];
    yaxisposition      = :right,
    ylabel             = "Cumulative %",
    ylabelcolor        = INK,
    yticklabelcolor    = INK_SOFT,
    ytickcolor         = INK_SOFT,
    rightspinecolor    = INK_SOFT,
    topspinevisible    = false,
    leftspinevisible   = false,
    bottomspinevisible = false,
    backgroundcolor    = :transparent,
    yticks             = (collect(0:20:100), ["0%", "20%", "40%", "60%", "80%", "100%"]),
    yticklabelsize     = 12,
    ylabelsize         = 14,
    xgridvisible       = false,
    ygridvisible       = false,
)
hidexdecorations!(ax2)
linkxaxes!(ax1, ax2)
ylims!(ax2, 0, 110)

# --- Bars (descending order, Imprint brand green) ----------------------------
barplot!(ax1, xs, counts;
    color       = IMPRINT_PALETTE[1],
    strokecolor = PAGE_BG,
    strokewidth = 1,
    gap         = 0.15,
)

# --- 80% threshold line (matte red dashed — semantic anchor for threshold) ---
hlines!(ax2, [80.0];
    color     = IMPRINT_PALETTE[5],
    linestyle = :dash,
    linewidth = 1.8,
)

# --- Cumulative % line with markers at bar centers ---------------------------
lines!(ax2, xs, cum_pct;
    color     = IMPRINT_PALETTE[3],
    linewidth = 2.5,
)
scatter!(ax2, xs, cum_pct;
    color        = IMPRINT_PALETTE[3],
    markersize   = 10,
    strokewidth  = 1.0,
    strokecolor  = PAGE_BG,
)

# --- 80% crossing annotation: diamond + label reveal the "vital few" --------
scatter!(ax2, [cross_x], [80.0];
    color       = IMPRINT_PALETTE[5],
    marker      = :diamond,
    markersize  = 14,
    strokewidth = 1.5,
    strokecolor = PAGE_BG,
)
text!(ax2, [cross_x + 0.2], [89.0];
    text     = ["Top $(cross_idx) of $(n) categories"],
    color    = INK,
    fontsize = 11,
    align    = [(:left, :bottom)],
)

# --- Legend ------------------------------------------------------------------
Legend(
    fig[1, 2],
    [
        PolyElement(color = IMPRINT_PALETTE[1], strokecolor = PAGE_BG, strokewidth = 1),
        LineElement(color = IMPRINT_PALETTE[3], linewidth = 2.5),
        LineElement(color = IMPRINT_PALETTE[5], linestyle = :dash, linewidth = 1.8),
    ],
    ["Ticket Count", "Cumulative %", "80% Threshold"],
    framecolor       = INK_SOFT,
    backgroundcolor  = ELEVATED_BG,
    labelcolor  = INK,
    labelsize   = 11,
    patchsize   = (24, 10),
    margin      = (8, 8, 8, 8),
)

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

Part of Pareto Chart with Cumulative Line on anyplot.ai.

Other implementations