{"spec_id":"bar-pareto","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nbar-pareto: Pareto Chart with Cumulative Line\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# This file is named 'bokeh.py' — same as the package it imports.\n# Remove the script's own directory from sys.path so 'from bokeh.io import ...'\n# resolves to the installed bokeh package, not this file.\n_sd = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if not p or os.path.abspath(p) != _sd]\ndel _sd\n\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, LinearAxis, PrintfTickFormatter, Range1d, Span\nfrom bokeh.plotting import figure\nfrom bokeh.resources import INLINE\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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 = \"#4467A3\"  # Imprint position 3 — cumulative line\nAMBER = \"#DDCC77\"  # semantic anchor — warning/threshold for 80% reference line\n\n# Data — manufacturing defect types sorted descending by frequency\ncategories = [\n    \"Scratches\",\n    \"Dents\",\n    \"Misalignment\",\n    \"Discoloration\",\n    \"Cracks\",\n    \"Burrs\",\n    \"Warping\",\n    \"Contamination\",\n    \"Chipping\",\n    \"Porosity\",\n]\ncounts = np.array([187, 143, 98, 72, 54, 38, 27, 19, 12, 7])\ncumulative_pct = np.cumsum(counts) / counts.sum() * 100\n\n# Vital few = bars up through the first one to push cumulative past 80%\nvital_mask = np.zeros(len(counts), dtype=bool)\nfor i, pct in enumerate(cumulative_pct):\n    vital_mask[i] = True\n    if pct >= 80:\n        break\n\nbar_colors = [VITAL_COLOR if v else INK_MUTED for v in vital_mask]\n\nsource = ColumnDataSource(\n    data={\n        \"categories\": categories,\n        \"counts\": counts.tolist(),\n        \"cumulative_pct\": cumulative_pct.tolist(),\n        \"colors\": bar_colors,\n        \"pct_label\": [f\"{p:.0f}%\" for p in cumulative_pct],\n    }\n)\n\n# Figure — 3200×1800 landscape; extra right border for secondary axis\np = figure(\n    x_range=categories,\n    width=3200,\n    height=1800,\n    title=\"bar-pareto · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Defect Type\",\n    y_axis_label=\"Defect Count\",\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=220,\n)\n\n# Bars — vital few in brand green, trivial many in muted neutral\np.vbar(\n    x=\"categories\",\n    top=\"counts\",\n    source=source,\n    width=0.72,\n    color=\"colors\",\n    alpha=0.9,\n    line_color=PAGE_BG,\n    line_width=2,\n    legend_label=\"Defect Count\",\n)\n\n# Secondary y-axis for cumulative percentage (0–100%)\np.extra_y_ranges = {\"pct\": Range1d(start=0, end=105)}\npct_axis = LinearAxis(\n    y_range_name=\"pct\",\n    axis_label=\"Cumulative %\",\n    axis_label_text_font_size=\"42pt\",\n    axis_label_text_color=INK,\n    major_label_text_font_size=\"34pt\",\n    major_label_text_color=INK_SOFT,\n    axis_line_color=INK_SOFT,\n    axis_line_width=2,\n    major_tick_line_color=None,\n    minor_tick_line_color=None,\n)\npct_axis.formatter = PrintfTickFormatter(format=\"%d%%\")\np.add_layout(pct_axis, \"right\")\n\n# Cumulative line\np.line(\n    x=\"categories\",\n    y=\"cumulative_pct\",\n    source=source,\n    y_range_name=\"pct\",\n    line_width=5,\n    line_color=LINE_COLOR,\n    line_join=\"round\",\n    legend_label=\"Cumulative %\",\n)\n\n# Markers on cumulative line\np.scatter(\n    x=\"categories\",\n    y=\"cumulative_pct\",\n    source=source,\n    y_range_name=\"pct\",\n    size=18,\n    color=LINE_COLOR,\n    line_color=PAGE_BG,\n    line_width=3,\n)\n\n# Percentage labels — use text glyph with categorical x for correct anchoring\n# (Label.x must be numeric; p.text() accepts string categories via ColumnDataSource)\nvital_indices = [i for i in range(len(categories)) if vital_mask[i]]\nlabel_source = ColumnDataSource(\n    data={\n        \"lx\": [categories[i] for i in vital_indices],\n        \"ly\": [float(cumulative_pct[i]) + 3 for i in vital_indices],  # 3 pct-pts above marker\n        \"lt\": [f\"{cumulative_pct[i]:.0f}%\" for i in vital_indices],\n    }\n)\np.text(\n    x=\"lx\",\n    y=\"ly\",\n    text=\"lt\",\n    source=label_source,\n    y_range_name=\"pct\",\n    text_font_size=\"24pt\",\n    text_color=LINE_COLOR,\n    text_font_style=\"bold\",\n    text_align=\"center\",\n    text_baseline=\"bottom\",\n)\n\n# 80% reference line\np.add_layout(\n    Span(\n        location=80,\n        dimension=\"width\",\n        line_color=AMBER,\n        line_dash=\"dashed\",\n        line_width=3,\n        line_alpha=0.9,\n        y_range_name=\"pct\",\n    )\n)\n\n# 80% threshold label\np.add_layout(\n    Label(\n        x=9,\n        y=80,\n        text=\"80% threshold\",\n        text_font_size=\"26pt\",\n        text_color=AMBER,\n        text_font_style=\"bold\",\n        text_align=\"right\",\n        x_offset=-10,\n        y_offset=14,\n        y_range_name=\"pct\",\n    )\n)\n\n# HoverTool — signature Bokeh interactive feature\np.add_tools(\n    HoverTool(tooltips=[(\"Defect\", \"@categories\"), (\"Count\", \"@counts\"), (\"Cumulative\", \"@pct_label\")], mode=\"vline\")\n)\n\n# Title\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\np.title.text_color = INK\n\n# Axis labels\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\n\n# Tick labels\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.major_label_orientation = 0.5\n\n# Axis lines\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.axis_line_width = 2\np.yaxis.axis_line_width = 2\n\n# Remove tick marks\np.xaxis.major_tick_line_color = None\np.yaxis.major_tick_line_color = None\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\n\n# Grid — horizontal only, very subtle\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.12\n\n# Y range with headroom for legend in top-left\np.y_range.start = 0\np.y_range.end = max(counts) * 1.25\n\n# Background and frame\np.outline_line_color = None\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Legend\nif p.legend:\n    legend = p.legend[0]\n    legend.location = \"top_left\"\n    legend.label_text_font_size = \"34pt\"\n    legend.label_text_color = INK_SOFT\n    legend.background_fill_color = ELEVATED_BG\n    legend.border_line_color = INK_SOFT\n    legend.padding = 15\n    legend.spacing = 8\n\n# Save HTML artifact (interactive) — inline resources so Selenium can render via file://\noutput_file(f\"plot-{THEME}.html\")\nsave(p, resources=INLINE)\n\n# Screenshot with Selenium headless Chrome\nW, H = 3200, 1800\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\n# Headless Chrome's inner viewport is smaller than the outer window by ~143px\n# (browser UI chrome). Compensate so the screenshot is exactly W×H pixels.\ninner_h = driver.execute_script(\"return window.innerHeight\")\nif inner_h < H:\n    driver.set_window_size(W, H + (H - inner_h))\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}