"""Tracés dédiés aux analyses du vent (roses et vecteurs agrégés).""" from __future__ import annotations from pathlib import Path from typing import Sequence import matplotlib.dates as mdates import matplotlib.pyplot as plt from matplotlib.ticker import FuncFormatter import numpy as np import pandas as pd from .base import export_plot_dataset __all__ = ['plot_wind_rose', 'plot_wind_vector_series'] def plot_wind_rose( frequencies: pd.DataFrame, speed_bin_labels: Sequence[str], output_path: str | Path, *, sector_size_deg: float, cmap: str = "viridis", ) -> Path: """ Trace une rose des vents empilée par classes de vitesses (en % du temps). """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) if frequencies.empty: fig, ax = plt.subplots(subplot_kw={"projection": "polar"}) ax.text(0.5, 0.5, "Données de vent insuffisantes.", ha="center", va="center") ax.set_axis_off() fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) return output_path.resolve() dataset = frequencies.copy() dataset.insert(0, "sector_start_deg", frequencies.index) dataset.insert(1, "sector_center_deg", frequencies.index + sector_size_deg / 2.0) export_plot_dataset(dataset, output_path) fig, ax = plt.subplots(subplot_kw={"projection": "polar"}, figsize=(6, 6)) cmap_obj = plt.get_cmap(cmap, len(speed_bin_labels)) colors = cmap_obj(np.linspace(0.2, 0.95, len(speed_bin_labels))) angles = np.deg2rad(frequencies.index.to_numpy(dtype=float) + sector_size_deg / 2.0) width = np.deg2rad(sector_size_deg) bottom = np.zeros_like(angles, dtype=float) for label, color in zip(speed_bin_labels, colors): values = frequencies[label].to_numpy(dtype=float) bars = ax.bar( angles, values, width=width, bottom=bottom, color=color, edgecolor="white", linewidth=0.5, align="center", ) bottom += values ax.set_theta_zero_location("N") ax.set_theta_direction(-1) ax.set_xticks(np.deg2rad(np.arange(0, 360, 45))) ax.set_xticklabels(["N", "NE", "E", "SE", "S", "SO", "O", "NO"]) max_radius = np.max(bottom) ax.set_ylim(0, max(max_radius * 1.1, 1)) ax.yaxis.set_major_formatter(FuncFormatter(lambda val, _pos: f"{val:.0f}%")) ax.set_title("Rose des vents (fréquence en %)") legend_handles = [ plt.Line2D([0], [0], color=color, linewidth=6, label=label) for label, color in zip(speed_bin_labels, colors) ] ax.legend( handles=legend_handles, loc="lower center", bbox_to_anchor=(0.5, -0.15), ncol=2, title="Vitesses (km/h)", ) fig.tight_layout() fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) return output_path.resolve() def plot_wind_vector_series( vector_df: pd.DataFrame, output_path: str | Path, *, title: str = "Vecteurs moyens du vent", ) -> Path: """ Représente les composantes moyennes du vent sous forme de flèches (u/v). """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) if vector_df.empty: fig, ax = plt.subplots() ax.text(0.5, 0.5, "Pas de données de vent.", 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(vector_df, output_path) times = vector_df.index x = mdates.date2num(times) u = vector_df["u"].to_numpy(dtype=float) v = vector_df["v"].to_numpy(dtype=float) speed = vector_df["speed"].to_numpy(dtype=float) fig, ax = plt.subplots(figsize=(12, 4)) q = ax.quiver( x, np.zeros_like(x), u, v, speed, angles="xy", scale_units="xy", scale=1, cmap="viridis", ) ax.axhline(0, color="black", linewidth=0.5) ax.set_ylim(-max(abs(v)) * 1.2 if np.any(v) else -1, max(abs(v)) * 1.2 if np.any(v) else 1) ax.xaxis.set_major_locator(mdates.AutoDateLocator()) ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())) ax.set_ylabel("Composante nord (v)") ax.set_xlabel("Date") ax.set_title(title) cbar = fig.colorbar(q, ax=ax) cbar.set_label("Vitesse moyenne (km/h)") fig.tight_layout() fig.savefig(output_path, dpi=150) plt.close(fig) return output_path.resolve()