{"spec_id":"line-reaction-coordinate","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-reaction-coordinate: Reaction Coordinate Energy Diagram\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-24\n\"\"\"\n\nimport io\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Prevent this file's directory from shadowing the installed bokeh package\nsys.path = [p for p in sys.path if os.path.abspath(p) != os.path.dirname(os.path.abspath(__file__))]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import Arrow, ColumnDataSource, Label, NormalHead, Span\nfrom bokeh.plotting import figure\nfrom PIL import Image\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 — positions used for annotation arrows\nBRAND = \"#009E73\"  # Imprint position 1 — main energy curve\nEA_COLOR = \"#BD8233\"  # Imprint position 4 — activation energy arrow\nDH_COLOR = \"#4467A3\"  # Imprint position 3 — enthalpy change arrow\n\n# Data\nreactant_energy = 50.0\ntransition_energy = 120.0\nproduct_energy = 20.0\n\nreaction_coord = np.linspace(0, 1, 400)\n\npeak_center = 0.45\npeak_width = 0.12\ngaussian_peak = (transition_energy - reactant_energy) * np.exp(\n    -((reaction_coord - peak_center) ** 2) / (2 * peak_width**2)\n)\n\nsigmoid = 1 / (1 + np.exp(30 * (reaction_coord - 0.55)))\nbaseline = reactant_energy * sigmoid + product_energy * (1 - sigmoid)\n\nenergy = baseline + gaussian_peak\n\nsource = ColumnDataSource(data={\"x\": reaction_coord, \"y\": energy})\n\n# Title — 54 chars, under the 67-char baseline so no font scaling needed\nTITLE = \"line-reaction-coordinate · python · bokeh · anyplot.ai\"\n\n# Plot\np = figure(\n    width=3200,\n    height=1800,\n    title=TITLE,\n    x_axis_label=\"Reaction Coordinate\",\n    y_axis_label=\"Potential Energy (kJ/mol)\",\n    x_range=(-0.08, 1.15),\n    y_range=(-5, 155),\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# Area fill under the energy curve — slightly stronger in dark mode for visibility\nfill_alpha = 0.08 if THEME == \"light\" else 0.14\np.varea(x=\"x\", y1=0, y2=\"y\", source=source, fill_color=BRAND, fill_alpha=fill_alpha)\n\n# Main energy curve\np.line(\"x\", \"y\", source=source, line_width=6, color=BRAND)\n\n# Transition state emphasis\nts_idx = int(np.argmax(energy))\nts_x_val = float(reaction_coord[ts_idx])\nts_y_val = float(energy[ts_idx])\np.scatter([ts_x_val], [ts_y_val], size=34, color=BRAND, alpha=0.18, line_color=None)\np.scatter([ts_x_val], [ts_y_val], size=18, color=BRAND, alpha=0.9, line_color=PAGE_BG, line_width=2)\n\n# Horizontal dashed reference lines at reactant and product levels\np.add_layout(Span(location=reactant_energy, dimension=\"width\", line_color=INK_SOFT, line_width=2, line_dash=\"dashed\"))\np.add_layout(Span(location=product_energy, dimension=\"width\", line_color=INK_SOFT, line_width=2, line_dash=\"dashed\"))\n\n# State labels\nstate_kwargs = {\"text_font_size\": \"28pt\", \"text_color\": INK, \"text_font_style\": \"bold\"}\np.add_layout(Label(x=0.0, y=reactant_energy, text=\"Reactants\", x_offset=-10, y_offset=14, **state_kwargs))\np.add_layout(Label(x=0.88, y=product_energy, text=\"Products\", x_offset=-10, y_offset=14, **state_kwargs))\np.add_layout(\n    Label(x=peak_center, y=transition_energy, text=\"Transition State\", x_offset=-120, y_offset=16, **state_kwargs)\n)\n\n# Activation energy (Eₐ) double-headed arrows\nea_x = 0.18\nhead_size = 20\np.add_layout(\n    Arrow(\n        end=NormalHead(size=head_size, fill_color=EA_COLOR, line_color=EA_COLOR),\n        x_start=ea_x,\n        y_start=reactant_energy,\n        x_end=ea_x,\n        y_end=transition_energy,\n        line_color=EA_COLOR,\n        line_width=3,\n    )\n)\np.add_layout(\n    Arrow(\n        end=NormalHead(size=head_size, fill_color=EA_COLOR, line_color=EA_COLOR),\n        x_start=ea_x,\n        y_start=transition_energy,\n        x_end=ea_x,\n        y_end=reactant_energy,\n        line_color=EA_COLOR,\n        line_width=3,\n    )\n)\np.add_layout(\n    Label(\n        x=ea_x,\n        y=(reactant_energy + transition_energy) / 2,\n        text=\"Ea = 70 kJ/mol\",\n        text_font_size=\"24pt\",\n        text_color=EA_COLOR,\n        text_font_style=\"bold\",\n        x_offset=14,\n        y_offset=-10,\n    )\n)\n\n# Enthalpy change (ΔH) double-headed arrows\ndh_x = 0.85\np.add_layout(\n    Arrow(\n        end=NormalHead(size=head_size, fill_color=DH_COLOR, line_color=DH_COLOR),\n        x_start=dh_x,\n        y_start=product_energy,\n        x_end=dh_x,\n        y_end=reactant_energy,\n        line_color=DH_COLOR,\n        line_width=3,\n    )\n)\np.add_layout(\n    Arrow(\n        end=NormalHead(size=head_size, fill_color=DH_COLOR, line_color=DH_COLOR),\n        x_start=dh_x,\n        y_start=reactant_energy,\n        x_end=dh_x,\n        y_end=product_energy,\n        line_color=DH_COLOR,\n        line_width=3,\n    )\n)\np.add_layout(\n    Label(\n        x=dh_x,\n        y=(reactant_energy + product_energy) / 2,\n        text=\"ΔH = −30 kJ/mol\",\n        text_font_size=\"24pt\",\n        text_color=DH_COLOR,\n        text_font_style=\"bold\",\n        x_offset=14,\n        y_offset=-10,\n    )\n)\n\n# Theme-adaptive chrome\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.text_font_style = \"normal\"\n\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_font_style = \"normal\"\np.yaxis.axis_label_text_font_style = \"normal\"\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\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 = None\np.yaxis.minor_tick_line_color = None\n\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.15\n\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\n# Save HTML (interactive catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome (Selenium 4 / Selenium Manager).\n# Chrome's internal UI overhead shrinks the viewport below --window-size by ~139 px.\n# Use a taller window (H + 200 buffer) so the viewport is >= H, then crop to exact dims.\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 + 200}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\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()\nimg = Image.open(io.BytesIO(raw)).crop((0, 0, W, H))\nimg.save(f\"plot-{THEME}.png\")\n"}