1

87 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Visualisation des pièces les plus rares observées dans les sets filtrés."""
import csv
from pathlib import Path
from typing import List
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnnotationBbox, OffsetImage
from PIL import Image
from lib.filesystem import ensure_parent_dir
def load_part_rarity(path: Path) -> List[dict]:
"""Charge le CSV des pièces rares."""
rows: List[dict] = []
with path.open() as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
rows.append(row)
return rows
def format_label(row: dict) -> str:
"""Formate létiquette de laxe vertical."""
return f"{row['part_num']}{row['part_name']}"
def load_part_image(row: dict, resources_dir: Path) -> Image.Image | None:
"""Charge l'image associée à une pièce si elle est disponible."""
path = resources_dir / row["sample_set_id"] / "rare_parts" / f"{row['part_num']}.jpg"
if not path.exists():
return None
return Image.open(path)
def plot_part_rarity(
path: Path,
destination_path: Path,
resources_dir: Path = Path("figures/rebrickable"),
show_images: bool = True,
) -> None:
"""Trace un bar chart horizontal des pièces les plus rares avec leurs visuels."""
rows = load_part_rarity(path)
selected = rows
labels = [format_label(row) for row in selected]
filtered_counts = [int(row["filtered_quantity"]) for row in selected]
other_counts = [int(row["other_sets_quantity"]) for row in selected]
positions = list(range(len(selected)))
fig, ax = plt.subplots(figsize=(13, 0.55 * len(selected) + 1.4))
ax.barh(positions, filtered_counts, color="#1f78b4", label="Sets filtrés")
ax.barh(positions, other_counts, left=filtered_counts, color="#b2df8a", label="Autres sets")
ax.set_yticks(positions)
ax.set_yticklabels(labels)
ax.set_xlabel("Occurrences de la pièce (rechanges incluses)")
ax.grid(axis="x", linestyle="--", alpha=0.4)
ax.legend()
if show_images:
max_count = max((f + o) for f, o in zip(filtered_counts, other_counts)) if selected else 0
pad = max_count * 0.15 if max_count > 0 else 1.0
ax.set_xlim(left=-pad, right=max_count + pad * 0.3)
for row, pos in zip(selected, positions):
image = load_part_image(row, resources_dir)
if image is None:
continue
target_height = 28
ratio = target_height / image.height
resized = image.resize((int(image.width * ratio), target_height))
imagebox = OffsetImage(resized)
ab = AnnotationBbox(
imagebox,
(-pad * 0.45, pos),
xycoords=("data", "data"),
box_alignment=(0.5, 0.5),
frameon=False,
)
ax.add_artist(ab)
fig.subplots_adjust(left=0.42)
fig.tight_layout()
ensure_parent_dir(destination_path)
fig.savefig(destination_path, dpi=150)
plt.close(fig)