"""Visualisation de la part des têtes jaunes sur le catalogue global.""" from pathlib import Path from typing import Dict, List import matplotlib.pyplot as plt from lib.filesystem import ensure_parent_dir from lib.milestones import load_milestones from lib.rebrickable.stats import read_rows def compute_yellow_share(rows: List[dict]) -> List[dict]: """Calcule la part de la couleur Yellow par année.""" aggregated: Dict[str, Dict[str, int]] = {} for row in rows: year = row["year"] if year not in aggregated: aggregated[year] = {"yellow": 0, "total": 0} aggregated[year]["total"] += int(row["quantity"]) if row["color_name"].lower() == "yellow" or row["color_rgb"].upper() == "FFFF00": aggregated[year]["yellow"] += int(row["quantity"]) results = [] for year in sorted(aggregated.keys(), key=int): total = aggregated[year]["total"] yellow = aggregated[year]["yellow"] share = yellow / total if total > 0 else 0 results.append({"year": int(year), "yellow_share": share, "total": total}) return results def plot_yellow_share(heads_path: Path, milestones_path: Path, destination_path: Path) -> None: """Trace l'évolution de la part de têtes jaunes dans le catalogue complet.""" rows = read_rows(heads_path) milestones = load_milestones(milestones_path) series = compute_yellow_share(rows) years = [item["year"] for item in series] shares = [item["yellow_share"] for item in series] fig, ax = plt.subplots(figsize=(13, 5.5)) ax.plot(years, shares, color="#f2c300", marker="o", linewidth=2.4, label="Part Yellow") ax.fill_between(years, shares, color="#f2c300", alpha=0.18) ax.set_ylim(0, min(1.0, max(shares + [0.01]) * 1.1)) ax.set_ylabel("Part de têtes Yellow") ax.set_xlabel("Année") if len(years) > 15: step = max(1, len(years) // 10) ax.set_xticks(years[::step]) else: ax.set_xticks(years) ax.set_title("Evolution de l'usage des têtes Yellow (catalogue complet)") ax.grid(True, linestyle="--", alpha=0.3) if milestones: min_year = min(years) max_year = max(years) milestones_in_range = sorted( [m for m in milestones if min_year <= m["year"] <= max_year], key=lambda m: (m["year"], m["description"]), ) offset_map: Dict[int, int] = {} offset_step = 0.35 top_limit = ax.get_ylim()[1] * 1.05 for milestone in milestones_in_range: year = milestone["year"] count_for_year = offset_map.get(year, 0) offset_map[year] = count_for_year + 1 horizontal_offset = offset_step * (count_for_year // 2 + 1) if count_for_year % 2 == 1: horizontal_offset *= -1 text_x = year + horizontal_offset ax.axvline(year, color="#d62728", linestyle="--", linewidth=1, alpha=0.65, zorder=1) ax.text( text_x, top_limit, milestone["description"], rotation=90, verticalalignment="top", horizontalalignment="center", fontsize=8, color="#d62728", ) ax.set_ylim(ax.get_ylim()[0], top_limit * (1 + max(offset_map.values(), default=0) * 0.02)) ensure_parent_dir(destination_path) fig.tight_layout() fig.savefig(destination_path, dpi=170) plt.close(fig)