{"spec_id":"area-cumulative-flow","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\narea-cumulative-flow: Cumulative Flow Diagram for Workflow Analytics\nLibrary: matplotlib 3.11.1 | Python 3.13.15\nQuality: 95/100 | Updated: 2026-08-18\n\"\"\"\n\nimport sys\n\n\n# Prevent this file from shadowing the installed matplotlib package when run\n# from its own directory (sys.path[0] would otherwise resolve to this file).\nsys.path.pop(0)\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\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# Okabe-Ito positions 1-5 (bottom to top: Done → Backlog)\nCOLORS = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data: 90-day software Kanban board simulation\nnp.random.seed(42)\nn_days = 90\ndates = pd.date_range(\"2024-01-02\", periods=n_days, freq=\"D\")\nt = np.arange(n_days)\n\n# Build cumulative counts from Done upward; each layer stacks WIP above the previous\n# Done: S-curve — accelerates through the sprint\ndone_raw = 120 * (1 / (1 + np.exp(-(t - 55) / 12)))\ndone_raw -= done_raw[0]\ndone = np.maximum.accumulate(np.maximum(np.round(done_raw + np.random.normal(0, 1.5, n_days)).astype(int), 0))\n\n# Testing: widens as a bottleneck builds from day 15-60, then drains\nwip_testing = np.zeros(n_days)\nwip_testing[15:60] = np.linspace(0, 28, 45) + np.random.normal(0, 1.5, 45)\nwip_testing[60:] = np.linspace(28, 12, n_days - 60) + np.random.normal(0, 1.0, n_days - 60)\nwip_testing = np.round(np.maximum(wip_testing, 0)).astype(int)\ntesting = np.maximum.accumulate(done + wip_testing)\n\n# Development: steady ~14 items, slight oscillation\nwip_dev = np.round(14 + 4 * np.sin(2 * np.pi * t / 28) + np.random.normal(0, 1.5, n_days)).astype(int)\nwip_dev[:12] = np.round(np.linspace(0, 14, 12)).astype(int)\nwip_dev = np.maximum(wip_dev, 2)\ndevelopment = np.maximum.accumulate(testing + wip_dev)\n\n# Analysis: small buffer of ~8 items\nwip_analysis = np.round(8 + np.random.normal(0, 1.2, n_days)).astype(int)\nwip_analysis[:7] = np.round(np.linspace(0, 8, 7)).astype(int)\nwip_analysis = np.maximum(wip_analysis, 1)\nanalysis = np.maximum.accumulate(development + wip_analysis)\n\n# Backlog: grows as new work arrives faster than it's pulled\nwip_backlog = np.round(22 + np.linspace(0, 12, n_days) + np.random.normal(0, 2.0, n_days)).astype(int)\nwip_backlog[:5] = np.round(np.linspace(5, 22, 5)).astype(int)\nwip_backlog = np.maximum(wip_backlog, 5)\nbacklog = np.maximum.accumulate(analysis + wip_backlog)\n\n# Layer contributions (bottom to top in stackplot order)\nc_done = done.astype(float)\nc_testing = np.maximum(testing - done, 0).astype(float)\nc_development = np.maximum(development - testing, 0).astype(float)\nc_analysis = np.maximum(analysis - development, 0).astype(float)\nc_backlog = np.maximum(backlog - analysis, 0).astype(float)\n\n# Peak of the Testing bottleneck band — drives the callout annotation below\npeak_idx = int(np.argmax(c_testing))\npeak_wip = int(round(c_testing[peak_idx]))\npeak_top = float(testing[peak_idx])\n\n# Plot — canonical 3200x1800 landscape canvas (figsize x dpi = 8 x 400, 4.5 x 400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nstage_labels = [\"Done\", \"Testing\", \"Development\", \"Analysis\", \"Backlog\"]\n\nax.stackplot(\n    dates,\n    c_done,\n    c_testing,\n    c_development,\n    c_analysis,\n    c_backlog,\n    labels=stage_labels,\n    colors=COLORS,\n    alpha=0.88,\n    edgecolor=INK,\n    linewidth=0.5,\n)\n\n# Style\ntitle = \"Sprint Kanban Board · area-cumulative-flow · python · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\nax.set_xlabel(\"Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Cumulative Items\", fontsize=10, color=INK)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, length=0)\n\n# X-axis: bi-weekly date ticks\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %d\"))\nax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=2))\nplt.setp(ax.get_xticklabels(), rotation=30, ha=\"right\", color=INK_SOFT)\nplt.setp(ax.get_yticklabels(), color=INK_SOFT)\n\nax.set_xlim(dates[0], dates[-1])\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor spine in (\"left\", \"bottom\"):\n    ax.spines[spine].set_color(INK_SOFT)\n\n# Grid (y-axis only, subtle)\nax.yaxis.grid(True, alpha=0.18, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\n\n# Shaded region highlighting Testing bottleneck buildup (days 15–60) — tint\n# drawn from INK rather than a categorical data color, with a theme-adaptive\n# alpha, so the highlight carries similar visual weight in both themes\nhighlight_alpha = 0.06 if THEME == \"light\" else 0.10\nax.axvspan(dates[15], dates[60], alpha=highlight_alpha, color=INK, zorder=0)\n\n# Arrow-annotated callout quantifying the bottleneck peak, with an elevated box\n# for typographic hierarchy beyond the title/axis/tick levels\nax.annotate(\n    f\"Testing WIP peaks at {peak_wip} items\",\n    xy=(dates[peak_idx], peak_top),\n    xytext=(dates[min(peak_idx + 14, len(dates) - 1)], peak_top + 45),\n    ha=\"left\",\n    va=\"center\",\n    fontsize=9,\n    color=INK_SOFT,\n    style=\"italic\",\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": INK_SOFT, \"lw\": 1.2, \"shrinkA\": 2, \"shrinkB\": 4},\n    bbox={\"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.92, \"boxstyle\": \"round,pad=0.35\"},\n)\n\n# Legend — reversed so Backlog appears at top, matching visual stacking order\nhandles, labels = ax.get_legend_handles_labels()\nleg = ax.legend(handles[::-1], labels[::-1], loc=\"upper left\", fontsize=8, framealpha=0.92)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\nplt.tight_layout()\n_out = os.path.join(os.path.dirname(os.path.abspath(__file__)), f\"plot-{THEME}.png\")\nplt.savefig(_out, dpi=400, facecolor=PAGE_BG)  # bbox_inches MUST stay default (None) — see canvas contract\n"}