{"spec_id":"bar-pareto","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nbar-pareto: Pareto Chart with Cumulative Line\nLibrary: altair 6.2.1 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\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\nVITAL_COLOR = \"#009E73\"  # Imprint position 1 — vital few bars\nLINE_COLOR = \"#BD8233\"  # Imprint position 4 — cumulative % line\n\n# Data — manufacturing defect analysis\ndefects = pd.DataFrame(\n    {\n        \"category\": [\n            \"Scratches\",\n            \"Dents\",\n            \"Misalignment\",\n            \"Cracks\",\n            \"Discoloration\",\n            \"Burrs\",\n            \"Warping\",\n            \"Contamination\",\n            \"Missing Parts\",\n            \"Wrong Dimensions\",\n        ],\n        \"count\": [187, 128, 95, 72, 54, 38, 27, 19, 12, 8],\n    }\n)\n\ndefects = defects.sort_values(\"count\", ascending=False).reset_index(drop=True)\ntotal = defects[\"count\"].sum()\ndefects[\"cumulative_pct\"] = defects[\"count\"].cumsum() / total * 100\ndefects[\"vital_few\"] = defects[\"cumulative_pct\"].shift(1, fill_value=0) < 80\nsort_order = defects[\"category\"].tolist()\n\nthreshold_df = pd.DataFrame({\"pct\": [80]})\n\n# Title with length-scaled fontsize (floor 11, default 16 at 67 chars)\ntitle_text = \"Manufacturing Defect Analysis · bar-pareto · python · altair · anyplot.ai\"\nn = len(title_text)\ntitle_fs = max(11, round(16 * 67 / n)) if n > 67 else 16\n\n# Bars: vital few → Imprint green, trivial many → theme-adaptive muted\nbars = (\n    alt.Chart(defects)\n    .mark_bar(cornerRadiusTopLeft=4, cornerRadiusTopRight=4)\n    .encode(\n        x=alt.X(\n            \"category:N\",\n            title=\"Defect Type\",\n            sort=sort_order,\n            axis=alt.Axis(labelAngle=-45, labelFontSize=10, titleFontSize=12),\n        ),\n        y=alt.Y(\"count:Q\", title=\"Frequency\", axis=alt.Axis(labelFontSize=10, titleFontSize=12)),\n        color=alt.condition(alt.datum.vital_few, alt.value(VITAL_COLOR), alt.value(INK_MUTED)),\n        tooltip=[\n            alt.Tooltip(\"category:N\", title=\"Defect\"),\n            alt.Tooltip(\"count:Q\", title=\"Count\"),\n            alt.Tooltip(\"cumulative_pct:Q\", title=\"Cumulative %\", format=\".1f\"),\n        ],\n    )\n)\n\n# Cumulative % line on secondary y-axis (Imprint ochre)\nline = (\n    alt.Chart(defects)\n    .mark_line(\n        color=LINE_COLOR,\n        strokeWidth=3,\n        point=alt.OverlayMarkDef(color=LINE_COLOR, size=80, filled=True, stroke=PAGE_BG, strokeWidth=1.5),\n    )\n    .encode(\n        x=alt.X(\"category:N\", sort=sort_order),\n        y=alt.Y(\n            \"cumulative_pct:Q\",\n            title=\"Cumulative Percentage (%)\",\n            scale=alt.Scale(domain=[0, 105]),\n            axis=alt.Axis(\n                labelFontSize=10, titleFontSize=12, titleColor=LINE_COLOR, labelColor=LINE_COLOR, format=\".0f\"\n            ),\n        ),\n        tooltip=[\n            alt.Tooltip(\"category:N\", title=\"Defect\"),\n            alt.Tooltip(\"cumulative_pct:Q\", title=\"Cumulative %\", format=\".1f\"),\n        ],\n    )\n)\n\n# 80% reference line\nrule = (\n    alt.Chart(threshold_df)\n    .mark_rule(strokeDash=[8, 6], strokeWidth=1.5, color=INK_MUTED)\n    .encode(y=alt.Y(\"pct:Q\", scale=alt.Scale(domain=[0, 105])))\n)\n\n# 80% label\nrule_label = (\n    alt.Chart(pd.DataFrame({\"pct\": [80], \"label\": [\"80%\"]}))\n    .mark_text(align=\"left\", dx=5, dy=-8, fontSize=10, fontWeight=\"bold\", color=INK_MUTED)\n    .encode(x=alt.value(10), y=alt.Y(\"pct:Q\", scale=alt.Scale(domain=[0, 105])), text=\"label:N\")\n)\n\n# Compose: bars + cumulative line + reference line, independent y-scales\nchart = (\n    alt.layer(bars, line + rule + rule_label)\n    .resolve_scale(y=\"independent\")\n    .properties(\n        width=620,\n        height=308,\n        background=PAGE_BG,\n        title=alt.Title(\n            text=title_text,\n            subtitle=\"Vital few categories (green) account for 80% of all defect occurrences\",\n            fontSize=title_fs,\n            subtitleFontSize=10,\n            subtitleColor=INK_SOFT,\n            color=INK,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(domainColor=INK_SOFT, tickColor=INK_SOFT, grid=False, labelColor=INK_SOFT, titleColor=INK)\n    .configure_axisY(grid=True, gridDash=[4, 4], gridColor=INK, gridOpacity=0.15)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save and pad to exact 3200 × 1800\nTW, TH = 3200, 1800\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n"}