{"spec_id":"scatter-regression-polynomial","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nscatter-regression-polynomial: Scatter Plot with Polynomial Regression\nLibrary: bokeh 3.9.2 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.events import MouseMove\nfrom bokeh.io import output_file, save\nfrom bokeh.models import Band, BoxAnnotation, ColumnDataSource, CustomJS, HoverTool, Label, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme 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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]  # data points - always first series\nACCENT = IMPRINT_PALETTE[1]  # regression curve - lavender, second series\nMUTED = INK_MUTED  # confidence band fill - \"other\" semantic anchor\n\n# Data - Manufacturing efficiency curve (diminishing returns pattern)\nnp.random.seed(42)\nn_points = 100\n\n# Investment amount (thousands of dollars)\nx = np.linspace(10, 100, n_points)\n# Efficiency gains follow a quadratic pattern with diminishing returns\n# True relationship: y = -0.005x^2 + 1.2x + 20 + noise\ny = -0.005 * x**2 + 1.2 * x + 20 + np.random.normal(0, 3, n_points)\n\n# Polynomial regression (degree 2 - quadratic)\ncoeffs = np.polyfit(x, y, 2)\npoly = np.poly1d(coeffs)\n\n# Calculate R-squared\ny_pred = poly(x)\nss_res = np.sum((y - y_pred) ** 2)\nss_tot = np.sum((y - np.mean(y)) ** 2)\nr_squared = 1 - (ss_res / ss_tot)\n\n# Create smooth curve for regression line + a 95% prediction band from the\n# residual spread (approximate, but conveys fit uncertainty at a glance)\nresidual_std = np.std(y - y_pred)\nx_smooth = np.linspace(x.min(), x.max(), 200)\ny_smooth = poly(x_smooth)\ny_lower = y_smooth - 1.96 * residual_std\ny_upper = y_smooth + 1.96 * residual_std\n\n# Format polynomial equation\na, b, c = coeffs\nequation = f\"y = {a:.4f}x² + {b:.2f}x + {c:.2f}\"\n\n# Marginal gain dy/dx = 2ax + b — the story this curve is telling. Mark where\n# the marginal gain has fallen to half its value at x.min(): everything past\n# that point is the \"diminishing returns\" regime the spec's domain is about.\nmarginal_at_xmin = 2 * a * x.min() + b\nzone_start = (0.5 * marginal_at_xmin - b) / (2 * a)\n\n# Create data sources\nscatter_source = ColumnDataSource(data={\"x\": x, \"y\": y})\nline_source = ColumnDataSource(data={\"x\": x_smooth, \"y\": y_smooth})\nband_source = ColumnDataSource(data={\"x\": x_smooth, \"lower\": y_lower, \"upper\": y_upper})\n\n# Create figure — canvas is the hard 3200x1800 contract; min_border reserves\n# room for the 34-42pt axis text so it isn't clipped at the PNG edge.\np = figure(\n    width=3200,\n    height=1800,\n    title=\"scatter-regression-polynomial · bokeh · anyplot.ai\",\n    x_axis_label=\"Investment (thousands $)\",\n    y_axis_label=\"Efficiency Gain (%)\",\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Shade the diminishing-returns regime (past zone_start) so the curve's own\n# story — gains flattening out — reads at a glance, not just from the equation\nbox = BoxAnnotation(left=zone_start, fill_color=MUTED, fill_alpha=0.07, line_color=None, level=\"underlay\")\np.add_layout(box)\nzone_label = Label(\n    x=zone_start + 1.5,\n    y=93,\n    text=\"Diminishing returns\",\n    text_font_size=\"22pt\",\n    text_font_style=\"italic\",\n    text_color=INK_MUTED,\n)\np.add_layout(zone_label)\n\n# Confidence band first so scatter + curve render on top of it. A faint\n# dashed edge (vs. no line) gives the band a defined silhouette instead of\n# just a flat fill — a small but deliberate refinement over the bare default.\nband = Band(\n    base=\"x\",\n    lower=\"lower\",\n    upper=\"upper\",\n    source=band_source,\n    fill_color=MUTED,\n    fill_alpha=0.18,\n    line_color=ACCENT,\n    line_alpha=0.3,\n    line_dash=\"dashed\",\n    line_width=1.5,\n)\nband.level = \"underlay\"\np.add_layout(band)\n\n# Plot scatter points\np.scatter(x=\"x\", y=\"y\", source=scatter_source, size=12, color=BRAND, alpha=0.65, legend_label=\"Data Points\")\n\n# Plot polynomial regression curve\np.line(x=\"x\", y=\"y\", source=line_source, line_width=3.5, color=ACCENT, legend_label=\"Polynomial Fit (degree 2)\")\n\n# Add HoverTool for interactivity\nhover = HoverTool(tooltips=[(\"Investment\", \"@x{0.0}\"), (\"Efficiency\", \"@y{0.0}\")])\np.add_tools(hover)\n\n# Add R² and equation annotation\nannotation_text = f\"R² = {r_squared:.4f}\\n{equation}\"\nannotation = Label(\n    x=68,\n    y=78,\n    text=annotation_text,\n    text_font_size=\"30pt\",\n    text_color=INK,\n    text_line_height=1.3,\n    background_fill_color=ELEVATED_BG,\n    background_fill_alpha=0.9,\n    border_line_color=INK_SOFT,\n)\np.add_layout(annotation)\n\n# Bokeh-distinctive touch: a live marginal-gain readout. CustomJS recomputes\n# dy/dx = 2ax + b from the mouse's data-space x on every move, so the HTML\n# detail view lets a reader probe exactly where the curve is still climbing\n# vs. already flattening — a live derivative isn't something a static-image\n# library can offer.\ncrosshair_x = 35.0\nmarginal_initial = 2 * a * crosshair_x + b\ncrosshair = Span(location=crosshair_x, dimension=\"height\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2)\np.add_layout(crosshair)\nmarginal_label = Label(\n    x=13,\n    y=34,\n    text=f\"Marginal gain: {marginal_initial:.2f}%/$k at $35k (hover to probe)\",\n    text_font_size=\"22pt\",\n    text_color=INK_SOFT,\n)\np.add_layout(marginal_label)\np.js_on_event(\n    MouseMove,\n    CustomJS(\n        args={\n            \"span\": crosshair,\n            \"label\": marginal_label,\n            \"a\": float(a),\n            \"b\": float(b),\n            \"xmin\": float(x.min()),\n            \"xmax\": float(x.max()),\n        },\n        code=\"\"\"\n        const px = cb_obj.x\n        if (px < xmin || px > xmax) { return }\n        span.location = px\n        const marginal = 2 * a * px + b\n        label.x = px < (xmin + xmax) / 2 ? px + 1 : px - 24\n        label.text = `Marginal gain: ${marginal.toFixed(2)}%/$k at $${px.toFixed(0)}k (hover to probe)`\n        \"\"\",\n    ),\n)\n\n# Styling - text sizes for the 3200x1800 canonical canvas\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\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\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.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = INK_SOFT\np.yaxis.minor_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_alpha = 0.35\np.yaxis.minor_tick_line_alpha = 0.35\n\n# Grid styling\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\n# Legend styling - top left placement for better visibility\np.legend.location = \"top_left\"\np.legend.label_text_font_size = \"34pt\"\np.legend.label_text_color = INK_SOFT\np.legend.background_fill_color = ELEVATED_BG\np.legend.background_fill_alpha = 0.9\np.legend.border_line_color = INK_SOFT\np.legend.border_line_width = 1.5\np.legend.padding = 16\np.legend.spacing = 10\np.legend.margin = 20\n\n# Background and outline\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\n# Save as HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome using Selenium\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)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves\n# a phantom title-bar height even headless; pin the viewport exactly via CDP.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}