{"spec_id":"feynman-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nfeynman-basic: Feynman Diagram for Particle Interactions\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 96/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens — Imprint palette 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 palette positions 1-4 mapped to the four particle types\ncolor_scale = alt.Scale(\n    domain=[\"fermion\", \"photon\", \"gluon\", \"boson\"], range=[\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n)\n\n# Higgs boson production via gluon fusion: g + g → [t loop] → H → γ + γ\n# All 4 particle types: fermion (straight+arrows), photon (wavy), gluon (curly), boson (dashed)\n\n# Vertex positions spread across canvas\nv1 = (2.5, 5.5)  # upper-left triangle vertex (g-t-t)\nv2 = (2.5, 1.5)  # lower-left triangle vertex (g-t-t)\nv3 = (5.2, 3.5)  # right triangle vertex (t-t-H)\nv4 = (7.5, 3.5)  # Higgs decay vertex (H-γ-γ)\n\nx_scale = alt.Scale(domain=[-0.3, 9.8])\ny_scale = alt.Scale(domain=[-0.3, 7.2])\n\nhighlight = alt.selection_point(fields=[\"particle_type\"], bind=\"legend\")\nopacity_cond = alt.condition(highlight, alt.value(1.0), alt.value(0.25))\n\n# Straight lines: fermion triangle edges + dashed Higgs boson propagator\nfermion_edges = [(v1, v2, \"t1\"), (v2, v3, \"t2\"), (v3, v1, \"t3\")]\nstraight_df = pd.DataFrame(\n    [\n        {\"x\": f[0], \"y\": f[1], \"x2\": t[0], \"y2\": t[1], \"particle_type\": \"fermion\", \"label\": \"t\", \"line_id\": pid}\n        for f, t, pid in fermion_edges\n    ]\n    + [{\"x\": v3[0], \"y\": v3[1], \"x2\": v4[0], \"y2\": v4[1], \"particle_type\": \"boson\", \"label\": \"H\", \"line_id\": \"H\"}]\n)\n\n# Fermion direction arrows at edge midpoints\narrows_df = pd.DataFrame(\n    [\n        {\n            \"x\": (f[0] + t[0]) / 2,\n            \"y\": (f[1] + t[1]) / 2,\n            \"angle\": np.degrees(np.arctan2(t[1] - f[1], t[0] - f[0])),\n            \"particle_type\": \"fermion\",\n            \"label\": \"t\",\n        }\n        for f, t, _ in fermion_edges\n    ]\n)\n\n# Wavy paths (photon) — sinusoidal transverse offset\nn_wavy = 200\nwavy_paths = []\nfor x0, y0, x1, y1, lid in [(*v4, 9.2, 5.8, \"γ1\"), (*v4, 9.2, 1.2, \"γ2\")]:\n    t = np.linspace(0, 1, n_wavy)\n    dx, dy = x1 - x0, y1 - y0\n    length = np.hypot(dx, dy)\n    nx, ny = -dy / length, dx / length\n    offset = 0.22 * np.sin(2 * np.pi * 8 * t)\n    wavy_paths.append(\n        pd.DataFrame(\n            {\n                \"x\": x0 + t * dx + offset * nx,\n                \"y\": y0 + t * dy + offset * ny,\n                \"order\": np.arange(n_wavy),\n                \"particle_type\": \"photon\",\n                \"label\": \"γ\",\n                \"line_id\": lid,\n            }\n        )\n    )\n\n# Curly paths (gluon) — helical loop offset\nn_curly, n_loops, loop_r = 400, 7, 0.18\ncurly_paths = []\nfor x0, y0, x1, y1, lid in [(0.3, 5.5, *v1, \"g1\"), (0.3, 1.5, *v2, \"g2\")]:\n    dx, dy = x1 - x0, y1 - y0\n    length = np.hypot(dx, dy)\n    tx, ty = dx / length, dy / length\n    nx, ny = -ty, tx\n    theta = np.linspace(0, n_loops * 2 * np.pi, n_curly)\n    base = np.linspace(0, length, n_curly)\n    curly_paths.append(\n        pd.DataFrame(\n            {\n                \"x\": x0 + (base + loop_r * np.sin(theta)) * tx + loop_r * np.cos(theta) * nx,\n                \"y\": y0 + (base + loop_r * np.sin(theta)) * ty + loop_r * np.cos(theta) * ny,\n                \"order\": np.arange(n_curly),\n                \"particle_type\": \"gluon\",\n                \"label\": \"g\",\n                \"line_id\": lid,\n            }\n        )\n    )\n\npath_df = pd.concat(wavy_paths + curly_paths, ignore_index=True)\n\n# Particle labels colored by type\nlabel_df = pd.DataFrame(\n    [\n        {\"x\": 3.1, \"y\": 3.5, \"label\": \"t\", \"particle_type\": \"fermion\"},\n        {\"x\": (v3[0] + v4[0]) / 2, \"y\": v3[1] + 0.5, \"label\": \"H\", \"particle_type\": \"boson\"},\n        {\"x\": 1.0, \"y\": 6.2, \"label\": \"g\", \"particle_type\": \"gluon\"},\n        {\"x\": 1.0, \"y\": 0.8, \"label\": \"g\", \"particle_type\": \"gluon\"},\n        {\"x\": 8.8, \"y\": 6.2, \"label\": \"γ\", \"particle_type\": \"photon\"},\n        {\"x\": 8.8, \"y\": 0.8, \"label\": \"γ\", \"particle_type\": \"photon\"},\n    ]\n)\n\n# Vertex interaction points\nvertex_df = pd.DataFrame(\n    [\n        {\"x\": v1[0], \"y\": v1[1], \"vertex\": \"g-t-t\"},\n        {\"x\": v2[0], \"y\": v2[1], \"vertex\": \"g-t-t\"},\n        {\"x\": v3[0], \"y\": v3[1], \"vertex\": \"t-t-H\"},\n        {\"x\": v4[0], \"y\": v4[1], \"vertex\": \"H-γ-γ\"},\n    ]\n)\n\nprocess_df = pd.DataFrame([{\"x\": 5.0, \"y\": 6.8, \"text\": \"g + g  →  H  →  γ + γ\"}])\ntime_line_df = pd.DataFrame([{\"x\": 1.5, \"y\": 0.2, \"x2\": 8.5, \"y2\": 0.2}])\ntime_arrow_df = pd.DataFrame([{\"x\": 8.5, \"y\": 0.2, \"angle\": 90}])\ntime_label_df = pd.DataFrame([{\"x\": 5.0, \"y\": -0.1, \"label\": \"time\"}])\n\n# Subtle elevated background panel for the interaction region\nbg_df = pd.DataFrame([{\"x\": 0.0, \"y\": 0.5, \"x2\": 9.6, \"y2\": 6.5}])\nbg_layer = (\n    alt.Chart(bg_df)\n    .mark_rect(color=ELEVATED_BG, cornerRadius=18, stroke=INK_SOFT, strokeWidth=0.5)\n    .encode(x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None), x2=\"x2:Q\", y2=\"y2:Q\")\n)\n\nstraight_layer = (\n    alt.Chart(straight_df)\n    .mark_rule(strokeWidth=3)\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None),\n        y=alt.Y(\"y:Q\", scale=y_scale, axis=None),\n        x2=\"x2:Q\",\n        y2=\"y2:Q\",\n        color=alt.Color(\n            \"particle_type:N\",\n            scale=color_scale,\n            legend=alt.Legend(title=\"Particle Type\", symbolSize=150, orient=\"top-right\", offset=8),\n        ),\n        strokeDash=alt.StrokeDash(\n            \"particle_type:N\", scale=alt.Scale(domain=[\"fermion\", \"boson\"], range=[[1, 0], [10, 6]]), legend=None\n        ),\n        opacity=opacity_cond,\n        tooltip=[alt.Tooltip(\"label:N\", title=\"Particle\"), alt.Tooltip(\"particle_type:N\", title=\"Type\")],\n    )\n    .add_params(highlight)\n)\n\npath_layer = (\n    alt.Chart(path_df)\n    .mark_line(strokeWidth=2.8)\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None),\n        y=alt.Y(\"y:Q\", scale=y_scale, axis=None),\n        color=alt.Color(\"particle_type:N\", scale=color_scale, legend=None),\n        detail=\"line_id:N\",\n        order=\"order:Q\",\n        opacity=opacity_cond,\n        tooltip=[alt.Tooltip(\"label:N\", title=\"Particle\"), alt.Tooltip(\"particle_type:N\", title=\"Type\")],\n    )\n    .add_params(highlight)\n)\n\narrow_layer = (\n    alt.Chart(arrows_df)\n    .mark_point(shape=\"triangle\", size=400, filled=True)\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None),\n        y=alt.Y(\"y:Q\", scale=y_scale, axis=None),\n        angle=alt.Angle(\"angle:Q\"),\n        color=alt.Color(\"particle_type:N\", scale=color_scale, legend=None),\n        opacity=opacity_cond,\n    )\n    .add_params(highlight)\n)\n\nvertex_shadow = (\n    alt.Chart(vertex_df)\n    .mark_circle(size=700, color=INK, opacity=0.08)\n    .encode(x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None))\n)\n\nvertex_layer = (\n    alt.Chart(vertex_df)\n    .mark_circle(size=500, color=INK, stroke=PAGE_BG, strokeWidth=2.5)\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None),\n        y=alt.Y(\"y:Q\", scale=y_scale, axis=None),\n        tooltip=[alt.Tooltip(\"vertex:N\", title=\"Interaction\")],\n    )\n)\n\nlabel_layer = (\n    alt.Chart(label_df)\n    .transform_calculate(description=\"datum.label + ' (' + datum.particle_type + ')'\")\n    .mark_text(fontSize=26, fontWeight=\"bold\", font=\"serif\", fontStyle=\"italic\")\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None),\n        y=alt.Y(\"y:Q\", scale=y_scale, axis=None),\n        text=\"label:N\",\n        color=alt.Color(\"particle_type:N\", scale=color_scale, legend=None),\n        opacity=opacity_cond,\n        tooltip=[alt.Tooltip(\"description:N\", title=\"Particle\")],\n    )\n    .add_params(highlight)\n)\n\nprocess_layer = (\n    alt.Chart(process_df)\n    .mark_text(fontSize=22, font=\"serif\", fontStyle=\"italic\", color=INK_MUTED)\n    .encode(x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None), text=\"text:N\")\n)\n\ntime_line_layer = (\n    alt.Chart(time_line_df)\n    .mark_rule(strokeWidth=1.5, color=INK_MUTED, strokeDash=[6, 4])\n    .encode(x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None), x2=\"x2:Q\", y2=\"y2:Q\")\n)\n\ntime_arrow_layer = (\n    alt.Chart(time_arrow_df)\n    .mark_point(shape=\"triangle\", size=200, filled=True, color=INK_MUTED)\n    .encode(\n        x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None), angle=alt.Angle(\"angle:Q\")\n    )\n)\n\ntime_label_layer = (\n    alt.Chart(time_label_df)\n    .mark_text(fontSize=18, color=INK_MUTED, fontStyle=\"italic\")\n    .encode(x=alt.X(\"x:Q\", scale=x_scale, axis=None), y=alt.Y(\"y:Q\", scale=y_scale, axis=None), text=\"label:N\")\n)\n\n# Title: 44 chars — shorter than 67-char baseline, keep at default 16px\ntitle_str = \"feynman-basic · python · altair · anyplot.ai\"\n\nchart = (\n    alt.layer(\n        bg_layer,\n        straight_layer,\n        path_layer,\n        arrow_layer,\n        vertex_shadow,\n        vertex_layer,\n        label_layer,\n        process_layer,\n        time_line_layer,\n        time_arrow_layer,\n        time_label_layer,\n    )\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(title_str, fontSize=16, fontWeight=\"normal\", anchor=\"middle\", color=INK, offset=10),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.15, labelColor=INK_SOFT, titleColor=INK\n    )\n    .configure_title(color=INK)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        titleFontSize=10,\n        labelFontSize=10,\n    )\n)\n\n# Save PNG and pad to exact 3200×1800 canvas\nTW, TH = 3200, 1800\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}