File size: 2,940 Bytes
f28d994
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""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"],
    )