sft_wick.render_style — Visual Style Specifications

Backend-agnostic visual styling for Feynman-diagram rendering.

This module defines a hierarchy of frozen dataclasses that capture what a diagram should look like (colors, line styles, marker sizes, fonts, layout parameters) without committing to how it gets drawn. The matplotlib renderer in sft_wick.drawing and the TikZ/PGF renderer in sft_wick.drawing_tikz both consume the same RenderStyle and translate it to backend-native settings.

Three named presets are provided:

  • default_style() — the colourful on-screen look (blue C, red R) used for quick inspection in notebooks.

  • publication_style() — a cleaner, smaller-marker look with publication-friendly fonts. Honours usetex if the user has a LaTeX install.

  • grayscale_style() — black/grey palette for printed papers and B&W reproduction.

  • minimal_style() — strips legend / labels / boxes for inset-style use inside a larger figure.

All four call into the same primitives, so users may freely mix parts (e.g. publication_style().with_overrides(show_legend=False)).

Three label-format flags control the default text of external vertices (the $\phi_a(\cdot)$ operators):

  • LABEL_COMPACT$\phi_a$ (no spatial argument). Default.

  • LABEL_FULL$\phi_a(x_1)$ (spatial argument retained — the pre-2026-04 default).

  • LABEL_TIME_F$\phi_a(t_f)$ (substitute t_f for the spatial argument).

Override these on a per-diagram or per-renderer basis with the label_format=… constructor argument or the external_labels keyword on DiagramRenderer.draw().

class sft_wick.render_style.PropagatorStyle(color, linestyle, linewidth, arrow=False, legend_label='')[source]

Bases: object

Visual style for a single propagator kind (C or R).

Variables:
  • color (str) – Stroke colour as a hex string ("#1f3a5f").

  • linestyle (str) – One of "solid" | "dashed" | "dotted".

  • linewidth (float) – Line thickness (matplotlib points / TikZ line width).

  • arrow (bool) – Whether to draw a directed arrowhead (used for R).

  • legend_label (str) – Text shown in the legend; empty string → no entry.

Parameters:
color: str
linestyle: str
linewidth: float
arrow: bool = False
legend_label: str = ''
class sft_wick.render_style.NodeStyle(shape, size, fill=True, color='black', edge_color='none')[source]

Bases: object

Visual style for an external point or interaction vertex.

Variables:
  • shape (str) – "circle" or "square".

  • size (float) – Marker size (matplotlib markersize / TikZ minimum size).

  • fill (bool) – Whether the marker is filled.

  • color (str) – Fill colour (or stroke if fill=False).

  • edge_color (str) – Outline colour; "none" for no outline.

Parameters:
shape: str
size: float
fill: bool = True
color: str = 'black'
edge_color: str = 'none'
class sft_wick.render_style.LabelStyle(fontsize, bold=False, bbox=True, bbox_alpha=0.85, offset_pt=26.0)[source]

Bases: object

Visual style for a label rendered next to a node.

Variables:
  • fontsize (float) – Label font size in points.

  • bold (bool) – If True, the label is bold.

  • bbox (bool) – If True, draw a white rounded box behind the label (matplotlib only; TikZ ignores this and lets the user style via every label/.style if desired).

  • bbox_alpha (float) – Opacity of that box.

  • offset_pt (float) – Distance from the node to the label centre, in typographic points.

Parameters:
fontsize: float
bold: bool = False
bbox: bool = True
bbox_alpha: float = 0.85
offset_pt: float = 26.0
class sft_wick.render_style.LayoutParams(ext_radius=2.5, spring_k=2.0, spring_iterations=200, spring_seed=42, min_vertex_dist=0.8, margin=0.75, component_gap=1.0, normalize_bbox=True, target_extent=(5.6, 3.6), parallel_edge_curvature=0.6)[source]

Bases: object

Numerical parameters controlling the position-computation step.

These are honoured identically by both backends.

Variables:
  • ext_radius (float) – Radius of the circle on which external nodes are placed.

  • spring_k (float) – Optimal edge length for spring_layout.

  • spring_iterations (int) – Iteration cap for spring_layout.

  • spring_seed (int | None) – Seed for spring_layout (None → non-deterministic).

  • min_vertex_dist (float) – Minimum allowed Euclidean distance between interaction vertices; vertices closer than this are pushed apart in a post-pass.

  • margin (float) – Padding added to axis limits around the diagram bounding box (matplotlib only).

  • component_gap (float) – Minimum horizontal gap between disconnected connected components before bbox normalisation. Helps factorised diagrams remain visually separated.

  • normalize_bbox (bool) – If True (default), the final layout is re-centred on the origin and uniformly scaled so it fits within target_extent without distortion. Gives every diagram in a grid roughly the same visual extent — recommended whenever drawing multiple diagrams side by side.

  • target_extent (tuple[float, float]) – Target (width, height) for the normalised bounding box, in matplotlib units. Default (6.0, 4.0) matches a 4:3 ish aspect-ratio panel.

  • parallel_edge_curvature (float) – Bow strength for parallel edges between the same node pair (matplotlib backend). Each edge in a bundle is drawn as an arc3 with rad = (key - (n-1)/2) * parallel_edge_curvature, so a 2-edge bundle bows by ±0.5 × this value to opposite sides. Larger → fuller arcs. Default 0.6 (apex offset ≈ 30 % of the chord for a 2-edge bundle).

Parameters:
ext_radius: float = 2.5
spring_k: float = 2.0
spring_iterations: int = 200
spring_seed: int | None = 42
min_vertex_dist: float = 0.8
margin: float = 0.75
component_gap: float = 1.0
normalize_bbox: bool = True
target_extent: tuple[float, float] = (5.6, 3.6)
parallel_edge_curvature: float = 0.6
class sft_wick.render_style.RenderStyle(propagators, external_node, vertex_node, external_label, vertex_label, title_fontsize=12.0, suptitle_fontsize=14.0, legend_fontsize=12.0, legend_loc='lower right', show_legend=True, layout=<factory>, rcparams=None, mathtext=True, usetex=False, label_format='compact')[source]

Bases: object

Complete visual specification for a Feynman-diagram render.

Both the matplotlib (sft_wick.drawing.DiagramRenderer) and the TikZ (sft_wick.drawing_tikz.TikzRenderer) backends consume an instance of this class and translate it to their native style options.

Use default_style(), publication_style(), grayscale_style(), or minimal_style() as a starting point and call with_overrides() to customise.

Variables:
  • propagators (collections.abc.Mapping[str, sft_wick.render_style.PropagatorStyle]) – Mapping {"C": PropagatorStyle, "R": PropagatorStyle}.

  • external_node (sft_wick.render_style.NodeStyle) – Style for external observable nodes.

  • vertex_node (sft_wick.render_style.NodeStyle) – Style for interaction vertices.

  • external_label (sft_wick.render_style.LabelStyle) – Style for labels next to external nodes.

  • vertex_label (sft_wick.render_style.LabelStyle) – Style for labels next to interaction vertices (typically the coupling name).

  • title_fontsize (float) – Font size of an axes title.

  • suptitle_fontsize (float) – Font size of the figure-level suptitle.

  • legend_fontsize (float) – Font size of the propagator-kind legend.

  • legend_loc (str) – Matplotlib legend location string.

  • show_legend (bool) – Master toggle for the legend.

  • layout (sft_wick.render_style.LayoutParams) – Numerical layout parameters.

  • rcparams (collections.abc.Mapping[str, Any] | None) – Optional mapping fed into a matplotlib.rc_context around the draw call. Useful for serif fonts, math rendering, etc. Ignored by TikZ.

  • mathtext (bool) – If True, labels are wrapped in $…$ so matplotlib renders them as math.

  • usetex (bool) – Convenience flag. When True, sets "text.usetex": True in rcparams (overriding any prior value) — requires a working LaTeX install on the user’s system.

  • label_format (str) – Default external-label format (LABEL_COMPACT etc.). May be overridden per-call.

Parameters:
propagators: Mapping[str, PropagatorStyle]
external_node: NodeStyle
vertex_node: NodeStyle
external_label: LabelStyle
vertex_label: LabelStyle
title_fontsize: float = 12.0
suptitle_fontsize: float = 14.0
legend_fontsize: float = 12.0
legend_loc: str = 'lower right'
show_legend: bool = True
layout: LayoutParams
rcparams: Mapping[str, Any] | None = None
mathtext: bool = True
usetex: bool = False
label_format: str = 'compact'
with_overrides(**kwargs)[source]

Return a new RenderStyle with the given top-level fields replaced. Nested fields (propagators["C"].color) need with_propagator() or manual construction.

Parameters:

kwargs (Any)

Return type:

RenderStyle

with_propagator(kind, **prop_kwargs)[source]

Return a new RenderStyle with one propagator’s style partially replaced.

Example:

style = publication_style().with_propagator("C", color="black")
Parameters:
  • kind (str)

  • prop_kwargs (Any)

Return type:

RenderStyle

effective_rcparams()[source]

Resolve rcparams + usetex into a single dict.

Used by the matplotlib backend to feed rc_context.

Return type:

dict[str, Any]

sft_wick.render_style.default_style()[source]

The colourful on-screen preset (blue C, red R, square vertices).

Matches the pre-2026-04 visual defaults — use this as the backward-compatible baseline.

Return type:

RenderStyle

sft_wick.render_style.publication_style(*, usetex=False)[source]

Cleaner preset for paper-quality figures.

Smaller, thinner markers, refined palette (slate-blue C, warm-grey R), serif labels via mathtext or LaTeX, compact (no boxed) labels, and a discreet legend. Pass usetex=True if you have a working LaTeX install — otherwise mathtext renders the math.

Parameters:

usetex (bool)

Return type:

RenderStyle

sft_wick.render_style.grayscale_style()[source]

Black-and-white preset.

Distinguishes propagator kinds by line style and arrow-head only; no colour information. Suitable for printed papers and B&W reproduction.

Return type:

RenderStyle

sft_wick.render_style.minimal_style()[source]

Stripped-down preset for inset use.

No legend, no boxes behind labels, no per-axes title. Good when embedding diagrams as small panels inside a larger composite figure.

Return type:

RenderStyle

sft_wick.render_layout — Layout Computation

Position computation for Feynman-diagram nodes (backend-agnostic).

This module owns the geometric layout of a FeynmanDiagram:

  1. external nodes are placed on a circle (or, for the common two-external case, mirrored on the x-axis),

  2. interaction vertices are seeded around the origin and refined by networkx.spring_layout with externals pinned,

  3. a post-pass enforces a minimum vertex separation,

  4. for two-external layouts, vertices are clamped horizontally so the externals stay outermost.

Both rendering backends (sft_wick.drawing and sft_wick.drawing_tikz) call compute_layout() and then translate the resulting positions to their own coordinate conventions.

The function accepts a manual_positions map so users can pin arbitrary nodes — particularly useful when the spring layout produces an aesthetically poor result and the user wants full control.

sft_wick.render_layout.compute_layout(diagram, params=LayoutParams(ext_radius=2.5, spring_k=2.0, spring_iterations=200, spring_seed=42, min_vertex_dist=0.8, margin=0.75, component_gap=1.0, normalize_bbox=True, target_extent=(5.6, 3.6), parallel_edge_curvature=0.6), manual_positions=None)[source]

Return {node_id: np.array([x, y])} for every node.

Parameters:
  • diagram (FeynmanDiagram) – The diagram to lay out.

  • params (LayoutParams) – Layout parameters (radius, spring-layout knobs, separation thresholds, …).

  • manual_positions (Mapping[str, tuple[float, float]] | None) – Optional pin map. Any node listed here is placed at the given coordinate and excluded from spring-layout updates. Externals not listed fall back to the circular placement; vertices not listed follow the standard spring + min-distance pipeline.

Returns:

A dict mapping node-id to a length-2 numpy array. Empty diagrams produce an empty dict; single-node diagrams return one entry at the origin.

Return type:

dict[str, ndarray]

sft_wick.render_layout.neighbor_center(g, node, pos)[source]

Centroid of a node’s non-self neighbours, used for self-loop direction.

If the node has no other neighbours, returns a point one unit below it so self-loops on isolated tadpoles point straight up.

Parameters:
Return type:

ndarray

sft_wick.render_layout.label_offset(point, center, distance=18.0)[source]

Return a label-offset vector (in points) pointing away from center.

Used by both backends to place labels on the outside of the bounding box where they are least likely to overlap edges.

Parameters:
  • point (ndarray)

  • center (ndarray)

  • distance (float)

Return type:

tuple[float, float]

sft_wick.render_labels — Label Formatting

Label-text generation for Feynman-diagram nodes.

Both rendering backends (matplotlib and TikZ) call into this module to decide what string sits next to each external point and each interaction vertex. Centralising the logic keeps the two backends visually consistent.

The default external label varies with the LABEL_COMPACT / LABEL_FULL / LABEL_TIME_F flag. Users can:

  1. Override individual node labels with the external_labels / vertex_labels keyword on sft_wick.drawing.DiagramRenderer.draw().

  2. Supply a callable fn(node_id: str, node_attrs: Mapping) -> str on the renderer constructor for systematic overrides.

  3. Keep the default and just switch the format flag globally.

Resolution order, highest priority first: overrides[node_id] > callable_fn(...) > default_fn(...).

Mathtext / dollar wrapping is the caller’s responsibility for overrides; the default formatters return strings already wrapped in $…$ so they render as math in matplotlib.

sft_wick.render_labels.default_external_label(node_attrs, fmt='compact')[source]

Format the default label for an external node.

Parameters:
  • node_attrs (Mapping[str, Any]) – Networkx node-data dict for the external point. Expected keys: field_type, component, spatial, and optionally full_label (for LABEL_FULL).

  • fmt (str) – One of LABEL_COMPACT, LABEL_FULL, LABEL_TIME_F.

Returns:

A string already wrapped in $…$ for math-mode rendering.

Raises:

ValueError – If fmt is not one of the recognised formats.

Return type:

str

sft_wick.render_labels.default_vertex_label(node_attrs)[source]

Format the default label for an interaction vertex.

Wraps the coupling name (e.g. "F") in $…$. Empty coupling → empty string (no label drawn).

Parameters:

node_attrs (Mapping[str, Any])

Return type:

str

sft_wick.render_labels.resolve_label(node_id, node_attrs, overrides, callable_fn, default_fn)[source]

Pick the best label for a node by checking sources in order.

Priority: overrides[node_id] > callable_fn(node_id, attrs) > default_fn(attrs).

A None or missing entry in overrides falls through. A callable returning None also falls through (so the callable can opt out for specific nodes).

Parameters:
Return type:

str