1

Ajoute les visualisations temporelles des palettes

This commit is contained in:
2025-12-01 22:44:05 +01:00
parent 19508a3c0f
commit dfd39d358a
6 changed files with 163 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
"""Visualisations temporelles des palettes de couleurs."""
from pathlib import Path
from typing import Dict, Iterable, List, Tuple
import matplotlib.pyplot as plt
import numpy as np
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.stats import read_rows
def load_rows(path: Path) -> List[dict]:
"""Charge un CSV simple en mémoire sous forme de dictionnaires."""
return read_rows(path)
def plot_translucent_share(timeline_path: Path, destination_path: Path) -> None:
"""Trace l'évolution de la part de pièces translucides et du nombre de couleurs."""
rows = load_rows(timeline_path)
years = [int(row["year"]) for row in rows]
shares = [float(row["share_translucent"]) for row in rows]
distinct_counts = [int(row["colors_distinct"]) for row in rows]
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(years, shares, color="#1f77b4", marker="o", linewidth=2.2, label="Part des translucides")
ax.fill_between(years, shares, color="#1f77b4", alpha=0.15)
ax.set_ylabel("Part translucide")
ax.set_ylim(0, min(1.0, max(shares) * 1.1))
ax.set_xlabel("Année")
ax.grid(True, linestyle="--", alpha=0.3)
ax2 = ax.twinx()
ax2.plot(years, distinct_counts, color="#ff7f0e", marker="s", linewidth=1.8, label="Couleurs distinctes")
ax2.set_ylabel("Nombre de couleurs distinctes")
handles = [
plt.Line2D([0], [0], color="#1f77b4", marker="o", label="Part des translucides"),
plt.Line2D([0], [0], color="#ff7f0e", marker="s", label="Couleurs distinctes"),
]
ax.legend(handles=handles, loc="upper left")
ax.set_title("Evolution des palettes : translucides vs. diversité des couleurs")
ax.set_xticks(years)
ax.tick_params(axis="x", labelrotation=45)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=160)
plt.close(fig)
def build_heatmap_data(rows: Iterable[dict]) -> Tuple[List[int], List[str], np.ndarray]:
"""Construit les vecteurs année, labels de couleur et matrice de quantités."""
years = sorted({int(row["year"]) for row in rows})
color_totals: Dict[Tuple[str, str, str], int] = {}
for row in rows:
key = (row["color_name"], row["color_rgb"], row["is_translucent"])
color_totals[key] = color_totals.get(key, 0) + int(row["quantity_total"])
sorted_colors = sorted(
color_totals.items(),
key=lambda item: (-item[1], item[0][0], item[0][1]),
)
color_keys = [color for color, _ in sorted_colors]
labels = [f"{name} ({'trans' if is_trans == 'true' else 'opaque'})" for name, _, is_trans in color_keys]
matrix = np.zeros((len(color_keys), len(years)), dtype=float)
index_by_year = {year: idx for idx, year in enumerate(years)}
index_by_color = {color: idx for idx, color in enumerate(color_keys)}
for row in rows:
color_key = (row["color_name"], row["color_rgb"], row["is_translucent"])
y_index = index_by_color[color_key]
x_index = index_by_year[int(row["year"])]
matrix[y_index, x_index] += int(row["quantity_total"])
return years, labels, matrix
def plot_colors_heatmap(matrix_path: Path, destination_path: Path) -> None:
"""Génère une heatmap année × couleur basée sur les quantités totales."""
rows = load_rows(matrix_path)
years, labels, matrix = build_heatmap_data(rows)
values = np.log1p(matrix)
fig, ax = plt.subplots(figsize=(14, max(6, len(labels) * 0.24)))
heatmap = ax.imshow(values, aspect="auto", cmap="magma", origin="lower")
ax.set_xticks(range(len(years)))
ax.set_xticklabels(years, rotation=45)
ax.set_yticks(range(len(labels)))
ax.set_yticklabels(labels)
ax.set_xlabel("Année")
ax.set_title("Intensité des couleurs par année (log1p des quantités)")
cbar = fig.colorbar(heatmap, ax=ax, shrink=0.82)
cbar.set_label("log1p(quantité totale)")
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)