{"spec_id":"spc-xbar-r","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nspc-xbar-r: Statistical Process Control Chart (X-bar/R)\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-06-20\n\"\"\"\n\nimport sys\n\n\n# Remove script directory from sys.path so 'bokeh.py' doesn't shadow the installed bokeh package\nif sys.path and sys.path[0] not in (\"\", None):\n    sys.path.pop(0)\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.layouts import column\nfrom bokeh.models import BoxAnnotation, ColumnDataSource, HoverTool, Label, Legend, LegendItem, Range1d, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme-adaptive chrome 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 — semantic roles for SPC signals\nCLR_IN_CONTROL = \"#009E73\"  # position 1 — in-control (green = OK)\nCLR_OOC = \"#AE3030\"  # position 5 — out-of-control (red = error)\nCLR_WARNING = \"#DDCC77\"  # amber anchor — ±2σ warning limits\n\n# === Data: CNC shaft diameter measurements (subgroups of 5) ===\nnp.random.seed(42)\nn_samples = 30\nsubgroup_size = 5\ntarget_diameter = 25.0  # mm\n\nmeasurements = np.random.normal(target_diameter, 0.05, (n_samples, subgroup_size))\nmeasurements[8] += 0.15  # process shift up at sample 9\nmeasurements[17] -= 0.18  # process shift down at sample 18\nmeasurements[24] += 0.20  # process shift up at sample 25\n\nsample_ids = np.arange(1, n_samples + 1)\nsample_means = measurements.mean(axis=1)\nsample_ranges = measurements.max(axis=1) - measurements.min(axis=1)\nsample_ranges[12] *= 3.5  # abnormal range spike at sample 13\n\n# SPC constants for subgroup size n=5\nA2, D3, D4 = 0.577, 0.0, 2.114\n\nx_bar_bar = sample_means.mean()\nr_bar = sample_ranges.mean()\n\n# X-bar chart limits (UCL/LCL at ±3σ, warning limits at ±2σ)\nucl_xbar = x_bar_bar + A2 * r_bar\nlcl_xbar = x_bar_bar - A2 * r_bar\nuwl_xbar = x_bar_bar + (2 / 3) * A2 * r_bar\nlwl_xbar = x_bar_bar - (2 / 3) * A2 * r_bar\n\n# R chart limits (LCL=0 for n≤6 since D3=0)\nucl_r = D4 * r_bar\nlcl_r = D3 * r_bar\nuwl_r = r_bar + (2 / 3) * (ucl_r - r_bar)\nlwl_r = max(0.0, r_bar - (2 / 3) * (r_bar - lcl_r))\n\n# Identify out-of-control points\nooc_xbar = (sample_means > ucl_xbar) | (sample_means < lcl_xbar)\nooc_r = (sample_ranges > ucl_r) | (sample_ranges < lcl_r)\n\n# ColumnDataSources\nsrc_xbar_ok = ColumnDataSource(\n    data={\"x\": sample_ids[~ooc_xbar], \"y\": sample_means[~ooc_xbar], \"status\": [\"In Control\"] * int((~ooc_xbar).sum())}\n)\nsrc_xbar_ooc = ColumnDataSource(\n    data={\"x\": sample_ids[ooc_xbar], \"y\": sample_means[ooc_xbar], \"status\": [\"Out of Control\"] * int(ooc_xbar.sum())}\n)\nsrc_xbar_line = ColumnDataSource(data={\"x\": sample_ids, \"y\": sample_means})\n\nsrc_r_ok = ColumnDataSource(\n    data={\"x\": sample_ids[~ooc_r], \"y\": sample_ranges[~ooc_r], \"status\": [\"In Control\"] * int((~ooc_r).sum())}\n)\nsrc_r_ooc = ColumnDataSource(\n    data={\"x\": sample_ids[ooc_r], \"y\": sample_ranges[ooc_r], \"status\": [\"Out of Control\"] * int(ooc_r.sum())}\n)\nsrc_r_line = ColumnDataSource(data={\"x\": sample_ids, \"y\": sample_ranges})\n\n# Hover tools\nhover_xbar = HoverTool(\n    tooltips=[\n        (\"Sample\", \"@x\"),\n        (\"X̄\", \"@y{0.000} mm\"),\n        (\"Status\", \"@status\"),\n        (\"UCL\", f\"{ucl_xbar:.3f}\"),\n        (\"CL (X̄̄)\", f\"{x_bar_bar:.3f}\"),\n        (\"LCL\", f\"{lcl_xbar:.3f}\"),\n    ]\n)\nhover_r = HoverTool(\n    tooltips=[\n        (\"Sample\", \"@x\"),\n        (\"Range\", \"@y{0.000} mm\"),\n        (\"Status\", \"@status\"),\n        (\"UCL\", f\"{ucl_r:.3f}\"),\n        (\"R̄\", f\"{r_bar:.3f}\"),\n    ]\n)\n\n# === Canvas: 3200 × 1800 — two panels (895 each) + 10px spacing ===\nW, H = 3200, 1800\nCH = 895  # 895 + 10 spacing + 895 = 1800\nx_range = Range1d(start=0.0, end=n_samples + 4.5)\nlabel_x = n_samples + 0.8\n\n# Label style for control-limit annotations\nlbl = {\"text_font_size\": \"24pt\", \"text_alpha\": 0.9, \"text_font_style\": \"bold\"}\n\n# === X-bar chart (top panel) ===\np_xbar = figure(\n    width=W,\n    height=CH,\n    title=\"spc-xbar-r · python · bokeh · anyplot.ai\",\n    x_range=x_range,\n    y_axis_label=\"X̄ (Sample Mean, mm)\",\n    toolbar_location=None,\n    min_border_top=110,\n    min_border_left=180,\n    min_border_right=150,\n    min_border_bottom=10,\n)\np_xbar.add_tools(hover_xbar)\n\n# Zone fills: Zone C (inner ±2σ) in green, Zone B (±2σ–±3σ) in amber\np_xbar.add_layout(BoxAnnotation(bottom=lwl_xbar, top=uwl_xbar, fill_color=CLR_IN_CONTROL, fill_alpha=0.07))\np_xbar.add_layout(BoxAnnotation(bottom=uwl_xbar, top=ucl_xbar, fill_color=CLR_WARNING, fill_alpha=0.08))\np_xbar.add_layout(BoxAnnotation(bottom=lcl_xbar, top=lwl_xbar, fill_color=CLR_WARNING, fill_alpha=0.08))\n\n# Data line and markers\np_xbar.line(\"x\", \"y\", source=src_xbar_line, line_width=3.0, line_color=CLR_IN_CONTROL, line_alpha=0.75)\nglyph_xbar_ok = p_xbar.scatter(\"x\", \"y\", source=src_xbar_ok, size=16, color=CLR_IN_CONTROL, alpha=0.9)\nglyph_xbar_ooc = p_xbar.scatter(\n    \"x\", \"y\", source=src_xbar_ooc, size=24, color=CLR_OOC, marker=\"diamond\", line_color=INK, line_width=1.5\n)\n\n# Control limit lines\np_xbar.add_layout(Span(location=ucl_xbar, dimension=\"width\", line_color=CLR_OOC, line_dash=\"dashed\", line_width=3.0))\np_xbar.add_layout(Span(location=lcl_xbar, dimension=\"width\", line_color=CLR_OOC, line_dash=\"dashed\", line_width=3.0))\np_xbar.add_layout(Span(location=x_bar_bar, dimension=\"width\", line_color=INK, line_width=3.0))\np_xbar.add_layout(\n    Span(location=uwl_xbar, dimension=\"width\", line_color=CLR_WARNING, line_dash=\"dotted\", line_width=2.0)\n)\np_xbar.add_layout(\n    Span(location=lwl_xbar, dimension=\"width\", line_color=CLR_WARNING, line_dash=\"dotted\", line_width=2.0)\n)\n\n# Limit annotations (right side, in data coordinates)\np_xbar.add_layout(Label(x=label_x, y=ucl_xbar, text=f\"UCL={ucl_xbar:.3f}\", text_color=CLR_OOC, **lbl))\np_xbar.add_layout(Label(x=label_x, y=lcl_xbar, text=f\"LCL={lcl_xbar:.3f}\", text_color=CLR_OOC, **lbl))\np_xbar.add_layout(Label(x=label_x, y=x_bar_bar, text=f\"X̄̄={x_bar_bar:.3f}\", text_color=INK, **lbl))\namber_lbl = {**lbl, \"background_fill_color\": ELEVATED_BG, \"background_fill_alpha\": 0.85}\np_xbar.add_layout(Label(x=label_x, y=uwl_xbar, text=\"+2σ\", text_color=CLR_WARNING, **amber_lbl))\np_xbar.add_layout(Label(x=label_x, y=lwl_xbar, text=\"−2σ\", text_color=CLR_WARNING, **amber_lbl))\n\n# Legend\nlegend_xbar = Legend(\n    items=[\n        LegendItem(label=\"In Control\", renderers=[glyph_xbar_ok]),\n        LegendItem(label=\"Out of Control\", renderers=[glyph_xbar_ooc]),\n    ],\n    location=\"top_left\",\n    label_text_font_size=\"34pt\",\n    label_text_color=INK_SOFT,\n    border_line_color=INK_SOFT,\n    background_fill_color=ELEVATED_BG,\n    background_fill_alpha=0.9,\n    padding=16,\n    spacing=10,\n)\np_xbar.add_layout(legend_xbar)\n\n# X-bar styling\np_xbar.title.text_font_size = \"50pt\"\np_xbar.title.text_color = INK\np_xbar.title.text_font_style = \"bold\"\np_xbar.yaxis.axis_label_text_font_size = \"42pt\"\np_xbar.yaxis.major_label_text_font_size = \"34pt\"\np_xbar.yaxis.axis_label_text_color = INK\np_xbar.yaxis.major_label_text_color = INK_SOFT\np_xbar.yaxis.axis_line_color = INK_SOFT\np_xbar.yaxis.major_tick_line_color = INK_SOFT\np_xbar.yaxis.minor_tick_line_color = None\np_xbar.xaxis.visible = False\np_xbar.ygrid.grid_line_color = INK\np_xbar.ygrid.grid_line_alpha = 0.12\np_xbar.xgrid.grid_line_alpha = 0.0\np_xbar.outline_line_color = None\np_xbar.background_fill_color = PAGE_BG\np_xbar.border_fill_color = PAGE_BG\n\n# === R chart (bottom panel) ===\np_r = figure(\n    width=W,\n    height=CH,\n    x_range=p_xbar.x_range,\n    x_axis_label=\"Sample Number\",\n    y_axis_label=\"R (Sample Range, mm)\",\n    toolbar_location=None,\n    min_border_top=10,\n    min_border_left=180,\n    min_border_right=150,\n    min_border_bottom=160,\n)\np_r.add_tools(hover_r)\n\n# Zone fills for R chart (asymmetric — LCL=0 for n≤6)\np_r.add_layout(BoxAnnotation(bottom=lwl_r, top=uwl_r, fill_color=CLR_IN_CONTROL, fill_alpha=0.07))\np_r.add_layout(BoxAnnotation(bottom=uwl_r, top=ucl_r, fill_color=CLR_WARNING, fill_alpha=0.08))\n\n# Data\np_r.line(\"x\", \"y\", source=src_r_line, line_width=3.0, line_color=CLR_IN_CONTROL, line_alpha=0.75)\nglyph_r_ok = p_r.scatter(\"x\", \"y\", source=src_r_ok, size=16, color=CLR_IN_CONTROL, alpha=0.9)\nglyph_r_ooc = p_r.scatter(\n    \"x\", \"y\", source=src_r_ooc, size=24, color=CLR_OOC, marker=\"diamond\", line_color=INK, line_width=1.5\n)\n\n# Control limits for R chart (skip LCL Span since lcl_r=0 overlaps x-axis)\np_r.add_layout(Span(location=ucl_r, dimension=\"width\", line_color=CLR_OOC, line_dash=\"dashed\", line_width=3.0))\np_r.add_layout(Span(location=r_bar, dimension=\"width\", line_color=INK, line_width=3.0))\np_r.add_layout(Span(location=uwl_r, dimension=\"width\", line_color=CLR_WARNING, line_dash=\"dotted\", line_width=2.0))\nif lwl_r > 0:\n    p_r.add_layout(Span(location=lwl_r, dimension=\"width\", line_color=CLR_WARNING, line_dash=\"dotted\", line_width=2.0))\n\n# Limit annotations for R chart\np_r.add_layout(Label(x=label_x, y=ucl_r, text=f\"UCL={ucl_r:.3f}\", text_color=CLR_OOC, **lbl))\np_r.add_layout(Label(x=label_x, y=r_bar, text=f\"R̄={r_bar:.3f}\", text_color=INK, **lbl))\np_r.add_layout(Label(x=label_x, y=uwl_r, text=\"+2σ\", text_color=CLR_WARNING, **amber_lbl))\nif lwl_r > 0:\n    p_r.add_layout(Label(x=label_x, y=lwl_r, text=\"−2σ\", text_color=CLR_WARNING, **amber_lbl))\n\n# Legend for R chart\nlegend_r = Legend(\n    items=[\n        LegendItem(label=\"In Control\", renderers=[glyph_r_ok]),\n        LegendItem(label=\"Out of Control\", renderers=[glyph_r_ooc]),\n    ],\n    location=\"top_left\",\n    label_text_font_size=\"34pt\",\n    label_text_color=INK_SOFT,\n    border_line_color=INK_SOFT,\n    background_fill_color=ELEVATED_BG,\n    background_fill_alpha=0.9,\n    padding=16,\n    spacing=10,\n)\np_r.add_layout(legend_r)\n\n# R chart styling\np_r.xaxis.axis_label_text_font_size = \"42pt\"\np_r.yaxis.axis_label_text_font_size = \"42pt\"\np_r.xaxis.major_label_text_font_size = \"34pt\"\np_r.yaxis.major_label_text_font_size = \"34pt\"\np_r.xaxis.axis_label_text_color = INK\np_r.yaxis.axis_label_text_color = INK\np_r.xaxis.major_label_text_color = INK_SOFT\np_r.yaxis.major_label_text_color = INK_SOFT\np_r.xaxis.axis_line_color = INK_SOFT\np_r.yaxis.axis_line_color = INK_SOFT\np_r.xaxis.major_tick_line_color = INK_SOFT\np_r.yaxis.major_tick_line_color = INK_SOFT\np_r.xaxis.minor_tick_line_color = None\np_r.yaxis.minor_tick_line_color = None\np_r.ygrid.grid_line_color = INK\np_r.ygrid.grid_line_alpha = 0.12\np_r.xgrid.grid_line_alpha = 0.0\np_r.outline_line_color = None\np_r.background_fill_color = PAGE_BG\np_r.border_fill_color = PAGE_BG\n\n# === Layout and Export ===\nlayout = column(p_xbar, p_r, spacing=10)\n\n# Write interactive HTML; strip default browser body margins for correct viewport fill\noutput_file(f\"plot-{THEME}.html\")\nsave(layout)\nhtml_path = Path(f\"plot-{THEME}.html\")\nhtml_content = html_path.read_text()\nhtml_content = html_content.replace(\"<body>\", f'<body style=\"margin:0;padding:0;background:{PAGE_BG};\">', 1)\nhtml_path.write_text(html_content)\n\n# Screenshot via headless Chrome (Selenium — export_png not available on this system)\n# Use CDP setDeviceMetricsOverride to guarantee an exact W×H viewport (DPR=1),\n# bypassing the window-chrome overhead that --window-size alone leaves.\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    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{html_path.resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}