from __future__ import annotations from typing import List import matplotlib.pyplot as plt import numpy as np import pandas as pd from matplotlib.lines import Line2D class RadarPlotter: def __init__(self, data_loader): self.data_loader = data_loader self.dimension_metrics = list(data_loader.dimension_metrics) self.dimension_labels = list(data_loader.dimension_display_labels) self.color_list = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf", ] def create_radar_chart(self, models_df: pd.DataFrame) -> plt.Figure: if models_df.empty or not self.dimension_metrics: fig, ax = plt.subplots(figsize=(8, 6)) ax.text(0.5, 0.5, "No dimension metrics available for radar chart", ha="center", va="center", fontsize=14) ax.axis("off") return fig plt.rcParams["font.family"] = "sans-serif" plt.rcParams["font.sans-serif"] = ["Arial", "Microsoft YaHei", "SimHei", "DejaVu Sans"] plt.rcParams["axes.unicode_minus"] = False labels = self.dimension_labels angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False).tolist() angles += angles[:1] fig = plt.figure(figsize=(13.5, 8.5)) grid = fig.add_gridspec(1, 2, width_ratios=[3.2, 1.35], wspace=0.02) ax = fig.add_subplot(grid[0, 0], polar=True) ax_legend = fig.add_subplot(grid[0, 1]) ax_legend.axis("off") ax.set_theta_offset(np.pi / 2) ax.set_theta_direction(-1) ax.set_ylim(0, 105) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels, fontsize=11, fontweight="bold") ax.set_yticks([20, 40, 60, 80, 100]) ax.set_yticklabels(["20", "40", "60", "80", "100"], color="#666666") ax.yaxis.grid(True, color="#D0D8E8", linewidth=0.8) ax.xaxis.grid(True, color="#D0D8E8", linewidth=0.8) ax.fill(np.linspace(0, 2 * np.pi, 400), [105] * 400, color="#EAF1FA", alpha=0.5) model_scores = [] for _, row in models_df.iterrows(): values = [float(row.get(metric, 0) or 0) for metric in self.dimension_metrics] valid_values = [value for value in values if value is not None] avg_score = sum(valid_values) / len(valid_values) if valid_values else 0 model_scores.append((row, avg_score)) model_scores.sort(key=lambda item: item[1]) legend_elements = [] for index, (row, _) in enumerate(model_scores): values = [float(row.get(metric, 0) or 0) for metric in self.dimension_metrics] values += values[:1] color = self.color_list[index % len(self.color_list)] ax.plot(angles, values, color=color, linewidth=2.0, label=row["Model"]) ax.fill(angles, values, color=color, alpha=0.06) ax.scatter(angles[:-1], values[:-1], color=color, s=36, edgecolors="white", linewidths=0.5) legend_elements.append( Line2D( [0], [0], color=color, linestyle="-", marker="o", markersize=6, linewidth=2.0, markerfacecolor=color, markeredgecolor="white", markeredgewidth=0.5, label=row["Model"], ) ) if legend_elements: ax_legend.legend( handles=legend_elements, loc="center left", frameon=True, fontsize=9, fancybox=True, shadow=False, edgecolor="#d9d9d9", facecolor="white", borderpad=0.8, labelspacing=0.55, handlelength=2.0, handletextpad=0.7, ) ax.set_title("Dimension Radar Chart", fontsize=18, fontweight="bold", pad=26) fig.subplots_adjust(left=0.05, right=0.97, top=0.92, bottom=0.06) return fig