{"spec_id":"bar-grouped","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nbar-grouped: Grouped Bar Chart\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 94/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as patheffects\nimport matplotlib.pyplot as plt\nimport numpy as np\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette (position 1 is always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data: quarterly sales by product line (thousands USD)\ncategories = [\"Q1\", \"Q2\", \"Q3\", \"Q4\"]\ngroups = [\"Electronics\", \"Clothing\", \"Home & Garden\"]\n\nsales_data = {\n    \"Electronics\": [245, 312, 287, 425],\n    \"Clothing\": [178, 195, 285, 310],\n    \"Home & Garden\": [125, 210, 195, 165],\n}\n\n# Setup for grouped bars\nx = np.arange(len(categories))\nn_groups = len(groups)\nbar_width = 0.25\noffsets = np.linspace(-(n_groups - 1) / 2, (n_groups - 1) / 2, n_groups) * bar_width\nmax_value = max(max(v) for v in sales_data.values())\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Track the top group per category for visual emphasis\nmax_values_per_category = {cat: max(sales_data[group][i] for group in groups) for i, cat in enumerate(categories)}\n\nbars = []\nfor i, (group, color) in enumerate(zip(groups, IMPRINT, strict=True)):\n    bar = ax.bar(\n        x + offsets[i], sales_data[group], bar_width, label=group, color=color, edgecolor=INK_SOFT, linewidth=1.0\n    )\n    bars.append(bar)\n\n    # Drop shadow for depth, via matplotlib's native path-effect (idiomatic vs. manual patches)\n    for rect in bar:\n        rect.set_path_effects([patheffects.withSimplePatchShadow(offset=(4, -4), shadow_rgbFace=\"#000000\", alpha=0.30)])\n\n# Value labels and top-performer markers\nfor bar_group in bars:\n    for j, bar in enumerate(bar_group):\n        height = bar.get_height()\n        is_max = height == max_values_per_category[categories[j]]\n\n        ax.scatter(\n            bar.get_x() + bar.get_width() / 2,\n            height,\n            s=60 if is_max else 34,\n            color=bar.get_facecolor(),\n            edgecolors=INK_SOFT,\n            linewidth=1.0 if is_max else 0.6,\n            alpha=1.0 if is_max else 0.6,\n            zorder=3,\n        )\n        ax.annotate(\n            f\"{int(height)}\",\n            xy=(bar.get_x() + bar.get_width() / 2, height),\n            xytext=(0, 6),\n            textcoords=\"offset points\",\n            ha=\"center\",\n            va=\"bottom\",\n            fontsize=9,\n            color=INK,\n            fontweight=\"bold\" if is_max else \"normal\",\n            zorder=4,\n        )\n\n# Explicit callout for the data story: Electronics vs. Clothing near-tie in Q3\nq3_idx = categories.index(\"Q3\")\ntop_group, second_group = sorted(groups, key=lambda g: sales_data[g][q3_idx], reverse=True)[:2]\ntop_i, second_i = groups.index(top_group), groups.index(second_group)\ntop_val, second_val = sales_data[top_group][q3_idx], sales_data[second_group][q3_idx]\nx1, x2 = x[q3_idx] + offsets[top_i], x[q3_idx] + offsets[second_i]\ntop_y, second_y = top_val + max_value * 0.08, second_val + max_value * 0.08\nbracket_y = max(top_y, second_y) + max_value * 0.04\nax.plot([x1, x1, x2, x2], [top_y, bracket_y, bracket_y, second_y], color=INK_MUTED, linewidth=1.0, zorder=5)\nax.text(\n    (x1 + x2) / 2,\n    bracket_y + max_value * 0.015,\n    \"Near-tie in Q3\",\n    ha=\"center\",\n    va=\"bottom\",\n    fontsize=8,\n    color=INK_SOFT,\n    style=\"italic\",\n    zorder=5,\n)\n\n# Style\ntitle = \"bar-grouped · python · matplotlib · anyplot.ai\"\ntitle_fontsize = round(13 * 67 / len(title)) if len(title) > 67 else 13\nax.set_title(title, fontsize=title_fontsize, fontweight=\"bold\", color=INK)\nax.set_xlabel(\"Quarter\", fontsize=10, color=INK)\nax.set_ylabel(\"Sales (Thousands USD)\", fontsize=10, color=INK)\n\nax.set_xticks(x)\nax.set_xticklabels(categories, fontsize=8, color=INK_SOFT)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\n\n# Legend, theme-adaptive\nleg = ax.legend(fontsize=8, loc=\"upper left\", framealpha=0.95)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    leg.get_frame().set_linewidth(0.8)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\n# Grid\nax.yaxis.grid(True, alpha=0.12, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.set_ylim(0, max_value * 1.15)\n\nfig.subplots_adjust(left=0.075, right=0.985, top=0.91, bottom=0.11)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}