{"spec_id":"bar-stacked-percent","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nbar-stacked-percent: 100% Stacked Bar Chart\nLibrary: seaborn 0.13.2 | Python 3.13.15\nQuality: 94/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport seaborn.objects as so\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 palette — canonical order, first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Configure seaborn with theme-adaptive colors\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.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data: annual operating budget allocation by department\nyears = [\"2019\", \"2020\", \"2021\", \"2022\", \"2023\", \"2024\"]\ndepartments = [\"Engineering\", \"Marketing\", \"Operations\", \"R&D\"]\n\nbudget_millions = pd.DataFrame(\n    {\n        \"Engineering\": [4.2, 4.6, 5.5, 6.8, 7.9, 8.6],\n        \"Marketing\": [2.8, 2.2, 2.6, 3.1, 3.0, 2.7],\n        \"Operations\": [3.5, 3.6, 3.4, 3.5, 3.3, 3.2],\n        \"R&D\": [1.5, 2.1, 2.9, 3.6, 4.4, 5.5],\n    },\n    index=years,\n)\n\n# Normalize to a 100% stacked share per year\nbudget_share = budget_millions.div(budget_millions.sum(axis=1), axis=0) * 100\nlong_form = budget_share.reset_index(names=\"year\").melt(id_vars=\"year\", var_name=\"department\", value_name=\"share_pct\")\nlong_form[\"department\"] = pd.Categorical(long_form[\"department\"], categories=departments, ordered=True)\n\n# Plot — seaborn's objects interface applies the percent-stacking transform natively\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\n\n(\n    so.Plot(long_form, x=\"year\", y=\"share_pct\", color=\"department\")\n    .add(so.Bar(alpha=1, edgewidth=1.5, edgecolor=PAGE_BG, width=0.65), so.Stack())\n    .scale(color=so.Nominal(IMPRINT))\n    .on(ax)\n    .plot()\n)\nfig.legends[0].set_visible(False)  # hide the objects-interface default legend, replaced below\n\n# Percentage labels inside segments large enough to hold text.\n# Text color is chosen per segment's own luminance (data colors stay fixed across\n# themes), not per page theme, so contrast holds against every Imprint hue.\ncumulative_share = budget_share.cumsum(axis=1)\nsegment_luminance = [\n    0.299 * int(hex_color[1:3], 16) + 0.587 * int(hex_color[3:5], 16) + 0.114 * int(hex_color[5:7], 16)\n    for hex_color in IMPRINT\n]\nlabel_colors = [\"#1A1A17\" if lum > 140 else \"#F0EFE8\" for lum in segment_luminance]\n\nfor i, department in enumerate(departments):\n    bottom = cumulative_share.iloc[:, i - 1].to_numpy() if i > 0 else np.zeros(len(years))\n    heights = budget_share[department].to_numpy()\n    for x_pos, (height, base) in enumerate(zip(heights, bottom, strict=True)):\n        if height > 8:  # skip labels on slivers too thin to hold text\n            ax.text(\n                x_pos,\n                base + height / 2,\n                f\"{height:.0f}%\",\n                ha=\"center\",\n                va=\"center\",\n                fontsize=11,\n                fontweight=\"bold\",\n                color=label_colors[i],\n            )\n\n# Styling — reserve headroom above the axes for the title + legend stack, and\n# footroom below for the data-storytelling footnote\nfig.subplots_adjust(top=0.76, bottom=0.20, left=0.09, right=0.97)\ntitle = \"bar-stacked-percent · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=48)\nax.set_xlabel(\"Fiscal Year\", fontsize=10, color=INK)\nax.set_ylabel(\"Budget Share (%)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_ylim(0, 100)\nax.set_yticks([0, 20, 40, 60, 80, 100])\n\n# Legend — left-to-right order mirrors bottom-to-top stacking order for easy\n# reading; frameless per the style guide's decoration-removal guidance\nlegend_handles = [\n    Patch(facecolor=color, label=department) for department, color in zip(departments, IMPRINT, strict=True)\n]\nax.legend(handles=legend_handles, loc=\"upper center\", bbox_to_anchor=(0.5, 1.16), ncol=4, fontsize=8, frameon=False)\n\n# Grid\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8)\nax.set_axisbelow(True)\nsns.despine(ax=ax)\n\n# Data-storytelling footnote — calls out the compositional shift the stack\n# alone doesn't narrate: Engineering + R&D's combined share of the budget\n# rising steadily while Marketing + Operations's share falls.\neng_rd_start = budget_share.loc[\"2019\", [\"Engineering\", \"R&D\"]].sum()\neng_rd_end = budget_share.loc[\"2024\", [\"Engineering\", \"R&D\"]].sum()\nfootnote = (\n    f\"Engineering + R&D's combined share of the budget grew from {eng_rd_start:.0f}% to \"\n    f\"{eng_rd_end:.0f}% between 2019 and 2024, as Marketing + Operations's share fell.\"\n)\nfig.text(0.5, 0.035, footnote, ha=\"center\", va=\"bottom\", fontsize=8, style=\"italic\", color=INK_SOFT)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}