feat(utils): add high-resolution chart export utilities

This commit is contained in:
Orchestrator
2026-06-04 17:11:10 -05:00
committed by marsultor
parent 6fcf1bdb02
commit 58e7aec449

102
src/utils/export.py Normal file
View File

@@ -0,0 +1,102 @@
"""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: str = "default",
) -> 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
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."""
from PIL import Image # Try PIL first, fallback below
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),
}