{"spec_id":"bar-diverging","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nbar-diverging: Diverging Bar Chart\nLibrary: letsplot 4.11.0 | Python 3.13.15\nQuality: 94/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave as export_ggsave\n\n\nLetsPlot.setup_html()\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 anchors for diverging sentiment bars\nPOSITIVE_COLOR = \"#009E73\"  # Brand green — positive sentiment\nNEGATIVE_COLOR = \"#AE3030\"  # Imprint matte red — semantic anchor for negative sentiment\n\n# Data - Customer satisfaction survey (Net Promoter Score style)\ncategories = [\n    \"Product Quality\",\n    \"Customer Service\",\n    \"Pricing\",\n    \"Delivery Speed\",\n    \"Website Usability\",\n    \"Return Policy\",\n    \"Product Selection\",\n    \"Payment Options\",\n    \"Mobile App\",\n    \"Packaging\",\n    \"Technical Support\",\n    \"Loyalty Program\",\n]\n\n# More balanced scores: 6 negative, 6 positive\nscores = [62, 48, -22, 35, -15, 52, 28, 68, -38, 42, -8, 38]\n\ndf = pd.DataFrame(\n    {\n        \"Category\": categories,\n        \"Score\": scores,\n        \"Sentiment\": [\"Positive\" if s >= 0 else \"Negative\" for s in scores],\n        \"AbsScore\": [abs(s) for s in scores],\n    }\n)\n\n# Sort by score for better pattern recognition\ndf = df.sort_values(\"Score\", ascending=True).reset_index(drop=True)\n\n# Preserve category order after sorting\ndf[\"Category\"] = pd.Categorical(df[\"Category\"], categories=df[\"Category\"].tolist(), ordered=True)\n\n# Direct value labels beyond each bar end — read the exact score without\n# cross-referencing the axis, and split into two layers so positive labels\n# sit left-aligned past the bar tip while negative labels sit right-aligned.\nlabel_pad = 3\npositives = df[df[\"Score\"] >= 0].copy()\npositives[\"label_x\"] = positives[\"Score\"] + label_pad\nnegatives = df[df[\"Score\"] < 0].copy()\nnegatives[\"label_x\"] = negatives[\"Score\"] - label_pad\n\n# Create horizontal diverging bar chart with theme-adaptive styling.\n# Fill opacity scales with |score| so the strongest sentiment (the most\n# actionable signal) visually dominates the mild responses, adding a second\n# encoding dimension on top of the positive/negative hue split.\nplot = (\n    ggplot(df, aes(x=\"Score\", y=\"Category\", fill=\"Sentiment\", alpha=\"AbsScore\"))\n    + geom_bar(\n        stat=\"identity\",\n        width=0.75,\n        tooltips=layer_tooltips()\n        .line(\"@Category\")\n        .line(\"Score|@Score\")\n        .line(\"Sentiment|@Sentiment\"),\n    )\n    + geom_vline(xintercept=0, color=INK_SOFT, size=1.0)\n    + geom_text(\n        aes(x=\"label_x\", y=\"Category\", label=\"Score\"),\n        data=positives,\n        hjust=0,\n        size=3.6,\n        fontface=\"bold\",\n        color=INK,\n    )\n    + geom_text(\n        aes(x=\"label_x\", y=\"Category\", label=\"Score\"),\n        data=negatives,\n        hjust=1,\n        size=3.6,\n        fontface=\"bold\",\n        color=INK,\n    )\n    + scale_fill_manual(\n        values={\"Positive\": POSITIVE_COLOR, \"Negative\": NEGATIVE_COLOR}\n    )\n    + scale_alpha(range=[0.55, 1.0], guide=\"none\")\n    + scale_x_continuous(expand=[0.12, 0])\n    + labs(\n        x=\"Net Promoter Score (-100 to +100)\", y=\"Category\", title=\"bar-diverging · letsplot · anyplot.ai\"\n    )\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major_x=element_line(color=INK, size=0.3),\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor=element_blank(),\n        plot_title=element_text(size=16, face=\"bold\", color=INK, hjust=0.5),\n        axis_title_x=element_text(size=12, color=INK),\n        axis_title_y=element_text(size=12, color=INK),\n        axis_text_x=element_text(size=10, color=INK_SOFT),\n        axis_text_y=element_text(size=10, color=INK_SOFT),\n        axis_line_x=element_line(color=INK_SOFT),\n        axis_line_y=element_line(color=INK_SOFT),\n        legend_title=element_text(size=10, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG),\n        legend_position=\"right\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save PNG with scale 4x to get 3200 × 1800 px\nexport_ggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=4)\n\n# Save HTML for interactive version\nexport_ggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}