{"spec_id":"line-growth-percentile","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-growth-percentile: Pediatric Growth Chart with Percentile Curves\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-06-20\n\"\"\"\n\nimport base64\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Legend\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens (Imprint)\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 (canonical order)\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Patient line: first categorical series (Imprint green — always first)\nPATIENT_COLOR = IMPRINT_PALETTE[0]  # #009E73\n# Percentile bands: Imprint blue (boys convention)\nBAND_COLOR = IMPRINT_PALETTE[2]  # #4467A3\n\n# --- Data: WHO-style weight-for-age reference for boys, 0–36 months ---\nnp.random.seed(42)\nage_months = np.linspace(0, 36, 73)\n\n# Realistic growth: rapid early gain tapering off\nmedian_weight = 3.3 + 0.7 * age_months - 0.012 * age_months**2 + 0.00012 * age_months**3\nsd_factor = 0.35 + 0.03 * age_months\n\npercentile_97 = median_weight + 1.88 * sd_factor\npercentile_90 = median_weight + 1.28 * sd_factor\npercentile_75 = median_weight + 0.67 * sd_factor\npercentile_50 = median_weight\npercentile_25 = median_weight - 0.67 * sd_factor\npercentile_10 = median_weight - 1.28 * sd_factor\npercentile_3 = median_weight - 1.88 * sd_factor\n\n# Individual patient: healthy boy tracked at well-child visits\npatient_age = np.array([0, 1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36])\npatient_weight = np.array([3.4, 4.3, 5.4, 6.8, 7.8, 9.0, 10.0, 10.8, 11.5, 12.6, 13.8, 14.8])\n\n# --- Plot ---\np = figure(\n    width=3200,\n    height=1800,\n    title=\"line-growth-percentile · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Age (months)\",\n    y_axis_label=\"Weight (kg)\",\n    toolbar_location=None,  # must be None — toolbar adds ~30–50 px above canvas\n    min_border_bottom=160,  # room for 34pt tick labels + 42pt axis label\n    min_border_left=180,  # room for 34pt tick labels + 42pt axis label\n    min_border_top=110,  # room for 50pt title\n    min_border_right=160,  # room for right-margin percentile labels (P3–P97)\n)\n\n# Percentile bands — graduated alpha: darker at extremes, lighter near median\nband_alphas = [0.55, 0.40, 0.22, 0.22, 0.40, 0.55]\nbands = [\n    (percentile_3, percentile_10),\n    (percentile_10, percentile_25),\n    (percentile_25, percentile_50),\n    (percentile_50, percentile_75),\n    (percentile_75, percentile_90),\n    (percentile_90, percentile_97),\n]\n\nrenderers = []\nfor i, (lower, upper) in enumerate(bands):\n    src = ColumnDataSource(data={\"x\": age_months, \"y1\": lower, \"y2\": upper})\n    r = p.varea(x=\"x\", y1=\"y1\", y2=\"y2\", source=src, fill_color=BAND_COLOR, fill_alpha=band_alphas[i])\n    renderers.append(r)\n\n# Percentile lines + right-margin labels\npercentile_data = [\n    (percentile_3, \"P3\", 2.0, 0.50),\n    (percentile_10, \"P10\", 2.0, 0.50),\n    (percentile_25, \"P25\", 2.0, 0.50),\n    (percentile_50, \"P50\", 5.0, 0.90),  # median emphasised\n    (percentile_75, \"P75\", 2.0, 0.50),\n    (percentile_90, \"P90\", 2.0, 0.50),\n    (percentile_97, \"P97\", 2.0, 0.50),\n]\n\nfor values, label, lw, la in percentile_data:\n    src = ColumnDataSource(data={\"x\": age_months, \"y\": values})\n    p.line(x=\"x\", y=\"y\", source=src, line_color=BAND_COLOR, line_width=lw, line_alpha=la)\n    lbl = Label(\n        x=age_months[-1] + 0.6,\n        y=values[-1],\n        text=label,\n        text_font_size=\"26pt\",\n        text_color=INK_SOFT,\n        text_baseline=\"middle\",\n    )\n    p.add_layout(lbl)\n\n# Patient trajectory — first categorical series (Imprint green)\npatient_src = ColumnDataSource(data={\"x\": patient_age, \"y\": patient_weight})\nr_line = p.line(x=\"x\", y=\"y\", source=patient_src, line_color=PATIENT_COLOR, line_width=5)\nr_pts = p.scatter(x=\"x\", y=\"y\", source=patient_src, size=20, color=PATIENT_COLOR, line_color=\"white\", line_width=2.5)\n\n# HoverTool for interactive HTML artifact\nhover = HoverTool(renderers=[r_pts], tooltips=[(\"Age\", \"@x months\"), (\"Weight\", \"@y kg\")], mode=\"mouse\")\np.add_tools(hover)\n\n# Legend\nlegend = Legend(\n    items=[(\"Patient (Boy A042)\", [r_line, r_pts]), (\"WHO Reference Bands (P3–P97)\", [renderers[2]])],\n    location=\"top_left\",\n)\np.add_layout(legend)\n\n# --- Typography (canonical bokeh 3200×1800 sizes) ---\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"bold\"\np.title.text_color = INK\n\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\n# Legend styling\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.92\np.legend.border_line_color = INK_SOFT\np.legend.glyph_height = 45\np.legend.glyph_width = 45\np.legend.spacing = 15\np.legend.padding = 25\n\n# --- Chrome (theme-adaptive) ---\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None  # L-shaped frame — no box outline\n\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.axis_line_width = 1.5\np.yaxis.axis_line_width = 1.5\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.major_tick_line_width = 1.5\np.yaxis.major_tick_line_width = 1.5\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\n\n# Grid: solid thin lines at 15% opacity\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# Axis ranges\np.y_range.start = 0\np.x_range.end = 38.5  # extra room for right-margin percentile labels\n\n# --- Save HTML (interactive catalog artifact) ---\noutput_file(f\"plot-{THEME}.html\", title=\"line-growth-percentile · bokeh · anyplot.ai\")\nsave(p)\n\n# --- Screenshot with Selenium (required — export_png fails on this system) ---\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    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\n# Force exact viewport via CDP — avoids headless-chrome window-size capping\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\n# CDP screenshot at exact viewport dimensions (bypasses Selenium viewport quirks)\nresult = driver.execute_cdp_cmd(\n    \"Page.captureScreenshot\", {\"format\": \"png\", \"fromSurface\": True, \"captureBeyondViewport\": False}\n)\nPath(f\"plot-{THEME}.png\").write_bytes(base64.b64decode(result[\"data\"]))\ndriver.quit()\n"}