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()