{"spec_id":"waterfall-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nwaterfall-basic: Basic Waterfall Chart\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as mticker\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.patches import Patch\n\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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — brand green for increases, semantic red for decreases,\n# blue for the start/end totals (spec calls out \"blue or gray\" for totals).\n# Sourced through sns.color_palette() rather than raw hex literals.\nBRAND_GREEN, ACCENT_RED, ACCENT_BLUE = sns.color_palette([\"#009E73\", \"#AE3030\", \"#4467A3\"])\n\n# Data: quarterly financial breakdown from revenue to net profit\ncategories = [\"Starting Balance\", \"Sales\", \"Returns\", \"COGS\", \"Operating Costs\", \"Taxes\", \"Net Profit\"]\nvalues = [100000, 150000, -25000, -60000, -30000, -18000, 117000]\nis_total = [True, False, False, False, False, False, True]\n\nrows = []\ncumulative = 0\nfor i, (cat, val, total) in enumerate(zip(categories, values, is_total, strict=True)):\n    if total:\n        start, end = 0, val if i == 0 else cumulative\n        color = ACCENT_BLUE\n    else:\n        start, end = cumulative, cumulative + val\n        color = BRAND_GREEN if val > 0 else ACCENT_RED\n    cumulative = end\n    rows.append({\"category\": cat, \"value\": val, \"start\": start, \"end\": end, \"color\": color, \"is_total\": total})\n\ndf = pd.DataFrame(rows)\n\n# Style — theme-adaptive chrome via seaborn's rc override\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.12,\n    },\n)\n\n# Plot — see default-style-guide.md \"Visual Sizing Defaults\" for canvas + sizing values\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nbar_width = 0.6\nfor idx, row in df.iterrows():\n    bottom = min(row[\"start\"], row[\"end\"])\n    height = abs(row[\"end\"] - row[\"start\"])\n    ax.bar(idx, height, bar_width, bottom=bottom, color=row[\"color\"], edgecolor=PAGE_BG, linewidth=1.2)\n\n    # Connecting line to the next bar, emphasizing the cumulative flow\n    if idx < len(df) - 1:\n        sns.lineplot(\n            x=[idx + bar_width / 2, idx + 1 - bar_width / 2],\n            y=[row[\"end\"], row[\"end\"]],\n            ax=ax,\n            color=INK_SOFT,\n            linewidth=1.2,\n            linestyle=\"--\",\n            alpha=0.6,\n            legend=False,\n        )\n\n    # Per-step delta label above each bar, plus a lighter running-total\n    # label underneath for intermediate steps (spec: \"Display running\n    # total labels on or near bars for clarity\").\n    top = max(row[\"start\"], row[\"end\"])\n    if row[\"is_total\"]:\n        ax.text(\n            idx, top + 4000, f\"${row['end']:,.0f}\", ha=\"center\", va=\"bottom\", fontsize=9, color=INK, fontweight=\"medium\"\n        )\n    else:\n        ax.text(\n            idx,\n            top + 4000,\n            f\"${row['value']:+,.0f}\",\n            ha=\"center\",\n            va=\"bottom\",\n            fontsize=9,\n            color=INK,\n            fontweight=\"medium\",\n        )\n        ax.text(idx, top + 15000, f\"Total: ${row['end']:,.0f}\", ha=\"center\", va=\"bottom\", fontsize=7, color=INK_SOFT)\n\n# Style\nax.set_xticks(range(len(df)))\nax.set_xticklabels(df[\"category\"], fontsize=8, rotation=20, ha=\"right\")\nax.set_ylabel(\"Amount ($)\", fontsize=10, color=INK)\ntitle = \"Quarterly Financial Breakdown · waterfall-basic · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=10, fontweight=\"medium\", color=INK, pad=14)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax.tick_params(axis=\"x\", length=0)\nsns.despine(ax=ax)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.set_ylim(0, max(df[\"end\"].max(), df[\"start\"].max()) * 1.25)\nax.yaxis.grid(True, alpha=0.12, linewidth=0.8)\nax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f\"${x / 1000:.0f}K\"))\n\n# Legend clarifying the increase/decrease/total color convention\nlegend_handles = [\n    Patch(facecolor=BRAND_GREEN, label=\"Increase\"),\n    Patch(facecolor=ACCENT_RED, label=\"Decrease\"),\n    Patch(facecolor=ACCENT_BLUE, label=\"Total\"),\n]\nax.legend(handles=legend_handles, loc=\"upper right\", fontsize=8, frameon=False, labelcolor=INK)\n\n# Save — bbox_inches MUST stay default (None) so figsize x dpi hits the exact canvas target\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}