{"spec_id":"pp-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\npp-basic: Probability-Probability (P-P) Plot\nLibrary: bokeh 3.9.1 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-06-16\n\"\"\"\n\nimport io\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import Band, ColorBar, ColumnDataSource, HoverTool, LinearColorMapper\nfrom bokeh.plotting import figure\nfrom bokeh.transform import transform\nfrom PIL import Image\nfrom scipy import stats\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens (see prompts/default-style-guide.md \"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_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nMUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint diverging colormap — matte-red <-> theme midpoint <-> blue (signed deviation)\nmid_hex = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nred_rgb = np.array([0xAE, 0x30, 0x30])\nblue_rgb = np.array([0x44, 0x67, 0xA3])\nmid_rgb = np.array([int(mid_hex[i : i + 2], 16) for i in (1, 3, 5)])\nramp_t = np.linspace(0, 1, 128)[:, None]\ndiv_rgb = np.vstack([red_rgb * (1 - ramp_t) + mid_rgb * ramp_t, mid_rgb * (1 - ramp_t) + blue_rgb * ramp_t])\nIMPRINT_DIV256 = [\"#%02X%02X%02X\" % tuple(c) for c in div_rgb.round().astype(int)]\n\n# Data — manufacturing QC: bolt tensile strength (right-skewed vs. normal reference)\nnp.random.seed(42)\nbolt_strength = np.random.normal(loc=850, scale=45, size=200) + np.random.exponential(scale=15, size=200)\nobserved_sorted = np.sort(bolt_strength)\nn = len(observed_sorted)\n\nmu, sigma = stats.norm.fit(observed_sorted)\nempirical_cdf = np.arange(1, n + 1) / (n + 1)\ntheoretical_cdf = stats.norm.cdf(observed_sorted, loc=mu, scale=sigma)\ndeviation = empirical_cdf - theoretical_cdf\n\n# Kolmogorov-Smirnov 95% confidence band, clipped to [0, 1]\nks_bound = 1.36 / np.sqrt(n)\nband_x = np.linspace(0, 1, 200)\nband_source = ColumnDataSource(\n    data={\"x\": band_x, \"upper\": np.clip(band_x + ks_bound, 0, 1), \"lower\": np.clip(band_x - ks_bound, 0, 1)}\n)\n\nsource = ColumnDataSource(\n    data={\n        \"theoretical\": theoretical_cdf,\n        \"empirical\": empirical_cdf,\n        \"deviation\": deviation,\n        \"strength\": observed_sorted,\n        \"rank\": np.arange(1, n + 1),\n    }\n)\n\nmax_dev = float(np.max(np.abs(deviation)))\ncolor_mapper = LinearColorMapper(palette=IMPRINT_DIV256, low=-max_dev, high=max_dev)\n\n# Plot — square canvas preserves the diagonal's visual meaning\np = figure(\n    width=2400,\n    height=2400,\n    title=\"pp-basic · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Theoretical Cumulative Probability (Normal Fit)\",\n    y_axis_label=\"Empirical Cumulative Probability\",\n    x_range=(-0.02, 1.02),\n    y_range=(-0.02, 1.02),\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=60,\n)\n\n# KS confidence band (muted structural layer, sits behind the data)\nband = Band(\n    base=\"x\",\n    upper=\"upper\",\n    lower=\"lower\",\n    source=band_source,\n    fill_alpha=0.10,\n    fill_color=MUTED,\n    line_color=MUTED,\n    line_alpha=0.3,\n    line_width=2,\n)\np.add_layout(band)\n\n# 45-degree reference line — perfect-fit baseline (neutral anchor)\np.line([0, 1], [0, 1], line_color=INK, line_width=4, line_dash=\"dashed\", line_alpha=0.55)\n\n# Data points colored by signed deviation from the diagonal\nscatter = p.scatter(\n    x=\"theoretical\",\n    y=\"empirical\",\n    source=source,\n    size=22,\n    fill_color=transform(\"deviation\", color_mapper),\n    fill_alpha=0.65,\n    line_color=MUTED,  # soft ink ring keeps near-zero-deviation points legible\n    line_alpha=0.7,\n    line_width=1.5,\n)\n\n# HoverTool — Bokeh-distinctive interactive inspection\nhover = HoverTool(\n    renderers=[scatter],\n    tooltips=[\n        (\"Bolt Strength\", \"@strength{0.1} MPa\"),\n        (\"Rank\", \"@rank / 200\"),\n        (\"Theoretical P\", \"@theoretical{0.000}\"),\n        (\"Empirical P\", \"@empirical{0.000}\"),\n        (\"Deviation\", \"@deviation{+0.000}\"),\n    ],\n    mode=\"mouse\",\n)\np.add_tools(hover)\n\n# Colorbar legend for the continuous deviation encoding\ncolor_bar = ColorBar(\n    color_mapper=color_mapper,\n    title=\"Empirical − Theoretical\",\n    title_text_font_size=\"30pt\",\n    title_text_color=INK_SOFT,\n    title_text_font_style=\"normal\",\n    major_label_text_font_size=\"26pt\",\n    major_label_text_color=INK_SOFT,\n    background_fill_color=PAGE_BG,\n    width=36,\n    padding=20,\n    bar_line_color=None,\n)\np.add_layout(color_bar, \"right\")\n\n# Style — theme-adaptive chrome\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis[0].ticker.desired_num_ticks = 6\np.yaxis[0].ticker.desired_num_ticks = 6\n\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.15\np.ygrid.grid_line_alpha = 0.15\n\np.outline_line_color = None\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Save — interactive HTML, then screenshot it with headless Chrome\noutput_file(f\"plot-{THEME}.html\", title=\"pp-basic · python · bokeh · anyplot.ai\")\nsave(p)\n\n# Window is taller than the canvas so bokeh fills it; crop to the exact dims.\nW, H = 2400, 2400\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 + 200}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H + 200)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\nraw = driver.get_screenshot_as_png()\ndriver.quit()\nImage.open(io.BytesIO(raw)).crop((0, 0, W, H)).save(f\"plot-{THEME}.png\")\n"}