{"spec_id":"bar-diverging-likert","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nbar-diverging-likert: Likert Scale Diverging Bar Chart\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-01\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's own directory from sys.path so that local matplotlib.py\n# does not shadow the installed matplotlib package.\nsys.path.pop(0)\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\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 — semantic mapping: positive→green, negative→red, neutral→muted\nCOL_SA = \"#009E73\"  # Strongly Agree    — brand green (most positive)\nCOL_A = \"#99B314\"  # Agree             — lime (softer positive)\nCOL_N = INK_MUTED  # Neutral           — theme-adaptive muted\nCOL_D = \"#BD8233\"  # Disagree          — ochre (mild negative)\nCOL_SD = \"#AE3030\"  # Strongly Disagree — matte red (most negative)\n\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 — employee engagement survey, 8 questions on 5-point Likert scale\nquestions = [\n    \"Career growth opportunities\",\n    \"Work-life balance\",\n    \"Team collaboration\",\n    \"Management communication\",\n    \"Compensation & benefits\",\n    \"Job security\",\n    \"Training & development\",\n    \"Workplace culture\",\n]\n\nsurvey_data = {\n    \"question\": questions,\n    \"strongly_disagree\": [5, 8, 3, 14, 20, 4, 10, 6],\n    \"disagree\": [10, 14, 7, 22, 28, 8, 18, 12],\n    \"neutral\": [15, 18, 12, 20, 17, 14, 16, 16],\n    \"agree\": [40, 35, 45, 28, 22, 42, 32, 38],\n    \"strongly_agree\": [30, 25, 33, 16, 13, 32, 24, 28],\n}\n\ndf = pd.DataFrame(survey_data)\n\ndf[\"net_agreement\"] = df[\"agree\"] + df[\"strongly_agree\"] - df[\"disagree\"] - df[\"strongly_disagree\"]\ndf = df.sort_values(\"net_agreement\").reset_index(drop=True)\n\ncategory_keys = [\"strongly_disagree\", \"disagree\", \"neutral\", \"agree\", \"strongly_agree\"]\ncategory_names = [\"Strongly Disagree\", \"Disagree\", \"Neutral\", \"Agree\", \"Strongly Agree\"]\ncolors = {\"strongly_disagree\": COL_SD, \"disagree\": COL_D, \"neutral\": COL_N, \"agree\": COL_A, \"strongly_agree\": COL_SA}\n\n# Cumulative values for diverging stacked layout (overlay technique)\nhalf_n = df[\"neutral\"] / 2\ndf[\"r_sa\"] = half_n + df[\"agree\"] + df[\"strongly_agree\"]\ndf[\"r_a\"] = half_n + df[\"agree\"]\ndf[\"r_n\"] = half_n\ndf[\"l_sd\"] = -(half_n + df[\"disagree\"] + df[\"strongly_disagree\"])\ndf[\"l_d\"] = -(half_n + df[\"disagree\"])\ndf[\"l_n\"] = -half_n\n\n# Question order: most positive at top (seaborn plots first item in order at top)\nq_order = df[\"question\"].tolist()[::-1]\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Subtle alternating row banding — seaborn-idiomatic scanline separation\nfor i in range(len(q_order)):\n    if i % 2 == 0:\n        ax.axhspan(i - 0.5, i + 0.5, facecolor=INK, alpha=0.03, zorder=0)\n\nbar_kw = {\n    \"y\": \"question\",\n    \"order\": q_order,\n    \"orient\": \"h\",\n    \"ax\": ax,\n    \"width\": 0.65,\n    \"edgecolor\": PAGE_BG,\n    \"linewidth\": 0.5,\n    \"errorbar\": None,\n}\n\n# Right side: outermost layer first, overlaid by progressively narrower inner layers\nsns.barplot(data=df, x=\"r_sa\", color=colors[\"strongly_agree\"], **bar_kw)\nsns.barplot(data=df, x=\"r_a\", color=colors[\"agree\"], **bar_kw)\nsns.barplot(data=df, x=\"r_n\", color=colors[\"neutral\"], **bar_kw)\n\n# Left side: outermost first\nsns.barplot(data=df, x=\"l_sd\", color=colors[\"strongly_disagree\"], **bar_kw)\nsns.barplot(data=df, x=\"l_d\", color=colors[\"disagree\"], **bar_kw)\nsns.barplot(data=df, x=\"l_n\", color=colors[\"neutral\"], **bar_kw)\n\n# Percentage labels inside segments ≥10%\nfor _, row in df.iterrows():\n    hn = row[\"neutral\"] / 2\n    sd_left = -hn - row[\"disagree\"] - row[\"strongly_disagree\"]\n    d_left = -hn - row[\"disagree\"]\n    a_left = hn\n    sa_left = hn + row[\"agree\"]\n\n    segments = [\n        (sd_left + row[\"strongly_disagree\"] / 2, row[\"strongly_disagree\"], \"white\"),\n        (d_left + row[\"disagree\"] / 2, row[\"disagree\"], INK),\n        (0, row[\"neutral\"], INK),\n        (a_left + row[\"agree\"] / 2, row[\"agree\"], INK),\n        (sa_left + row[\"strongly_agree\"] / 2, row[\"strongly_agree\"], \"white\"),\n    ]\n    y_pos = q_order.index(row[\"question\"])\n    for x_center, value, text_color in segments:\n        if value >= 10:\n            ax.text(\n                x_center,\n                y_pos,\n                f\"{value}%\",\n                ha=\"center\",\n                va=\"center\",\n                fontsize=8,\n                fontweight=\"medium\",\n                color=text_color,\n            )\n\n# Net agreement callout — colored score badge at right edge for each question\nfor _, row in df.iterrows():\n    y_pos = q_order.index(row[\"question\"])\n    net = int(row[\"net_agreement\"])\n    sign = \"+\" if net > 0 else \"\"\n    color = COL_SA if net > 15 else COL_SD if net < 0 else INK_MUTED\n    ax.text(\n        88,\n        y_pos,\n        f\"net {sign}{net}%\",\n        ha=\"left\",\n        va=\"center\",\n        fontsize=7,\n        color=color,\n        alpha=0.8,\n        fontweight=\"bold\" if abs(net) > 40 else \"normal\",\n    )\n\n# Style\ntitle = \"Employee Engagement Survey · bar-diverging-likert · python · seaborn · anyplot.ai\"\ntitle_len = len(title)\ntitle_fontsize = max(8, round(12 * 67 / title_len))\n\nax.set_ylabel(\"\")\nax.set_xlabel(\"Percentage\", fontsize=10, color=INK, labelpad=8)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=10)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT)\nax.axvline(0, color=INK, linewidth=0.8, zorder=3)\nax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f\"{abs(int(x))}%\"))\nax.set_xlim(-70, 112)\nax.xaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.yaxis.grid(False)\nax.set_axisbelow(True)\n\n# Bold extreme question labels for storytelling emphasis\nfor i, label in enumerate(ax.get_yticklabels()):\n    if i == 0 or i == len(q_order) - 1:\n        label.set_fontweight(\"bold\")\n\nsns.despine(ax=ax)\n\nlegend_handles = [\n    Patch(facecolor=colors[k], edgecolor=PAGE_BG, linewidth=0.5, label=cat_name)\n    for k, cat_name in zip(category_keys, category_names, strict=True)\n]\nax.legend(\n    handles=legend_handles,\n    loc=\"upper center\",\n    bbox_to_anchor=(0.5, -0.15),\n    ncol=5,\n    fontsize=8,\n    frameon=False,\n    labelcolor=INK,\n)\n\nfig.subplots_adjust(left=0.27, right=0.97, top=0.91, bottom=0.21)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}