1

Ajoute la richesse chromatique par set

This commit is contained in:
2025-12-02 16:59:59 +01:00
parent 231a9af28d
commit c2bf12e3fe
12 changed files with 592 additions and 11 deletions

130
lib/plots/color_richness.py Normal file
View File

@@ -0,0 +1,130 @@
"""Visualisations de la richesse chromatique par set."""
from pathlib import Path
from typing import Iterable, List, Tuple
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.stats import read_rows
def load_richness_rows(path: Path) -> List[dict]:
"""Charge les métriques de richesse chromatique."""
return read_rows(path)
def build_boxplot_data(rows: Iterable[dict]) -> Tuple[List[List[int]], List[str]]:
"""Prépare les valeurs de boxplot par année."""
grouped: dict[str, List[int]] = {}
for row in rows:
year_rows = grouped.get(row["year"])
if year_rows is None:
year_rows = []
grouped[row["year"]] = year_rows
year_rows.append(int(row["colors_distinct"]))
years = sorted(grouped.keys(), key=int)
data = [grouped[year] for year in years]
return data, years
def plot_richness_boxplot(richness_path: Path, destination_path: Path) -> None:
"""Trace le boxplot du nombre de couleurs distinctes par set et par année."""
rows = load_richness_rows(richness_path)
if not rows:
return
data, years = build_boxplot_data(rows)
fig, ax = plt.subplots(figsize=(12, 7))
box = ax.boxplot(
data,
orientation="vertical",
patch_artist=True,
tick_labels=years,
boxprops=dict(facecolor="#1f77b4", alpha=0.3),
medianprops=dict(color="#0d0d0d", linewidth=1.5),
whiskerprops=dict(color="#555555", linestyle="--"),
capprops=dict(color="#555555"),
)
for patch in box["boxes"]:
patch.set_edgecolor("#1f77b4")
ax.set_xlabel("Année")
ax.set_ylabel("Nombre de couleurs distinctes (hors rechanges)")
ax.set_title("Richesse chromatique par set (répartition annuelle)")
ax.grid(axis="y", linestyle="--", alpha=0.3)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)
def select_top_sets(rows: Iterable[dict], limit: int = 15) -> List[dict]:
"""Retient les sets les plus colorés et les plus concentrés."""
sorted_rows = sorted(
rows,
key=lambda row: (-int(row["colors_distinct"]), float(row["top3_share"]), row["set_num"]),
)
return sorted_rows[:limit]
def plot_richness_top_sets(richness_path: Path, destination_path: Path) -> None:
"""Trace le top des sets les plus riches en couleurs."""
rows = load_richness_rows(richness_path)
if not rows:
return
top_rows = select_top_sets(rows)
y_positions = np.arange(len(top_rows))
counts = [int(row["colors_distinct"]) for row in top_rows]
labels = [f"{row['set_num']} · {row['name']} ({row['year']})" for row in top_rows]
owned_mask = [row["in_collection"] == "true" for row in top_rows]
fig, ax = plt.subplots(figsize=(11, 8))
for y, value, owned in zip(y_positions, counts, owned_mask):
alpha = 0.92 if owned else 0.45
ax.barh(y, value, color="#2ca02c", alpha=alpha)
ax.set_yticks(y_positions)
ax.set_yticklabels(labels)
ax.invert_yaxis()
ax.set_xlabel("Couleurs distinctes (hors rechanges)")
ax.set_title("Top des sets les plus colorés")
ax.grid(axis="x", linestyle="--", alpha=0.3)
legend = [
Patch(facecolor="#2ca02c", edgecolor="none", alpha=0.92, label="Set possédé"),
Patch(facecolor="#2ca02c", edgecolor="none", alpha=0.45, label="Set manquant"),
]
ax.legend(handles=legend, loc="lower right", frameon=False)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)
def plot_concentration_scatter(richness_path: Path, destination_path: Path) -> None:
"""Visualise la concentration de palette vs nombre de couleurs."""
rows = load_richness_rows(richness_path)
if not rows:
return
x_values = [int(row["colors_distinct"]) for row in rows]
y_values = [float(row["top3_share"]) for row in rows]
owned_mask = [row["in_collection"] == "true" for row in rows]
colors = ["#1f77b4" if owned else "#bbbbbb" for owned in owned_mask]
fig, ax = plt.subplots(figsize=(10, 7))
ax.scatter(x_values, y_values, c=colors, alpha=0.7, s=32)
ax.set_xlabel("Nombre de couleurs distinctes (hors rechanges)")
ax.set_ylabel("Part des 3 couleurs principales")
ax.set_title("Concentration des palettes")
ax.grid(True, linestyle="--", alpha=0.3)
legend = [
Patch(facecolor="#1f77b4", edgecolor="none", alpha=0.7, label="Set possédé"),
Patch(facecolor="#bbbbbb", edgecolor="none", alpha=0.7, label="Set manquant"),
]
ax.legend(handles=legend, loc="upper right", frameon=False)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)