MarkFeyn renders fenced feynman code blocks as native SVG diagrams in MkDocs and ProperDocs.
From PyPI:
uv add markfeynThis installs both supported documentation engines as runtime dependencies so the same package exposes both plugin entry points. Install Material for MkDocs when using the Material theme:
uv add mkdocs-materialFrom a source checkout:
uv syncFor local development:
uv sync --group devEnable the plugin in mkdocs.yml:
theme:
name: material
plugins:
- search
- feynman-diagrams
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
pygments_lang_class: true
- pymdownx.superfences:
custom_fences:
- name: feynman
class: language-feynman
format: !!python/name:pymdownx.superfences.fence_code_formatEnable the same plugin in properdocs.yml:
theme:
name: material
plugins:
- feynman-diagrams
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
pygments_lang_class: true
- pymdownx.superfences:
custom_fences:
- name: feynman
class: language-feynman
format: !!python/name:pymdownx.superfences.fence_code_formatThe plugin injects and copies the bundled browser renderer automatically. The Markdown parser stays lightweight because diagram parsing and SVG generation happen in the browser.
For ProperDocs with theme: material, do not add search explicitly; Material's search plugin is MkDocs-only.
To customize the emitted script path:
plugins:
- search
- feynman-diagrams:
script_path: assets/javascripts/feynman-diagrams.jsscript_path is interpreted relative to the generated site directory. Parent-directory traversal such as ../feynman.js is rejected.
```feynman
incoming e_minus e_plus
outgoing mu_plus mu_minus
fermion e_minus->ann
anti fermion e_plus->ann
photon ann->prod[momentum'=k]
anti fermion prod->mu_plus
fermion prod->mu_minus
label e_minus:e^- e_plus:e^+ mu_plus:\mu^+ mu_minus:\mu^- ann->prod:\gamma
```Visible degree-1 endpoints without incoming or outgoing declarations are
kept as unclassified external states and laid out symmetrically when no process
direction is declared. Balanced two-center trees and symmetric two-point
self-energy loops without explicit roles receive focused reflection-symmetric
refinement; this is not a full automorphism engine. Add incoming and
outgoing for left-to-right process diagrams and explicit terminal ordering.
Supported particles:
plain,line, orpropagator: solid line without an arrowfermion: solid line with centered arrowanti fermionoranti-fermion: solid line with reversed arrowphoton: sinusoidal waveboson: alias forphotongluon: looped/cycloid pathscalar: dashed lineghost: dotted lineinvisibleorhidden: layout-only edge
Labels use node:text. Edge labels are also accepted with from->to:text.
Labels support a small TeX-like subset for common symbols and scripts:
label electron:e^- muon:\mu^- vertex:\gamma momentum:p_{T}
Per-edge options support TikZ-Feynman-style curves and inline labels:
fermion b->c[half left, momentum=k] c->b[half left, momentum'=k-p]
boson d->e[bend left, edge label=W^+]
fermion f->g[out=180, in=45]
Use brace from->to[side]:label for grouping braces on manually positioned
diagrams.
Diagram-level options can select layout algorithms, orientation, and sizing:
```feynman
layout spring
orientation vertical
size small
options width=560 height=420
incoming mu
outgoing numu nue e
fermion mu->w w->numu
boson w->v
anti fermion nue->v
fermion v->e
invisible numu->e
label mu:\mu^- numu:\nu_\mu nue:\nu_e e:e^- w->v:W^-
```Supported layouts are spring (default), spring-electrical, layered, and
tree, all backed by bundled ELK.js graph layout. Pin nodes manually with
position node x y when an automatic layout is not enough.
Common loop topologies also get semantic candidate layouts: triangle loops, box loops, tadpoles, generic polygon loops, centered vacuum one-loop diagrams, and bounded two-loop nested or overlapping regions are detected and placed deterministically before falling back to the general graph layout. Candidate scoring includes approximate label, momentum, multiloop recognizability, and previous-layout stability checks. Final layouts also run deterministic, bounding-box-based label placement so node labels, edge labels, and momentum labels can move away from nearby vertices, propagators, loop interiors, and other labels while preserving explicit primed/right-side choices. It still does not attempt arbitrary higher-order multiloop optimization or exact TeX/TikZ font metrics.
TikZ-Feynman-style post-layout alignment is available with commands such as
horizontal a to b, horizontal' a to b, vertical a to b, and
vertical' a to b.
Vertex shapes can be selected with vertex node:shape pairs:
```feynman
incoming a
outgoing b c
fermion a->v v->b
photon v->blob blob->c
vertex v:dot blob:blob
label a:e^- b:e^- c:\gamma
```Internal vertices are unmarked by default; use vertex v:dot for a small
filled interaction point.
Supported shapes are dot, square-dot, empty-dot, crossed-dot, cross,
blob, and disk.
Blob and disk vertices can use optional bracketed styling:
```feynman
incoming a
outgoing b
plain a->disk disk->b
vertex disk:disk[hatch=cross,width=96,height=44]
```Supported hatch fills are diagonal, diagonal-reverse, cross,
horizontal, vertical, and grid; TikZ-style aliases such as
north east lines and north west lines are accepted. size and radius
set the circular radius in SVG pixels, and diameter sets the full circle
width. Use width and height for full elliptical extents, or rx and ry
for horizontal and vertical radii. Unequal horizontal and vertical radii render
as an SVG ellipse.
If you do not want to use a plugin, copy the bundled renderer from:
src/markfeyn/assets/feynman-diagrams.js
to:
docs/javascripts/feynman-diagrams.js
Then configure MkDocs or ProperDocs directly:
extra_javascript:
- javascripts/feynman-diagrams.js
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
pygments_lang_class: true
- pymdownx.superfences:
custom_fences:
- name: feynman
class: language-feynman
format: !!python/name:pymdownx.superfences.fence_code_formatsrc/markfeyn/
__init__.py
core.py
mkdocs_plugin.py
properdocs_plugin.py
renderer/
feynman-diagrams.js
assets/
feynman-diagrams.js
The package exposes both plugin entry points:
[project.entry-points."mkdocs.plugins"]
feynman-diagrams = "markfeyn.mkdocs_plugin:FeynmanDiagramsPlugin"
[project.entry-points."properdocs.plugins"]
feynman-diagrams = "markfeyn.properdocs_plugin:FeynmanDiagramsPlugin"make test
make docs
make buildServe docs locally:
make serve-properdocs
make serve-mkdocsThe serve targets use localhost:8001 by default. Override with DOCS_ADDR=localhost:8010 if that port is busy.
Build and publish to PyPI:
make publishThe publish targets upload only dist/markfeyn-* artifacts.
Publish to TestPyPI first:
make publish-testuv publish reads credentials from the environment. For PyPI token auth, set:
export UV_PUBLISH_TOKEN="pypi-..."- Rendering happens in the browser, so MkDocs and ProperDocs builds remain static and fast.
- MkDocs and ProperDocs use separate adapter classes over the same core implementation.
- The script supports themes that expose Material-style instant loading through
document$.subscribe. - It falls back to
DOMContentLoadedfor standard static documentation themes. - The browser renderer has no JavaScript runtime dependencies.