{"spec_id":"spc-xbar-r","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nspc-xbar-r: Statistical Process Control Chart (X-bar/R)\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent the local plotly.py from shadowing the installed plotly package\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _this_dir]\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\n\n# Theme setup — read before anything else\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens (Imprint palette spec)\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — data colors\nBRAND_GREEN = \"#009E73\"  # first categorical series — always\nLIMIT_RED = \"#AE3030\"  # matte red — UCL/LCL alarm lines\nWARN_AMBER = \"#DDCC77\"  # amber — warning limit lines (caution anchor)\nOOC_COLOR = \"#AE3030\"  # matte red — out-of-control markers\n\n# Zone fills (semi-transparent): alert near control limits, caution near warning limits\nZONE_ALERT = \"rgba(174,48,48,0.07)\"\nZONE_WARN = \"rgba(221,204,119,0.09)\"\n\n# Data\nnp.random.seed(42)\nn_samples = 30\nsubgroup_size = 5\n\n# Control chart constants for subgroup size n=5\nA2 = 0.577\nD3 = 0.0\nD4 = 2.114\n\n# Realistic shaft diameter measurements (mm) from a CNC machining process\nprocess_mean = 25.0\nprocess_std = 0.05\nmeasurements = np.random.normal(process_mean, process_std, (n_samples, subgroup_size))\n\n# Inject out-of-control signals\nmeasurements[7] += 0.15\nmeasurements[18] -= 0.12\nmeasurements[24] += 0.18\n\n# Calculate X-bar and R for each subgroup\nsample_means = measurements.mean(axis=1)\nsample_ranges = measurements.max(axis=1) - measurements.min(axis=1)\n\n# X-bar chart control limits\nx_bar_bar = sample_means.mean()\nr_bar = sample_ranges.mean()\nucl_xbar = x_bar_bar + A2 * r_bar\nlcl_xbar = x_bar_bar - A2 * r_bar\nupper_warn_xbar = x_bar_bar + (2 / 3) * A2 * r_bar\nlower_warn_xbar = x_bar_bar - (2 / 3) * A2 * r_bar\n\n# R chart control limits\nucl_r = D4 * r_bar\nlcl_r = D3 * r_bar\nupper_warn_r = r_bar + (2 / 3) * (ucl_r - r_bar)\nlower_warn_r = max(0, r_bar - (2 / 3) * (r_bar - lcl_r))\n\nsample_ids = np.arange(1, n_samples + 1)\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)\nn_ooc = int(ooc_xbar.sum() + ooc_r.sum())\nooc_samples = \", \".join(f\"#{s}\" for s in sample_ids[ooc_xbar])\n\nX_BAR = \"X̄\"  # X̄\n\n# Figure with dual subplots (X-bar top, R bottom, shared x-axis)\nfig = make_subplots(\n    rows=2,\n    cols=1,\n    shared_xaxes=True,\n    vertical_spacing=0.10,\n    subplot_titles=[f\"<b>{X_BAR} Chart</b>  · Sample Means\", \"<b>R Chart</b>  · Sample Ranges\"],\n    row_heights=[0.55, 0.45],\n)\n\n# Zone shading — X-bar chart\nfor y0, y1, color in [\n    (upper_warn_xbar, ucl_xbar, ZONE_ALERT),\n    (lcl_xbar, lower_warn_xbar, ZONE_ALERT),\n    (x_bar_bar + (upper_warn_xbar - x_bar_bar) / 2, upper_warn_xbar, ZONE_WARN),\n    (lower_warn_xbar, x_bar_bar - (x_bar_bar - lower_warn_xbar) / 2, ZONE_WARN),\n]:\n    fig.add_shape(\n        type=\"rect\",\n        x0=0.5,\n        x1=n_samples + 0.5,\n        y0=min(y0, y1),\n        y1=max(y0, y1),\n        fillcolor=color,\n        line={\"width\": 0},\n        layer=\"below\",\n        xref=\"x\",\n        yref=\"y\",\n    )\n\n# Zone shading — R chart\nfor y0, y1, color in [\n    (upper_warn_r, ucl_r, ZONE_ALERT),\n    (lcl_r, lower_warn_r, ZONE_ALERT),\n    (r_bar + (upper_warn_r - r_bar) / 2, upper_warn_r, ZONE_WARN),\n    (lower_warn_r, r_bar - (r_bar - lower_warn_r) / 2, ZONE_WARN),\n]:\n    fig.add_shape(\n        type=\"rect\",\n        x0=0.5,\n        x1=n_samples + 0.5,\n        y0=min(y0, y1),\n        y1=max(y0, y1),\n        fillcolor=color,\n        line={\"width\": 0},\n        layer=\"below\",\n        xref=\"x2\",\n        yref=\"y2\",\n    )\n\n# --- X-bar Chart traces ---\nfig.add_trace(\n    go.Scatter(\n        x=sample_ids,\n        y=sample_means,\n        mode=\"lines+markers\",\n        marker={\"size\": 8, \"color\": BRAND_GREEN},\n        line={\"width\": 2.5, \"color\": BRAND_GREEN},\n        name=X_BAR,\n        hovertemplate=\"Sample %{x}<br>Mean: %{y:.4f} mm<extra></extra>\",\n    ),\n    row=1,\n    col=1,\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=sample_ids[ooc_xbar],\n        y=sample_means[ooc_xbar],\n        mode=\"markers\",\n        marker={\"size\": 14, \"color\": OOC_COLOR, \"symbol\": \"diamond\", \"line\": {\"width\": 2, \"color\": INK}},\n        name=\"Out of Control\",\n        hovertemplate=\"Sample %{x} (OOC)<br>Mean: %{y:.4f} mm<extra></extra>\",\n    ),\n    row=1,\n    col=1,\n)\n\n# OOC annotations — X-bar\nfor idx in np.where(ooc_xbar)[0]:\n    above = sample_means[idx] > x_bar_bar\n    fig.add_annotation(\n        x=sample_ids[idx],\n        y=sample_means[idx],\n        text=f\"<b>#{sample_ids[idx]}</b>\",\n        font={\"size\": 11, \"color\": OOC_COLOR},\n        showarrow=True,\n        arrowhead=0,\n        arrowwidth=1.5,\n        arrowcolor=OOC_COLOR,\n        ay=-28 if above else 28,\n        ax=0,\n        xref=\"x\",\n        yref=\"y\",\n        bgcolor=ELEVATED_BG,\n        bordercolor=OOC_COLOR,\n        borderwidth=1,\n        borderpad=3,\n    )\n\n# X-bar control limit lines\nfig.add_hline(y=x_bar_bar, line={\"color\": INK_SOFT, \"width\": 2}, row=1, col=1)\nfig.add_hline(y=ucl_xbar, line={\"color\": LIMIT_RED, \"width\": 2, \"dash\": \"dash\"}, row=1, col=1)\nfig.add_hline(y=lcl_xbar, line={\"color\": LIMIT_RED, \"width\": 2, \"dash\": \"dash\"}, row=1, col=1)\nfig.add_hline(y=upper_warn_xbar, line={\"color\": WARN_AMBER, \"width\": 1.5, \"dash\": \"dot\"}, row=1, col=1)\nfig.add_hline(y=lower_warn_xbar, line={\"color\": WARN_AMBER, \"width\": 1.5, \"dash\": \"dot\"}, row=1, col=1)\n\n# X-bar limit labels (right side)\nfor y_val, label, color in [(ucl_xbar, \"UCL\", LIMIT_RED), (lcl_xbar, \"LCL\", LIMIT_RED), (x_bar_bar, \"CL\", INK_SOFT)]:\n    fig.add_annotation(\n        x=1.0,\n        y=y_val,\n        text=f\"<b>{label}</b>\",\n        font={\"size\": 11, \"color\": color},\n        showarrow=False,\n        xref=\"x domain\",\n        yref=\"y\",\n        xanchor=\"left\",\n        xshift=8,\n    )\n\n# --- R Chart traces ---\nfig.add_trace(\n    go.Scatter(\n        x=sample_ids,\n        y=sample_ranges,\n        mode=\"lines+markers\",\n        marker={\"size\": 8, \"color\": BRAND_GREEN},\n        line={\"width\": 2.5, \"color\": BRAND_GREEN},\n        name=\"Range\",\n        showlegend=False,\n        hovertemplate=\"Sample %{x}<br>Range: %{y:.4f} mm<extra></extra>\",\n    ),\n    row=2,\n    col=1,\n)\n\nif ooc_r.any():\n    fig.add_trace(\n        go.Scatter(\n            x=sample_ids[ooc_r],\n            y=sample_ranges[ooc_r],\n            mode=\"markers\",\n            marker={\"size\": 14, \"color\": OOC_COLOR, \"symbol\": \"diamond\", \"line\": {\"width\": 2, \"color\": INK}},\n            name=\"Out of Control (R)\",\n            showlegend=False,\n        ),\n        row=2,\n        col=1,\n    )\n    for idx in np.where(ooc_r)[0]:\n        above = sample_ranges[idx] > r_bar\n        fig.add_annotation(\n            x=sample_ids[idx],\n            y=sample_ranges[idx],\n            text=f\"<b>#{sample_ids[idx]}</b>\",\n            font={\"size\": 11, \"color\": OOC_COLOR},\n            showarrow=True,\n            arrowhead=0,\n            arrowwidth=1.5,\n            arrowcolor=OOC_COLOR,\n            ay=-28 if above else 28,\n            ax=0,\n            xref=\"x2\",\n            yref=\"y2\",\n            bgcolor=ELEVATED_BG,\n            bordercolor=OOC_COLOR,\n            borderwidth=1,\n            borderpad=3,\n        )\n\n# R chart control limit lines\nfig.add_hline(y=r_bar, line={\"color\": INK_SOFT, \"width\": 2}, row=2, col=1)\nfig.add_hline(y=ucl_r, line={\"color\": LIMIT_RED, \"width\": 2, \"dash\": \"dash\"}, row=2, col=1)\nfig.add_hline(y=lcl_r, line={\"color\": LIMIT_RED, \"width\": 2, \"dash\": \"dash\"}, row=2, col=1)\nfig.add_hline(y=upper_warn_r, line={\"color\": WARN_AMBER, \"width\": 1.5, \"dash\": \"dot\"}, row=2, col=1)\nfig.add_hline(y=lower_warn_r, line={\"color\": WARN_AMBER, \"width\": 1.5, \"dash\": \"dot\"}, row=2, col=1)\n\n# R chart limit labels\nfor y_val, label, color in [(ucl_r, \"UCL\", LIMIT_RED), (lcl_r, \"LCL\", LIMIT_RED), (r_bar, \"CL\", INK_SOFT)]:\n    fig.add_annotation(\n        x=1.0,\n        y=y_val,\n        text=f\"<b>{label}</b>\",\n        font={\"size\": 11, \"color\": color},\n        showarrow=False,\n        xref=\"x2 domain\",\n        yref=\"y2\",\n        xanchor=\"left\",\n        xshift=8,\n    )\n\n# Process summary callout\nfig.add_annotation(\n    text=(\n        f\"<b>⚠ {n_ooc} OOC signal{'s' if n_ooc != 1 else ''} detected</b><br>\"\n        f\"<span style='font-size:10px'>Samples {ooc_samples}<br>\"\n        f\"{X_BAR}̅ = {x_bar_bar:.3f} mm · R̄ = {r_bar:.3f} mm</span>\"\n    ),\n    xref=\"x domain\",\n    yref=\"y domain\",\n    x=0.98,\n    y=0.02,\n    xanchor=\"right\",\n    yanchor=\"bottom\",\n    font={\"size\": 11, \"color\": INK},\n    bgcolor=ELEVATED_BG,\n    bordercolor=WARN_AMBER,\n    borderwidth=1.5,\n    borderpad=8,\n    showarrow=False,\n)\n\n# Layout\ntitle_text = (\n    \"<b>CNC Shaft Diameter Monitoring</b>\"\n    f\"<br><span style='font-size:12px;color:{INK_MUTED}'>\"\n    f\"spc-xbar-r · python · plotly · anyplot.ai\"\n    f\" | n=5 per subgroup, A₂=0.577, D₃=0, D₄=2.114</span>\"\n)\n\nfig.update_layout(\n    autosize=False,\n    title={\"text\": title_text, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.02, \"xanchor\": \"left\"},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    showlegend=True,\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"x\": 0.01,\n        \"y\": 0.98,\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 60, \"t\": 80, \"b\": 60},\n)\n\n# Subplot title font — match INK color for theme adaptation\nfor ann in fig.layout.annotations:\n    if \"Chart\" in (ann.text or \"\"):\n        ann.font = {\"size\": 14, \"color\": INK}\n\n# Spike lines for cross-chart sample comparison\nfig.update_xaxes(\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    gridcolor=GRID,\n    linecolor=INK_SOFT,\n    zerolinecolor=GRID,\n    spikemode=\"across\",\n    spikethickness=1,\n    spikecolor=INK_MUTED,\n    spikedash=\"dot\",\n    row=1,\n    col=1,\n)\nfig.update_xaxes(\n    title={\"text\": \"Sample Number\", \"font\": {\"size\": 12, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    gridcolor=GRID,\n    linecolor=INK_SOFT,\n    zerolinecolor=GRID,\n    spikemode=\"across\",\n    spikethickness=1,\n    spikecolor=INK_MUTED,\n    spikedash=\"dot\",\n    row=2,\n    col=1,\n)\nfig.update_yaxes(\n    title={\"text\": \"Sample Mean (mm)\", \"font\": {\"size\": 12, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    gridcolor=GRID,\n    linecolor=INK_SOFT,\n    zerolinecolor=GRID,\n    row=1,\n    col=1,\n)\nfig.update_yaxes(\n    title={\"text\": \"Sample Range (mm)\", \"font\": {\"size\": 12, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    gridcolor=GRID,\n    linecolor=INK_SOFT,\n    zerolinecolor=GRID,\n    row=2,\n    col=1,\n)\n\n# Save — canvas: 3200×1800 (landscape, 16:9)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\", config={\"displayModeBar\": True, \"scrollZoom\": True})\n"}