{"spec_id":"bar-pareto","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nbar-pareto: Pareto Chart with Cumulative Line\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 86/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if os.path.abspath(p) != os.getcwd()]\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_bar,\n    geom_hline,\n    geom_line,\n    geom_point,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_x_discrete,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nVITAL_COLOR = \"#009E73\"  # Imprint position 1 — vital few (highest-impact complaints)\nCUMLINE_COLOR = \"#C475FD\"  # Imprint position 2 — cumulative percentage line\n\n# Data — call-centre complaint categories, Q1 (9 categories)\ncategories = [\n    \"Billing Error\",\n    \"Technical Issue\",\n    \"Account Access\",\n    \"Product Defect\",\n    \"Late Delivery\",\n    \"Missing Item\",\n    \"Price Dispute\",\n    \"Policy Question\",\n    \"Other\",\n]\ncounts = [245, 198, 156, 123, 89, 67, 45, 28, 14]\n\ndf = pd.DataFrame({\"category\": categories, \"count\": counts})\ndf = df.sort_values(\"count\", ascending=False).reset_index(drop=True)\ndf[\"category\"] = pd.Categorical(df[\"category\"], categories=df[\"category\"], ordered=True)\n\n# Cumulative percentage scaled to primary y-axis\ntotal = df[\"count\"].sum()\ndf[\"cum_pct\"] = df[\"count\"].cumsum() / total * 100\nmax_count = df[\"count\"].max()\nscale_factor = max_count / 100\ndf[\"cum_scaled\"] = df[\"cum_pct\"] * scale_factor\n\n# Vital few (all bars where cumulative % before them is < 80%) vs useful many\ndf[\"vital\"] = df[\"cum_pct\"].shift(1, fill_value=0) < 80\ndf[\"bar_fill\"] = df[\"vital\"].map({True: \"vital\", False: \"useful\"})\n\n# Cumulative percentage labels\ndf[\"pct_label\"] = df[\"cum_pct\"].apply(lambda v: f\"{v:.0f}%\")\n\ny_max = max_count * 1.15\ntitle = \"bar-pareto · python · plotnine · anyplot.ai\"\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"category\"))\n    + geom_bar(aes(y=\"count\", fill=\"bar_fill\"), stat=\"identity\", width=0.7)\n    + scale_fill_manual(\n        values={\"vital\": VITAL_COLOR, \"useful\": INK_MUTED},\n        breaks=[\"vital\", \"useful\"],\n        labels=[\"Vital Few\", \"Useful Many\"],\n        name=\"\",\n    )\n    + geom_line(aes(y=\"cum_scaled\", group=1), color=CUMLINE_COLOR, size=1.2)\n    + geom_point(aes(y=\"cum_scaled\"), color=CUMLINE_COLOR, fill=PAGE_BG, size=3, stroke=1.2)\n    + geom_text(\n        aes(y=\"cum_scaled\", label=\"pct_label\"),\n        size=3.5,\n        va=\"bottom\",\n        nudge_y=10,\n        color=CUMLINE_COLOR,\n        fontweight=\"bold\",\n    )\n    + geom_hline(yintercept=80 * scale_factor, linetype=\"dashed\", color=INK_MUTED, size=0.6)\n    + annotate(\"text\", x=0.5, y=80 * scale_factor + 10, label=\"80% threshold\", size=3, color=INK_MUTED, ha=\"left\")\n    + scale_y_continuous(name=\"Complaint Count\", expand=(0, 0, 0.08, 0), limits=(0, y_max))\n    + scale_x_discrete(expand=(0.05, 0.6))\n    + labs(x=\"Complaint Category\", title=title)\n    + theme_minimal(base_size=10)\n    + theme(\n        figure_size=(8, 4.5),\n        plot_title=element_text(size=12, weight=\"bold\", color=INK, margin={\"b\": 10}),\n        axis_title_x=element_text(size=10, color=INK, margin={\"t\": 8}),\n        axis_title_y=element_text(size=10, color=INK, margin={\"r\": 8}),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        axis_text_x=element_text(rotation=30, ha=\"right\"),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(alpha=0.15, size=0.3, color=INK),\n        axis_ticks=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        legend_position=(0.87, 0.72),\n        legend_direction=\"vertical\",\n        legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT, size=0.3),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_blank(),\n        legend_key=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_margin=0.02,\n    )\n)\n\n# Draw to matplotlib figure, then add secondary y-axis for cumulative %\nfig = plot.draw()\nax = fig.axes[0]\n\nax2 = ax.twinx()\nax2.set_ylim(0, y_max / scale_factor)\nax2.set_yticks([0, 20, 40, 60, 80, 100])\nax2.set_yticklabels([f\"{t}%\" for t in [0, 20, 40, 60, 80, 100]], fontsize=8, color=INK_SOFT)\nax2.tick_params(axis=\"y\", length=0, pad=3)\nfor spine in ax2.spines.values():\n    spine.set_visible(False)\n\n# Adjust layout so secondary y-axis fits within canvas without bbox_inches='tight'\nfig.subplots_adjust(right=0.87)\n\n# Save — figure_size=(8, 4.5) dpi=400 → 3200×1800 px; no bbox_inches='tight'\nfig.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}