plotter — figure rendering + mathtext facade

Purpose

The pipeline’s only rendering layer: prepared series (PlotLine/PlotAxis/FigureSpec) → PNG.
matplotlib/seaborn/pylab live nowhere else (hook-enforced). render_line_panels is the one
sanctioned figure entry point for validation scripts and agents.

Role in the system

  • Consumed by orchestrator (Plotter().plot_configured_figures, plot_reduction_comparison,
    figure_spec_from_fields) — the only module that drives the run-spec figures:/overlay: path.
  • Consumed by committed validation/ scripts via the render_line_panels shim (they prepare series
    through the signal_measurement facade and hand them here). Worked examples:
    validation/washout_theta3_figures.py, validation/chaos_divergence_figures.py.
  • Imports GuidanceMode from guidance_classes (shade styling) and Package/object_fields from infra.
  • Renders prepared axes from data_analyzer (DataSummary.plot_axis); never reduces data itself.
  • Sibling of plotter3d (3-D scene rendering, separate path).

Inputs / Outputs

  • In: prepared PlotAxis/FigureSpec objects (or raw panel dicts via the shim), figure defaults
    from analysis_YAMLs/default_figure_specs.yaml.
  • Out: PNG files written to the run-stamped figures dir; the list of saved paths. The Agg
    backend is forced at import (headless).

Key methods / functions

  • render_line_panels — sanctioned shim: stacked panel dicts → rendered PNG(s), no matplotlib import for the caller — analysis/plotter.py:986
  • Plotter.plot_configured_figures — render a list of FigureSpecs; returns all saved paths — :963
  • figure_spec_from_fields — build a FigureSpec from a loose field mapping + axes + figsize — :469
  • PlotAxis.plot — render one subplot end-to-end (settings, shades, lines, scatters, markers, twin, decorate) — :277
  • PlotAxis.plot_secondary_lines — twin y-axis for a second signal at its own scale — :289
  • to_mathtext — label → matplotlib mathtext (wrap math-like tokens in $…$, title-case prose) — :492
  • ComparisonPlotter.plot_reduction_comparison — one figure per (group, reduction) block across variants — :798
  • figure_smoke — Stage-3 boundary smoke (python -m analysis.plotter) — :1033

Footguns

plot_section coerces a terminal list to {}

plot_section("comparison", "figure") returns a dict even when figsize is a list-valued leaf.
Read figsize directly from the mapping with .get(), never via plot_section
(reduction_comparison_figure). (analysis/INSIGHTS.md [footgun])

Normalize Line2D aliases before they reach ax.plot

render_line_panels runs cbook.normalize_kwargs(…, Line2D) so aliases (lwlinewidth,
lslinestyle, ccolor) collapse to canonical keys before merging with per-line overrides.
Without it a default lw and a caller linewidth both reach ax.plot → duplicate-argument error.
(analysis/INSIGHTS.md [footgun])

Markers carry a value key that axhline/axvline reject

marker_kwargs drops the value key (it is the line position, not a Line2D kwarg) and applies
to_mathtext to the label before passing through. (analysis/INSIGHTS.md [plotting])

The versine map doesn't commute with rms

Pointing labels are math-like (z_b, p_e): to_mathtext mathtext-wraps them, but the number
stays the catalog versine — render an angle only with as_pointing_deg for human prints, never
rms the degrees. (See signal_measurement / MEASUREMENT.md.)

Pseudocode (render_line_panels)

default_line = normalize_kwargs(line.default, Line2D)   # collapse aliases first
for each panel:
    lines      = PlotLine(x, y, label, {default_line, **normalize_kwargs(extra)})
    PlotAxis(loc, lines, scatters, hlines/vlines, secondary_lines/hlines, scales, xlim)
spec = figure_spec_from_fields({name, suptitle}, axes, figsize)
return Plotter().plot_configured_figures(out_dir, [spec])   # mkdir + savefig + provenance

Panel dict schema: lines / scatter / hlines / vlines / secondary_lines /
secondary_hlines / secondary_ylabel / title / xlabel / ylabel / xscale / yscale /
xlim. Panels stack top-to-bottom; pass layout=(rows, cols) for a row-major grid.

Mathtext (to_mathtext family)

  • needs_math flags a token as math-like via the MATH_TOKENS Greek table or short multi-part
    snake-case (p_e); plain prose is title-cased through display_label.
  • Space-separated strings get per-token treatment so decimals survive (“gate 0.99” stays, never
    “Gate 0 99”).
  • math_word/math_subscript/math_name render snake/caret words as TeX (subscripts, superscripts,
    \mathrm{…} for multi-letter bases).

orchestrator · data_analyzer · signal_measurement · star_reporter · plotter3d · guidance_classes · infra · terminology