{"spec_id":"histogram-capability","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nhistogram-capability: Process Capability Plot with Specification Limits\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 82/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Script filename shadows the installed `pygal` package when run as `python pygal.py`;\n# dropping the script directory from sys.path lets the real package resolve.\nsys.path.pop(0)\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\nfrom scipy import stats\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_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data — shaft diameter measurements (mm)\nnp.random.seed(42)\nlsl = 9.95\nusl = 10.05\ntarget = 10.00\nmeasurements = np.random.normal(loc=10.002, scale=0.012, size=200)\n\n# Statistics\nmean = np.mean(measurements)\nsigma = np.std(measurements, ddof=1)\ncp = (usl - lsl) / (6 * sigma)\ncpk = min((usl - mean) / (3 * sigma), (mean - lsl) / (3 * sigma))\n\n# Histogram bins\nn_bins = 20\ncounts, bin_edges = np.histogram(measurements, bins=n_bins)\nbin_width = bin_edges[1] - bin_edges[0]\n\n# Normal distribution curve — scaled to match histogram frequency axis\nn_curve_pts = 60\nx_curve = np.linspace(mean - 4 * sigma, mean + 4 * sigma, n_curve_pts)\ny_curve = stats.norm.pdf(x_curve, mean, sigma) * len(measurements) * bin_width\ndx_curve = x_curve[1] - x_curve[0]\n\n# Title — scale font size to prevent overflow (67-char baseline → font size 66)\ntitle = f\"histogram-capability · python · pygal · anyplot.ai  |  Cp = {cp:.2f}  ·  Cpk = {cpk:.2f}\"\nn = len(title)\ntitle_font_size = max(44, round(66 * 67 / n)) if n > 67 else 66\n\n# Style — Imprint palette with semantic color mapping:\n#   series 1: measurements histogram → brand green #009E73 (always first series)\n#   series 2: normal fit curve → lavender #C475FD\n#   series 3+4: LSL/USL spec limits → semantic red #AE3030 (danger/out-of-spec boundary)\n#   series 5: target nominal → blue #4467A3\n#   series 6: process mean → ink neutral (theme-adaptive)\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(\"#009E73\", \"#C475FD\", \"#AE3030\", \"#AE3030\", \"#4467A3\", INK),\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n    font_family=\"'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\",\n)\n\ny_ceil = float(max(counts) * 1.3)\n\nchart = pygal.Histogram(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"Shaft Diameter (mm)\",\n    y_title=\"Frequency\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_box_size=22,\n    show_y_guides=True,\n    show_x_guides=False,\n    truncate_label=-1,\n    truncate_legend=-1,\n    margin_top=60,\n    margin_right=120,\n    margin_bottom=60,\n    margin_left=30,\n    x_value_formatter=lambda x: f\"{x:.3f}\",\n    y_value_formatter=lambda y: f\"{y:.0f}\",\n    xrange=(lsl - 3 * sigma, usl + 3 * sigma),\n    range=(0, y_ceil),\n    css=[\n        \"file://style.css\",\n        f\"inline:.plot .background {{ fill: {PAGE_BG} !important; stroke: none !important; }}\",\n        f\"inline:.graph > .background {{ fill: {PAGE_BG} !important; stroke: none !important; }}\",\n        \"inline:.axis .guides .line { stroke-width: 0.8px; opacity: 0.25; }\",\n        \"inline:.axis.x > path.line { stroke: none !important; }\",\n        \"inline:.axis.y > path.line { stroke: none !important; }\",\n        \"inline:text.title { font-weight: 600 !important; }\",\n        \"inline:text.plot_title { text-anchor: middle; }\",\n        f\"inline:.legends text {{ fill: {INK} !important; }}\",\n        \"inline:.serie-2 { opacity: 0.6 !important; }\",\n        \"inline:.serie-3 { opacity: 0.6 !important; }\",\n    ],\n    js=[],\n)\n\n# Histogram bars — native pygal.Histogram format: (height, start, end)\nhist_data = [(float(counts[i]), float(bin_edges[i]), float(bin_edges[i + 1])) for i in range(len(counts))]\nchart.add(\"Measurements\", hist_data)\n\n# Normal distribution curve — rendered as histogram bars for a smooth bell-curve envelope\ncurve_data = [(float(y), float(x), float(x + dx_curve)) for x, y in zip(x_curve, y_curve, strict=True)]\nchart.add(\"Normal fit\", curve_data, stroke_style={\"width\": 3, \"linecap\": \"round\"})\n\n# Specification limit lines — very thin bars rendered as dashed vertical boundaries\nspec_lw = 0.0008  # LSL/USL (thin, opacity 0.6 via CSS)\ntarget_lw = 0.0020  # Target wider so it stands apart from Mean despite near-identical x\nmean_lw = 0.0005  # Mean narrower for clear visual separation from Target\nchart.add(\n    \"LSL (9.95)\", [(y_ceil, float(lsl - spec_lw), float(lsl + spec_lw))], stroke_style={\"width\": 8, \"dasharray\": \"18,8\"}\n)\nchart.add(\n    \"USL (10.05)\",\n    [(y_ceil, float(usl - spec_lw), float(usl + spec_lw))],\n    stroke_style={\"width\": 8, \"dasharray\": \"18,8\"},\n)\n\n# Target and mean reference lines — different widths so they're distinguishable at x≈10.000\nchart.add(\n    \"Target (10.00)\",\n    [(y_ceil, float(target - target_lw), float(target + target_lw))],\n    stroke_style={\"width\": 6, \"dasharray\": \"12,6\"},\n)\nchart.add(\n    f\"Mean ({mean:.3f})\",\n    [(y_ceil, float(mean - mean_lw), float(mean + mean_lw))],\n    stroke_style={\"width\": 4, \"dasharray\": \"4,8\"},\n)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}