{"spec_id":"area-cumulative-flow","library":"makie","language":"julia","code":"# anyplot.ai\n# area-cumulative-flow: Cumulative Flow Diagram for Workflow Analytics\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 95/100 | Created: 2026-08-19\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Dates\n\nRandom.seed!(42)\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEV_BG  = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint categorical palette — position 1 (#009E73) is always the first series\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#AE3030\",  # 5 — matte red\n]\n\n# Data — 90-day Kanban board with a Backlog -> Analysis -> Development ->\n# Testing -> Done pipeline. Each stage's cumulative curve is the arrival\n# curve re-sampled at a dwell-time offset, so the vertical gap between two\n# adjacent curves is exactly the WIP for the stage in between. Development\n# carries a time-varying dwell that bulges mid-range (a hiring crunch) and\n# drains by the end — the widening/narrowing bottleneck signal a CFD is\n# meant to surface.\nn_days = 90\nstart_date = Date(2024, 3, 3)\ndates = start_date .+ Day.(0:(n_days - 1))\nday_index = 1:n_days\nt = collect(1:n_days)\n\ndaily_arrivals = rand(2:6, n_days)\nbacklog_cum = cumsum(daily_arrivals)\n\ndwell_backlog = 4\nanalysis_cum = backlog_cum[clamp.(t .- dwell_backlog, 1, n_days)]\n\ncrunch = clamp.(1 .- abs.(t .- 50) ./ 25, 0.0, 1.0)\ndwell_development = round.(Int, 5 .+ 16 .* crunch)\ndevelopment_cum = analysis_cum[clamp.(t .- dwell_development, 1, n_days)]\n\ndwell_testing = 4\ntesting_cum = development_cum[clamp.(t .- dwell_testing, 1, n_days)]\n\ndwell_done = 3\ndone_cum = testing_cum[clamp.(t .- dwell_done, 1, n_days)]\n\nzero_cum = zeros(Int, n_days)\n\n# A short centered moving average softens the day-to-day arrival noise into\n# a smoother band silhouette. Averaging is linear, so it preserves both the\n# non-decreasing property of each curve and the ordering between adjacent\n# curves (their pointwise-non-negative difference stays non-negative once\n# averaged) — the CFD's monotonicity and stacking guarantees survive intact.\nfunction smooth_curve(x::AbstractVector{<:Real}, window::Int)\n    n = length(x)\n    half = window ÷ 2\n    return [sum(x[max(1, i - half):min(n, i + half)]) /\n            (min(n, i + half) - max(1, i - half) + 1) for i in 1:n]\nend\n\nbacklog_smooth     = smooth_curve(backlog_cum, 5)\nanalysis_smooth    = smooth_curve(analysis_cum, 5)\ndevelopment_smooth = smooth_curve(development_cum, 5)\ntesting_smooth     = smooth_curve(testing_cum, 5)\ndone_smooth        = smooth_curve(done_cum, 5)\nzero_smooth        = Float64.(zero_cum)\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              = \"area-cumulative-flow · julia · makie · anyplot.ai\",\n    titlesize          = 24,\n    titlecolor         = INK,\n    xlabel             = \"Date\",\n    ylabel             = \"Cumulative Items\",\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)\n\n# Makie-distinctive touch: shade + label the \"hiring crunch\" window behind\n# the bands so the Development bottleneck reads at a glance, not just from\n# band thickness. Drawn first so the stacked bands sit on top of it.\ncrunch_lo, crunch_hi = t[findfirst(>(0), crunch)], t[findlast(>(0), crunch)]\nvspan!(ax, crunch_lo, crunch_hi; color = RGBAf(INK.r, INK.g, INK.b, 0.06))\ntext!(ax, t[argmax(crunch)], maximum(backlog_smooth) * 0.97;\n      text = \"Development bottleneck window\", align = (:center, :top),\n      color = INK_SOFT, fontsize = 13)\n\n# Bands stacked with the earliest stage on top (per spec: Backlog above,\n# Done below), drawn from the smoothed cumulative curves — the vertical\n# thickness of each band is already the WIP for that stage since counts\n# are monotonically non-decreasing downstream.\nstage_lowers = (analysis_smooth, development_smooth, testing_smooth, done_smooth, zero_smooth)\nstage_uppers = (backlog_smooth, analysis_smooth, development_smooth, testing_smooth, done_smooth)\nstage_labels = (\"Backlog\", \"Analysis\", \"Development\", \"Testing\", \"Done\")\n\nfor i in 1:5\n    band!(ax, day_index, stage_lowers[i], stage_uppers[i];\n          color = IMPRINT_PALETTE[i], label = stage_labels[i])\nend\n\n# Thin page-background strokes crisp up the boundaries between bands\nfor curve in (backlog_smooth, analysis_smooth, development_smooth, testing_smooth, done_smooth)\n    lines!(ax, day_index, curve; color = PAGE_BG, linewidth = 1.5)\nend\n\nxlims!(ax, 1, n_days)\nylims!(ax, 0, maximum(backlog_smooth) * 1.05)\n\ntick_idx = 1:15:n_days\nax.xticks = (Float64.(collect(tick_idx)), Dates.format.(dates[tick_idx], \"u dd\"))\n\nLegend(\n    fig[1, 2], ax, \"Stage\";\n    framevisible  = false,\n    backgroundcolor = ELEV_BG,\n    labelcolor    = INK_SOFT,\n    titlecolor    = INK,\n)\ncolsize!(fig.layout, 1, Relative(0.84))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}