"""Shared ACM-style plotting configuration for figures_v2.""" from __future__ import annotations from pathlib import Path import matplotlib as mpl import matplotlib.pyplot as plt SINGLE_COL = 3.35 DOUBLE_COL = 6.95 TITLE_SIZE = 10 SUBTITLE_SIZE = 9 LABEL_SIZE = 8.5 TICK_SIZE = 7.5 LEGEND_SIZE = 7.5 ANNOTATION_SIZE = 7 PANEL_LABEL_SIZE = 9 LINEWIDTH = 1.6 MARKER_SIZE = 4.5 AXIS_LINEWIDTH = 0.8 GRID_LINEWIDTH = 0.4 BAR_EDGEWIDTH = 0.4 COLORS = { "blue": "#4C72B0", "orange": "#DD8452", "green": "#55A868", "red": "#C44E52", "purple": "#8172B2", "gray": "#7F7F7F", "dark": "#2B2B2B", "light_gray": "#EAEAEA", "pale_blue": "#EAF1FB", "pale_green": "#ECF6EF", "pale_orange": "#FBF0E9", "pale_red": "#F9EDEE", } def apply_style() -> None: """Apply compact, print-friendly defaults for ACM two-column figures.""" mpl.rcParams.update( { "font.family": "DejaVu Sans", "font.size": LABEL_SIZE, "axes.titlesize": SUBTITLE_SIZE, "axes.labelsize": LABEL_SIZE, "xtick.labelsize": TICK_SIZE, "ytick.labelsize": TICK_SIZE, "legend.fontsize": LEGEND_SIZE, "axes.linewidth": AXIS_LINEWIDTH, "axes.spines.top": False, "axes.spines.right": False, "axes.grid": True, "grid.color": "#D8D8D8", "grid.alpha": 0.55, "grid.linewidth": GRID_LINEWIDTH, "lines.linewidth": LINEWIDTH, "lines.markersize": MARKER_SIZE, "figure.dpi": 150, "savefig.dpi": 300, "savefig.bbox": "tight", "savefig.pad_inches": 0.03, "pdf.fonttype": 42, "ps.fonttype": 42, "svg.fonttype": "none", "legend.frameon": False, } ) def output_dirs(root: Path) -> dict[str, Path]: base = root / "figures_v2" dirs = {} for name in ("pdf", "png", "svg", "data"): p = base / name p.mkdir(parents=True, exist_ok=True) dirs[name] = p return dirs def save_all(fig: plt.Figure, name: str, dirs: dict[str, Path]) -> list[str]: """Save PDF/SVG as vector output and PNG as a 300 dpi preview.""" if not fig.get_constrained_layout(): fig.tight_layout() outputs = [] for ext in ("pdf", "png", "svg"): path = dirs[ext] / f"{name}.{ext}" if ext == "png": fig.savefig(path, dpi=300, bbox_inches="tight", pad_inches=0.03) else: fig.savefig(path, bbox_inches="tight", pad_inches=0.03) outputs.append(str(path)) plt.close(fig) return outputs def panel_label(ax, label: str) -> None: ax.text( -0.10, 1.04, label, transform=ax.transAxes, fontsize=PANEL_LABEL_SIZE, fontweight="bold", va="bottom", ha="left", color=COLORS["dark"], )