| from __future__ import annotations |
|
|
| from typing import Optional |
|
|
| import matplotlib.pyplot as plt |
| import numpy as np |
| import pandas as pd |
|
|
|
|
| class Plotter: |
| def __init__(self, data_loader): |
| self.data_loader = data_loader |
|
|
| def create_comparison_plot( |
| self, |
| model_filter: str, |
| entry_type_filter: str, |
| selected_plot_metric: str, |
| plot_sort_mode: str, |
| display_metric_name: Optional[str] = None, |
| ) -> plt.Figure: |
| metric_display_name = display_metric_name or selected_plot_metric |
| df = self.data_loader.df_all |
|
|
| if df is None or df.empty: |
| return self._empty_plot("No data available for plotting") |
|
|
| if model_filter and model_filter.strip(): |
| df = df[df["Model"].str.contains(model_filter, case=False, na=False)] |
|
|
| if entry_type_filter and entry_type_filter != "All": |
| df = df[df["entry_type"] == entry_type_filter] |
|
|
| if df.empty: |
| return self._empty_plot("No models match the filter criteria") |
|
|
| if selected_plot_metric not in df.columns: |
| return self._empty_plot(f"Metric '{metric_display_name}' not found") |
|
|
| plot_df = df[["Model", selected_plot_metric]].dropna(subset=[selected_plot_metric]).copy() |
| if plot_df.empty: |
| return self._empty_plot(f"No data for metric '{metric_display_name}'") |
|
|
| plot_df.columns = ["Model", "Score"] |
| plot_df["Score"] = pd.to_numeric(plot_df["Score"], errors="coerce") |
| plot_df = plot_df.dropna(subset=["Score"]) |
| if plot_df.empty: |
| return self._empty_plot(f"No valid data for metric '{metric_display_name}'") |
| plot_df["Score"] = plot_df["Score"].round(2) |
|
|
| ascending = plot_sort_mode == "Ascending (low → high)" |
| plot_df = plot_df.sort_values("Score", ascending=ascending) |
|
|
| plt.rcParams["font.sans-serif"] = ["Arial", "Microsoft YaHei", "SimHei", "DejaVu Sans"] |
| plt.rcParams["axes.unicode_minus"] = False |
|
|
| model_count = len(plot_df) |
| figure_height = max(8, min(20, 0.48 * model_count + 4)) |
| ytick_fontsize = max(11, min(22, 26 - model_count * 0.3)) |
| fig, ax = plt.subplots(figsize=(16, figure_height), dpi=100) |
|
|
| colors = plt.get_cmap("coolwarm_r")(np.linspace(0.12, 0.9, len(plot_df))) |
| background_target = max(100, plot_df["Score"].max() * 1.12) |
|
|
| ax.barh(plot_df["Model"], [background_target] * len(plot_df), color="#FAFAFA", edgecolor="none", height=0.7) |
| bars = ax.barh(plot_df["Model"], plot_df["Score"], color=colors, edgecolor="none", height=0.7) |
|
|
| for bar in bars: |
| width = bar.get_width() |
| ax.text(width + background_target * 0.015, bar.get_y() + bar.get_height() / 2, f"{width:.2f}", ha="left", va="center", fontsize=max(10, ytick_fontsize - 1), fontweight="bold", color="#444444") |
|
|
| for spine in ax.spines.values(): |
| spine.set_visible(False) |
| ax.tick_params(axis="both", which="both", length=0) |
| ax.set_xticks([]) |
| ax.set_xlim(0, background_target * 1.05) |
| ax.set_xlabel(metric_display_name, fontsize=24, fontweight="bold", labelpad=5, x=0.32, horizontalalignment="center") |
| ax.set_ylabel("Model", fontsize=24, fontweight="bold") |
| plt.yticks(fontsize=ytick_fontsize, fontweight="bold") |
| plt.subplots_adjust(left=0.28 if model_count <= 16 else 0.42) |
| ax.set_title(f"{metric_display_name} Leaderboard", fontsize=20, fontweight="bold", pad=30, x=0.32, horizontalalignment="center", y=1.05) |
| plt.tight_layout() |
| return fig |
|
|
| def _empty_plot(self, message: str) -> plt.Figure: |
| fig, ax = plt.subplots(figsize=(8, 6)) |
| ax.text(0.5, 0.5, message, ha="center", va="center", fontsize=14) |
| ax.axis("off") |
| return fig |
|
|