Feynman Diagram for Particle Interactions — Matplotlib

A Feynman diagram visualizes interactions between subatomic particles in quantum field theory. Different line styles represent different particle types: straight lines for fermions (electrons, quarks), wavy lines for photons, curly/looped lines for gluons, and dashed lines for scalar bosons (e.g., Higgs). Lines meet at vertices representing interaction points. Invented by Richard Feynman, these diagrams are both a computational tool and a cultural icon of modern physics.

Feynman Diagram for Particle Interactions rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
feynman-basic: Feynman Diagram for Particle Interactions
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 91/100 | Updated: 2026-06-03
"""

import os

import matplotlib.patheffects as pe
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D
from matplotlib.patches import FancyArrowPatch


THEME = os.getenv("ANYPLOT_THEME", "light")

# Theme-adaptive chrome — Imprint palette
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint categorical palette — particle types (positions 1–4)
FERMION_COLOR = "#009E73"  # pos 1: brand green
PHOTON_COLOR = "#C475FD"  # pos 2: lavender
GLUON_COLOR = "#4467A3"  # pos 3: blue
BOSON_COLOR = "#BD8233"  # pos 4: ochre

# Two-process layout: top QED (e+e- annihilation), bottom QCD (gg -> H)
vertices = {"v1": (0.28, 0.74), "v2": (0.72, 0.74), "v3": (0.28, 0.26), "v4": (0.72, 0.26)}

propagators = [
    # QED: e-e+ -> gamma -> mu-mu+
    {"from": (0.04, 0.92), "to": "v1", "type": "fermion", "label": r"$e^-$", "arrow_fwd": True},
    {"from": (0.04, 0.56), "to": "v1", "type": "fermion", "label": r"$e^+$", "arrow_fwd": False},
    {"from": "v1", "to": "v2", "type": "photon", "label": r"$\gamma$"},
    {"from": "v2", "to": (0.96, 0.92), "type": "fermion", "label": r"$\mu^-$", "arrow_fwd": True},
    {"from": "v2", "to": (0.96, 0.56), "type": "fermion", "label": r"$\mu^+$", "arrow_fwd": False},
    # QCD: gg -> H -> bb-bar
    {"from": (0.04, 0.44), "to": "v3", "type": "gluon", "label": r"$g$"},
    {"from": (0.04, 0.08), "to": "v3", "type": "gluon", "label": r"$g$"},
    {"from": "v3", "to": "v4", "type": "boson", "label": r"$H$"},
    {"from": "v4", "to": (0.96, 0.44), "type": "fermion", "label": r"$b$", "arrow_fwd": True},
    {"from": "v4", "to": (0.96, 0.08), "type": "fermion", "label": r"$\bar{b}$", "arrow_fwd": False},
]

# Canvas: landscape 3200×1800 px (figsize=(8, 4.5), dpi=400)
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
ax.set_xlim(-0.08, 1.08)
ax.set_ylim(-0.08, 1.08)

for prop in propagators:
    start = vertices[prop["from"]] if isinstance(prop["from"], str) else prop["from"]
    end = vertices[prop["to"]] if isinstance(prop["to"], str) else prop["to"]
    sx, sy = start
    ex, ey = end
    mx, my = (sx + ex) / 2, (sy + ey) / 2
    dx, dy = ex - sx, ey - sy
    length = np.sqrt(dx**2 + dy**2)
    ux, uy = dx / length, dy / length
    px, py = -uy, ux  # perpendicular unit vector

    if prop["type"] == "fermion":
        arrow_fwd = prop.get("arrow_fwd", True)
        # FancyArrowPatch: direct matplotlib Patch API for arrow rendering
        arrow = FancyArrowPatch(
            posA=(sx, sy) if arrow_fwd else (ex, ey),
            posB=(ex, ey) if arrow_fwd else (sx, sy),
            arrowstyle="-|>",
            color=FERMION_COLOR,
            linewidth=2.5,
            mutation_scale=20,
            transform=ax.transData,
            zorder=3,
        )
        # patheffects: background-color halo separates the arrow from any crossing lines
        arrow.set_path_effects([pe.withStroke(linewidth=5, foreground=PAGE_BG)])
        ax.add_patch(arrow)
        ax.text(
            mx + px * 0.06,
            my + py * 0.06,
            prop["label"],
            fontsize=14,
            ha="center",
            va="center",
            color=FERMION_COLOR,
            fontweight="bold",
        )

    elif prop["type"] == "photon":
        t = np.linspace(0, 1, 500)
        wave = 0.035 * np.sin(2 * np.pi * 8 * t) * np.sin(np.pi * t) ** 0.3
        (line,) = ax.plot(
            sx + t * dx + wave * px, sy + t * dy + wave * py, color=PHOTON_COLOR, lw=2.5, solid_capstyle="round"
        )
        # patheffects: wider same-color stroke gives the wave a luminous, glowing appearance
        line.set_path_effects([pe.withStroke(linewidth=6, foreground=PHOTON_COLOR)])
        ax.text(
            mx - px * 0.07,
            my - py * 0.07,
            prop["label"],
            fontsize=14,
            ha="center",
            va="center",
            color=PHOTON_COLOR,
            fontweight="bold",
        )

    elif prop["type"] == "gluon":
        t = np.linspace(0, 1, 3000)
        phase = 2 * np.pi * 9 * t
        envelope = np.sin(np.pi * t) ** 0.4
        along_mod = 0.025 * 0.7 * np.sin(phase) * envelope
        perp_disp = 0.025 * np.cos(phase) * envelope
        (line,) = ax.plot(
            sx + t * dx - along_mod * ux + perp_disp * px,
            sy + t * dy - along_mod * uy + perp_disp * py,
            color=GLUON_COLOR,
            lw=2.0,
            solid_capstyle="round",
        )
        # patheffects: wider stroke gives the coil visual weight consistent with the photon glow
        line.set_path_effects([pe.withStroke(linewidth=5, foreground=GLUON_COLOR)])
        ax.text(
            mx + px * 0.06,
            my + py * 0.06,
            prop["label"],
            fontsize=14,
            ha="center",
            va="center",
            color=GLUON_COLOR,
            fontweight="bold",
        )

    elif prop["type"] == "boson":
        ax.plot([sx, ex], [sy, ey], color=BOSON_COLOR, lw=2.5, linestyle=(0, (8, 5)), dash_capstyle="round")
        ax.text(
            mx - px * 0.06,
            my - py * 0.06,
            prop["label"],
            fontsize=14,
            ha="center",
            va="center",
            color=BOSON_COLOR,
            fontweight="bold",
        )

# Vertex dots (theme-adaptive ink)
for vx, vy in vertices.values():
    ax.plot(vx, vy, "o", color=INK, markersize=10, zorder=5)

# Separator between QED and QCD processes
ax.axhline(y=0.50, xmin=0.05, xmax=0.95, color=INK_MUTED, lw=0.8, linestyle="--", alpha=0.5)

# Process labels — nudged inward and bumped to 10pt for mobile readability
ax.text(0.02, 0.74, "QED", fontsize=10, ha="left", va="center", color=INK_MUTED, fontstyle="italic", rotation=90)
ax.text(0.02, 0.26, "QCD", fontsize=10, ha="left", va="center", color=INK_MUTED, fontstyle="italic", rotation=90)

# Time arrow (inside axes area, above legend)
ax.annotate("", xy=(0.95, -0.03), xytext=(0.05, -0.03), arrowprops={"arrowstyle": "->", "color": INK_MUTED, "lw": 1.2})
ax.text(0.5, -0.05, "time", fontsize=10, ha="center", va="top", color=INK_MUTED, fontstyle="italic")

# Title (48 chars < 67 threshold — default 12pt)
ax.set_title("feynman-basic · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", pad=10, color=INK)
ax.axis("off")

# Legend (frameless for minimal aesthetic)
legend_handles = [
    Line2D(
        [0],
        [0],
        color=FERMION_COLOR,
        lw=2.5,
        marker=">",
        markersize=6,
        markeredgecolor=FERMION_COLOR,
        label="Fermion (solid + arrow)",
    ),
    Line2D([0], [0], color=PHOTON_COLOR, lw=2.5, label="Photon (wavy)"),
    Line2D([0], [0], color=GLUON_COLOR, lw=2.0, label="Gluon (coiled)"),
    Line2D([0], [0], color=BOSON_COLOR, lw=2.5, linestyle="--", label="Boson (dashed)"),
    Line2D([0], [0], marker="o", color=PAGE_BG, markerfacecolor=INK, markersize=7, lw=0, label="Vertex"),
]
leg = ax.legend(
    handles=legend_handles,
    fontsize=8,
    loc="lower center",
    ncol=5,
    frameon=False,
    handlelength=2.5,
    columnspacing=1.5,
    bbox_to_anchor=(0.5, 0.0),
    bbox_transform=ax.transAxes,
)
plt.setp(leg.get_texts(), color=INK_SOFT)

# Reserve bottom 8% of figure for legend below the axes
plt.tight_layout(rect=[0, 0.08, 1, 1])
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)

Part of Feynman Diagram for Particle Interactions on anyplot.ai.

Other implementations