{"spec_id":"feynman-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// feynman-basic: Feynman Diagram for Particle Interactions\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-03\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Imprint palette: fermion=green, photon=lavender, gluon=blue, scalar boson=ochre\nconst FERMION_COLOR = t.palette[0]; // #009E73\nconst PHOTON_COLOR  = t.palette[1]; // #C475FD\nconst GLUON_COLOR   = t.palette[2]; // #4467A3\nconst BOSON_COLOR   = t.palette[3]; // #BD8233\n\n// Two processes showing all 4 Feynman particle line types:\n// Left:  e⁺e⁻ → γ* → μ⁺μ⁻  (fermion solid+arrow, photon wavy)\n// Right: H → b b̄ g           (scalar boson dashed, fermion, gluon curly)\n\nconst vertices = [\n  { x: 2.0, y: 3.5 }, // V1: e+e- annihilation\n  { x: 3.8, y: 3.5 }, // V2: γ* → μ+μ- pair creation\n  { x: 6.8, y: 3.5 }, // V3: H → bb̄ decay\n  { x: 8.0, y: 5.0 }, // V4: b → b + g gluon emission\n];\n\nconst lines = [\n  // (a) QED: e+e- → γ* → μ+μ-\n  { from: [0.8, 5.3], to: [2.0, 3.5], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"forward\",  label: \"e⁻\",   labelAt: [0.55, 5.75] },\n  { from: [0.8, 1.7], to: [2.0, 3.5], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"backward\", label: \"e⁺\",   labelAt: [0.55, 1.25] },\n  { from: [2.0, 3.5], to: [3.8, 3.5], type: \"photon\",  color: PHOTON_COLOR,  arrowDir: \"none\",     label: \"γ*\",   labelAt: [2.9,  4.2]  },\n  { from: [3.8, 3.5], to: [4.5, 5.3], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"forward\",  label: \"μ⁻\", labelAt: [4.62, 5.75] },\n  { from: [3.8, 3.5], to: [4.5, 1.7], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"backward\", label: \"μ⁺\", labelAt: [4.62, 1.25] },\n  // (b) QCD+Higgs: H → b b̄ g\n  { from: [5.5, 3.5], to: [6.8, 3.5], type: \"boson\",   color: BOSON_COLOR,   arrowDir: \"none\",     label: \"H\",         labelAt: [5.85, 4.1]  },\n  { from: [6.8, 3.5], to: [8.0, 5.0], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"forward\",  label: \"b\",         labelAt: [7.15, 4.1]  },\n  { from: [8.0, 5.0], to: [9.5, 6.0], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"forward\",  label: \"b\",         labelAt: [9.35, 6.35] },\n  { from: [8.0, 5.0], to: [9.5, 5.0], type: \"gluon\",   color: GLUON_COLOR,   arrowDir: \"none\",     label: \"g\",         labelAt: [9.35, 5.45] },\n  { from: [6.8, 3.5], to: [9.5, 1.7], type: \"fermion\", color: FERMION_COLOR, arrowDir: \"backward\", label: \"b̅\",   labelAt: [9.45, 1.25] },\n];\n\nfunction toPx(chart, dx, dy) {\n  return [chart.scales.x.getPixelForValue(dx), chart.scales.y.getPixelForValue(dy)];\n}\n\nfunction arrowHead(ctx, mx, my, ax, ay, bx, by, color, sz) {\n  const angle = Math.atan2(by - ay, bx - ax);\n  ctx.save();\n  ctx.translate(mx, my);\n  ctx.rotate(angle);\n  ctx.beginPath();\n  ctx.moveTo(0, 0);\n  ctx.lineTo(-sz, -sz * 0.38);\n  ctx.lineTo(-sz,  sz * 0.38);\n  ctx.closePath();\n  ctx.fillStyle = color;\n  ctx.fill();\n  ctx.restore();\n}\n\nfunction fermionLine(ctx, x1, y1, x2, y2, color, lw, arrowDir) {\n  ctx.save();\n  ctx.beginPath();\n  ctx.moveTo(x1, y1);\n  ctx.lineTo(x2, y2);\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lw;\n  ctx.setLineDash([]);\n  ctx.stroke();\n  const mx = (x1 + x2) / 2;\n  const my = (y1 + y2) / 2;\n  if (arrowDir === \"forward\")  arrowHead(ctx, mx, my, x1, y1, x2, y2, color, 13);\n  if (arrowDir === \"backward\") arrowHead(ctx, mx, my, x2, y2, x1, y1, color, 13);\n  ctx.restore();\n}\n\nfunction photonLine(ctx, x1, y1, x2, y2, color, lw) {\n  const dx = x2 - x1, dy = y2 - y1;\n  const length = Math.sqrt(dx * dx + dy * dy);\n  const angle  = Math.atan2(dy, dx);\n  const amp = 10, wl = 24;\n  const steps = Math.ceil(length * 1.2);\n  ctx.save();\n  ctx.translate(x1, y1);\n  ctx.rotate(angle);\n  ctx.beginPath();\n  ctx.moveTo(0, 0);\n  for (let i = 1; i <= steps; i++) {\n    const xp = (i / steps) * length;\n    ctx.lineTo(xp, amp * Math.sin(2 * Math.PI * xp / wl));\n  }\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lw;\n  ctx.setLineDash([]);\n  ctx.stroke();\n  ctx.restore();\n}\n\n// Gluon: curly/looped line — series of semicircular arcs below the line axis\nfunction gluonLine(ctx, x1, y1, x2, y2, color, lw) {\n  const dx = x2 - x1, dy = y2 - y1;\n  const length = Math.sqrt(dx * dx + dy * dy);\n  const angle = Math.atan2(dy, dx);\n  const loopR = 11;\n  const nLoops = Math.max(4, Math.round(length / (2.2 * loopR)));\n  const step = length / nLoops;\n  const r = step / 2;\n\n  ctx.save();\n  ctx.translate(x1, y1);\n  ctx.rotate(angle);\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lw;\n  ctx.setLineDash([]);\n  ctx.beginPath();\n  for (let i = 0; i < nLoops; i++) {\n    const cx = (i + 0.5) * step;\n    // anticlockwise=true in canvas → loops droop below the line axis\n    ctx.arc(cx, 0, r, Math.PI, 0, true);\n  }\n  ctx.stroke();\n  ctx.restore();\n}\n\n// Scalar boson: dashed straight line\nfunction bosonLine(ctx, x1, y1, x2, y2, color, lw) {\n  ctx.save();\n  ctx.beginPath();\n  ctx.moveTo(x1, y1);\n  ctx.lineTo(x2, y2);\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lw;\n  ctx.setLineDash([10, 7]);\n  ctx.stroke();\n  ctx.setLineDash([]);\n  ctx.restore();\n}\n\nconst feynmanPlugin = {\n  id: \"feynman\",\n  beforeDraw(chart) {\n    const { ctx } = chart;\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, chart.width, chart.height);\n  },\n  afterDraw(chart) {\n    const { ctx } = chart;\n    ctx.save();\n\n    const lw = 3.5;\n\n    // Dashed divider between the two processes\n    const [divX]        = toPx(chart, 5.0, 0);\n    const [, divTop]    = toPx(chart, 0, 6.7);\n    const [, divBottom] = toPx(chart, 0, 0.4);\n    ctx.beginPath();\n    ctx.moveTo(divX, divTop);\n    ctx.lineTo(divX, divBottom);\n    ctx.strokeStyle = t.grid;\n    ctx.lineWidth = 1;\n    ctx.setLineDash([6, 4]);\n    ctx.stroke();\n    ctx.setLineDash([]);\n\n    // Draw propagators\n    lines.forEach(line => {\n      const [x1, y1] = toPx(chart, line.from[0], line.from[1]);\n      const [x2, y2] = toPx(chart, line.to[0],   line.to[1]);\n      if      (line.type === \"fermion\") fermionLine(ctx, x1, y1, x2, y2, line.color, lw, line.arrowDir);\n      else if (line.type === \"photon\")  photonLine( ctx, x1, y1, x2, y2, line.color, lw);\n      else if (line.type === \"gluon\")   gluonLine(  ctx, x1, y1, x2, y2, line.color, lw);\n      else if (line.type === \"boson\")   bosonLine(  ctx, x1, y1, x2, y2, line.color, lw);\n    });\n\n    // Vertex dots\n    vertices.forEach(v => {\n      const [px, py] = toPx(chart, v.x, v.y);\n      ctx.beginPath();\n      ctx.arc(px, py, 6, 0, 2 * Math.PI);\n      ctx.fillStyle = t.ink;\n      ctx.fill();\n    });\n\n    // Particle labels\n    ctx.font = \"bold 20px 'Helvetica Neue', Helvetica, Arial, sans-serif\";\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    lines.forEach(line => {\n      const [lx, ly] = toPx(chart, line.labelAt[0], line.labelAt[1]);\n      ctx.fillStyle = line.color;\n      ctx.fillText(line.label, lx, ly);\n    });\n\n    // Time direction indicator\n    const [tx1, ty] = toPx(chart, 1.5, 0.7);\n    const [tx2]     = toPx(chart, 8.5, 0.7);\n    ctx.beginPath();\n    ctx.moveTo(tx1, ty);\n    ctx.lineTo(tx2 - 6, ty);\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth = 1.5;\n    ctx.setLineDash([5, 5]);\n    ctx.stroke();\n    ctx.setLineDash([]);\n    arrowHead(ctx, tx2, ty, tx1, ty, tx2, ty, t.inkSoft, 8);\n    ctx.font = \"italic 15px 'Helvetica Neue', Helvetica, Arial, sans-serif\";\n    ctx.fillStyle = t.inkSoft;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"top\";\n    ctx.fillText(\"time\", (tx1 + tx2) / 2, ty + 8);\n\n    ctx.restore();\n  },\n};\n\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [{\n      data: [{ x: 0, y: 0 }, { x: 10, y: 7 }],\n      pointRadius: 0,\n      borderWidth: 0,\n      backgroundColor: \"transparent\",\n    }],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"feynman-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"600\" },\n        padding: { top: 20, bottom: 14 },\n      },\n      legend: { display: false },\n    },\n    scales: {\n      x: { min: 0, max: 10, display: false },\n      y: { min: 0, max: 7,  display: false },\n    },\n    layout: {\n      padding: { left: 60, right: 60, top: 10, bottom: 30 },\n    },\n  },\n  plugins: [feynmanPlugin],\n});\n"}