You've already forked etude_lego_jurassic_world
Ajoute l'analyse des pièces rares
This commit is contained in:
77
lib/plots/rare_parts.py
Normal file
77
lib/plots/rare_parts.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""Graphique des pièces rares par set."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Patch
|
||||
|
||||
from lib.filesystem import ensure_parent_dir
|
||||
from lib.rebrickable.stats import read_rows
|
||||
|
||||
|
||||
def load_top_sets(path: Path, limit: int = 15) -> List[dict]:
|
||||
"""Charge les sets triés par nombre de pièces rares et limite le top."""
|
||||
rows = read_rows(path)
|
||||
sorted_rows = sorted(
|
||||
rows,
|
||||
key=lambda row: (
|
||||
-int(row["rare_parts_distinct"]),
|
||||
-int(row["rare_parts_quantity"]),
|
||||
row["set_num"],
|
||||
),
|
||||
)
|
||||
return sorted_rows[:limit]
|
||||
|
||||
|
||||
def split_counts(rows: List[dict]) -> Tuple[List[int], List[int]]:
|
||||
"""Sépare les comptages minifig vs hors minifig."""
|
||||
non_minifig: List[int] = []
|
||||
minifig: List[int] = []
|
||||
for row in rows:
|
||||
total = int(row["rare_parts_distinct"])
|
||||
minifig_count = int(row["rare_minifig_parts_distinct"])
|
||||
non_minifig.append(total - minifig_count)
|
||||
minifig.append(minifig_count)
|
||||
return non_minifig, minifig
|
||||
|
||||
|
||||
def plot_rare_parts_per_set(rare_by_set_path: Path, destination_path: Path) -> None:
|
||||
"""Trace le top des sets contenant des pièces exclusives."""
|
||||
rows = load_top_sets(rare_by_set_path)
|
||||
if not rows:
|
||||
return
|
||||
non_minifig, minifig = split_counts(rows)
|
||||
y_positions = list(range(len(rows)))
|
||||
labels = [f"{row['set_num']} · {row['name']} ({row['year']})" for row in rows]
|
||||
owned_mask = [row["in_collection"] == "true" for row in rows]
|
||||
|
||||
base_color = "#1f77b4"
|
||||
accent_color = "#f28e2b"
|
||||
fig, ax = plt.subplots(figsize=(11, 8))
|
||||
for y, value, is_owned in zip(y_positions, non_minifig, owned_mask):
|
||||
alpha = 0.92 if is_owned else 0.45
|
||||
ax.barh(y, value, color=base_color, alpha=alpha, label=None)
|
||||
for y, value, offset, is_owned in zip(y_positions, minifig, non_minifig, owned_mask):
|
||||
alpha = 0.92 if is_owned else 0.45
|
||||
ax.barh(y, value, left=offset, color=accent_color, alpha=alpha, label=None)
|
||||
|
||||
ax.set_yticks(y_positions)
|
||||
ax.set_yticklabels(labels)
|
||||
ax.invert_yaxis()
|
||||
ax.set_xlabel("Variantes de pièces exclusives (hors rechanges)")
|
||||
ax.set_title("Pièces rares par set (top)")
|
||||
ax.grid(axis="x", linestyle="--", alpha=0.35)
|
||||
|
||||
handles = [
|
||||
Patch(facecolor=base_color, edgecolor="none", label="Pièces hors minifigs"),
|
||||
Patch(facecolor=accent_color, edgecolor="none", label="Pièces de minifigs"),
|
||||
Patch(facecolor="#000000", edgecolor="none", alpha=0.92, label="Set possédé"),
|
||||
Patch(facecolor="#000000", edgecolor="none", alpha=0.45, label="Set manquant"),
|
||||
]
|
||||
ax.legend(handles=handles, loc="lower right", frameon=False)
|
||||
|
||||
ensure_parent_dir(destination_path)
|
||||
fig.tight_layout()
|
||||
fig.savefig(destination_path, dpi=170)
|
||||
plt.close(fig)
|
||||
Reference in New Issue
Block a user