{"spec_id":"waterfall-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nwaterfall-basic: Basic Waterfall Chart\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Patch\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 semantic anchors for waterfall categories\nINCREASE_COLOR = \"#009E73\"  # green — increase\nDECREASE_COLOR = \"#AE3030\"  # red — decrease\nTOTAL_COLOR = \"#4467A3\"  # blue — total/baseline\n\n# Data: Project Budget Allocation (from initial budget to final allocation)\n# Different scenario from the original (avoiding Altair match)\ncategories = [\"Initial Budget\", \"Personnel\", \"Technology\", \"Marketing\", \"Contingency\", \"Savings\", \"Final Budget\"]\n# Values: First and last are totals, middle values are changes (None = calculated totals)\nvalues = [1000, -350, -200, -150, 100, 50, None]\n\n# Calculate running totals and determine actual bar values\nn = len(categories)\nrunning_total = np.zeros(n)\nbar_values = np.zeros(n)\nbar_bottom = np.zeros(n)\nbar_colors = []\n\nrunning_total[0] = values[0]\nbar_values[0] = values[0]\nbar_bottom[0] = 0\nbar_colors.append(TOTAL_COLOR)\n\ncurrent = values[0]\nfor i in range(1, n):\n    if values[i] is None:\n        # This is a subtotal bar (Final Budget)\n        running_total[i] = current\n        bar_values[i] = current\n        bar_bottom[i] = 0\n        bar_colors.append(TOTAL_COLOR)\n    else:\n        # This is a change bar\n        running_total[i] = current + values[i]\n        bar_values[i] = values[i]\n        if values[i] >= 0:\n            bar_bottom[i] = current\n            bar_colors.append(INCREASE_COLOR)\n        else:\n            bar_bottom[i] = current + values[i]\n            bar_colors.append(DECREASE_COLOR)\n        current = running_total[i]\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nx = np.arange(n)\nbar_width = 0.6\n\n# Draw bars\nbars = ax.bar(\n    x, np.abs(bar_values), width=bar_width, bottom=bar_bottom, color=bar_colors, edgecolor=INK_SOFT, linewidth=1.5\n)\n\n# Draw connecting lines between bars, with join markers to punctuate the running total\nfor i in range(n - 1):\n    ax.plot(\n        [x[i] + bar_width / 2, x[i + 1] - bar_width / 2],\n        [running_total[i], running_total[i]],\n        color=INK_SOFT,\n        linestyle=\"--\",\n        linewidth=1.5,\n        zorder=1,\n    )\n    ax.scatter(\n        [x[i] + bar_width / 2, x[i + 1] - bar_width / 2],\n        [running_total[i], running_total[i]],\n        s=18,\n        color=INK_SOFT,\n        zorder=2,\n    )\n\n# Value labels, centered inside each bar via the native bar_label API\nbar_labels = []\nfor i in range(n):\n    if values[i] is None:\n        bar_labels.append(f\"${int(bar_values[i])}\")\n    elif values[i] >= 0:\n        bar_labels.append(f\"+${int(values[i])}\")\n    else:\n        bar_labels.append(f\"-${int(abs(values[i]))}\")\nax.bar_label(bars, labels=bar_labels, label_type=\"center\", fontsize=9, fontweight=\"bold\", color=INK)\n\n# Styling\nax.set_xlabel(\"Budget Component\", fontsize=10, color=INK)\nax.set_ylabel(\"Amount ($K)\", fontsize=10, color=INK)\nax.set_title(\"waterfall-basic · python · matplotlib · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xticks(x)\nax.set_xticklabels(categories, fontsize=8, rotation=15, ha=\"right\", color=INK_SOFT)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\n\n# Grid\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK_SOFT)\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\n# Set y-axis to start from 0\nax.set_ylim(bottom=0)\n\n# Add legend for color meanings\nlegend_elements = [\n    Patch(facecolor=TOTAL_COLOR, edgecolor=INK_SOFT, label=\"Total\", linewidth=1.5),\n    Patch(facecolor=INCREASE_COLOR, edgecolor=INK_SOFT, label=\"Increase\", linewidth=1.5),\n    Patch(facecolor=DECREASE_COLOR, edgecolor=INK_SOFT, label=\"Decrease\", linewidth=1.5),\n]\nleg = ax.legend(handles=legend_elements, fontsize=8, loc=\"upper right\", framealpha=0.95)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}