"""House style for the figures: Matplotlib settings, a palette, and a save helper.
The settings are deliberately small and explicit, so a figure looks the same whoever builds
it. :func:`house_style` returns a context manager that applies them around figure
construction without leaking into the global state, and :func:`save_figure` writes one file
per format alongside a JSON provenance sidecar.
"""
from __future__ import annotations
import json
from collections.abc import Iterator, Sequence
from contextlib import contextmanager
from datetime import UTC, datetime
from pathlib import Path
import matplotlib as mpl
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from figures import __version__
# Colourblind-safe qualitative palette (Wong, Nature Methods 2011), used in order for the
# criterion lines.
PALETTE: tuple[str, ...] = (
"#0072B2",
"#E69F00",
"#009E73",
"#D55E00",
"#CC79A7",
"#56B4E9",
"#F0E442",
"#000000",
)
# Colour of the reference lines that mark the chosen number of classes.
REFERENCE_COLOUR = "#444444"
# Short display labels for the seven enrichment categories, so axis ticks stay legible. Keyed
# by the names the analysis writes (American spelling and slashes preserved to match the data).
CATEGORY_LABELS: dict[str, str] = {
"anxiety/mood": "anxiety/mood",
"attention": "attention",
"disruptive behavior": "disruptive",
"self-injury": "self-injury",
"social/communication": "social/comm",
"restricted/repetitive": "restricted/rep",
"developmental": "developmental",
}
[docs]
def panel_title(ax: Axes, letter: str, text: str) -> None:
"""Set a left-aligned panel title prefixed with a bold letter label.
The letter is rendered in bold and the scientific title follows in the regular weight, so
each panel reads as, for example, "A Information criteria", left-aligned over the axes.
Parameters
----------
ax : matplotlib.axes.Axes
The axis to title.
letter : str
The panel letter, shown in bold (for example ``"A"``).
text : str
The scientific title that follows the letter.
"""
ax.set_title(rf"$\mathbf{{{letter}}}$ {text}", loc="left")
_RC_PARAMS: dict[str, object] = {
"figure.dpi": 120,
"font.size": 9,
"axes.titlesize": 9,
"axes.labelsize": 9,
"axes.spines.top": False,
"axes.spines.right": False,
"axes.grid": True,
"grid.alpha": 0.3,
"grid.linewidth": 0.5,
"legend.frameon": False,
"legend.fontsize": 7,
"lines.linewidth": 1.5,
"lines.markersize": 4,
"xtick.labelsize": 8,
"ytick.labelsize": 8,
}
[docs]
@contextmanager
def house_style() -> Iterator[None]:
"""Apply the package Matplotlib settings for the duration of the context.
Yields
------
None
Control returns to the caller with the package ``rcParams`` in effect; the previous
settings are restored on exit, so the styling does not leak into other figures.
"""
with mpl.rc_context(_RC_PARAMS):
yield