87 lines
3.4 KiB
Python
87 lines
3.4 KiB
Python
"""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)
|