1
etude_lego_jurassic_world/lib/plots/minifig_skin_tones.py

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)