{"spec_id":"line-arrhenius","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-arrhenius: Arrhenius Plot for Reaction Kinetics\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Prevent this file (bokeh.py) from shadowing the installed bokeh package when\n# Python prepends the script's own directory to sys.path on startup.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, LinearAxis, NumeralTickFormatter\nfrom bokeh.models.tickers import FixedTicker\nfrom bokeh.plotting import figure\nfrom bokeh.resources import CDN\nfrom scipy import stats\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens (Imprint palette + theme-adaptive chrome)\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 categorical palette — positions used here\nBRAND = \"#009E73\"  # position 1 — data points\nLINE_COLOR = \"#4467A3\"  # position 3 — regression line\n\n# Data — first-order decomposition reaction rate constants at various temperatures\nnp.random.seed(42)\ntemperature_K = np.array([300, 325, 350, 375, 400, 425, 450, 475, 500, 525, 550, 575, 600])\nactivation_energy = 75000  # J/mol (typical organic decomposition)\nR_gas = 8.314  # gas constant J/(mol·K)\npre_exponential = 1e12  # s^-1\n\n# Generate rate constants from Arrhenius equation with experimental noise\nln_k_true = np.log(pre_exponential) - activation_energy / (R_gas * temperature_K)\nnoise = np.random.normal(0, 0.15, len(temperature_K))\nln_k = ln_k_true + noise\ninv_T = 1.0 / temperature_K\n\n# Linear regression: ln(k) = ln(A) - Ea/R * (1/T)\nslope, intercept, r_value, p_value, std_err = stats.linregress(inv_T, ln_k)\nr_squared = r_value**2\nEa_fitted = -slope * R_gas  # activation energy from slope\n\n# Regression line\ninv_T_line = np.linspace(inv_T.min() - 0.00005, inv_T.max() + 0.00005, 200)\nln_k_line = slope * inv_T_line + intercept\n\n# 95% confidence interval band around regression\nn_pts = len(inv_T)\nx_mean = inv_T.mean()\nSxx = np.sum((inv_T - x_mean) ** 2)\ns_e = np.sqrt(np.sum((ln_k - (slope * inv_T + intercept)) ** 2) / (n_pts - 2))\nt_val = stats.t.ppf(0.975, df=n_pts - 2)\nse_band = s_e * np.sqrt(1.0 / n_pts + (inv_T_line - x_mean) ** 2 / Sxx)\nci_upper = ln_k_line + t_val * se_band\nci_lower = ln_k_line - t_val * se_band\n\n# Data sources\nscatter_source = ColumnDataSource(\n    data={\n        \"inv_T\": inv_T,\n        \"ln_k\": ln_k,\n        \"T_K\": temperature_K,\n        \"inv_T_fmt\": [f\"{x:.4f}\" for x in inv_T],\n        \"ln_k_fmt\": [f\"{y:.2f}\" for y in ln_k],\n    }\n)\nline_source = ColumnDataSource(data={\"inv_T\": inv_T_line, \"ln_k\": ln_k_line})\nci_source = ColumnDataSource(data={\"inv_T\": inv_T_line, \"ci_upper\": ci_upper, \"ci_lower\": ci_lower})\n\n# Title — canonical format; length=43 chars < 67, use default 50pt\nTITLE = \"line-arrhenius · python · bokeh · anyplot.ai\"\n\n# Figure — canvas exactly 3200×1800 (landscape); toolbar_location=None for correct PNG size\np = figure(\n    width=3200,\n    height=1800,\n    title=TITLE,\n    x_axis_label=\"1/T (K⁻¹)\",\n    y_axis_label=\"ln(k)\",\n    x_range=(inv_T.min() - 0.00015, inv_T.max() + 0.00015),\n    y_range=(ln_k.min() - 1.5, ln_k.max() + 5.0),\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=80,\n)\n\n# Background\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_alpha = 0  # L-shaped frame: remove box outline, keep axis lines\n\n# 95% confidence band behind regression line\np.varea(x=\"inv_T\", y1=\"ci_lower\", y2=\"ci_upper\", source=ci_source, fill_color=LINE_COLOR, fill_alpha=0.15)\n\n# Regression line (Imprint position 3 — blue)\np.line(\n    \"inv_T\",\n    \"ln_k\",\n    source=line_source,\n    line_color=LINE_COLOR,\n    line_width=5,\n    line_alpha=0.9,\n    legend_label=\"Linear Fit (ln k = ln A − Eₐ/RT)\",\n)\n\n# Data points (Imprint position 1 — brand green)\nscatter_renderer = p.scatter(\n    \"inv_T\",\n    \"ln_k\",\n    source=scatter_source,\n    size=22,\n    color=BRAND,\n    alpha=0.92,\n    line_color=PAGE_BG,\n    line_width=2,\n    legend_label=\"Experimental Data\",\n)\n\n# HoverTool\nhover = HoverTool(\n    renderers=[scatter_renderer],\n    tooltips=[(\"Temperature\", \"@T_K K\"), (\"1/T\", \"@inv_T_fmt K⁻¹\"), (\"ln(k)\", \"@ln_k_fmt\")],\n    mode=\"mouse\",\n)\np.add_tools(hover)\n\n# Annotation: slope, activation energy, R²\neq_text = f\"Eₐ = {Ea_fitted / 1000:.1f} kJ/mol\\nSlope = {slope:.1f} K\\nR² = {r_squared:.4f}\"\neq_label = Label(\n    x=inv_T[8],\n    y=ln_k[6] + 3.5,\n    text=eq_text,\n    text_font_size=\"30pt\",\n    text_color=INK,\n    text_font_style=\"bold\",\n    background_fill_color=ELEVATED_BG,\n    background_fill_alpha=0.92,\n    border_line_color=INK_SOFT,\n    border_line_alpha=0.5,\n    border_line_width=2,\n)\np.add_layout(eq_label)\n\n# Secondary x-axis for temperature (above)\ntemp_label_values = [300, 350, 400, 500, 600]\ntemp_tick_positions = [1.0 / t for t in temp_label_values]\ntemp_axis = LinearAxis(\n    axis_label=\"Temperature (K)\",\n    axis_label_text_font_size=\"42pt\",\n    axis_label_text_color=INK,\n    axis_label_text_font_style=\"bold\",\n    major_label_text_font_size=\"34pt\",\n    major_label_text_color=INK_SOFT,\n    ticker=FixedTicker(ticks=temp_tick_positions),\n    major_tick_line_color=INK_SOFT,\n    minor_tick_line_color=None,\n    axis_line_color=INK_SOFT,\n)\np.add_layout(temp_axis, \"above\")\n\n# Temperature tick labels via Label annotations (inside plot area, near top)\nfor t_val in temp_label_values:\n    inv_t_val = 1.0 / t_val\n    temp_label = Label(\n        x=inv_t_val,\n        y=ln_k.max() + 3.0,\n        text=str(t_val),\n        text_font_size=\"28pt\",\n        text_color=INK_SOFT,\n        text_align=\"center\",\n        text_baseline=\"bottom\",\n    )\n    p.add_layout(temp_label)\n\n# Axis references — after add_layout(above): xaxis[0]=top, xaxis[1]=bottom\nbottom_ax = p.xaxis[1]\ntop_ax = p.xaxis[0]\n\n# Bottom x-axis: fixed ticks with formatting\nbottom_ticks = [round(1.0 / t, 4) for t in [600, 500, 400, 350, 300]]\nbottom_ax.ticker = FixedTicker(ticks=bottom_ticks)\nbottom_ax.formatter = NumeralTickFormatter(format=\"0.0000\")\n\n# Hide top axis tick labels (Label annotations used instead)\ntop_ax.major_label_text_font_size = \"0pt\"\n\n# Title styling\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.align = \"center\"\np.title.text_font_style = \"bold\"\n\n# Bottom x-axis styling\nbottom_ax.axis_label_text_font_size = \"42pt\"\nbottom_ax.axis_label_text_color = INK\nbottom_ax.axis_label_text_font_style = \"bold\"\nbottom_ax.major_label_text_font_size = \"34pt\"\nbottom_ax.major_label_text_color = INK_SOFT\n\n# Y-axis styling\np.yaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_font_style = \"bold\"\np.yaxis.major_label_text_font_size = \"34pt\"\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.location = \"bottom_left\"\np.legend.background_fill_color = ELEVATED_BG\np.legend.background_fill_alpha = 0.92\np.legend.border_line_color = INK_SOFT\np.legend.border_line_alpha = 0.5\np.legend.border_line_width = 2\np.legend.glyph_height = 40\np.legend.glyph_width = 40\np.legend.padding = 25\np.legend.spacing = 14\np.legend.margin = 20\n\n# Grid — subtle, 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 lines and ticks\nfor ax in [bottom_ax, p.yaxis[0]]:\n    ax.axis_line_color = INK_SOFT\n    ax.axis_line_width = 2\n    ax.minor_tick_line_color = None\n    ax.major_tick_line_color = INK_SOFT\n    ax.major_tick_out = 8\n    ax.major_tick_in = 0\n\n# Save interactive HTML artifact\noutput_file(f\"plot-{THEME}.html\")\nsave(p, resources=CDN, title=\"Arrhenius Plot for Reaction Kinetics\")\n\n# Screenshot with headless Chrome (Selenium 4).\n# Use CDP Emulation.setDeviceMetricsOverride so the viewport is exactly W×H\n# regardless of any browser-chrome offset that would otherwise clip the canvas.\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.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)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}