1
etude_lego_jurassic_world/lib/plots/global_minifig_heads.py

91 lines
3.6 KiB
Python

"""Visualisation des couleurs de têtes de minifigs sur le catalogue complet."""
from pathlib import Path
from typing import Dict, Iterable, List, Tuple
import matplotlib.pyplot as plt
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.stats import read_rows
def load_global_heads(heads_path: Path) -> List[dict]:
"""Charge l'agrégat global des têtes par année."""
return read_rows(heads_path)
def select_top_colors(rows: Iterable[dict], limit: int = 12) -> List[Tuple[str, str, str]]:
"""Retourne les couleurs les plus fréquentes globalement (nom, rgb, is_translucent)."""
totals: Dict[Tuple[str, str, str], int] = {}
for row in rows:
key = (row["color_name"], row["color_rgb"], row["is_translucent"])
totals[key] = totals.get(key, 0) + int(row["quantity"])
sorted_colors = sorted(totals.items(), key=lambda item: (-item[1], item[0][0], item[0][1]))
return [color for color, _ in sorted_colors[:limit]]
def build_share_matrix(
rows: Iterable[dict], top_colors: List[Tuple[str, str, str]]
) -> Tuple[List[int], List[Tuple[str, str, str]], List[Dict[str, float]]]:
"""Construit les parts par année en regroupant les couleurs hors top dans 'Autres'."""
years = sorted({int(row["year"]) for row in rows})
colors = top_colors + [("Autres", "444444", "false")]
shares_by_year: List[Dict[str, float]] = []
rows_by_year: Dict[int, List[dict]] = {year: [] for year in years}
for row in rows:
rows_by_year[int(row["year"])].append(row)
for year in years:
year_rows = rows_by_year[year]
total = sum(int(r["quantity"]) for r in year_rows)
shares: Dict[str, float] = {color[0]: 0.0 for color in colors}
for r in year_rows:
key = (r["color_name"], r["color_rgb"], r["is_translucent"])
quantity = int(r["quantity"])
target = "Autres" if key not in top_colors else r["color_name"]
shares[target] = shares.get(target, 0.0) + quantity / total if total > 0 else 0.0
shares_by_year.append(shares)
return years, colors, shares_by_year
def plot_global_head_shares(
heads_path: Path,
destination_path: Path,
top_limit: int = 12,
) -> None:
"""Trace les parts des couleurs de têtes de minifigs par année (catalogue complet)."""
rows = load_global_heads(heads_path)
top_colors = select_top_colors(rows, limit=top_limit)
years, colors, shares_by_year = build_share_matrix(rows, top_colors)
fig, ax = plt.subplots(figsize=(14, 6))
bottoms = [0.0] * len(years)
y_positions = list(range(len(years)))
for name, color_rgb, is_trans in colors:
values = [shares[name] for shares in shares_by_year]
edge = "#f2f2f2" if is_trans == "true" else "#0d0d0d"
ax.bar(
years,
values,
bottom=bottoms,
color=f"#{color_rgb}",
edgecolor=edge,
label=name,
linewidth=0.7,
)
bottoms = [b + v for b, v in zip(bottoms, values)]
ax.set_ylim(0, 1.05)
ax.set_ylabel("Part des couleurs (têtes de minifigs, catalogue complet)")
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("Répartition des couleurs de têtes de minifigs par année (catalogue complet)")
ax.legend(loc="upper left", bbox_to_anchor=(1.02, 1), frameon=False)
ax.grid(True, axis="y", linestyle="--", alpha=0.25)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)