From 7fe02ea2636058c702a2cdd502ecd1772513795f Mon Sep 17 00:00:00 2001 From: Richard Dern Date: Mon, 1 Dec 2025 23:18:02 +0100 Subject: [PATCH] =?UTF-8?q?Ajoute=20jalons=20et=20variantes=20de=20heatmap?= =?UTF-8?q?s=20normalis=C3=A9es=20=C3=A0=20l'=C3=A9tape=2015?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/plots/colors_timeline.py | 47 +++++++++++++++++++++++++----- scripts/plot_colors_timeline.py | 3 +- tests/test_colors_timeline_plot.py | 6 ++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/lib/plots/colors_timeline.py b/lib/plots/colors_timeline.py index 67a4ea9..a339ddd 100644 --- a/lib/plots/colors_timeline.py +++ b/lib/plots/colors_timeline.py @@ -7,6 +7,7 @@ import matplotlib.pyplot as plt import numpy as np from lib.filesystem import ensure_parent_dir +from lib.milestones import load_milestones from lib.rebrickable.stats import read_rows @@ -15,31 +16,63 @@ def load_rows(path: Path) -> List[dict]: 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.""" +def plot_translucent_share(timeline_path: Path, milestones_path: Path, destination_path: Path) -> None: + """Trace l'évolution de la part de pièces translucides et du nombre de couleurs avec jalons.""" rows = load_rows(timeline_path) + milestones = load_milestones(milestones_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) + fig, ax = plt.subplots(figsize=(13, 5.5)) + ax.plot(years, shares, color="#1f77b4", marker="o", linewidth=2.2, label="Part des translucides", zorder=3) + ax.fill_between(years, shares, color="#1f77b4", alpha=0.15, zorder=2) 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.plot(years, distinct_counts, color="#ff7f0e", marker="s", linewidth=1.8, label="Couleurs distinctes", zorder=3) 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.legend(handles=handles, loc="upper left", bbox_to_anchor=(1.02, 1)) ax.set_title("Evolution des palettes : translucides vs. diversité des couleurs") ax.set_xticks(years) ax.tick_params(axis="x", labelrotation=45) + + if milestones: + min_year = min(years) + max_year = max(years) + milestones_in_range = sorted( + [m for m in milestones if min_year <= m["year"] <= max_year], + key=lambda m: (m["year"], m["description"]), + ) + offset_step = 0.35 + offset_map: Dict[int, int] = {} + top_limit = ax.get_ylim()[1] * 1.05 + for milestone in milestones_in_range: + year = milestone["year"] + count_for_year = offset_map.get(year, 0) + offset_map[year] = count_for_year + 1 + horizontal_offset = offset_step * (count_for_year // 2 + 1) + if count_for_year % 2 == 1: + horizontal_offset *= -1 + text_x = year + horizontal_offset + ax.axvline(year, color="#d62728", linestyle="--", linewidth=1, alpha=0.65, zorder=1) + ax.text( + text_x, + top_limit, + milestone["description"], + rotation=90, + verticalalignment="top", + horizontalalignment="center", + fontsize=8, + color="#d62728", + ) + ax.set_ylim(ax.get_ylim()[0], top_limit * (1 + max(offset_map.values(), default=0) * 0.02)) ensure_parent_dir(destination_path) fig.tight_layout() fig.savefig(destination_path, dpi=160) diff --git a/scripts/plot_colors_timeline.py b/scripts/plot_colors_timeline.py index 640c0e2..5e2df81 100644 --- a/scripts/plot_colors_timeline.py +++ b/scripts/plot_colors_timeline.py @@ -7,6 +7,7 @@ from lib.plots.colors_timeline import plot_colors_heatmap, plot_translucent_shar TIMELINE_PATH = Path("data/intermediate/colors_timeline.csv") MATRIX_PATH = Path("data/intermediate/colors_year_color_matrix.csv") +MILESTONES_PATH = Path("config/milestones.csv") TRANSLUCENT_DESTINATION = Path("figures/step15/colors_translucent_share.png") HEATMAP_LINEAR_DESTINATION = Path("figures/step15/colors_heatmap_linear.png") HEATMAP_LOG_DESTINATION = Path("figures/step15/colors_heatmap_log.png") @@ -15,7 +16,7 @@ HEATMAP_SHARE_DESTINATION = Path("figures/step15/colors_heatmap_share.png") def main() -> None: """Construit les visuels d'évolution annuelle des palettes.""" - plot_translucent_share(TIMELINE_PATH, TRANSLUCENT_DESTINATION) + plot_translucent_share(TIMELINE_PATH, MILESTONES_PATH, TRANSLUCENT_DESTINATION) plot_colors_heatmap(MATRIX_PATH, HEATMAP_LINEAR_DESTINATION, use_log_scale=False) plot_colors_heatmap(MATRIX_PATH, HEATMAP_LOG_DESTINATION, use_log_scale=True) plot_colors_heatmap(MATRIX_PATH, HEATMAP_SHARE_DESTINATION, normalize_by_year=True) diff --git a/tests/test_colors_timeline_plot.py b/tests/test_colors_timeline_plot.py index 18b4325..faec18a 100644 --- a/tests/test_colors_timeline_plot.py +++ b/tests/test_colors_timeline_plot.py @@ -12,14 +12,16 @@ matplotlib.use("Agg") def test_plot_translucent_share(tmp_path: Path) -> None: """Produit un graphique de part de translucides et diversité des couleurs.""" timeline_path = tmp_path / "colors_timeline.csv" - destination = tmp_path / "figures" / "step14" / "colors_translucent_share.png" + milestones_path = tmp_path / "milestones.csv" + destination = tmp_path / "figures" / "step15" / "colors_translucent_share.png" timeline_path.write_text( "year,colors_distinct,colors_new,colors_lost,share_translucent,total_quantity,top_colors\n" "2020,2,2,0,0.25,100,Blue (60),Red (40)\n" "2021,3,1,0,0.40,120,Blue (50),Trans-Black (48)\n" ) + milestones_path.write_text("year,description\n2020,Jalon Test\n") - plot_translucent_share(timeline_path, destination) + plot_translucent_share(timeline_path, milestones_path, destination) assert destination.exists() assert destination.stat().st_size > 0