{"spec_id":"feynman-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nfeynman-basic: Feynman Diagram for Particle Interactions\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-06-03\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, BoxAnnotation, Label, NormalHead, Range1d\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme-adaptive chrome 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 — canonical order, positions 1-4\nFERMION_COLOR = \"#009E73\"  # pos 1: brand green  — fermion lines (e, μ, b quarks)\nPHOTON_COLOR = \"#C475FD\"  # pos 2: lavender     — wavy photon/Z propagators\nBOSON_COLOR = \"#4467A3\"  # pos 3: blue         — dashed scalar Higgs\nGLUON_COLOR = \"#BD8233\"  # pos 4: ochre        — curly gluon radiation\n\nTYPE_COLORS = {\"fermion\": FERMION_COLOR, \"photon\": PHOTON_COLOR, \"boson\": BOSON_COLOR, \"gluon\": GLUON_COLOR}\n\n# Data — Higgs-strahlung: e⁻e⁺ → Z* → ZH → μ⁻μ⁺ + bb̄ + g\n# Coordinates spread wide to fill the 16:9 canvas (x: 0.3–13.8, y: −0.7–7.0)\nv1 = (3.0, 3.75)  # e⁻e⁺ annihilation vertex\nv2 = (7.0, 3.75)  # virtual Z*/γ endpoint + ZH-splitting vertex\nv3 = (10.5, 5.8)  # Z → μ⁻μ⁺ decay vertex\nv4 = (10.5, 1.7)  # H → bb̄g decay vertex\n\npropagators = [\n    # Incoming fermions\n    {\"start\": (0.3, 7.0), \"end\": v1, \"type\": \"fermion\", \"label\": \"e⁻\", \"arrow\": \"forward\"},\n    {\"start\": (0.3, 0.5), \"end\": v1, \"type\": \"fermion\", \"label\": \"e⁺\", \"arrow\": \"backward\"},\n    # Virtual and real vector bosons (wavy)\n    {\"start\": v1, \"end\": v2, \"type\": \"photon\", \"label\": \"Z*/γ\"},\n    {\"start\": v2, \"end\": v3, \"type\": \"photon\", \"label\": \"Z\"},\n    # Scalar Higgs (dashed)\n    {\"start\": v2, \"end\": v4, \"type\": \"boson\", \"label\": \"H\"},\n    # Z decay products\n    {\"start\": v3, \"end\": (13.8, 7.0), \"type\": \"fermion\", \"label\": \"μ⁻\", \"arrow\": \"forward\"},\n    {\"start\": v3, \"end\": (13.8, 4.6), \"type\": \"fermion\", \"label\": \"μ⁺\", \"arrow\": \"backward\"},\n    # H decay products\n    {\"start\": v4, \"end\": (13.8, 3.2), \"type\": \"fermion\", \"label\": \"b\", \"arrow\": \"forward\"},\n    {\"start\": v4, \"end\": (13.8, 0.7), \"type\": \"fermion\", \"label\": \"b̄\", \"arrow\": \"backward\"},\n    {\"start\": v4, \"end\": (13.5, -0.7), \"type\": \"gluon\", \"label\": \"g\"},\n]\n\n# Canvas — 3200×1800 (landscape), title 43 chars < 67 baseline so no size scaling\ntitle_str = \"feynman-basic · python · bokeh · anyplot.ai\"\n\np = figure(\n    width=3200,\n    height=1800,\n    title=title_str,\n    x_range=Range1d(-1.0, 17.0),\n    y_range=Range1d(-1.5, 9.0),\n    toolbar_location=None,\n    min_border_top=110,\n    min_border_bottom=100,\n    min_border_left=100,\n    min_border_right=80,\n)\n\np.axis.visible = False\np.grid.visible = False\np.outline_line_color = None\n\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.align = \"center\"\n\n# Draw propagators\nfor prop in propagators:\n    x0, y0 = prop[\"start\"]\n    x1, y1 = prop[\"end\"]\n    dx, dy = x1 - x0, y1 - y0\n    length = np.sqrt(dx**2 + dy**2)\n    color = TYPE_COLORS[prop[\"type\"]]\n    perp_x, perp_y = -dy / length, dx / length\n\n    if prop[\"type\"] == \"fermion\":\n        p.line([x0, x1], [y0, y1], line_width=5, color=color)\n        # Directional arrow at midpoint (forward = particle, backward = antiparticle)\n        mid_x, mid_y = (x0 + x1) / 2, (y0 + y1) / 2\n        off = 0.28\n        if prop.get(\"arrow\") == \"backward\":\n            sx, sy = mid_x + off * dx / length, mid_y + off * dy / length\n            ex, ey = mid_x - off * dx / length, mid_y - off * dy / length\n        else:\n            sx, sy = mid_x - off * dx / length, mid_y - off * dy / length\n            ex, ey = mid_x + off * dx / length, mid_y + off * dy / length\n        p.add_layout(\n            Arrow(\n                end=NormalHead(size=30, fill_color=color, line_color=color),\n                x_start=sx,\n                y_start=sy,\n                x_end=ex,\n                y_end=ey,\n                line_width=0,\n                line_alpha=0,\n            )\n        )\n\n    elif prop[\"type\"] == \"photon\":\n        n_waves = max(6, int(length * 3.5))\n        t = np.linspace(0, 1, 600)\n        wave = 0.22 * np.sin(2 * np.pi * n_waves * t)\n        p.line(\n            (x0 + t * dx + wave * perp_x).tolist(), (y0 + t * dy + wave * perp_y).tolist(), line_width=5, color=color\n        )\n\n    elif prop[\"type\"] == \"gluon\":\n        n_coils = max(5, int(length * 2.5))\n        t = np.linspace(0, 1, 1400)\n        angle = 2 * np.pi * n_coils * t\n        taper = np.minimum(t * 5, 1.0) * np.minimum((1 - t) * 5, 1.0)\n        eff_t = t - (0.18 * 1.2 / length) * np.sin(angle)\n        p.line(\n            (x0 + eff_t * dx + 0.18 * np.sin(angle) * perp_x * taper).tolist(),\n            (y0 + eff_t * dy + 0.18 * np.sin(angle) * perp_y * taper).tolist(),\n            line_width=4,\n            color=color,\n        )\n\n    elif prop[\"type\"] == \"boson\":\n        p.line([x0, x1], [y0, y1], line_width=7, color=color, line_dash=[28, 14])\n\n    # Particle label — offset perpendicular to the propagator\n    mid_x, mid_y = (x0 + x1) / 2, (y0 + y1) / 2\n    p.add_layout(\n        Label(\n            x=mid_x + 0.45 * perp_x,\n            y=mid_y + 0.45 * perp_y,\n            text=prop[\"label\"],\n            text_font_size=\"34pt\",\n            text_font_style=\"italic\",\n            text_color=INK,\n            text_align=\"center\",\n            text_baseline=\"middle\",\n        )\n    )\n\n# Vertex dots (white-bordered filled circles)\nverts_x = [v1[0], v2[0], v3[0], v4[0]]\nverts_y = [v1[1], v2[1], v3[1], v4[1]]\np.scatter(verts_x, verts_y, size=28, color=INK, line_color=PAGE_BG, line_width=4)\n\n# Legend — upper-right corner, past the outgoing particle endpoints\np.add_layout(\n    BoxAnnotation(\n        left=13.8,\n        right=16.7,\n        bottom=4.5,\n        top=8.3,\n        fill_color=ELEVATED_BG,\n        fill_alpha=0.92,\n        line_color=INK_SOFT,\n        line_width=1,\n        line_alpha=0.5,\n    )\n)\n\nlegend_entries = [\n    (\"fermion\", FERMION_COLOR, \"solid\"),\n    (\"photon / Z\", PHOTON_COLOR, \"wavy\"),\n    (\"Higgs (H)\", BOSON_COLOR, \"dashed\"),\n    (\"gluon\", GLUON_COLOR, \"curly\"),\n]\nleg_x0, leg_len = 14.1, 0.9\nfor i, (name, color, style) in enumerate(legend_entries):\n    y = 7.8 - i * 0.85\n    lx1 = leg_x0 + leg_len\n\n    if style == \"solid\":\n        p.line([leg_x0, lx1], [y, y], line_width=5, color=color)\n        p.add_layout(\n            Arrow(\n                end=NormalHead(size=22, fill_color=color, line_color=color),\n                x_start=leg_x0 + 0.22,\n                y_start=y,\n                x_end=lx1 - 0.08,\n                y_end=y,\n                line_width=0,\n                line_alpha=0,\n            )\n        )\n    elif style == \"wavy\":\n        t_l = np.linspace(0, 1, 200)\n        p.line(\n            (leg_x0 + t_l * leg_len).tolist(),\n            (y + 0.10 * np.sin(2 * np.pi * 4 * t_l)).tolist(),\n            line_width=5,\n            color=color,\n        )\n    elif style == \"curly\":\n        t_l = np.linspace(0, 1, 400)\n        al = 2 * np.pi * 3 * t_l\n        tap = np.minimum(t_l * 5, 1.0) * np.minimum((1 - t_l) * 5, 1.0)\n        eff = t_l - (0.08 / leg_len) * np.sin(al)\n        p.line((leg_x0 + eff * leg_len).tolist(), (y + 0.08 * np.sin(al) * tap).tolist(), line_width=4, color=color)\n    elif style == \"dashed\":\n        p.line([leg_x0, lx1], [y, y], line_width=7, color=color, line_dash=[16, 8])\n\n    p.add_layout(\n        Label(\n            x=lx1 + 0.18,\n            y=y,\n            text=name,\n            text_font_size=\"28pt\",\n            text_color=INK_SOFT,\n            text_align=\"left\",\n            text_baseline=\"middle\",\n        )\n    )\n\n# Time axis arrow along the bottom\np.add_layout(\n    Arrow(\n        end=NormalHead(size=24, fill_color=INK_MUTED, line_color=INK_MUTED),\n        x_start=2.5,\n        y_start=-1.1,\n        x_end=11.5,\n        y_end=-1.1,\n        line_width=3,\n        line_color=INK_MUTED,\n    )\n)\np.add_layout(\n    Label(\n        x=7.0,\n        y=-1.3,\n        text=\"time\",\n        text_font_size=\"28pt\",\n        text_color=INK_MUTED,\n        text_align=\"center\",\n        text_baseline=\"top\",\n    )\n)\n\n# Process equation at top center\np.add_layout(\n    Label(\n        x=7.0,\n        y=8.65,\n        text=\"e⁻e⁺ → Z* → ZH → μ⁻μ⁺ + bb̄ + g\",\n        text_font_size=\"32pt\",\n        text_color=INK_SOFT,\n        text_font_style=\"italic\",\n        text_align=\"center\",\n        text_baseline=\"middle\",\n    )\n)\n\n# Save HTML (interactive catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Inject CSS reset so the figure starts at y=0 with no body margins\nhtml_path = Path(f\"plot-{THEME}.html\")\nhtml_src = html_path.read_text()\ncss_reset = \"<style>html,body{margin:0;padding:0;overflow:hidden}</style>\"\nhtml_path.write_text(html_src.replace(\"<head>\", f\"<head>{css_reset}\"))\n\n# Screenshot with headless Chrome — set outer window to 3200×1939 so that\n# the inner viewport (after Chrome's 139px internal overhead) is exactly 3200×1800.\nW, H = 3200, 1800\nOUTER_H = 1939  # W × (H + 139px Chrome overhead) = correct inner viewport of H\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{OUTER_H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, OUTER_H)\ndriver.get(f\"file://{html_path.resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}