diff --git a/README.md b/README.md index 98b2348..32c5c4d 100644 --- a/README.md +++ b/README.md @@ -183,4 +183,4 @@ Le script lit `data/intermediate/colors_by_set.csv` et produit deux agrégats : 1. `source .venv/bin/activate` 2. `python -m scripts.plot_colors_timeline` -Le script lit les deux agrégats de l'étape précédente et produit `figures/step14/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes) ainsi que `figures/step14/colors_heatmap.png` (heatmap année × couleur basée sur les quantités totales en échelle log1p). +Le script lit les deux agrégats de l'étape précédente et produit `figures/step14/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes), `figures/step14/colors_heatmap_linear.png` (heatmap année × couleur en quantités brutes) et `figures/step14/colors_heatmap_log.png` (heatmap avec échelle log1p). diff --git a/lib/plots/colors_timeline.py b/lib/plots/colors_timeline.py index 6658f98..f0fa238 100644 --- a/lib/plots/colors_timeline.py +++ b/lib/plots/colors_timeline.py @@ -67,26 +67,53 @@ def build_heatmap_data(rows: Iterable[dict]) -> Tuple[List[int], List[str], np.n 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 + swatches = [ + { + "name": name, + "rgb": color_rgb, + "is_translucent": is_trans, + "display_color": f"#{color_rgb}", + } + for name, color_rgb, is_trans in color_keys + ] + return years, labels, matrix, swatches -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.""" +def plot_colors_heatmap(matrix_path: Path, destination_path: Path, use_log_scale: bool = False) -> None: + """Génère une heatmap année × couleur basée sur les quantités totales avec pastilles.""" rows = load_rows(matrix_path) - years, labels, matrix = build_heatmap_data(rows) - values = np.log1p(matrix) + years, labels, matrix, swatches = build_heatmap_data(rows) + values = np.log1p(matrix) if use_log_scale else matrix - fig, ax = plt.subplots(figsize=(14, max(6, len(labels) * 0.24))) + fig, ax = plt.subplots(figsize=(14, max(6, len(labels) * 0.26))) + y_positions = np.arange(len(labels)) 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) + facecolors = [swatch["display_color"] for swatch in swatches] + edgecolors = ["#f2f2f2" if swatch["is_translucent"] == "true" else "#0b0b0b" for swatch in swatches] + for y, face, edge in zip(y_positions, facecolors, edgecolors): + ax.scatter( + -0.8, + y, + s=150, + marker="o", + color=face, + edgecolors=edge, + linewidths=1.1, + zorder=3, + clip_on=False, + ) + ax.set_xlim(-1.1, len(years) - 0.5) 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)") + title_scale = "log1p des quantités" if use_log_scale else "quantités totales" + ax.set_title(f"Intensité des couleurs par année ({title_scale})") + cbar = fig.colorbar(heatmap, ax=ax, shrink=0.82, pad=0.018) + cbar_label = "log1p(quantité totale)" if use_log_scale else "quantité totale" + cbar.set_label(cbar_label) ensure_parent_dir(destination_path) - fig.tight_layout() + fig.subplots_adjust(left=0.26, right=0.97, bottom=0.08, top=0.94) fig.savefig(destination_path, dpi=170) plt.close(fig) diff --git a/scripts/plot_colors_timeline.py b/scripts/plot_colors_timeline.py index dad829c..718d25f 100644 --- a/scripts/plot_colors_timeline.py +++ b/scripts/plot_colors_timeline.py @@ -8,13 +8,15 @@ 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") TRANSLUCENT_DESTINATION = Path("figures/step14/colors_translucent_share.png") -HEATMAP_DESTINATION = Path("figures/step14/colors_heatmap.png") +HEATMAP_LINEAR_DESTINATION = Path("figures/step14/colors_heatmap_linear.png") +HEATMAP_LOG_DESTINATION = Path("figures/step14/colors_heatmap_log.png") def main() -> None: """Construit les visuels d'évolution annuelle des palettes.""" plot_translucent_share(TIMELINE_PATH, TRANSLUCENT_DESTINATION) - plot_colors_heatmap(MATRIX_PATH, HEATMAP_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) if __name__ == "__main__": diff --git a/tests/test_colors_timeline_plot.py b/tests/test_colors_timeline_plot.py index 39bcae8..7f3cced 100644 --- a/tests/test_colors_timeline_plot.py +++ b/tests/test_colors_timeline_plot.py @@ -28,7 +28,8 @@ def test_plot_translucent_share(tmp_path: Path) -> None: def test_plot_colors_heatmap(tmp_path: Path) -> None: """Génère une heatmap année × couleur.""" matrix_path = tmp_path / "colors_year_color_matrix.csv" - destination = tmp_path / "figures" / "step14" / "colors_heatmap.png" + destination_linear = tmp_path / "figures" / "step14" / "colors_heatmap_linear.png" + destination_log = tmp_path / "figures" / "step14" / "colors_heatmap_log.png" matrix_path.write_text( "year,color_rgb,is_translucent,color_name,quantity_total\n" "2020,AAAAAA,false,Gray,5\n" @@ -37,7 +38,10 @@ def test_plot_colors_heatmap(tmp_path: Path) -> None: "2021,CCCCCC,false,Blue,4\n" ) - plot_colors_heatmap(matrix_path, destination) + plot_colors_heatmap(matrix_path, destination_linear, use_log_scale=False) + plot_colors_heatmap(matrix_path, destination_log, use_log_scale=True) - assert destination.exists() - assert destination.stat().st_size > 0 + assert destination_linear.exists() + assert destination_linear.stat().st_size > 0 + assert destination_log.exists() + assert destination_log.stat().st_size > 0