| """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"], |
| ) |
|
|