"""High-resolution chart export utilities.""" import os from pathlib import Path from typing import Optional import matplotlib.figure from src.utils.styling import EXPORT_DPI def ensure_output_dir(path: str) -> Path: """Ensure output directory exists and return Path.""" p = Path(path) p.mkdir(parents=True, exist_ok=True) return p def save_chart( fig: matplotlib.figure.Figure, filename: str, output_dir: str = "output/charts", dpi: int = EXPORT_DPI, bbox_inches: Optional[str] = None, ) -> str: """Save a matplotlib figure as high-resolution PNG. Args: fig: matplotlib Figure to save filename: Output filename (e.g., '01_shiller_cape.png') output_dir: Base output directory dpi: Resolution (default 300) bbox_inches: Bbox mode for tight layout (default None, matplotlib auto) Returns: Full path to saved file """ output_path = ensure_output_dir(output_dir) / filename fig.savefig( str(output_path), dpi=dpi, bbox_inches=bbox_inches, facecolor=fig.get_facecolor(), edgecolor="none", ) return str(output_path) def save_chart_tight( fig: matplotlib.figure.Figure, filename: str, output_dir: str = "output/charts", dpi: int = EXPORT_DPI, ) -> str: """Save chart with tight layout to prevent label clipping.""" return save_chart(fig, filename, output_dir, dpi, bbox_inches="tight") def save_combined_chart( fig: matplotlib.figure.Figure, filename: str, dpi: int = EXPORT_DPI, ) -> str: """Save a combined/multi-panel dashboard chart.""" return save_chart(fig, filename, "output/combined", dpi, bbox_inches="tight") def list_output_charts(output_dir: str = "output/charts") -> list[str]: """List all PNG files in output directory.""" p = Path(output_dir) if not p.exists(): return [] return sorted([f.name for f in p.glob("*.png")]) def get_chart_metadata(filepath: str) -> dict: """Get basic metadata about a saved chart file.""" p = Path(filepath) if not p.exists(): return {"exists": False} stat = p.stat() size_mb = stat.st_size / (1024 * 1024) # Try to get DPI from PNG try: # Use matplotlib to read back (works without PIL) import matplotlib.image as mpimg img = mpimg.imread(str(p)) return { "exists": True, "size_mb": round(size_mb, 2), "shape": img.shape if hasattr(img, "shape") else "unknown", "path": str(p), } except Exception: return { "exists": True, "size_mb": round(size_mb, 2), "path": str(p), }