{"spec_id":"bar-diverging","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nbar-diverging: Diverging Bar Chart\nLibrary: altair 6.2.2 | Python 3.13.15\nQuality: 92/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent import collision with this script's filename\nsys.path = [p for p in sys.path if not p.endswith(\"/python\")]\n\nimport altair as alt\nimport pandas as pd\nfrom PIL import Image\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 — brand green for positive; the semantic red anchor (position\n# 5) for negative, since sentiment polarity is a canonical red/green pairing\nPOSITIVE_COLOR = \"#009E73\"\nNEGATIVE_COLOR = \"#AE3030\"\n\n# Data - Customer satisfaction survey results by department\ndata = pd.DataFrame(\n    {\n        \"department\": [\n            \"Customer Service\",\n            \"Engineering\",\n            \"Sales\",\n            \"Marketing\",\n            \"HR\",\n            \"Finance\",\n            \"Operations\",\n            \"IT Support\",\n            \"R&D\",\n            \"Quality Assurance\",\n            \"Legal\",\n            \"Logistics\",\n        ],\n        \"satisfaction_score\": [42, 35, 28, 15, 8, -5, -12, -18, -25, -32, -38, -45],\n    }\n)\ndata[\"sentiment\"] = data[\"satisfaction_score\"].apply(lambda x: \"Positive\" if x >= 0 else \"Negative\")\ndata[\"magnitude\"] = data[\"satisfaction_score\"].abs()\ndata = data.sort_values(\"satisfaction_score\", ascending=True)\n\n# Hover selection highlights one bar at a time in the exported HTML (genuine\n# altair/vega-lite interactivity, not a static-image simulation)\nhover = alt.selection_point(on=\"pointerover\", fields=[\"department\"], empty=False)\n\n# Opacity scales with magnitude so the most extreme scores — the story — pull\n# the eye before the near-neutral ones\nbars = (\n    alt.Chart(data)\n    .mark_bar(cornerRadius=3, height=18)\n    .encode(\n        x=alt.X(\n            \"satisfaction_score:Q\",\n            title=\"Net Satisfaction Score\",\n            axis=alt.Axis(titleFontSize=12, labelFontSize=10, tickCount=10),\n            scale=alt.Scale(domain=[-60, 60]),\n        ),\n        y=alt.Y(\n            \"department:N\",\n            title=None,\n            sort=alt.EncodingSortField(field=\"satisfaction_score\", order=\"ascending\"),\n            axis=alt.Axis(labelFontSize=10),\n        ),\n        color=alt.Color(\n            \"sentiment:N\",\n            scale=alt.Scale(domain=[\"Positive\", \"Negative\"], range=[POSITIVE_COLOR, NEGATIVE_COLOR]),\n            legend=alt.Legend(title=\"Sentiment\", titleFontSize=10, labelFontSize=10, orient=\"bottom-right\", offset=8),\n        ),\n        opacity=alt.Opacity(\"magnitude:Q\", scale=alt.Scale(range=[0.55, 1.0]), legend=None),\n        stroke=alt.value(INK),\n        strokeWidth=alt.condition(hover, alt.value(2.5), alt.value(0)),\n        tooltip=[alt.Tooltip(\"department:N\", title=\"Department\"), alt.Tooltip(\"satisfaction_score:Q\", title=\"Score\")],\n    )\n    .add_params(hover)\n)\n\n# Zero baseline rule with theme-adaptive color\nzero_line = alt.Chart(pd.DataFrame({\"x\": [0]})).mark_rule(color=INK_SOFT, strokeWidth=2).encode(x=\"x:Q\")\n\n# Combine chart and zero line with theme-adaptive styling\nchart = (\n    (bars + zero_line)\n    .properties(\n        width=620,\n        height=320,\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        title=alt.Title(\"bar-diverging · python · altair · anyplot.ai\", fontSize=16, anchor=\"middle\"),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, continuousWidth=620, continuousHeight=320)\n    .configure_axis(\n        grid=True,\n        gridOpacity=0.15,\n        gridColor=INK,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_title(color=INK, anchor=\"middle\")\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save as PNG, then pad to the exact canonical canvas — vl-convert pads title\n# and legend outside width/height, so the raw save rarely lands on target\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTARGET_W, TARGET_H = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TARGET_W or _h > TARGET_H:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}x{_h}, exceeds target {TARGET_W}x{TARGET_H}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TARGET_W or _h < TARGET_H:\n    _canvas = Image.new(\"RGB\", (TARGET_W, TARGET_H), PAGE_BG)\n    _canvas.paste(_img, ((TARGET_W - _w) // 2, (TARGET_H - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}