{"spec_id":"area-cumulative-flow","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\narea-cumulative-flow: Cumulative Flow Diagram for Workflow Analytics\nLibrary: seaborn 0.13.2 | Python 3.13.15\nQuality: 91/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport seaborn.objects as so\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — canonical order, first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — 90-day Kanban board simulation\nnp.random.seed(42)\nn_days = 90\ndates = pd.date_range(\"2024-01-15\", periods=n_days, freq=\"D\")\n\n# Cumulative items entering each stage (each stage lags and is capped by upstream)\narrivals = np.random.poisson(6, n_days)\nbacklog_cum = np.cumsum(arrivals).astype(float)\n\nanalysis_cum = np.minimum(backlog_cum, np.cumsum(np.random.poisson(5, n_days)).astype(float))\ndev_cum = np.minimum(analysis_cum, np.cumsum(np.random.poisson(4.3, n_days)).astype(float))\ntesting_cum = np.minimum(dev_cum, np.cumsum(np.random.poisson(3.6, n_days)).astype(float))\ndone_cum = np.minimum(testing_cum, np.cumsum(np.random.poisson(3.0, n_days)).astype(float))\n\n# WIP per stage: vertical band height = items currently in that stage\ndone_wip = done_cum\ntesting_wip = testing_cum - done_cum\ndev_wip = dev_cum - testing_cum\nanalysis_wip = analysis_cum - dev_cum\nbacklog_wip = backlog_cum - analysis_cum\n\n# Long-form frame for seaborn's objects interface — \"stage\" is an ordered\n# category so.Stack() reads bottom-up, giving the CFD convention directly:\n# Done (bottom) ... Backlog (top)\nstage_labels = [\"Done\", \"Testing\", \"Development\", \"Analysis\", \"Backlog\"]\nwip_by_stage = {\n    \"Done\": done_wip,\n    \"Testing\": testing_wip,\n    \"Development\": dev_wip,\n    \"Analysis\": analysis_wip,\n    \"Backlog\": backlog_wip,\n}\ncfd = pd.DataFrame(\n    {\n        \"date\": np.tile(dates, len(stage_labels)),\n        \"stage\": pd.Categorical(np.repeat(stage_labels, n_days), categories=stage_labels, ordered=True),\n        \"wip\": np.concatenate([wip_by_stage[stage] for stage in stage_labels]),\n    }\n)\n\n# Plot — seaborn's objects interface stacks Area marks bottom-up per CFD\n# convention; the custom legend below (not the built-in one) mirrors the\n# visual stack order\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n(\n    so.Plot(cfd, x=\"date\", y=\"wip\", color=\"stage\")\n    .add(so.Area(alpha=0.85, edgewidth=0), so.Stack(), legend=False)\n    .scale(color=so.Nominal(IMPRINT, order=stage_labels))\n    .on(ax)\n    .plot()\n)\n\n# Annotate the widening Backlog band — intake (rate 6/day) outpaces Analysis\n# throughput (rate 5/day), the CFD's key bottleneck signal in this dataset\nannot_day = int(n_days * 0.83)\nannot_y = analysis_cum[annot_day] + backlog_wip[annot_day] / 2\nax.annotate(\n    \"Backlog bottleneck\",\n    xy=(dates[annot_day], annot_y),\n    xytext=(-95, 18),\n    textcoords=\"offset points\",\n    fontsize=9,\n    color=INK,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK, \"lw\": 1.2},\n)\n\n# Style\nax.set_xlabel(\"Date\", fontsize=11, color=INK)\nax.set_ylabel(\"Cumulative Items\", fontsize=11, color=INK)\nax.set_title(\"area-cumulative-flow · python · seaborn · anyplot.ai\", fontsize=13, fontweight=\"medium\", color=INK)\n\nax.tick_params(axis=\"both\", labelsize=9, colors=INK_SOFT)\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %d\"))\nax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=0, interval=2))\nplt.setp(ax.xaxis.get_majorticklabels(), rotation=30, ha=\"right\")\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\n\n# Legend in visual order — Backlog at top matches its position in the chart\nlegend_handles = [\n    mpatches.Patch(facecolor=color, alpha=0.85, label=stage) for stage, color in zip(stage_labels, IMPRINT, strict=True)\n][::-1]\nax.legend(\n    handles=legend_handles,\n    loc=\"upper left\",\n    fontsize=9,\n    framealpha=0.9,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    labelcolor=INK,\n)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}