"""Profils horaires/saisonniers liés à l'irradiance et aux cycles diurnes.""" from __future__ import annotations from pathlib import Path from typing import Sequence import matplotlib.dates as mdates import matplotlib.pyplot as plt import numpy as np import pandas as pd from .base import export_plot_dataset from meteo.analysis import DiurnalCycleStats from meteo.variables import Variable __all__ = ['plot_diurnal_cycle', 'plot_seasonal_hourly_profiles', 'plot_daylight_hours'] def plot_diurnal_cycle( stats: DiurnalCycleStats, variables: Sequence[Variable], output_path: str | Path, ) -> Path: """ Trace les cycles diurnes moyens (moyenne/médiane + quantiles). """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) export_plot_dataset( { "mean": stats.mean, "median": stats.median, "quantile_low": stats.quantile_low, "quantile_high": stats.quantile_high, }, output_path, ) hours = stats.mean.index.to_numpy(dtype=float) n_vars = len(variables) fig, axes = plt.subplots(n_vars, 1, figsize=(10, 3 * n_vars), sharex=True) if n_vars == 1: axes = [axes] for ax, var in zip(axes, variables): col = var.column ax.plot(hours, stats.mean[col], label="Moyenne", color="tab:blue") ax.plot(hours, stats.median[col], label="Médiane", color="tab:orange", linestyle="--") if stats.quantile_low is not None and stats.quantile_high is not None: ax.fill_between( hours, stats.quantile_low[col], stats.quantile_high[col], color="tab:blue", alpha=0.15, label=( f"Quantiles {int(stats.quantile_low_level * 100)}–{int(stats.quantile_high_level * 100)}%" if stats.quantile_low_level is not None and stats.quantile_high_level is not None else "Quantiles" ), ) ylabel = f"{var.label} ({var.unit})" if var.unit else var.label ax.set_ylabel(ylabel) ax.grid(True, linestyle=":", alpha=0.5) axes[-1].set_xlabel("Heure locale") axes[0].legend(loc="upper right") axes[-1].set_xticks(range(0, 24, 2)) axes[-1].set_xlim(0, 23) fig.suptitle("Cycle diurne moyen") fig.tight_layout(rect=[0, 0, 1, 0.97]) fig.savefig(output_path, dpi=150) plt.close(fig) return output_path.resolve() def plot_seasonal_hourly_profiles( profile_df: pd.DataFrame, output_path: str | Path, *, title: str, ylabel: str, ) -> Path: """ Courbes moyennes par heure pour chaque saison. """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) if profile_df.empty or profile_df.isna().all().all(): fig, ax = plt.subplots() ax.text(0.5, 0.5, "Pas de profil saisonnier disponible.", ha="center", va="center") ax.set_axis_off() fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) return output_path.resolve() export_plot_dataset(profile_df, output_path) hours = profile_df.index.to_numpy(dtype=float) fig, ax = plt.subplots(figsize=(10, 4)) colors = plt.get_cmap("turbo")(np.linspace(0.1, 0.9, profile_df.shape[1])) for color, season in zip(colors, profile_df.columns): ax.plot(hours, profile_df[season], label=season.capitalize(), color=color) ax.set_xlabel("Heure locale") ax.set_ylabel(ylabel) ax.set_title(title) ax.grid(True, linestyle=":", alpha=0.5) ax.legend() fig.tight_layout() fig.savefig(output_path, dpi=150) plt.close(fig) return output_path.resolve() def plot_daylight_hours( monthly_series: pd.Series, output_path: str | Path, *, title: str = "Durée moyenne de luminosité (> seuil)", ) -> Path: """ Représente la durée moyenne quotidienne de luminosité par mois. """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) if monthly_series.empty: fig, ax = plt.subplots() ax.text(0.5, 0.5, "Pas de données sur la luminosité.", ha="center", va="center") ax.set_axis_off() fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) return output_path.resolve() export_plot_dataset(monthly_series, output_path) months = monthly_series.index fig, ax = plt.subplots(figsize=(10, 4)) ax.bar(months, monthly_series.values, color="goldenrod", alpha=0.8) ax.set_ylabel("Heures de luminosité par jour") ax.set_xlabel("Mois") ax.xaxis.set_major_locator(mdates.AutoDateLocator()) ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())) ax.set_title(title) ax.grid(True, axis="y", linestyle=":", alpha=0.5) fig.tight_layout() fig.savefig(output_path, dpi=150) plt.close(fig) return output_path.resolve()