{"spec_id":"energy-level-atomic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nenergy-level-atomic: Atomic Energy Level Diagram\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import Arrow, ColumnDataSource, HoverTool, Label, NormalHead, Range1d\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# --- Theme ---\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens (Imprint palette system)\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\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# --- Data ---\n# Hydrogen atom energy levels (E_n = -13.6 / n² eV)\nlevels = [1, 2, 3, 4, 5, 6]\nenergies = {n: -13.6 / n**2 for n in levels}\n\n# Visual y-positions: sqrt compression for nonlinear mapping\nvisual_y = {n: -np.sqrt(abs(energies[n])) for n in levels}\nionization_visual_y = 0.0\n\n# Spectral series transitions: (upper_level, lower_level, color, label)\n# Lyman series (n→1, UV) — hue-varied for better visual differentiation\nlyman = [\n    (2, 1, \"#3B1FA8\", \"Lyα 121.6 nm\"),  # deep blue-violet\n    (3, 1, \"#912DB8\", \"Lyβ 102.6 nm\"),  # purple-magenta\n    (4, 1, \"#C4278A\", \"Lyγ 97.3 nm\"),  # rose-magenta\n]\n\n# Balmer series (n→2, visible light) — spectral colors\nbalmer = [\n    (3, 2, \"#C0392B\", \"Hα 656.3 nm\"),\n    (4, 2, \"#2980B9\", \"Hβ 486.1 nm\"),\n    (5, 2, \"#6C3483\", \"Hγ 434.0 nm\"),\n    (6, 2, \"#512E5F\", \"Hδ 410.2 nm\"),\n]\n\n# Paschen series (n→3, IR) — warm tones\npaschen = [(4, 3, \"#D35400\", \"Paα 1875 nm\"), (5, 3, \"#922B21\", \"Paβ 1282 nm\"), (6, 3, \"#641E16\", \"Paγ 1094 nm\")]\n\n# Layout: level lines extended to ±1.5 for better canvas utilization\nlevel_x0, level_x1 = -1.5, 1.5\nlyman_x_start = -3.5  # 3 arrows: -3.50, -2.95, -2.40\nbalmer_x_start = 3.2  # 4 arrows:  3.20,  3.75,  4.30,  4.85\npaschen_x_start = 5.7  # 3 arrows:  5.70,  6.25,  6.80\narrow_spacing = 0.55\n\n# Custom y-axis ticks for sqrt-compressed nonlinear scale\ntick_energies = [0, -0.5, -1.0, -1.5, -3.4, -5.0, -10.0, -13.6]\ntick_visual = [-np.sqrt(abs(e)) if e != 0 else 0.0 for e in tick_energies]\ntick_labels = [f\"{e:.1f}\" for e in tick_energies]\n\n# ColumnDataSource for energy level lines (enables HoverTool)\nlevel_source = ColumnDataSource(\n    data={\n        \"x0\": [level_x0] * len(levels),\n        \"y0\": [visual_y[n] for n in levels],\n        \"x1\": [level_x1] * len(levels),\n        \"y1\": [visual_y[n] for n in levels],\n        \"quantum_n\": [f\"n = {n}\" for n in levels],\n        \"energy\": [f\"{energies[n]:.2f} eV\" for n in levels],\n        \"degeneracy\": [f\"{n**2}-fold\" for n in levels],\n    }\n)\n\n# --- Plot (3200×1800 landscape canvas) ---\nW, H = 3200, 1800\n\np = figure(\n    width=W,\n    height=H,\n    title=\"energy-level-atomic · bokeh · anyplot.ai\",\n    x_range=Range1d(-5.0, 8.5),\n    y_range=Range1d(visual_y[1] - 0.3, ionization_visual_y + 0.8),\n    toolbar_location=None,\n    min_border_bottom=100,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Custom y-axis ticks\np.yaxis.ticker = tick_visual\np.yaxis.major_label_overrides = dict(zip(tick_visual, tick_labels, strict=True))\np.yaxis.axis_label = \"Energy (eV)\"\n\n# Energy level lines (Imprint blue #4467A3)\nlevel_glyph = p.segment(x0=\"x0\", y0=\"y0\", x1=\"x1\", y1=\"y1\", source=level_source, line_width=8, line_color=\"#4467A3\")\n\n# HoverTool for interactive HTML\nhover = HoverTool(\n    renderers=[level_glyph], tooltips=[(\"Level\", \"@quantum_n\"), (\"Energy\", \"@energy\"), (\"Degeneracy\", \"@degeneracy\")]\n)\np.add_tools(hover)\n\n# Level labels (right of level lines)\nfor n in levels:\n    vy = visual_y[n]\n    e = energies[n]\n    y_off = 18 if n == 6 else (-18 if n == 5 else 0)\n    p.add_layout(\n        Label(\n            x=level_x1 + 0.15,\n            y=vy,\n            text=f\"n={n}  ({e:.2f} eV)\",\n            text_font_size=\"22pt\",\n            text_color=INK_SOFT,\n            text_baseline=\"middle\",\n            y_offset=y_off,\n        )\n    )\n\n# Ionization limit dashed line\np.segment(\n    x0=[-5.0],\n    y0=[ionization_visual_y],\n    x1=[8.5],\n    y1=[ionization_visual_y],\n    line_width=3,\n    line_color=INK_MUTED,\n    line_dash=\"dashed\",\n)\np.add_layout(\n    Label(\n        x=level_x1 + 0.15,\n        y=ionization_visual_y + 0.12,\n        text=\"Ionization (0 eV)\",\n        text_font_size=\"22pt\",\n        text_color=INK_MUTED,\n    )\n)\n\n# Transition arrows and labels\nall_series = [(lyman, lyman_x_start, \"left\"), (balmer, balmer_x_start, \"right\"), (paschen, paschen_x_start, \"right\")]\n\nfor transitions, x_start, label_side in all_series:\n    for i, (n_upper, n_lower, color, label_text) in enumerate(transitions):\n        x_pos = x_start + i * arrow_spacing\n        p.add_layout(\n            Arrow(\n                end=NormalHead(size=22, fill_color=color, line_color=color),\n                x_start=x_pos,\n                y_start=visual_y[n_upper],\n                x_end=x_pos,\n                y_end=visual_y[n_lower],\n                line_color=color,\n                line_width=4,\n            )\n        )\n        mid_y = (visual_y[n_upper] + visual_y[n_lower]) / 2\n        align = \"right\" if label_side == \"left\" else \"left\"\n        x_off = -12 if label_side == \"left\" else 12\n        p.add_layout(\n            Label(\n                x=x_pos,\n                y=mid_y,\n                text=label_text,\n                text_font_size=\"20pt\",\n                text_color=color,\n                text_align=align,\n                x_offset=x_off,\n            )\n        )\n\n# Series header labels\nheader_y = ionization_visual_y + 0.58\nlyman_cx = lyman_x_start + arrow_spacing  # center of 3 arrows\nbalmer_cx = balmer_x_start + 1.5 * arrow_spacing  # center of 4 arrows\npaschen_cx = paschen_x_start + arrow_spacing  # center of 3 arrows\n\nfor cx, text, color in [\n    (lyman_cx, \"Lyman Series (UV)\", \"#3B1FA8\"),\n    (balmer_cx, \"Balmer Series (Visible)\", \"#C0392B\"),\n    (paschen_cx, \"Paschen Series (IR)\", \"#D35400\"),\n]:\n    p.add_layout(\n        Label(\n            x=cx,\n            y=header_y,\n            text=text,\n            text_font_size=\"26pt\",\n            text_font_style=\"bold\",\n            text_color=color,\n            text_align=\"center\",\n        )\n    )\n\n# --- Style (theme-adaptive chrome) ---\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.title.text_color = INK\n\np.yaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.yaxis.axis_label_text_color = INK\np.yaxis.major_label_text_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\np.xaxis.visible = False\np.xgrid.visible = False\np.ygrid.grid_line_alpha = 0.15\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_width = 1\n\np.outline_line_color = None\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# --- Save ---\noutput_file(f\"plot-{THEME}.html\", title=\"Atomic Energy Level Diagram\")\nsave(p)\n\n# Screenshot with headless Chrome (Selenium 4 / Selenium Manager)\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)\n# Force viewport to exactly W×H via CDP — Chrome's window chrome otherwise\n# shrinks the visible area (e.g. 3200×1800 window → 3200×1661 viewport).\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"}