iWorldBench commited on
Commit
2b552ac
·
1 Parent(s): 9cf40f9

Implement click-to-sort leaderboard using sorttable.js

Browse files
Files changed (3) hide show
  1. app.py +19 -15
  2. src/leaderboard.py +2 -9
  3. src/styling.py +50 -101
app.py CHANGED
@@ -7,7 +7,7 @@ from src.data_loader import DataLoader
7
  from src.leaderboard import Leaderboard
8
  from src.plotter import Plotter
9
  from src.radar_plotter import RadarPlotter
10
- from src.styling import get_academic_css
11
  from src.utils import get_metric_choices, clean_metric_names
12
 
13
  # Initialize components
@@ -24,11 +24,11 @@ def reload_data():
24
  dummy_fig, ax = plt.subplots(figsize=(6, 3))
25
  ax.text(0.5, 0.5, msg, ha="center", va="center")
26
  ax.axis("off")
27
- # Return empty DataFrame for leaderboard
28
  return "", gr.update(choices=["All"], value="All"), \
29
  gr.update(choices=["All"], value="All"), \
30
  gr.update(choices=["All"], value="All"), \
31
- pd.DataFrame(), dummy_fig
32
 
33
  open_source_choices = data_loader.get_open_source_choices()
34
  year_choices = data_loader.get_year_choices()
@@ -47,12 +47,16 @@ def reload_data():
47
  selected_metrics=clean_metric_names(all_metrics_with_markers),
48
  )
49
 
 
 
 
50
  radar_fig = radar_plotter.create_radar_chart()
 
51
  return "", \
52
  gr.update(choices=open_source_choices, value="All"), \
53
  gr.update(choices=year_choices, value="All"), \
54
  gr.update(choices=category_choices, value="All"), \
55
- table_df, radar_fig
56
 
57
  def update_leaderboard_wrapper(metric, top_k, model_filter, open_source_filter,
58
  year_filter, category_filter, sort_mode, selected_metrics):
@@ -64,6 +68,9 @@ def update_leaderboard_wrapper(metric, top_k, model_filter, open_source_filter,
64
  year_filter, category_filter, sort_mode, clean_selected_metrics
65
  )
66
 
 
 
 
67
  displayed_models = table_df["Model"].tolist() if not table_df.empty else []
68
  if displayed_models and data_loader.df_all is not None:
69
  radar_df = data_loader.df_all[data_loader.df_all["Model"].isin(displayed_models)].copy()
@@ -71,7 +78,7 @@ def update_leaderboard_wrapper(metric, top_k, model_filter, open_source_filter,
71
  radar_df = pd.DataFrame()
72
 
73
  radar_fig = radar_plotter.create_radar_chart(radar_df)
74
- return table_df, radar_fig
75
 
76
  def create_comparison_plot_wrapper(model_filter, open_source_filter, year_filter,
77
  category_filter, selected_plot_metric, plot_sort_mode):
@@ -160,13 +167,10 @@ with gr.Blocks(css=academic_css) as demo:
160
  reload_button = gr.Button("🔄 Reload Data", variant="secondary", size="sm")
161
  update_button = gr.Button("✅ Update Leaderboard", variant="primary", size="sm")
162
 
163
- # Leaderboard DataFrame with sorting enabled
164
- leaderboard_df = gr.DataFrame(
165
- label="Leaderboard",
166
- headers=[],
167
- datatable=True, # Enable column sorting
168
- interactive=False,
169
- wrap=True,
170
  )
171
 
172
  with gr.Row():
@@ -193,7 +197,7 @@ with gr.Blocks(css=academic_css) as demo:
193
  reload_button.click(
194
  fn=reload_data,
195
  inputs=[],
196
- outputs=[status_box, open_source_dropdown, year_dropdown, category_dropdown, leaderboard_df, radar_plot],
197
  )
198
 
199
  update_button.click(
@@ -203,7 +207,7 @@ with gr.Blocks(css=academic_css) as demo:
203
  open_source_dropdown, year_dropdown, category_dropdown,
204
  sort_mode_radio, metrics_select,
205
  ],
206
- outputs=[leaderboard_df, radar_plot],
207
  )
208
 
209
  plot_update_button.click(
@@ -218,7 +222,7 @@ with gr.Blocks(css=academic_css) as demo:
218
  demo.load(
219
  fn=reload_data,
220
  inputs=[],
221
- outputs=[status_box, open_source_dropdown, year_dropdown, category_dropdown, leaderboard_df, radar_plot],
222
  )
223
 
224
  if __name__ == "__main__":
 
7
  from src.leaderboard import Leaderboard
8
  from src.plotter import Plotter
9
  from src.radar_plotter import RadarPlotter
10
+ from src.styling import dataframe_to_html, get_academic_css
11
  from src.utils import get_metric_choices, clean_metric_names
12
 
13
  # Initialize components
 
24
  dummy_fig, ax = plt.subplots(figsize=(6, 3))
25
  ax.text(0.5, 0.5, msg, ha="center", va="center")
26
  ax.axis("off")
27
+ placeholder_html = "<div class='placeholder'>No data available</div>"
28
  return "", gr.update(choices=["All"], value="All"), \
29
  gr.update(choices=["All"], value="All"), \
30
  gr.update(choices=["All"], value="All"), \
31
+ placeholder_html, dummy_fig
32
 
33
  open_source_choices = data_loader.get_open_source_choices()
34
  year_choices = data_loader.get_year_choices()
 
47
  selected_metrics=clean_metric_names(all_metrics_with_markers),
48
  )
49
 
50
+ # Convert DataFrame to HTML with sorting capability
51
+ html_table = dataframe_to_html(table_df)
52
+
53
  radar_fig = radar_plotter.create_radar_chart()
54
+
55
  return "", \
56
  gr.update(choices=open_source_choices, value="All"), \
57
  gr.update(choices=year_choices, value="All"), \
58
  gr.update(choices=category_choices, value="All"), \
59
+ html_table, radar_fig
60
 
61
  def update_leaderboard_wrapper(metric, top_k, model_filter, open_source_filter,
62
  year_filter, category_filter, sort_mode, selected_metrics):
 
68
  year_filter, category_filter, sort_mode, clean_selected_metrics
69
  )
70
 
71
+ # Convert DataFrame to HTML with sorting capability
72
+ html_table = dataframe_to_html(table_df)
73
+
74
  displayed_models = table_df["Model"].tolist() if not table_df.empty else []
75
  if displayed_models and data_loader.df_all is not None:
76
  radar_df = data_loader.df_all[data_loader.df_all["Model"].isin(displayed_models)].copy()
 
78
  radar_df = pd.DataFrame()
79
 
80
  radar_fig = radar_plotter.create_radar_chart(radar_df)
81
+ return html_table, radar_fig
82
 
83
  def create_comparison_plot_wrapper(model_filter, open_source_filter, year_filter,
84
  category_filter, selected_plot_metric, plot_sort_mode):
 
167
  reload_button = gr.Button("🔄 Reload Data", variant="secondary", size="sm")
168
  update_button = gr.Button("✅ Update Leaderboard", variant="primary", size="sm")
169
 
170
+ # Leaderboard HTML with sorting capability
171
+ leaderboard_html = gr.HTML(
172
+ label="Leaderboard Table",
173
+ value="<div class='placeholder'>Leaderboard will be displayed here...</div>"
 
 
 
174
  )
175
 
176
  with gr.Row():
 
197
  reload_button.click(
198
  fn=reload_data,
199
  inputs=[],
200
+ outputs=[status_box, open_source_dropdown, year_dropdown, category_dropdown, leaderboard_html, radar_plot],
201
  )
202
 
203
  update_button.click(
 
207
  open_source_dropdown, year_dropdown, category_dropdown,
208
  sort_mode_radio, metrics_select,
209
  ],
210
+ outputs=[leaderboard_html, radar_plot],
211
  )
212
 
213
  plot_update_button.click(
 
222
  demo.load(
223
  fn=reload_data,
224
  inputs=[],
225
+ outputs=[status_box, open_source_dropdown, year_dropdown, category_dropdown, leaderboard_html, radar_plot],
226
  )
227
 
228
  if __name__ == "__main__":
src/leaderboard.py CHANGED
@@ -55,14 +55,7 @@ class Leaderboard:
55
  lambda x: f"{x:.4f}" if pd.notna(x) and isinstance(x, (int, float)) else "-"
56
  )
57
 
58
- # Create hyperlinks if columns exist
59
- if "Paper" in result_df.columns:
60
- result_df["Paper"] = result_df["Paper"].apply(
61
- lambda x: f'<a href="{x}" target="_blank">📄</a>' if pd.notna(x) and x != "-" else "-"
62
- )
63
- if "Code" in result_df.columns:
64
- result_df["Code"] = result_df["Code"].apply(
65
- lambda x: f'<a href="{x}" target="_blank">💻</a>' if pd.notna(x) and x != "-" else "-"
66
- )
67
 
68
  return result_df
 
55
  lambda x: f"{x:.4f}" if pd.notna(x) and isinstance(x, (int, float)) else "-"
56
  )
57
 
58
+ # Do NOT generate HTML links; keep as plain text
59
+ # (If you want links, you can add them here, but for sorting they are fine as text)
 
 
 
 
 
 
 
60
 
61
  return result_df
src/styling.py CHANGED
@@ -3,7 +3,7 @@ import pandas as pd
3
  def get_academic_css() -> str:
4
  return """
5
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
6
-
7
  :root {
8
  /* Light mode (default) */
9
  --primary: #2563eb;
@@ -15,21 +15,20 @@ def get_academic_css() -> str:
15
  --border: #e5e5e5;
16
  --success: #10b981;
17
  --danger: #ef4444;
18
-
19
- /* Table colors - only for table, not affecting global background */
20
- --table-bg: #ffffff; /* overall table background */
21
- --table-bg-odd: #f8fafc; /* odd row background */
22
- --table-bg-even: #f1f5f9; /* even row background */
23
- --table-bg-hover: #e2e8f0; /* hover row background */
24
  --table-header: #64748b;
25
  --table-header-light: #94a3b8;
26
  --table-border: #cbd5e0;
 
 
 
27
  --best-score-bg: #e6fffa;
28
  --best-score-color: #2c7a7b;
29
  --category-bg: #e2e8f0;
30
  --category-color: #475569;
31
  }
32
-
33
  @media (prefers-color-scheme: dark) {
34
  :root {
35
  --primary: #3b82f6;
@@ -41,66 +40,60 @@ def get_academic_css() -> str:
41
  --border: #3a4a5a;
42
  --success: #34d399;
43
  --danger: #f87171;
44
-
45
- --table-bg: #1a2632;
46
- --table-bg-odd: #2a3745;
47
- --table-bg-even: #1e2b38;
48
- --table-bg-hover: #354454;
49
  --table-header: #4a5a6a;
50
  --table-header-light: #5a6a7a;
51
  --table-border: #3a4a5a;
 
 
 
52
  --best-score-bg: #1a3a3a;
53
  --best-score-color: #7fc9c9;
54
  --category-bg: #3a4a5a;
55
  --category-color: #cbd5e0;
56
  }
57
  }
58
-
59
  body {
60
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
61
  color: var(--text-primary);
62
  margin: 0;
63
  padding: 0;
64
- /* No background set - use system default */
65
  }
66
-
67
  .gradio-container {
68
  max-width: 1400px !important;
69
- /* No background set - inherit from system */
70
  color: var(--text-primary);
71
  }
72
-
73
  h1 {
74
  color: var(--primary) !important;
75
  font-weight: 700 !important;
76
  }
77
-
78
  .subtitle {
79
  color: var(--text-secondary);
80
  font-size: 1.1rem;
81
  margin-top: 0.5rem;
82
  opacity: 0.8;
83
  }
84
-
85
  .emoji {
86
  font-size: 1.5em;
87
  }
88
-
89
- /* Tables - Low saturation, with backgrounds only within table */
90
- .table-wrapper {
91
- margin: 1.5rem 0;
92
- overflow-x: auto;
93
- }
94
-
95
  .leaderboard-table {
96
  width: 100%;
97
  border-collapse: collapse;
98
  font-size: 0.9rem;
99
  margin-top: 1rem;
100
  border: 1px solid var(--table-border);
101
- background-color: var(--table-bg); /* Table background */
102
  }
103
-
104
  .leaderboard-table th {
105
  background: var(--table-header);
106
  color: white;
@@ -110,12 +103,17 @@ def get_academic_css() -> str:
110
  position: sticky;
111
  top: 0;
112
  border-right: 1px solid rgba(255,255,255,0.15);
 
113
  }
114
-
115
  .leaderboard-table th:last-child {
116
  border-right: none;
117
  }
118
-
 
 
 
 
119
  .leaderboard-table td {
120
  padding: 10px 8px;
121
  text-align: center;
@@ -123,36 +121,35 @@ def get_academic_css() -> str:
123
  border-right: 1px solid var(--table-border);
124
  color: var(--text-secondary);
125
  }
126
-
127
  .leaderboard-table td:last-child {
128
  border-right: none;
129
  }
130
-
131
- /* All rows have background colors */
132
  .leaderboard-table tr:nth-child(odd) {
133
  background-color: var(--table-bg-odd);
134
  }
135
-
136
  .leaderboard-table tr:nth-child(even) {
137
  background-color: var(--table-bg-even);
138
  }
139
-
140
  .leaderboard-table tr:hover {
141
  background-color: var(--table-bg-hover) !important;
142
  }
143
-
144
- /* Rank styles (override row colors) */
145
  .rank-1 { background: linear-gradient(135deg, #ffd700 0%, #ffed4a 100%) !important; font-weight: bold; color: #1a1a1a !important; }
146
  .rank-2 { background: linear-gradient(135deg, #c0c0c0 0%, #e5e7eb 100%) !important; font-weight: bold; color: #1a1a1a !important; }
147
  .rank-3 { background: linear-gradient(135deg, #cd7f32 0%, #fdba74 100%) !important; font-weight: bold; color: #1a1a1a !important; }
148
-
149
  /* Best score highlight */
150
  .best-score {
151
  font-weight: 700;
152
  color: var(--best-score-color);
153
  background: var(--best-score-bg) !important;
154
  }
155
-
156
  /* Category row (for group headers) */
157
  .category-row {
158
  background: var(--category-bg) !important;
@@ -161,25 +158,25 @@ def get_academic_css() -> str:
161
  text-align: left;
162
  font-size: 0.85rem;
163
  }
164
-
165
  .category-row td {
166
  padding: 10px 15px;
167
  color: var(--category-color);
168
  border-bottom: 2px solid var(--table-border);
169
  }
170
-
171
  .check {
172
  color: var(--success);
173
  font-weight: bold;
174
  font-size: 1.2em;
175
  }
176
-
177
  .cross {
178
  color: var(--danger);
179
  font-weight: bold;
180
  font-size: 1.2em;
181
  }
182
-
183
  .category-tag {
184
  display: inline-block;
185
  padding: 2px 8px;
@@ -190,7 +187,7 @@ def get_academic_css() -> str:
190
  .cat-text { background: #dbeafe; color: #1e40af; }
191
  .cat-onehot { background: #d1fae5; color: #065f46; }
192
  .cat-camera { background: #fef3c7; color: #92400e; }
193
-
194
  /* Links */
195
  .leaderboard-table a {
196
  color: var(--primary);
@@ -200,74 +197,26 @@ def get_academic_css() -> str:
200
  .leaderboard-table a:hover {
201
  opacity: 0.7;
202
  }
203
-
204
  /* Buttons */
205
  button.primary {
206
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%) !important;
207
  color: white !important;
208
  border: none;
209
  }
210
-
211
- /* Status indicators (hidden) */
212
- .status-success { color: var(--success); }
213
- .status-error { color: var(--danger); }
214
-
215
- table.dataframe {
216
- width: 100%;
217
- border-collapse: collapse;
218
- font-size: 0.9rem;
219
- border: 1px solid var(--table-border);
220
- background-color: var(--table-bg);
221
- }
222
-
223
- table.dataframe thead tr th {
224
- background: var(--table-header);
225
- color: white;
226
- padding: 12px 8px;
227
- text-align: center;
228
- font-weight: 600;
229
- position: sticky;
230
- top: 0;
231
- border-right: 1px solid rgba(255,255,255,0.15);
232
- }
233
-
234
- table.dataframe thead tr th:last-child {
235
- border-right: none;
236
- }
237
-
238
- table.dataframe tbody tr:nth-child(odd) {
239
- background-color: var(--table-bg-odd);
240
- }
241
-
242
- table.dataframe tbody tr:nth-child(even) {
243
- background-color: var(--table-bg-even);
244
- }
245
-
246
- table.dataframe tbody tr:hover {
247
- background-color: var(--table-bg-hover) !important;
248
- }
249
-
250
- table.dataframe td {
251
- padding: 10px 8px;
252
- text-align: center;
253
- border-bottom: 1px solid var(--table-border);
254
- border-right: 1px solid var(--table-border);
255
- color: var(--text-secondary);
256
- }
257
-
258
- table.dataframe td:last-child {
259
- border-right: none;
260
- }
261
-
262
 
 
 
 
263
  """
264
 
265
-
266
-
267
  def dataframe_to_html(df: pd.DataFrame) -> str:
268
  if df.empty:
269
  return "<div class='placeholder'>No data available</div>"
270
- html = ['<table class="leaderboard-table">']
 
 
 
271
  html.append("<thead><tr>")
272
  for col in df.columns:
273
  html.append(f"<th>{col}</th>")
 
3
  def get_academic_css() -> str:
4
  return """
5
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
6
+
7
  :root {
8
  /* Light mode (default) */
9
  --primary: #2563eb;
 
15
  --border: #e5e5e5;
16
  --success: #10b981;
17
  --danger: #ef4444;
18
+
19
+ /* Table colors */
 
 
 
 
20
  --table-header: #64748b;
21
  --table-header-light: #94a3b8;
22
  --table-border: #cbd5e0;
23
+ --table-bg-odd: #f8fafc;
24
+ --table-bg-even: #f1f5f9;
25
+ --table-bg-hover: #e2e8f0;
26
  --best-score-bg: #e6fffa;
27
  --best-score-color: #2c7a7b;
28
  --category-bg: #e2e8f0;
29
  --category-color: #475569;
30
  }
31
+
32
  @media (prefers-color-scheme: dark) {
33
  :root {
34
  --primary: #3b82f6;
 
40
  --border: #3a4a5a;
41
  --success: #34d399;
42
  --danger: #f87171;
43
+
 
 
 
 
44
  --table-header: #4a5a6a;
45
  --table-header-light: #5a6a7a;
46
  --table-border: #3a4a5a;
47
+ --table-bg-odd: #2a3745;
48
+ --table-bg-even: #1e2b38;
49
+ --table-bg-hover: #354454;
50
  --best-score-bg: #1a3a3a;
51
  --best-score-color: #7fc9c9;
52
  --category-bg: #3a4a5a;
53
  --category-color: #cbd5e0;
54
  }
55
  }
56
+
57
  body {
58
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
59
  color: var(--text-primary);
60
  margin: 0;
61
  padding: 0;
62
+ /* Background set by system */
63
  }
64
+
65
  .gradio-container {
66
  max-width: 1400px !important;
67
+ /* Background set by system */
68
  color: var(--text-primary);
69
  }
70
+
71
  h1 {
72
  color: var(--primary) !important;
73
  font-weight: 700 !important;
74
  }
75
+
76
  .subtitle {
77
  color: var(--text-secondary);
78
  font-size: 1.1rem;
79
  margin-top: 0.5rem;
80
  opacity: 0.8;
81
  }
82
+
83
  .emoji {
84
  font-size: 1.5em;
85
  }
86
+
87
+ /* Leaderboard table styling */
 
 
 
 
 
88
  .leaderboard-table {
89
  width: 100%;
90
  border-collapse: collapse;
91
  font-size: 0.9rem;
92
  margin-top: 1rem;
93
  border: 1px solid var(--table-border);
94
+ background-color: transparent;
95
  }
96
+
97
  .leaderboard-table th {
98
  background: var(--table-header);
99
  color: white;
 
103
  position: sticky;
104
  top: 0;
105
  border-right: 1px solid rgba(255,255,255,0.15);
106
+ cursor: pointer; /* Indicate sortable */
107
  }
108
+
109
  .leaderboard-table th:last-child {
110
  border-right: none;
111
  }
112
+
113
+ .leaderboard-table th:hover {
114
+ background-color: var(--table-header-light);
115
+ }
116
+
117
  .leaderboard-table td {
118
  padding: 10px 8px;
119
  text-align: center;
 
121
  border-right: 1px solid var(--table-border);
122
  color: var(--text-secondary);
123
  }
124
+
125
  .leaderboard-table td:last-child {
126
  border-right: none;
127
  }
128
+
 
129
  .leaderboard-table tr:nth-child(odd) {
130
  background-color: var(--table-bg-odd);
131
  }
132
+
133
  .leaderboard-table tr:nth-child(even) {
134
  background-color: var(--table-bg-even);
135
  }
136
+
137
  .leaderboard-table tr:hover {
138
  background-color: var(--table-bg-hover) !important;
139
  }
140
+
141
+ /* Rank styles */
142
  .rank-1 { background: linear-gradient(135deg, #ffd700 0%, #ffed4a 100%) !important; font-weight: bold; color: #1a1a1a !important; }
143
  .rank-2 { background: linear-gradient(135deg, #c0c0c0 0%, #e5e7eb 100%) !important; font-weight: bold; color: #1a1a1a !important; }
144
  .rank-3 { background: linear-gradient(135deg, #cd7f32 0%, #fdba74 100%) !important; font-weight: bold; color: #1a1a1a !important; }
145
+
146
  /* Best score highlight */
147
  .best-score {
148
  font-weight: 700;
149
  color: var(--best-score-color);
150
  background: var(--best-score-bg) !important;
151
  }
152
+
153
  /* Category row (for group headers) */
154
  .category-row {
155
  background: var(--category-bg) !important;
 
158
  text-align: left;
159
  font-size: 0.85rem;
160
  }
161
+
162
  .category-row td {
163
  padding: 10px 15px;
164
  color: var(--category-color);
165
  border-bottom: 2px solid var(--table-border);
166
  }
167
+
168
  .check {
169
  color: var(--success);
170
  font-weight: bold;
171
  font-size: 1.2em;
172
  }
173
+
174
  .cross {
175
  color: var(--danger);
176
  font-weight: bold;
177
  font-size: 1.2em;
178
  }
179
+
180
  .category-tag {
181
  display: inline-block;
182
  padding: 2px 8px;
 
187
  .cat-text { background: #dbeafe; color: #1e40af; }
188
  .cat-onehot { background: #d1fae5; color: #065f46; }
189
  .cat-camera { background: #fef3c7; color: #92400e; }
190
+
191
  /* Links */
192
  .leaderboard-table a {
193
  color: var(--primary);
 
197
  .leaderboard-table a:hover {
198
  opacity: 0.7;
199
  }
200
+
201
  /* Buttons */
202
  button.primary {
203
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%) !important;
204
  color: white !important;
205
  border: none;
206
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ /* Status indicators (hidden) */
209
+ .status-success { color: var(--success); }
210
+ .status-error { color: var(--danger); }
211
  """
212
 
 
 
213
  def dataframe_to_html(df: pd.DataFrame) -> str:
214
  if df.empty:
215
  return "<div class='placeholder'>No data available</div>"
216
+
217
+ # Include sorttable.js for column sorting
218
+ html = ['<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>']
219
+ html.append('<table class="leaderboard-table sortable">')
220
  html.append("<thead><tr>")
221
  for col in df.columns:
222
  html.append(f"<th>{col}</th>")