Cumulative Flow Diagram for Workflow Analytics — Makie.jl

A cumulative flow diagram (CFD) displays the cumulative count of items in each workflow stage as stacked areas over time. Each band represents a stage (e.g., Backlog, In Progress, Done), and the vertical distance between two adjacent band boundaries shows the number of items currently in that stage (work-in-progress). This is a key visualization in Lean and Agile project management for identifying bottlenecks, monitoring throughput, and assessing flow efficiency across a delivery pipeline.

Cumulative Flow Diagram for Workflow Analytics rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# area-cumulative-flow: Cumulative Flow Diagram for Workflow Analytics
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 95/100 | Created: 2026-08-19

using CairoMakie
using Colors
using Random
using Dates

Random.seed!(42)

# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
const THEME    = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG  = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEV_BG  = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK      = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

# Imprint categorical palette — position 1 (#009E73) is always the first series
const IMPRINT_PALETTE = [
    colorant"#009E73",  # 1 — brand green
    colorant"#C475FD",  # 2 — lavender
    colorant"#4467A3",  # 3 — blue
    colorant"#BD8233",  # 4 — ochre
    colorant"#AE3030",  # 5 — matte red
]

# Data — 90-day Kanban board with a Backlog -> Analysis -> Development ->
# Testing -> Done pipeline. Each stage's cumulative curve is the arrival
# curve re-sampled at a dwell-time offset, so the vertical gap between two
# adjacent curves is exactly the WIP for the stage in between. Development
# carries a time-varying dwell that bulges mid-range (a hiring crunch) and
# drains by the end — the widening/narrowing bottleneck signal a CFD is
# meant to surface.
n_days = 90
start_date = Date(2024, 3, 3)
dates = start_date .+ Day.(0:(n_days - 1))
day_index = 1:n_days
t = collect(1:n_days)

daily_arrivals = rand(2:6, n_days)
backlog_cum = cumsum(daily_arrivals)

dwell_backlog = 4
analysis_cum = backlog_cum[clamp.(t .- dwell_backlog, 1, n_days)]

crunch = clamp.(1 .- abs.(t .- 50) ./ 25, 0.0, 1.0)
dwell_development = round.(Int, 5 .+ 16 .* crunch)
development_cum = analysis_cum[clamp.(t .- dwell_development, 1, n_days)]

dwell_testing = 4
testing_cum = development_cum[clamp.(t .- dwell_testing, 1, n_days)]

dwell_done = 3
done_cum = testing_cum[clamp.(t .- dwell_done, 1, n_days)]

zero_cum = zeros(Int, n_days)

# A short centered moving average softens the day-to-day arrival noise into
# a smoother band silhouette. Averaging is linear, so it preserves both the
# non-decreasing property of each curve and the ordering between adjacent
# curves (their pointwise-non-negative difference stays non-negative once
# averaged) — the CFD's monotonicity and stacking guarantees survive intact.
function smooth_curve(x::AbstractVector{<:Real}, window::Int)
    n = length(x)
    half = window ÷ 2
    return [sum(x[max(1, i - half):min(n, i + half)]) /
            (min(n, i + half) - max(1, i - half) + 1) for i in 1:n]
end

backlog_smooth     = smooth_curve(backlog_cum, 5)
analysis_smooth    = smooth_curve(analysis_cum, 5)
development_smooth = smooth_curve(development_cum, 5)
testing_smooth     = smooth_curve(testing_cum, 5)
done_smooth        = smooth_curve(done_cum, 5)
zero_smooth        = Float64.(zero_cum)

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

ax = Axis(
    fig[1, 1];
    title              = "area-cumulative-flow · julia · makie · anyplot.ai",
    titlesize          = 24,
    titlecolor         = INK,
    xlabel             = "Date",
    ylabel             = "Cumulative Items",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = 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),
)

# Makie-distinctive touch: shade + label the "hiring crunch" window behind
# the bands so the Development bottleneck reads at a glance, not just from
# band thickness. Drawn first so the stacked bands sit on top of it.
crunch_lo, crunch_hi = t[findfirst(>(0), crunch)], t[findlast(>(0), crunch)]
vspan!(ax, crunch_lo, crunch_hi; color = RGBAf(INK.r, INK.g, INK.b, 0.06))
text!(ax, t[argmax(crunch)], maximum(backlog_smooth) * 0.97;
      text = "Development bottleneck window", align = (:center, :top),
      color = INK_SOFT, fontsize = 13)

# Bands stacked with the earliest stage on top (per spec: Backlog above,
# Done below), drawn from the smoothed cumulative curves — the vertical
# thickness of each band is already the WIP for that stage since counts
# are monotonically non-decreasing downstream.
stage_lowers = (analysis_smooth, development_smooth, testing_smooth, done_smooth, zero_smooth)
stage_uppers = (backlog_smooth, analysis_smooth, development_smooth, testing_smooth, done_smooth)
stage_labels = ("Backlog", "Analysis", "Development", "Testing", "Done")

for i in 1:5
    band!(ax, day_index, stage_lowers[i], stage_uppers[i];
          color = IMPRINT_PALETTE[i], label = stage_labels[i])
end

# Thin page-background strokes crisp up the boundaries between bands
for curve in (backlog_smooth, analysis_smooth, development_smooth, testing_smooth, done_smooth)
    lines!(ax, day_index, curve; color = PAGE_BG, linewidth = 1.5)
end

xlims!(ax, 1, n_days)
ylims!(ax, 0, maximum(backlog_smooth) * 1.05)

tick_idx = 1:15:n_days
ax.xticks = (Float64.(collect(tick_idx)), Dates.format.(dates[tick_idx], "u dd"))

Legend(
    fig[1, 2], ax, "Stage";
    framevisible  = false,
    backgroundcolor = ELEV_BG,
    labelcolor    = INK_SOFT,
    titlecolor    = INK,
)
colsize!(fig.layout, 1, Relative(0.84))

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

Part of Cumulative Flow Diagram for Workflow Analytics on anyplot.ai.

Other implementations