Spaces:
Running
Running
| import numpy as np | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from matplotlib.lines import Line2D | |
| from typing import Dict, List | |
| class RadarPlotter: | |
| def __init__(self, data_loader): | |
| self.data_loader = data_loader | |
| self.dimension_metrics = list(data_loader.DIMENSION_MAP.keys()) | |
| # 预定义颜色列表,确保足够多 | |
| self.COLOR_LIST = [ | |
| "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", | |
| "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf", | |
| "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", | |
| "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" | |
| ] | |
| # 标记样式 | |
| self.MARKER_STYLES = ['o', 's', '^', 'D', 'v', 'p', 'h', '*', 'X', 'P', 'd'] | |
| # 线型样式 | |
| self.LINE_STYLES = ['-', '--', '-.', ':'] | |
| # 颜色缓存 | |
| self.model_color_cache = {} | |
| self.model_style_cache = {} | |
| def create_radar_chart(self, models_df: pd.DataFrame) -> plt.Figure: | |
| """创建雷达图 - 展示维度指标""" | |
| if models_df.empty or len(models_df) == 0: | |
| fig, ax = plt.subplots(figsize=(8, 8)) | |
| ax.text(0.5, 0.5, "No data available for radar chart", | |
| ha="center", va="center", fontsize=14) | |
| ax.axis("off") | |
| return fig | |
| # 获取维度指标 | |
| labels = self.dimension_metrics | |
| # 设置中文字体 | |
| plt.rcParams['font.family'] = 'sans-serif' | |
| plt.rcParams['font.sans-serif'] = ['Arial', 'Microsoft YaHei', 'SimHei', 'DejaVu Sans'] | |
| plt.rcParams['axes.unicode_minus'] = False | |
| # 创建图形,左侧为雷达图,右侧留出图例空间 | |
| fig, (ax, ax_legend) = plt.subplots( | |
| 1, 2, | |
| figsize=(14, 8), | |
| gridspec_kw={'width_ratios': [3, 1]}, | |
| subplot_kw={'polar': True} | |
| ) | |
| # 绘制雷达图 | |
| self._draw_radar(ax, models_df, labels) | |
| # 在右侧轴上创建图例 | |
| ax_legend.axis('off') # 关闭右侧轴的显示 | |
| legend_elements = self._create_legend_elements(models_df) | |
| if legend_elements: | |
| ax_legend.legend( | |
| handles=legend_elements, | |
| loc='center', | |
| frameon=True, | |
| fontsize=10, | |
| fancybox=True, | |
| shadow=True, | |
| edgecolor='gray', | |
| facecolor='white' | |
| ) | |
| plt.tight_layout() | |
| return fig | |
| def _draw_radar(self, ax, models_df: pd.DataFrame, labels: List[str]): | |
| """绘制雷达图(参考您提供的代码风格)""" | |
| N = len(labels) | |
| angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist() | |
| angles += angles[:1] | |
| # 设置雷达图基本参数 | |
| DISPLAY_MAX = 105 | |
| ax.set_theta_offset(np.pi / 2) | |
| ax.set_theta_direction(-1) | |
| ax.set_ylim(0, DISPLAY_MAX) | |
| # 隐藏默认的极坐标边框 | |
| ax.spines["polar"].set_visible(False) | |
| ax.set_xticks([]) | |
| ax.set_yticks([]) | |
| # 网格刻度(参考您提供的代码) | |
| true_ticks = [0, 20, 40, 60, 80, 100] | |
| ax.set_yticks(true_ticks) | |
| ax.set_yticklabels([str(t) for t in true_ticks], fontsize=10, color="#666666") | |
| ax.yaxis.grid(True, color="#D0D8E8", linewidth=0.8, linestyle='-') | |
| # 蓝色背景填充(参考您提供的代码) | |
| ax.fill(np.linspace(0, 2*np.pi, 400), [DISPLAY_MAX]*400, color="#EAF1FA", alpha=0.6) | |
| # 径向参考线(参考您提供的代码) | |
| for angle in angles[:-1]: | |
| ax.plot([angle, angle], [0, DISPLAY_MAX], color="#D0D8E8", linewidth=0.8, zorder=1) | |
| # 外围灰色弧线作为标签背景(参考您提供的代码) | |
| outer_r = DISPLAY_MAX + 1.5 | |
| arc_width = 2 * np.pi / N * 0.70 | |
| for angle in angles[:-1]: | |
| theta = np.linspace(angle - arc_width/2, angle + arc_width/2, 80) | |
| ax.plot(theta, [outer_r]*len(theta), color="#C8C8C8", linewidth=6, solid_capstyle="round") | |
| # 标签位置映射(参考您提供的代码) | |
| label_r_map = { | |
| "Visual Quality": DISPLAY_MAX + 8, | |
| "Motion Quality": DISPLAY_MAX + 15, | |
| "Content Consistency": DISPLAY_MAX + 15, | |
| "Physics Adherence": DISPLAY_MAX + 8, | |
| "3D Accuracy": DISPLAY_MAX + 15, | |
| "Controllability": DISPLAY_MAX + 15, | |
| } | |
| # 绘制标签(支持换行,参考您提供的代码) | |
| for angle, label in zip(angles[:-1], labels): | |
| label_r = label_r_map.get(label, DISPLAY_MAX + 10) | |
| # 将长标签分两行显示 | |
| display_label = label | |
| if label == "Content Consistency": | |
| display_label = "Content\nConsistency" | |
| elif label == "Physics Adherence": | |
| display_label = "Physics\nAdherence" | |
| elif label == "Visual Quality": | |
| display_label = "Visual\nQuality" | |
| elif label == "Motion Quality": | |
| display_label = "Motion\nQuality" | |
| elif label == "3D Accuracy": | |
| display_label = "3D\nAccuracy" | |
| elif label == "Controllability": | |
| display_label = "Controllability" | |
| ax.text(angle, label_r, display_label, ha="center", va="center", | |
| fontsize=11, fontweight="bold", clip_on=False, color="#333333") | |
| # 为每个模型分配颜色和样式(如果尚未分配) | |
| all_models = models_df["Model"].values.tolist() | |
| for i, model in enumerate(all_models): | |
| if model not in self.model_color_cache: | |
| # 使用循环的颜色列表 | |
| color_idx = i % len(self.COLOR_LIST) | |
| self.model_color_cache[model] = self.COLOR_LIST[color_idx] | |
| if model not in self.model_style_cache: | |
| # 分配线型和标记 | |
| line_idx = i % len(self.LINE_STYLES) | |
| marker_idx = i % len(self.MARKER_STYLES) | |
| self.model_style_cache[model] = { | |
| 'linestyle': self.LINE_STYLES[line_idx], | |
| 'marker': self.MARKER_STYLES[marker_idx] | |
| } | |
| # 计算每个模型的平均分并排序(从低到高绘制,高分在上层) | |
| model_scores = {} | |
| for model in all_models: | |
| model_data = models_df[models_df["Model"] == model] | |
| score_sum = 0 | |
| count = 0 | |
| for dim in labels: | |
| if dim in model_data.columns: | |
| val = model_data[dim].values[0] | |
| if not pd.isna(val): | |
| score_sum += float(val) | |
| count += 1 | |
| model_scores[model] = score_sum / count if count > 0 else 0 | |
| # 按平均分排序(从低到高) | |
| sorted_models = sorted(model_scores.items(), key=lambda x: x[1]) | |
| # 绘制每个模型的数据 | |
| for idx, (model, _) in enumerate(sorted_models): | |
| model_data = models_df[models_df["Model"] == model] | |
| values = [] | |
| # 收集每个维度的值 | |
| for dim in labels: | |
| if dim in model_data.columns: | |
| val = model_data[dim].values[0] | |
| if pd.isna(val): | |
| values.append(0) | |
| else: | |
| values.append(float(val)) | |
| else: | |
| values.append(0) | |
| # 闭合多边形 | |
| values += values[:1] | |
| # 获取颜色和样式 | |
| color = self.model_color_cache.get(model, "#333333") | |
| style = self.model_style_cache.get(model, {'linestyle': '-', 'marker': 'o'}) | |
| # 绘制雷达图线(参考您提供的代码风格) | |
| ax.plot(angles, values, color=color, linewidth=2.0, | |
| linestyle=style['linestyle'], zorder=10 + idx) | |
| ax.scatter(angles[:-1], values[:-1], color=color, s=40, | |
| marker=style['marker'], zorder=20 + idx, edgecolors='white', linewidths=0.6) | |
| ax.fill(angles, values, color=color, alpha=0.05) | |
| def _create_legend_elements(self, models_df: pd.DataFrame): | |
| """创建图例元素(参考您提供的代码风格)""" | |
| legend_elements = [] | |
| all_models = models_df["Model"].values.tolist() | |
| if not all_models: | |
| return legend_elements | |
| # 计算平均分并排序 | |
| model_scores = {} | |
| for model in all_models: | |
| model_data = models_df[models_df["Model"] == model] | |
| score_sum = 0 | |
| count = 0 | |
| for dim in self.dimension_metrics: | |
| if dim in model_data.columns: | |
| val = model_data[dim].values[0] | |
| if not pd.isna(val): | |
| score_sum += float(val) | |
| count += 1 | |
| model_scores[model] = score_sum / count if count > 0 else 0 | |
| # 按平均分排序 | |
| sorted_models = sorted(model_scores.items(), key=lambda x: x[1]) | |
| for model, _ in sorted_models: | |
| color = self.model_color_cache.get(model, "#333333") | |
| style = self.model_style_cache.get(model, {'linestyle': '-', 'marker': 'o'}) | |
| legend_elements.append( | |
| Line2D([0], [0], color=color, linestyle=style['linestyle'], | |
| marker=style['marker'], markersize=6, linewidth=2.0, | |
| markerfacecolor=color, markeredgecolor='white', markeredgewidth=0.5, | |
| label=model) | |
| ) | |
| return legend_elements | |
| def get_dimension_scores(self, model_name: str) -> Dict[str, float]: | |
| """获取指定模型的维度分数""" | |
| if self.data_loader.df_all is None or model_name not in self.data_loader.df_all["Model"].values: | |
| return {} | |
| model_data = self.data_loader.df_all[self.data_loader.df_all["Model"] == model_name] | |
| scores = {} | |
| for dim in self.dimension_metrics: | |
| if dim in model_data.columns: | |
| scores[dim] = float(model_data[dim].values[0]) if not pd.isna(model_data[dim].values[0]) else 0.0 | |
| return scores | |