{"spec_id":"area-cumulative-flow","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\narea-cumulative-flow: Cumulative Flow Diagram for Workflow Analytics\nLibrary: letsplot 4.11.0 | Python 3.13.15\nQuality: 94/100 | Created: 2026-08-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_area,\n    geom_label,\n    geom_line,\n    ggplot,\n    ggsave,\n    ggsize,\n    ggtitle,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_datetime,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette (categorical, canonical order) — position 1 always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data — a production line's stage-by-stage cumulative item counts (manufacturing CFD)\nnp.random.seed(42)\nn_days = 90\ndates = pd.date_range(\"2024-04-01\", periods=n_days, freq=\"D\")\nstages = [\"Raw Materials\", \"Machining\", \"Assembly\", \"Quality Check\", \"Shipped\"]\nbase_capacity = [None, 19, 15, 11, 8]  # daily throughput ceiling per downstream stage\n\ndaily_intake = np.random.randint(15, 26, size=n_days)\ncumulative = np.zeros((len(stages), n_days))\ncumulative[0] = np.cumsum(daily_intake)\n\n# Assembly gets a temporary weekend-shift surge (days 50-70) that lifts its\n# capacity above Machining's steady output, letting it drain the queue of\n# Machining-finished items faster than Machining refills it -- the visible\n# Machining band (between the Assembly and Machining curves) narrows during\n# that window before widening again once the surge ends, covering both the\n# widening- and narrowing-band behaviors named in the spec.\ncapacity_schedules = []\nfor stage_idx, cap in enumerate(base_capacity):\n    if cap is None:\n        capacity_schedules.append(None)\n        continue\n    schedule = np.full(n_days, cap, dtype=float)\n    if stages[stage_idx] == \"Assembly\":\n        schedule[50:70] = 30\n    capacity_schedules.append(schedule)\n\nfor stage_idx in range(1, len(stages)):\n    upstream = cumulative[stage_idx - 1]\n    capacity = capacity_schedules[stage_idx]\n    processed = 0.0\n    for day in range(n_days):\n        throughput = min(upstream[day] - processed, capacity[day])\n        processed += throughput\n        cumulative[stage_idx, day] = processed\n\n# Locate the widest Machining-band moment (pre-surge congestion peak) to call\n# out the bottleneck directly rather than leaving it for the viewer to spot.\nmachining_band_width = cumulative[1] - cumulative[2]\npeak_day = int(np.argmax(machining_band_width))\npeak_label = pd.DataFrame(\n    {\n        \"date\": [dates[peak_day]],\n        \"count\": [(cumulative[1, peak_day] + cumulative[2, peak_day]) / 2],\n        \"label\": [\"Machining congestion peak\"],\n    }\n)\n\n# geom_area(position='identity') stacks layers by *factor level* order: the first\n# level renders in front, the last level renders behind. The smallest (latest-stage)\n# curve must be the first level so it sits in front, and the largest (earliest-stage)\n# curve must be the last level so it sits behind and shows through as the top band.\nz_order = list(reversed(stages))\n\nflow_df = pd.DataFrame(\n    {\n        \"date\": np.tile(dates, len(stages)),\n        \"stage\": pd.Categorical(np.repeat(stages, n_days), categories=z_order, ordered=True),\n        \"count\": cumulative.flatten(),\n    }\n)\n\n# Plot — see default-style-guide.md \"Visual Sizing Defaults\" for the canvas + sizing values\nplot = (\n    ggplot(flow_df, aes(x=\"date\", y=\"count\", fill=\"stage\"))\n    + geom_area(position=\"identity\", color=PAGE_BG, size=0.6)\n    + geom_line(aes(color=\"stage\"), position=\"identity\", size=0.9, show_legend=False)\n    + geom_label(\n        aes(x=\"date\", y=\"count\", label=\"label\"),\n        data=peak_label,\n        inherit_aes=False,\n        color=INK,\n        fill=ELEVATED_BG,\n        size=3.5,\n        label_padding=0.3,\n    )\n    + scale_fill_manual(values=IMPRINT_PALETTE, breaks=stages, name=\"Stage\")\n    + scale_color_manual(values=IMPRINT_PALETTE, breaks=stages)\n    + scale_x_datetime(format=\"%b %d\")\n    + labs(x=\"Date\", y=\"Cumulative Items\")\n    + ggtitle(\"area-cumulative-flow · python · letsplot · anyplot.ai\")\n    + ggsize(800, 450)\n)\n\n# Style — theme-adaptive chrome (see prompts/library/letsplot.md)\nplot = (\n    plot\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=RULE, size=0.3),\n        panel_border=element_blank(),\n        axis_title=element_text(color=INK, size=12),\n        axis_text=element_text(color=INK_SOFT, size=10),\n        axis_line_x=element_line(color=INK_SOFT),\n        axis_line_y=element_line(color=INK_SOFT),\n        plot_title=element_text(color=INK, size=16),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(color=INK_SOFT, size=10),\n        legend_title=element_text(color=INK, size=11),\n        legend_position=\"right\",\n    )\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}