File size: 6,688 Bytes
d91766b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap
from plot_lines import TECH_STYLE
def plot_radar_chart(data: dict, attributes: list, assigned_colors: dict = None, save_path: str = None, is_dark_mode: bool = True, font_size_axis: int = 12, font_size_legend: int = 12):
"""
Plot radar chart for AUP results with tech style.
Normalizes each attribute to [0, 1] based on the maximum value across methods.
Axes show the real values for each task.
data: {method_name: [val1, val2, ...]}
attributes: list of attribute names (e.g., datasets)
is_dark_mode: whether to use dark mode (True) or light mode (False)
font_size_axis: font size for axis labels
font_size_legend: font size for legend
"""
# Setup angles
N = len(attributes)
angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
angles += [angles[0]] # Close the loop
# Normalize data independently per attribute
method_names = list(data.keys())
raw_matrix = np.array([data[m] for m in method_names]) # (num_methods, num_attributes)
# Calculate max for each attribute independently
attr_maxs = np.max(raw_matrix, axis=0) # (num_attributes,)
attr_maxs = np.array([np.ceil(m / 5.0) * 5.0 if m > 0 else 5.0 for m in attr_maxs])
# Normalize each attribute independently to [0, 1]
normalized_matrix = raw_matrix / attr_maxs[np.newaxis, :]
norm_data = {m: normalized_matrix[i] for i, m in enumerate(method_names)}
# Determine style settings
if is_dark_mode:
style_context = 'dark_background'
bg_color = 'black'
fg_color = 'white'
grid_color = 'white'
grid_alpha = 0.3
spine_color = 'white'
else:
style_context = 'default'
bg_color = '#F5F5F7' # Apple light gray
fg_color = 'black'
grid_color = '#86868b'
grid_alpha = 0.3
spine_color = '#86868b'
with plt.style.context(style_context):
fig, ax = plt.subplots(figsize=(12, 12), subplot_kw=dict(polar=True))
if not is_dark_mode:
fig.patch.set_facecolor(bg_color)
ax.set_facecolor(bg_color)
for i, (method_name, v) in enumerate(norm_data.items()):
# Determine color
if assigned_colors:
c_name = assigned_colors[i] if isinstance(assigned_colors, list) else assigned_colors.get(method_name, 'grey')
else:
c_name = 'grey'
grad_colors = TECH_STYLE.get(c_name, TECH_STYLE['grey'])
main_color = grad_colors[1]
cmap = LinearSegmentedColormap.from_list(f"tech_{c_name}", grad_colors)
# Prepare data for loop
v_loop = np.concatenate([v, [v[0]]])
# 1. Draw Gradient Line using LineCollection
points = np.array([angles, v_loop]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm = plt.Normalize(0, 2 * np.pi)
lc = LineCollection(segments, cmap=cmap, norm=norm)
lc.set_array(np.array(angles))
lc.set_linewidth(4) # Thicker lines
lc.set_alpha(0.9)
ax.add_collection(lc)
# 2. Fill area with low alpha
ax.fill(angles, v_loop, color=main_color, alpha=0.1)
# 3. Add markers
edge_c = main_color if is_dark_mode else 'white'
# Actually keep white edge on dark bg, and maybe main_color edge on light?
# Let's stick to white edge for contrast if filled with white, but here filled with nothing?
# Previous code: color='white', edgecolors=main_color
ax.scatter(angles, v_loop, color='white', edgecolors=main_color, s=100, zorder=10, lw=2)
# 4. Add to legend (proxy artist)
ax.plot([], [], color=main_color, label=method_name, linewidth=4)
# Styling
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
# Grid and Spines
ax.grid(True, linestyle='--', alpha=grid_alpha, color=grid_color, linewidth=1)
ax.spines['polar'].set_visible(False)
# X-axis (Attributes) labels
ax.set_xticks(angles[:-1])
labels = ax.set_xticklabels(attributes, fontsize=font_size_axis, color=fg_color, fontweight='bold')
ax.tick_params(axis='x', pad=30)
# Special handling: move 'humaneval+' label right if 4 tasks
if len(attributes) == 4:
import matplotlib.transforms as mtransforms
for label in labels:
if label.get_text() == 'humaneval+':
# Shift the label to the right to prevent occlusion
offset = mtransforms.ScaledTranslation(40/72, 0, fig.dpi_scale_trans)
label.set_transform(label.get_transform() + offset)
# Y-axis settings
ax.set_yticklabels([]) # Hide default global ticks
ax.set_ylim(0, 1.05)
# Add custom ticks for each axis (Real values, independent scales)
grid_ticks = [0.2, 0.4, 0.6, 0.8, 1.0]
for i, angle in enumerate(angles[:-1]):
max_val = attr_maxs[i]
for t in grid_ticks:
val = t * max_val
if val.is_integer():
label = f"{int(val)}"
else:
label = f"{val:.1f}"
# Add text with background to improve readability
box_color = '#202020' if is_dark_mode else '#e0e0e0'
text_color_tick = 'white' if is_dark_mode else 'black'
ax.text(angle, t, label,
color=text_color_tick, fontsize=font_size_axis, fontweight='bold',
ha='center', va='center',
bbox=dict(facecolor=box_color, edgecolor='none', alpha=0.8, boxstyle='round,pad=0.2'))
# Legend and Title
plt.legend(loc='upper right', bbox_to_anchor=(1.1, 1.1), frameon=False, fontsize=font_size_legend, labelcolor=fg_color)
# plt.title('Multi-Task AUP Comparison (Normalized)', fontsize=font_size_axis, fontweight='bold', color=fg_color, pad=50)
if save_path:
fc = bg_color if not is_dark_mode else 'black'
plt.savefig(save_path, dpi=300, bbox_inches='tight', facecolor=fc)
print(f"Radar chart saved to {save_path}")
# plt.show()
|