"""Shared paths, plotting style, and helpers for the SPID research project.""" from __future__ import annotations import json from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib as mpl ROOT = Path(__file__).resolve().parent.parent # research/ DB = ROOT.parent / "data" / "db" / "spid.duckdb" # project-db/data/db/spid.duckdb DATA = ROOT / "data" FIG = ROOT / "figures" TAB = ROOT / "tables" RES = ROOT / "results" for d in (DATA, FIG, TAB, RES): d.mkdir(parents=True, exist_ok=True) # ---- publication plotting style ------------------------------------------- PALETTE = ["#1b3a5b", "#c44e52", "#2e8b57", "#dd8452", "#8172b3", "#937860", "#da8bc3", "#8c8c8c", "#ccb974", "#64b5cd"] def set_style() -> None: mpl.rcParams.update({ "figure.dpi": 130, "savefig.dpi": 200, "font.size": 11, "font.family": "serif", "axes.titlesize": 12, "axes.titleweight": "bold", "axes.labelsize": 11, "axes.spines.top": False, "axes.spines.right": False, "axes.grid": True, "grid.alpha": 0.25, "grid.linewidth": 0.6, "legend.frameon": False, "axes.prop_cycle": mpl.cycler(color=PALETTE), }) def savefig(fig, name: str) -> Path: p = FIG / name fig.tight_layout() fig.savefig(p, bbox_inches="tight") fig.savefig(p.with_suffix(".png"), bbox_inches="tight") plt.close(fig) print(f" [fig] {p.name}") return p def con(read_only: bool = True): import duckdb return duckdb.connect(str(DB), read_only=read_only) def save_results(name: str, obj: dict) -> None: p = RES / f"{name}.json" p.write_text(json.dumps(obj, indent=2, default=str)) print(f" [res] {p.name}") def write_table(name: str, latex: str) -> None: p = TAB / f"{name}.tex" p.write_text(latex) print(f" [tab] {p.name}") def esc(s) -> str: """Escape a string for LaTeX.""" s = str(s) for a, b in [("\\", r"\textbackslash{}"), ("&", r"\&"), ("%", r"\%"), ("$", r"\$"), ("#", r"\#"), ("_", r"\_"), ("{", r"\{"), ("}", r"\}"), ("~", r"\textasciitilde{}"), ("^", r"\textasciicircum{}")]: s = s.replace(a, b) return s