File size: 5,829 Bytes
bc37111 |
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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
"""
Leaderboard Styling Module
Handles color gradients and visual styling for the leaderboard.
"""
import logging
import html
from typing import Dict, Tuple, List
import pandas as pd
from matplotlib.colors import LinearSegmentedColormap
from ..core.columns import column_registry
from .transformer import format_parameter_count
logger = logging.getLogger(__name__)
class LeaderboardStyler:
"""
Applies visual styling to leaderboard DataFrames.
Uses Excel-like Red-Yellow-Green color gradients for score columns.
"""
# Excel-style color gradient: Red -> Yellow -> Green
GRADIENT_COLORS = [
(0.9, 0.1, 0.2), # Red (low scores)
(1.0, 1.0, 0.0), # Yellow (medium scores)
(0/255, 176/255, 80/255) # Excel Green (high scores)
]
def __init__(self):
self._colormap = LinearSegmentedColormap.from_list(
"ExcelRedYellowGreen",
self.GRADIENT_COLORS,
N=256
)
@staticmethod
def rgb_to_hex(rgb: Tuple[float, float, float]) -> str:
"""Convert RGB tuple (0-1 range) to hex color."""
r = int(rgb[0] * 255)
g = int(rgb[1] * 255)
b = int(rgb[2] * 255)
return f"#{r:02x}{g:02x}{b:02x}"
def get_color_for_value(self, value: float, min_val: float, max_val: float) -> str:
"""Get hex color for a value within a range."""
if max_val == min_val:
normalized = 0.5
else:
normalized = (value - min_val) / (max_val - min_val)
# Clamp to [0, 0.999] to avoid edge case at exactly 1.0
normalized = max(0, min(0.999, normalized))
rgba = self._colormap(normalized)
return self.rgb_to_hex(rgba[:3])
def calculate_color_ranges(self, df: pd.DataFrame) -> Dict[str, Dict[str, float]]:
"""Calculate min/max for each score column."""
ranges = {}
for col_name in column_registry.score_columns:
if col_name not in df.columns:
continue
numeric_values = pd.to_numeric(df[col_name], errors='coerce')
if numeric_values.isna().all():
continue
ranges[col_name] = {
'min': numeric_values.min(),
'max': numeric_values.max()
}
return ranges
def apply_styling(self, df: pd.DataFrame) -> "pd.io.formats.style.Styler":
"""
Apply color styling to DataFrame.
Returns a pandas Styler object that Gradio can render.
"""
if df.empty:
return df.style
df_copy = df.copy()
# Convert "N/A" to NaN for proper formatting
for col in column_registry.score_columns:
if col in df_copy.columns:
df_copy[col] = df_copy[col].replace("N/A", pd.NA)
df_copy[col] = pd.to_numeric(df_copy[col], errors='coerce')
# Calculate color ranges
color_ranges = self.calculate_color_ranges(df_copy)
# Create style function
def apply_gradient(val, col_name: str):
if col_name not in color_ranges:
return ''
if pd.isna(val):
return ''
try:
min_val = color_ranges[col_name]['min']
max_val = color_ranges[col_name]['max']
color_hex = self.get_color_for_value(float(val), min_val, max_val)
return f'background-color: {color_hex}; text-align: center; font-weight: bold; color: #333;'
except (ValueError, TypeError):
return ''
# Apply styling
styler = df_copy.style
for col in column_registry.score_columns:
if col in df_copy.columns:
styler = styler.map(
lambda val, c=col: apply_gradient(val, c),
subset=[col]
)
# Format numeric columns
format_dict = {}
for col_name in column_registry.numeric_columns:
if col_name in df_copy.columns:
col_def = column_registry.get(col_name)
# Special handling for Parameters column - use human-readable format
if col_name == "Parameters":
format_dict[col_name] = format_parameter_count
elif col_def and col_def.decimals == 0:
format_dict[col_name] = '{:.0f}'
elif col_def and col_def.decimals == 3:
format_dict[col_name] = '{:.3f}'
else:
format_dict[col_name] = '{:.2f}'
# Format model column as hyperlink without mutating the underlying data
if "Model" in df_copy.columns:
def _model_link_formatter(value: object) -> str:
model_name = html.escape(str(value))
return (
f'<a href="https://huggingface.co/{model_name}" target="_blank" '
f'style="color: #2563eb; text-decoration: underline;">{model_name}</a>'
)
format_dict["Model"] = _model_link_formatter
if format_dict:
# Don't replace NA values - let them display as they are in the CSV
styler = styler.format(format_dict, na_rep='', escape=None)
return styler
def get_datatypes(self, columns: List[str]) -> List[str]:
"""Get Gradio datatypes for columns."""
return column_registry.get_datatypes(columns)
def get_column_widths(self, columns: List[str]) -> List[str]:
"""Get column widths for columns."""
return column_registry.get_widths(columns)
|