Fancy-yousa commited on
Commit
3ac33b9
·
verified ·
1 Parent(s): 7f554a6

Upload 12 files

Browse files
Webapp/__pycache__/app.cpython-311.pyc ADDED
Binary file (41.5 kB). View file
 
Webapp/__pycache__/app.cpython-37.pyc ADDED
Binary file (18.4 kB). View file
 
Webapp/app.py CHANGED
@@ -1,41 +1,50 @@
1
  import os
2
  import sys
3
- import pickle
4
  import json
5
  import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from flask import Flask, jsonify, request, render_template, send_from_directory
 
 
 
7
 
8
- # Add project root to sys.path to import leaderboard
9
  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
10
  from leaderboard import rank_results
11
- # ===============================
12
- # 基本路径配置
13
- # ===============================
14
- PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
 
 
 
 
 
15
  RESULT_DIR = os.path.join(PROJECT_ROOT, "results")
16
- DATASET_DIR = os.path.join(PROJECT_ROOT, "datasets")
17
  PDF_DIR = os.path.join(PROJECT_ROOT, "pdf")
18
 
19
  os.makedirs(RESULT_DIR, exist_ok=True)
20
- os.makedirs(DATASET_DIR, exist_ok=True)
21
 
22
- # ===============================
23
- # Flask App
24
- # ===============================
25
- app = Flask(__name__)
26
 
27
- # ===============================
28
- # 内存缓存
29
- # ===============================
30
- RESULT_CACHE = {} # {dataset_name: results}
31
 
32
 
33
- # ===============================
34
- # PKL IO 工具
35
- # ===============================
36
  def save_result_json(dataset, results):
37
  path = os.path.join(RESULT_DIR, f"{dataset}.json")
38
- with open(path, "w", encoding='utf-8') as f:
39
  json.dump(results, f, indent=4)
40
 
41
 
@@ -43,245 +52,1026 @@ def load_result_json(dataset):
43
  path = os.path.join(RESULT_DIR, f"{dataset}.json")
44
  if not os.path.exists(path):
45
  return None
46
- with open(path, 'r', encoding='utf-8') as f:
47
  return json.load(f)
48
 
49
 
50
  def list_available_datasets():
51
  datasets = set()
52
-
53
  for f in os.listdir(RESULT_DIR):
54
  if f.endswith(".json"):
55
  datasets.add(f.replace(".json", ""))
56
-
57
- # 默认展示数据集
58
  datasets.add("Authorship")
59
-
60
  return sorted(datasets)
61
 
62
 
63
- # ===============================
64
- # ⚠️ 你自己的 Agent 入口
65
- # ===============================
66
  def run_agent_for_dataset(dataset):
67
- """
68
- ⚠️ 用你自己的 router / agent 替换这里
69
- 必须返回 List[Dict]
70
- """
71
- # ---------------------------
72
- # 示例(占位)
73
- # ---------------------------
74
- return [
75
-
76
- ]
77
- # {
78
- # "algorithm": "shibaile",
79
- # "num_features": 15,
80
- # "mean_f1": 0.9233716475,
81
- # "mean_auc": 0.9816098520,
82
- # "time": 5.75,
83
- # "score": 0.9408431088,
84
- # },
85
- # {
86
- # "algorithm": "JMchongxinzailai",
87
- # "num_features": 15,
88
- # "mean_f1": 0.918,
89
- # "mean_auc": 0.979,
90
- # "time": 7.32,
91
- # "score": 0.932,
92
- # },
93
-
94
- # ===============================
95
- # 页面
96
- # ===============================
97
- @app.route("/")
98
- def index():
99
- return render_template(
100
- "index.html",
101
- datasets=list_available_datasets(),
102
- default_dataset="Authorship",
103
- )
104
 
105
 
106
- @app.route("/global")
107
- def global_view():
108
- return render_template("global.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
 
111
- # ===============================
112
- # API:获取结果
113
- # ===============================
114
- @app.route("/api/results")
115
- def get_results():
116
- dataset = request.args.get("dataset", "Authorship")
117
-
118
- # 内存缓存 (Disabled for debugging)
119
- # if dataset in RESULT_CACHE:
120
- # print("------------------------------------------------------------------zoude cache")
121
- # return jsonify(RESULT_CACHE[dataset])
122
-
123
- # ② 磁盘 pkl/json
124
- results = load_result_json(dataset)
125
- print(111,results)
126
- if results is not None:
127
- print("------------------------------------------------------------------zoude json\n",results)
128
- RESULT_CACHE[dataset] = results
129
- leaderboard = rank_results(results)
130
- return jsonify(leaderboard)
131
-
132
- # ③ Agent 实时运行
133
- results = run_agent_for_dataset(dataset)
134
- print(222,results)
135
-
136
- # 存储
137
- if results and len(results) > 0:
138
- save_result_json(dataset, results)
139
- RESULT_CACHE[dataset] = results
140
-
141
- print("------------------------------------------------------------------zoude agent")
142
- leaderboard = rank_results(results)
143
-
144
- # Ensure leaderboard is a list
 
 
 
 
 
 
 
 
 
 
 
 
145
  if not isinstance(leaderboard, list):
146
- print(f"[WARNING] rank_results returned {type(leaderboard)}, converting to list")
147
- if hasattr(leaderboard, 'to_dict'): # Pandas DataFrame
148
- leaderboard = leaderboard.to_dict(orient='records')
149
  else:
150
  leaderboard = list(leaderboard)
 
151
 
152
- return jsonify(leaderboard)
153
- # print(333,leaderboard)
154
- # @app.route("/api/results")
155
- # def get_results():
156
- dataset = request.args.get("dataset", "Authorship")
157
 
158
- print(f"[DEBUG] request dataset = {dataset}")
 
 
 
 
 
 
 
159
 
160
- if dataset in RESULT_CACHE:
161
- print("[DEBUG] hit memory cache")
162
- return jsonify(RESULT_CACHE[dataset])
163
 
164
- results = load_result_pkl(dataset)
165
- if results is not None:
166
- print("[DEBUG] hit pkl cache")
167
- RESULT_CACHE[dataset] = results
168
- return jsonify(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- print("[DEBUG] run agent")
171
- results = run_agent_for_dataset(dataset)
172
- print("[DEBUG] agent results =", results)
173
 
174
- save_result_pkl(dataset, results)
175
- RESULT_CACHE[dataset] = results
176
- return jsonify(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
- # ===============================
180
- # API:数据集列表
181
- # ===============================
182
- @app.route("/api/datasets")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  def api_datasets():
184
  try:
185
  datasets = []
186
- ds_names = list_available_datasets()
187
- for name in ds_names:
188
- # Get modification time of the result file
189
- result_path = os.path.join(RESULT_DIR, f"{name}.json")
190
- last_updated = "Unknown"
191
- if os.path.exists(result_path):
192
- mtime = os.path.getmtime(result_path)
193
- last_updated = datetime.datetime.fromtimestamp(mtime).strftime('%Y-%m-%d')
194
-
195
  datasets.append({
196
  "name": name,
197
- "last_updated": last_updated
 
 
198
  })
199
- return jsonify(datasets)
200
  except Exception as e:
201
- import traceback
202
- traceback.print_exc()
203
- return jsonify({"error": str(e)}), 500
204
-
205
-
206
- @app.route("/api/global_stats")
207
- def get_global_stats():
208
- # 1. Load all datasets
209
- all_datasets = list_available_datasets()
210
-
211
- # 2. Aggregate stats by algorithm
212
- # Structure: { "AlgoName": { "f1_sum": 0, "auc_sum": 0, "time_sum": 0, "count": 0 } }
213
- agg_stats = {}
214
-
215
- for ds_name in all_datasets:
216
- results = load_result_json(ds_name)
217
- if not results:
218
- continue
219
-
220
- # Ensure results is a list (reuse logic from get_results)
221
- if not isinstance(results, list):
222
- if hasattr(results, 'to_dict'):
223
- results = results.to_dict(orient='records')
224
- elif isinstance(results, dict) and 'data' in results:
225
- results = results['data']
226
- elif isinstance(results, dict) and 'results' in results:
227
- results = results['results']
228
- elif isinstance(results, dict) and 'algorithm' in results:
229
- results = [results]
230
- else:
231
- continue # Skip invalid data
232
-
233
- for res in results:
234
- algo = res.get("algorithm", "Unknown")
235
-
236
- # Extract metrics
237
- # Assuming rank_results logic: mean_f1, mean_auc might be pre-calculated in JSON
238
- # or we calculate them on the fly if missing
239
- metrics = res.get("metrics", {})
240
-
241
- mean_f1 = res.get("mean_f1")
242
- mean_auc = res.get("mean_auc")
243
-
244
- # If mean values missing, calculate from metrics
245
- if mean_f1 is None and metrics:
246
- f1s = [m.get("f1", 0) for m in metrics.values() if isinstance(m, dict)]
247
- mean_f1 = sum(f1s) / len(f1s) if f1s else 0
248
-
249
- if mean_auc is None and metrics:
250
- aucs = [m.get("auc", 0) for m in metrics.values() if isinstance(m, dict)]
251
- mean_auc = sum(aucs) / len(aucs) if aucs else 0
252
-
253
- time_val = res.get("time", 0)
254
-
255
- if algo not in agg_stats:
256
- agg_stats[algo] = {"f1_sum": 0.0, "auc_sum": 0.0, "time_sum": 0.0, "count": 0}
257
-
258
- agg_stats[algo]["f1_sum"] += float(mean_f1 or 0)
259
- agg_stats[algo]["auc_sum"] += float(mean_auc or 0)
260
- agg_stats[algo]["time_sum"] += float(time_val or 0)
261
- agg_stats[algo]["count"] += 1
262
-
263
- # 3. Calculate averages
264
- final_list = []
265
- for algo, stats in agg_stats.items():
266
- count = stats["count"]
267
- if count == 0: continue
268
-
269
- final_list.append({
270
- "algorithm": algo,
271
- "mean_f1_global": stats["f1_sum"] / count,
272
- "mean_auc_global": stats["auc_sum"] / count,
273
- "mean_time_global": stats["time_sum"] / count,
274
- "datasets_count": count
275
- })
276
-
277
- return jsonify(final_list)
278
-
279
-
280
- @app.route("/pdfs/<path:filename>")
281
  def serve_pdf(filename):
282
  return send_from_directory(PDF_DIR, filename)
283
 
284
 
285
  if __name__ == "__main__":
286
- port = int(os.environ.get("PORT", 7860))
 
287
  app.run(host="0.0.0.0", port=port, debug=False)
 
1
  import os
2
  import sys
 
3
  import json
4
  import datetime
5
+ import math
6
+ try:
7
+ import scipy.io
8
+ except ImportError:
9
+ scipy = None
10
+ try:
11
+ import numpy as np
12
+ except ImportError:
13
+ np = None
14
+ try:
15
+ import pandas as pd
16
+ except ImportError:
17
+ pd = None
18
  from flask import Flask, jsonify, request, render_template, send_from_directory
19
+ from dash import Dash, html, dcc, dash_table, Input, Output, State, callback_context
20
+ import dash_mantine_components as dmc
21
+ import plotly.graph_objects as go
22
 
 
23
  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
24
  from leaderboard import rank_results
25
+ try:
26
+ from complex_com import algorithms as ALGO_COMPLEXITY
27
+ except ImportError:
28
+ ALGO_COMPLEXITY = {}
29
+
30
+ base_dir = os.getcwd()
31
+ if not os.path.isdir(os.path.join(base_dir, "results")):
32
+ base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
33
+ PROJECT_ROOT = base_dir
34
  RESULT_DIR = os.path.join(PROJECT_ROOT, "results")
35
+ DATA_DIR = os.path.join(PROJECT_ROOT, "data")
36
  PDF_DIR = os.path.join(PROJECT_ROOT, "pdf")
37
 
38
  os.makedirs(RESULT_DIR, exist_ok=True)
 
39
 
40
+ server = Flask(__name__)
 
 
 
41
 
42
+ RESULT_CACHE = {}
 
 
 
43
 
44
 
 
 
 
45
  def save_result_json(dataset, results):
46
  path = os.path.join(RESULT_DIR, f"{dataset}.json")
47
+ with open(path, "w", encoding="utf-8") as f:
48
  json.dump(results, f, indent=4)
49
 
50
 
 
52
  path = os.path.join(RESULT_DIR, f"{dataset}.json")
53
  if not os.path.exists(path):
54
  return None
55
+ with open(path, "r", encoding="utf-8") as f:
56
  return json.load(f)
57
 
58
 
59
  def list_available_datasets():
60
  datasets = set()
 
61
  for f in os.listdir(RESULT_DIR):
62
  if f.endswith(".json"):
63
  datasets.add(f.replace(".json", ""))
 
 
64
  datasets.add("Authorship")
 
65
  return sorted(datasets)
66
 
67
 
 
 
 
68
  def run_agent_for_dataset(dataset):
69
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
 
72
+ def build_dataset_metadata():
73
+ datasets = {}
74
+ for name in list_available_datasets():
75
+ last_updated = datetime.datetime.fromtimestamp(1707382400).strftime("%Y-%m-%d")
76
+ num_samples = None
77
+ total_features = None
78
+ if scipy:
79
+ mat_path = os.path.join(DATA_DIR, f"{name}.mat")
80
+ if os.path.exists(mat_path):
81
+ try:
82
+ mat = scipy.io.loadmat(mat_path)
83
+ if "X" in mat:
84
+ X = mat["X"]
85
+ num_samples, total_features = X.shape
86
+ except Exception:
87
+ num_samples = None
88
+ total_features = None
89
+ datasets[name] = {
90
+ "name": name,
91
+ "last_updated": last_updated,
92
+ "num_samples": num_samples,
93
+ "total_features": total_features,
94
+ }
95
+ return datasets
96
+
97
+
98
+ DATASET_METADATA = build_dataset_metadata()
99
 
100
 
101
+ def build_complexity_display():
102
+ display_complexity = {}
103
+ for algo, comp in ALGO_COMPLEXITY.items():
104
+ t = comp.get("time", "")
105
+ s = comp.get("space", "")
106
+ t_disp = t.replace("**", "^").replace(" * ", "")
107
+ if "O(" not in t_disp:
108
+ t_disp = f"O({t_disp})" if t_disp else ""
109
+ s_disp = s.replace("**", "^").replace(" * ", "")
110
+ if "O(" not in s_disp:
111
+ s_disp = f"O({s_disp})" if s_disp else ""
112
+ display_complexity[algo] = {"time": t_disp, "space": s_disp}
113
+ return display_complexity
114
+
115
+
116
+ DISPLAY_COMPLEXITY = build_complexity_display()
117
+
118
+ VIEW_CONFIG = {
119
+ "overall": [
120
+ {"key": "mean_f1", "label": "Mean F1"},
121
+ {"key": "mean_auc", "label": "Mean AUC"},
122
+ ],
123
+ "classifiers-f1": [
124
+ {"key": "metrics.nb.f1", "label": "NB F1"},
125
+ {"key": "metrics.svm.f1", "label": "SVM F1"},
126
+ {"key": "metrics.rf.f1", "label": "RF F1"},
127
+ ],
128
+ "classifiers-auc": [
129
+ {"key": "metrics.nb.auc", "label": "NB AUC"},
130
+ {"key": "metrics.svm.auc", "label": "SVM AUC"},
131
+ {"key": "metrics.rf.auc", "label": "RF AUC"},
132
+ ],
133
+ }
134
+
135
+
136
+ def get_results_for_dataset(dataset):
137
+ if dataset in RESULT_CACHE:
138
+ leaderboard = rank_results(RESULT_CACHE[dataset])
139
+ else:
140
+ results = load_result_json(dataset)
141
+ if results is None:
142
+ results = run_agent_for_dataset(dataset)
143
+ if results:
144
+ save_result_json(dataset, results)
145
+ RESULT_CACHE[dataset] = results or []
146
+ leaderboard = rank_results(results or [])
147
  if not isinstance(leaderboard, list):
148
+ if hasattr(leaderboard, "to_dict"):
149
+ leaderboard = leaderboard.to_dict(orient="records")
 
150
  else:
151
  leaderboard = list(leaderboard)
152
+ return leaderboard
153
 
 
 
 
 
 
154
 
155
+ def get_metric_value(row, key):
156
+ value = row
157
+ for part in key.split("."):
158
+ if isinstance(value, dict):
159
+ value = value.get(part)
160
+ else:
161
+ return None
162
+ return value
163
 
 
 
 
164
 
165
+ def get_feature_count(row):
166
+ num_features = row.get("num_features")
167
+ if isinstance(num_features, (int, float)):
168
+ return int(num_features)
169
+ selected = row.get("selected_features")
170
+ if isinstance(selected, list):
171
+ return len(selected)
172
+ return 0
173
+
174
+
175
+ def apply_filters(results, dataset_meta, min_f1, max_features, del_range, complexity, selected_algos):
176
+ total_features = dataset_meta.get("total_features") if dataset_meta else None
177
+ filtered = []
178
+ min_del = (del_range[0] if del_range else 0) / 100
179
+ max_del = (del_range[1] if del_range else 100) / 100
180
+ min_f1 = min_f1 if min_f1 is not None else 0
181
+ max_features = max_features if max_features is not None else float("inf")
182
+ selected_algos = selected_algos if selected_algos else None
183
+ for r in results:
184
+ algo = r.get("algorithm")
185
+ if selected_algos and algo not in selected_algos:
186
+ continue
187
+ raw_f1 = r.get("mean_f1")
188
+ try:
189
+ f1 = float(raw_f1) if raw_f1 is not None else 0
190
+ except (TypeError, ValueError):
191
+ f1 = 0
192
+ if f1 < min_f1:
193
+ continue
194
+ feats = get_feature_count(r)
195
+ if feats > max_features:
196
+ continue
197
+ if isinstance(total_features, (int, float)) and total_features > 0:
198
+ del_rate = 1 - (feats / total_features)
199
+ if del_rate < min_del or del_rate > max_del:
200
+ continue
201
+ if complexity and complexity != "all":
202
+ comp = DISPLAY_COMPLEXITY.get(algo, {}).get("time")
203
+ if comp != complexity:
204
+ continue
205
+ filtered.append(r)
206
+ return filtered
207
+
208
+
209
+ def build_score_figure(results, view_mode):
210
+ if not results:
211
+ fig = go.Figure()
212
+ fig.add_annotation(
213
+ text="No Data Available",
214
+ x=0.5,
215
+ y=0.5,
216
+ xref="paper",
217
+ yref="paper",
218
+ showarrow=False,
219
+ font=dict(size=16, color="#999"),
220
+ )
221
+ fig.update_layout(
222
+ xaxis=dict(visible=False),
223
+ yaxis=dict(visible=False),
224
+ margin=dict(l=20, r=20, t=20, b=20),
225
+ )
226
+ return fig
227
+ top = results[:20]
228
+ labels = [r.get("algorithm") for r in top]
229
+ fig = go.Figure()
230
+ if view_mode == "overall":
231
+ fig.add_trace(go.Bar(
232
+ name="Mean F1",
233
+ y=labels,
234
+ x=[r.get("mean_f1") for r in top],
235
+ orientation="h",
236
+ marker_color="rgba(52, 152, 219, 0.7)",
237
+ ))
238
+ fig.add_trace(go.Bar(
239
+ name="Mean AUC",
240
+ y=labels,
241
+ x=[r.get("mean_auc") for r in top],
242
+ orientation="h",
243
+ marker_color="rgba(46, 204, 113, 0.7)",
244
+ ))
245
+ elif view_mode == "classifiers-f1":
246
+ for idx, clf in enumerate(["nb", "svm", "rf"]):
247
+ fig.add_trace(go.Bar(
248
+ name=clf.upper(),
249
+ y=labels,
250
+ x=[get_metric_value(r, f"metrics.{clf}.f1") for r in top],
251
+ orientation="h",
252
+ marker_color=f"hsla({200 + idx * 40}, 70%, 60%, 0.7)",
253
+ ))
254
+ else:
255
+ for idx, clf in enumerate(["nb", "svm", "rf"]):
256
+ fig.add_trace(go.Bar(
257
+ name=clf.upper(),
258
+ y=labels,
259
+ x=[get_metric_value(r, f"metrics.{clf}.auc") for r in top],
260
+ orientation="h",
261
+ marker_color=f"hsla({100 + idx * 40}, 70%, 60%, 0.7)",
262
+ ))
263
+ fig.update_layout(
264
+ barmode="group",
265
+ margin=dict(l=20, r=20, t=20, b=20),
266
+ legend=dict(orientation="h"),
267
+ yaxis=dict(autorange="reversed"),
268
+ )
269
+ return fig
270
+
271
+
272
+ def build_pareto_figure(results):
273
+ x_vals = [get_feature_count(r) for r in results]
274
+ y_vals = [r.get("mean_f1") for r in results]
275
+ labels = [r.get("algorithm") for r in results]
276
+ fig = go.Figure()
277
+ fig.add_trace(go.Scatter(
278
+ x=x_vals,
279
+ y=y_vals,
280
+ mode="markers",
281
+ text=labels,
282
+ marker=dict(color="rgba(230, 126, 34, 0.7)", size=10),
283
+ ))
284
+ fig.update_layout(
285
+ margin=dict(l=20, r=20, t=20, b=20),
286
+ xaxis_title="Num Features",
287
+ yaxis_title="Mean F1",
288
+ )
289
+ return fig
290
+
291
+
292
+ def build_table(results, view_mode):
293
+ config = VIEW_CONFIG[view_mode]
294
+ headers = ["Rank", "Algorithm"] + [c["label"] for c in config] + ["Selected Features"]
295
+ col_keys = [c["key"] for c in config]
296
+ max_map = {}
297
+ for key in col_keys:
298
+ vals = []
299
+ for r in results:
300
+ v = get_metric_value(r, key)
301
+ try:
302
+ v = float(v) if v is not None else None
303
+ except Exception:
304
+ v = None
305
+ if v is not None:
306
+ vals.append(v)
307
+ max_map[key] = max(vals) if vals else 0
308
+ thead = html.Thead(
309
+ html.Tr([html.Th(h, className="cth") for h in headers], className="chead")
310
+ )
311
+ rows = []
312
+ if not results:
313
+ empty_cells = [html.Td("", className="ctd")] * (len(headers) - 2)
314
+ rows.append(
315
+ html.Tr(
316
+ [html.Td("", className="ctd"), html.Td("No Data Available", className="ctd")]+empty_cells,
317
+ className="crow"
318
+ )
319
+ )
320
+ else:
321
+ for idx, r in enumerate(results):
322
+ rank = idx + 1
323
+ medal = {1: "🥇", 2: "🥈", 3: "🥉"}.get(rank, str(rank))
324
+ row_class = (
325
+ "crow crow-gold" if rank == 1 else
326
+ "crow crow-silver" if rank == 2 else
327
+ "crow crow-bronze" if rank == 3 else
328
+ "crow"
329
+ )
330
+ algo = r.get("algorithm") or "Unknown"
331
+ metric_tds = []
332
+ for c in config:
333
+ key = c["key"]
334
+ raw = get_metric_value(r, key)
335
+ try:
336
+ val = float(raw) if raw is not None else 0.0
337
+ except Exception:
338
+ val = 0.0
339
+ m = max_map.get(key) or 0.0
340
+ pct = (val / m * 100.0) if m > 0 else 0
341
+ is_max = (m > 0 and abs(val - m) < 1e-12)
342
+ bar = html.Div(
343
+ [
344
+ html.Div(className="bar-track", children=html.Div(className="bar-fill", style={"width": f"{pct:.2f}%"})),
345
+ html.Span(f"{val:.4f}", className=("bar-text is-max" if is_max else "bar-text")),
346
+ ],
347
+ className="bar-cell",
348
+ title=f"max={m:.4f}" if is_max else None,
349
+ )
350
+ cell = html.Td(bar, className="ctd cnum")
351
+ metric_tds.append(cell)
352
+ selected = r.get("selected_features")
353
+ feat_count = get_feature_count(r)
354
+ if isinstance(selected, list):
355
+ features_title = ", ".join(str(s) for s in selected)
356
+ else:
357
+ features_title = "N/A"
358
+ feature_td = html.Td(
359
+ f"{feat_count} features",
360
+ className="ctd cfeat",
361
+ title=features_title,
362
+ style={"whiteSpace": "nowrap"},
363
+ )
364
+ row = html.Tr(
365
+ [html.Td(medal, className="ctd crank"), html.Td(algo, className="ctd calgo")] + metric_tds + [feature_td],
366
+ className=row_class,
367
+ )
368
+ rows.append(row)
369
+ tbody = html.Tbody(rows)
370
+ table = html.Table([thead, tbody], className="custom-table")
371
+ return html.Div(className="table-container", children=table)
372
+
373
+
374
+ dataset_options = [{"label": name, "value": name} for name in sorted(DATASET_METADATA.keys())]
375
+ default_dataset = "Authorship" if "Authorship" in DATASET_METADATA else (dataset_options[0]["value"] if dataset_options else "Authorship")
376
+
377
+ complexity_options = sorted({v.get("time") for v in DISPLAY_COMPLEXITY.values() if v.get("time")})
378
+ complexity_data = [{"label": "All Complexities", "value": "all"}] + [
379
+ {"label": c, "value": c} for c in complexity_options
380
+ ]
381
 
382
+ dash_app = Dash(__name__, server=server, url_base_pathname="/")
383
+ app = dash_app
 
384
 
385
+ css = """
386
+ :root {
387
+ --primary-color: #3498db;
388
+ --secondary-color: #2c3e50;
389
+ --background-color: #f8f9fa;
390
+ --text-color: #333;
391
+ --border-color: #dee2e6;
392
+ --hover-color: #f1f1f1;
393
+ --accent-color: #e67e22;
394
+ --sidebar-width: 280px;
395
+ }
396
+ body {
397
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
398
+ margin: 0;
399
+ padding: 0;
400
+ background-color: var(--background-color);
401
+ color: var(--text-color);
402
+ }
403
+ .app-shell {
404
+ display: flex;
405
+ min-height: 100vh;
406
+ }
407
+ .sidebar {
408
+ width: var(--sidebar-width);
409
+ background-color: var(--secondary-color);
410
+ color: white;
411
+ position: fixed;
412
+ height: 100vh;
413
+ overflow-y: auto;
414
+ padding: 20px;
415
+ box-sizing: border-box;
416
+ left: 0;
417
+ top: 0;
418
+ z-index: 100;
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: 20px;
422
+ }
423
+ .sidebar h2 {
424
+ font-size: 1.1em;
425
+ margin-bottom: 10px;
426
+ color: #ecf0f1;
427
+ border-bottom: 1px solid #34495e;
428
+ padding-bottom: 5px;
429
+ }
430
+ .main-content {
431
+ margin-left: var(--sidebar-width);
432
+ padding: 24px;
433
+ width: calc(100% - var(--sidebar-width));
434
+ box-sizing: border-box;
435
+ }
436
+ .stats-grid {
437
+ display: grid;
438
+ grid-template-columns: 1fr;
439
+ gap: 10px;
440
+ }
441
+ .stat-card {
442
+ background: rgba(255,255,255,0.1);
443
+ padding: 10px;
444
+ border-radius: 6px;
445
+ text-align: center;
446
+ }
447
+ .stat-value {
448
+ font-size: 1.2em;
449
+ font-weight: 600;
450
+ color: var(--accent-color);
451
+ }
452
+ .stat-label {
453
+ font-size: 0.8em;
454
+ color: #bdc3c7;
455
+ }
456
+ .card {
457
+ background: white;
458
+ padding: 16px;
459
+ border-radius: 8px;
460
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
461
+ }
462
+ .chart-card {
463
+ background: white;
464
+ padding: 16px;
465
+ border-radius: 8px;
466
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
467
+ height: 420px;
468
+ display: flex;
469
+ flex-direction: column;
470
+ }
471
+ .chart-card .dash-graph {
472
+ flex: 1;
473
+ }
474
+ .table-container {
475
+ background: white;
476
+ padding: 12px;
477
+ border-radius: 8px;
478
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
479
+ }
480
+ .nav-links {
481
+ list-style: none;
482
+ padding: 0;
483
+ margin: 0;
484
+ }
485
+ .nav-links li a {
486
+ display: block;
487
+ padding: 8px;
488
+ color: #bdc3c7;
489
+ text-decoration: none;
490
+ border-radius: 4px;
491
+ }
492
+ .nav-links li a:hover {
493
+ background: rgba(255,255,255,0.1);
494
+ color: white;
495
+ }
496
 
497
+ /* Custom Table Styles */
498
+ .custom-table-wrapper {
499
+ overflow-x: auto;
500
+ overflow-y: auto;
501
+ max-height: 520px;
502
+ background: #fff;
503
+ border: 1px solid #eee;
504
+ border-radius: 8px;
505
+ box-shadow: 0 1px 3px rgba(0,0,0,0.06);
506
+ }
507
+ .custom-table {
508
+ width: 100%;
509
+ border-collapse: separate;
510
+ border-spacing: 0;
511
+ table-layout: fixed;
512
+ }
513
+ .custom-table thead th {
514
+ text-align: left;
515
+ border-bottom: 2px solid var(--border-color);
516
+ padding: 10px;
517
+ }
518
+ .custom-table tbody td {
519
+ padding: 8px 10px;
520
+ border-bottom: 1px solid #eee;
521
+ vertical-align: middle;
522
+ }
523
+ .custom-table tbody tr:hover {
524
+ background: #fafafa;
525
+ transition: background 0.2s ease;
526
+ }
527
+ .custom-table tbody tr:nth-child(even) { background: #fcfcfc; }
528
+ .custom-table thead th {
529
+ position: sticky;
530
+ top: 0;
531
+ background: #fff;
532
+ z-index: 1;
533
+ }
534
+ .cnum { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; text-align: right; }
535
+ .is-max { font-weight: 600; color: var(--accent-color); }
536
+ .crow-gold { background: rgba(255,215,0,0.12); }
537
+ .crow-silver { background: rgba(192,192,192,0.12); }
538
+ .crow-bronze { background: rgba(205,127,50,0.12); }
539
+ .crank { width: 56px; text-align: center; }
540
+ .calgo { font-weight: 500; }
541
+ .cth { background: var(--background-color); }
542
+ .cfeat { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
543
 
544
+ /* In-cell Data Bars */
545
+ .bar-cell {
546
+ position: relative;
547
+ height: 26px;
548
+ display: flex;
549
+ align-items: center;
550
+ justify-content: flex-end;
551
+ }
552
+ .bar-track {
553
+ position: absolute;
554
+ left: 6px;
555
+ right: 6px;
556
+ height: 60%;
557
+ background: #f4f7fb;
558
+ border: 1px solid #e6eef7;
559
+ border-radius: 6px;
560
+ overflow: hidden;
561
+ }
562
+ .bar-fill {
563
+ height: 100%;
564
+ background: linear-gradient(90deg, #7db9e8 0%, #3498db 100%);
565
+ opacity: 0.35;
566
+ transition: width 200ms ease;
567
+ }
568
+ .bar-text {
569
+ position: relative;
570
+ z-index: 1;
571
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
572
+ padding: 0 6px;
573
+ line-height: 1;
574
+ }
575
+ """
576
+
577
+ dash_app.index_string = f"""
578
+ <!DOCTYPE html>
579
+ <html>
580
+ <head>
581
+ {{%metas%}}
582
+ <title>AutoFS Leaderboard</title>
583
+ {{%favicon%}}
584
+ {{%css%}}
585
+ <style>{css}</style>
586
+ </head>
587
+ <body>
588
+ {{%app_entry%}}
589
+ <footer>
590
+ {{%config%}}
591
+ {{%scripts%}}
592
+ {{%renderer%}}
593
+ </footer>
594
+ </body>
595
+ </html>
596
+ """
597
+
598
+ dash_app.layout = dmc.MantineProvider(
599
+ children=html.Div(
600
+ className="app-shell",
601
+ children=[
602
+ html.Aside(
603
+ className="sidebar",
604
+ children=[
605
+ html.Div(
606
+ [
607
+ html.H1("AutoFS", style={"fontSize": "1.5em", "margin": 0, "color": "white"}),
608
+ html.Div("Feature Selection Leaderboard", style={"fontSize": "0.8em", "color": "#bdc3c7"}),
609
+ ],
610
+ style={"textAlign": "center", "marginBottom": "10px"},
611
+ ),
612
+ html.Div(
613
+ className="stats-grid",
614
+ children=[
615
+ html.Div([html.Div("-", id="stat-count", className="stat-value"), html.Div("Methods", className="stat-label")], className="stat-card"),
616
+ html.Div([html.Div("-", id="stat-best", className="stat-value"), html.Div("Best F1", className="stat-label")], className="stat-card"),
617
+ html.Div([html.Div("-", id="stat-updated", className="stat-value"), html.Div("Updated", className="stat-label")], className="stat-card"),
618
+ ],
619
+ ),
620
+ html.Div(
621
+ [
622
+ html.H2("Navigation"),
623
+ html.Ul(
624
+ className="nav-links",
625
+ children=[
626
+ html.Li(html.A("📊 Overview", href="#overview")),
627
+ html.Li(html.A("🏆 Leaderboard", href="#main-table")),
628
+ html.Li(html.A("📈 Charts", href="#charts")),
629
+ html.Li(html.A("ℹ️ Details", href="#details")),
630
+ html.Li(html.A("🌍 Global Rankings", href="/global")),
631
+ html.Li(html.A("📤 Submit Data/Method", href="/submit")),
632
+ ],
633
+ ),
634
+ ]
635
+ ),
636
+ html.Div(
637
+ [
638
+ html.H2("Global Controls"),
639
+ dmc.Select(
640
+ id="view-mode",
641
+ data=[
642
+ {"label": "Overall (Mean)", "value": "overall"},
643
+ {"label": "F1 by Classifier", "value": "classifiers-f1"},
644
+ {"label": "AUC by Classifier", "value": "classifiers-auc"},
645
+ ],
646
+ value="overall",
647
+ clearable=False,
648
+ style={"marginBottom": "10px"},
649
+ ),
650
+ ]
651
+ ),
652
+ html.Div(
653
+ [
654
+ html.H2("Filters"),
655
+ dmc.Select(
656
+ id="dataset-select",
657
+ data=dataset_options,
658
+ value="Authorship",
659
+ clearable=False,
660
+ style={"marginBottom": "10px"},
661
+ ),
662
+ html.Div(
663
+ [
664
+ html.Div(
665
+ [
666
+ html.Span("Min F1 Score: "),
667
+ html.Span("0.0000", id="val-f1", style={"color": "var(--accent-color)"}),
668
+ ],
669
+ style={"marginBottom": "6px", "color": "#bdc3c7"},
670
+ ),
671
+ dmc.Slider(id="filter-f1", min=0, max=1, step=0.0001, value=0),
672
+ ],
673
+ style={"marginBottom": "12px"},
674
+ ),
675
+ html.Div(
676
+ [
677
+ html.Div(
678
+ [
679
+ html.Span("Del. Rate: "),
680
+ html.Span("0% - 100%", id="val-del-rate", style={"color": "var(--accent-color)"}),
681
+ ],
682
+ style={"marginBottom": "6px", "color": "#bdc3c7"},
683
+ ),
684
+ dmc.RangeSlider(id="filter-del-rate", min=0, max=100, value=[0, 100], step=1),
685
+ ],
686
+ style={"marginBottom": "12px"},
687
+ ),
688
+ html.Div(
689
+ [
690
+ html.Div(
691
+ [
692
+ html.Span("Max Features: "),
693
+ html.Span("All", id="val-feats", style={"color": "var(--accent-color)"}),
694
+ ],
695
+ style={"marginBottom": "6px", "color": "#bdc3c7"},
696
+ ),
697
+ dmc.Slider(id="filter-feats", min=1, max=100, step=1, value=100),
698
+ ],
699
+ style={"marginBottom": "12px"},
700
+ ),
701
+ dmc.Select(
702
+ id="filter-complexity",
703
+ data=complexity_data,
704
+ value="all",
705
+ clearable=False,
706
+ style={"marginBottom": "12px"},
707
+ ),
708
+ dmc.CheckboxGroup(id="filter-algos", children=[], value=[], orientation="vertical"),
709
+ ]
710
+ ),
711
+ ],
712
+ ),
713
+ html.Main(
714
+ className="main-content",
715
+ children=[
716
+ html.Header(
717
+ [
718
+ html.H1("🏆 Leaderboard Dashboard", style={"color": "var(--secondary-color)", "margin": 0}),
719
+ html.Div("Comprehensive benchmark of feature selection algorithms across diverse datasets.", className="subtitle"),
720
+ ]
721
+ ),
722
+ html.Div(
723
+ className="card",
724
+ children=[
725
+ html.P([
726
+ "Feature selection is a critical step in machine learning and data analysis, aimed at ",
727
+ html.Strong("identifying the most relevant subset of features"),
728
+ " from a high-dimensional dataset. By eliminating irrelevant or redundant features, feature selection not only ",
729
+ html.Strong("improves model interpretability"),
730
+ " but also ",
731
+ html.Strong("enhances predictive performance"),
732
+ " and ",
733
+ html.Strong("reduces computational cost"),
734
+ ".",
735
+ ]),
736
+ html.P([
737
+ "This leaderboard presents a comprehensive comparison of various feature selection algorithms across multiple benchmark datasets. It includes several ",
738
+ html.Strong("information-theoretic and mutual information-based methods"),
739
+ ", which quantify the statistical dependency between features and the target variable to rank feature relevance. Mutual information approaches are particularly effective in ",
740
+ html.Strong("capturing both linear and non-linear relationships"),
741
+ ", making them suitable for complex datasets where classical correlation-based methods may fail.",
742
+ ]),
743
+ html.P([
744
+ "The leaderboard is structured to reflect algorithm performance across different datasets, allowing for an objective assessment of each method’s ability to select informative features. For each method and dataset combination, metrics such as ",
745
+ html.Strong("classification accuracy, F1-score, and area under the ROC curve (AUC)"),
746
+ " are reported, providing insights into how the selected features contribute to predictive modeling.",
747
+ ]),
748
+ html.P([
749
+ "By examining this feature selection leaderboard, researchers and practitioners can gain a better understanding of which methods perform consistently well across diverse domains, helping to guide the choice of feature selection strategies in real-world applications. This serves as a valuable resource for both benchmarking and method development in the field of feature selection.",
750
+ ]),
751
+ ],
752
+ style={"marginTop": "16px"},
753
+ ),
754
+ dmc.Grid(
755
+ id="overview",
756
+ gutter="md",
757
+ style={"marginTop": "16px"},
758
+ children=[
759
+ dmc.Col(
760
+ span=12,
761
+ md=6,
762
+ children=html.Div(
763
+ className="card",
764
+ children=[
765
+ html.H3("About This Dataset"),
766
+ html.P(["Analyzing performance on ", html.Strong(html.Span("Selected", id="desc-dataset-name")), "."]),
767
+ ],
768
+ ),
769
+ ),
770
+ dmc.Col(
771
+ span=12,
772
+ md=6,
773
+ children=html.Div(
774
+ className="card",
775
+ children=[
776
+ html.H3("Dataset Metadata"),
777
+ html.Div(["Name: ", html.Span("-", id="meta-name")]),
778
+ html.Div(["Samples: ", html.Span("-", id="meta-samples"), " | Features: ", html.Span("-", id="meta-features")]),
779
+ html.Div(["Last Updated: ", html.Span("-", id="meta-updated")]),
780
+ ],
781
+ ),
782
+ ),
783
+ ],
784
+ ),
785
+ html.Div(
786
+ id="main-table",
787
+ style={"marginTop": "24px"},
788
+ children=[
789
+ html.H3("📋 Detailed Rankings"),
790
+ html.Div(id="custom-table-container", className="custom-table-wrapper"),
791
+ ],
792
+ ),
793
+ html.Div(
794
+ id="charts",
795
+ style={"marginTop": "24px"},
796
+ children=[
797
+ dmc.Grid(
798
+ gutter="md",
799
+ children=[
800
+ dmc.Col(
801
+ span=12,
802
+ md=6,
803
+ children=html.Div(
804
+ className="chart-card",
805
+ children=[
806
+ html.H3("📊 Performance Comparison"),
807
+ dcc.Graph(id="score-graph", config={"responsive": True}, style={"height": "100%"}),
808
+ ],
809
+ ),
810
+ ),
811
+ dmc.Col(
812
+ span=12,
813
+ md=6,
814
+ children=html.Div(
815
+ className="chart-card",
816
+ children=[
817
+ html.H3("📉 Pareto Frontier (Trade-off)"),
818
+ html.Div("X: Selected Features vs Y: F1 Score (Top-Left is better)", style={"fontSize": "0.9em", "color": "#666"}),
819
+ dcc.Graph(id="pareto-graph", config={"responsive": True}, style={"height": "100%"}),
820
+ ],
821
+ ),
822
+ ),
823
+ ],
824
+ )
825
+ ],
826
+ ),
827
+ html.Div(
828
+ id="details",
829
+ style={"marginTop": "50px", "color": "#999", "textAlign": "center", "borderTop": "1px solid #eee", "paddingTop": "20px"},
830
+ children="AutoFS Benchmark Platform © 2026",
831
+ ),
832
+ ],
833
+ ),
834
+ ],
835
+ )
836
+ )
837
+
838
+
839
+ @dash_app.callback(
840
+ Output("filter-feats", "max"),
841
+ Output("filter-feats", "value"),
842
+ Output("filter-f1", "min"),
843
+ Output("filter-f1", "max"),
844
+ Output("filter-f1", "value"),
845
+ Output("filter-algos", "children"),
846
+ Output("filter-algos", "value"),
847
+ Output("meta-name", "children"),
848
+ Output("meta-samples", "children"),
849
+ Output("meta-features", "children"),
850
+ Output("meta-updated", "children"),
851
+ Output("desc-dataset-name", "children"),
852
+ Output("stat-updated", "children"),
853
+ Output("stat-count", "children"),
854
+ Output("stat-best", "children"),
855
+ Output("score-graph", "figure"),
856
+ Output("pareto-graph", "figure"),
857
+ Output("custom-table-container", "children"),
858
+ Output("val-f1", "children"),
859
+ Output("val-feats", "children"),
860
+ Output("val-del-rate", "children"),
861
+ Input("dataset-select", "value"),
862
+ Input("view-mode", "value"),
863
+ Input("filter-f1", "value"),
864
+ Input("filter-feats", "value"),
865
+ Input("filter-del-rate", "value"),
866
+ Input("filter-complexity", "value"),
867
+ Input("filter-algos", "value"),
868
+ State("filter-f1", "min"),
869
+ State("filter-f1", "max"),
870
+ State("filter-feats", "max"),
871
+ State("filter-algos", "children"),
872
+ State("filter-algos", "value"),
873
+ )
874
+ def update_dashboard_all(
875
+ dataset,
876
+ view_mode,
877
+ min_f1_value,
878
+ max_features_value,
879
+ del_range,
880
+ complexity,
881
+ selected_algos,
882
+ f1_min_state,
883
+ f1_max_state,
884
+ feats_max_state,
885
+ algo_children_state,
886
+ algo_value_state,
887
+ ):
888
+ triggered_id = callback_context.triggered_id if callback_context.triggered else None
889
+ dataset_changed = triggered_id == "dataset-select" or triggered_id is None
890
+ selected = dataset or "Authorship"
891
+ meta = DATASET_METADATA.get(selected, {"name": selected, "last_updated": "-", "num_samples": None, "total_features": None})
892
+ results = get_results_for_dataset(selected)
893
+ algo_list = sorted({r.get("algorithm") for r in results if r.get("algorithm")})
894
+ if dataset_changed:
895
+ f1_scores = [r.get("mean_f1") for r in results if r.get("mean_f1") is not None]
896
+ if f1_scores:
897
+ min_f1 = min(f1_scores)
898
+ safe_min = max(0, math.floor((min_f1 - 0.1) * 10) / 10)
899
+ else:
900
+ safe_min = 0
901
+ max_feats = meta.get("total_features") or 100
902
+ f1_min = safe_min
903
+ f1_max = 1
904
+ f1_value = safe_min
905
+ feats_max = max_feats
906
+ feats_value = max_feats
907
+ algo_children = [dmc.Checkbox(label=a, value=a) for a in algo_list]
908
+ algo_value = algo_list
909
+ else:
910
+ f1_min = f1_min_state if f1_min_state is not None else 0
911
+ f1_max = f1_max_state if f1_max_state is not None else 1
912
+ f1_value = min_f1_value if min_f1_value is not None else f1_min
913
+ feats_max = feats_max_state if feats_max_state is not None else (meta.get("total_features") or 100)
914
+ feats_value = max_features_value if max_features_value is not None else feats_max
915
+ if algo_children_state:
916
+ algo_children = algo_children_state
917
+ else:
918
+ algo_children = [dmc.Checkbox(label=a, value=a) for a in algo_list]
919
+ if selected_algos is not None:
920
+ algo_value = selected_algos
921
+ else:
922
+ algo_value = algo_value_state if algo_value_state is not None else algo_list
923
+ filtered = apply_filters(results, meta, f1_value or 0, feats_value, del_range, complexity, algo_value or [])
924
+ count = len(filtered)
925
+ if filtered:
926
+ best = max(filtered, key=lambda r: r.get("mean_f1") or 0)
927
+ best_text = f"{best.get('algorithm')} ({(best.get('mean_f1') or 0):.3f})"
928
+ else:
929
+ best_text = "-"
930
+ score_fig = build_score_figure(filtered, view_mode or "overall")
931
+ pareto_fig = build_pareto_figure(filtered)
932
+ table_component = build_table(filtered, view_mode or "overall")
933
+ val_f1 = f"{(f1_value or 0):.4f}"
934
+ val_feats = str(int(feats_value)) if isinstance(feats_value, (int, float)) else "All"
935
+ del_min = del_range[0] if del_range else 0
936
+ del_max = del_range[1] if del_range else 100
937
+ val_del = f"{del_min:.0f}% - {del_max:.0f}%"
938
+ meta_samples = meta.get("num_samples") if meta.get("num_samples") is not None else "Unavailable"
939
+ meta_features = meta.get("total_features") if meta.get("total_features") is not None else "Unavailable"
940
+ return (
941
+ feats_max,
942
+ feats_value,
943
+ f1_min,
944
+ f1_max,
945
+ f1_value,
946
+ algo_children,
947
+ algo_value,
948
+ meta.get("name", "-"),
949
+ meta_samples,
950
+ meta_features,
951
+ meta.get("last_updated", "-"),
952
+ meta.get("name", "-"),
953
+ meta.get("last_updated", "-"),
954
+ count,
955
+ best_text,
956
+ score_fig,
957
+ pareto_fig,
958
+ table_component,
959
+ val_f1,
960
+ val_feats,
961
+ val_del,
962
+ )
963
+
964
+
965
+ def sanitize_json(value):
966
+ if value is None or isinstance(value, (str, int, float, bool)):
967
+ return value
968
+ if np and isinstance(value, np.generic):
969
+ return value.item()
970
+ if np and isinstance(value, np.ndarray):
971
+ return value.tolist()
972
+ if pd and isinstance(value, (pd.DataFrame, pd.Series)):
973
+ if isinstance(value, pd.DataFrame):
974
+ return value.to_dict(orient="records")
975
+ return value.to_dict()
976
+ if isinstance(value, (datetime.datetime, datetime.date)):
977
+ return value.isoformat()
978
+ if isinstance(value, dict):
979
+ return {str(k): sanitize_json(v) for k, v in value.items()}
980
+ if isinstance(value, (list, tuple, set)):
981
+ return [sanitize_json(v) for v in value]
982
+ if hasattr(value, "to_dict"):
983
+ return sanitize_json(value.to_dict())
984
+ return str(value)
985
+
986
+
987
+ @server.route("/global")
988
+ def global_view():
989
+ return render_template("global.html")
990
+
991
+
992
+ @server.route("/submit")
993
+ def submit_view():
994
+ return render_template("submit.html")
995
+
996
+
997
+ @server.route("/api/results")
998
+ def get_results_api():
999
+ try:
1000
+ dataset = request.args.get("dataset") or "Authorship"
1001
+ leaderboard = get_results_for_dataset(dataset)
1002
+ return jsonify(sanitize_json(leaderboard))
1003
+ except Exception as e:
1004
+ print(e)
1005
+ return jsonify({"error": str(e)})
1006
+
1007
+
1008
+ @server.route("/api/datasets")
1009
  def api_datasets():
1010
  try:
1011
  datasets = []
1012
+ for name, meta in DATASET_METADATA.items():
 
 
 
 
 
 
 
 
1013
  datasets.append({
1014
  "name": name,
1015
+ "last_updated": meta.get("last_updated"),
1016
+ "num_samples": meta.get("num_samples") if meta.get("num_samples") is not None else "Unavailable",
1017
+ "total_features": meta.get("total_features") if meta.get("total_features") is not None else "Unavailable",
1018
  })
1019
+ return jsonify(sanitize_json(datasets))
1020
  except Exception as e:
1021
+ print(e)
1022
+ return jsonify({"error": str(e)})
1023
+
1024
+
1025
+ @server.route("/api/global_stats")
1026
+ def api_global_stats():
1027
+ try:
1028
+ algo_totals = {}
1029
+ algo_counts = {}
1030
+ for dataset in DATASET_METADATA.keys():
1031
+ results = get_results_for_dataset(dataset) or []
1032
+ for row in results:
1033
+ algo = row.get("algorithm") or "Unknown"
1034
+ mean_f1 = row.get("mean_f1")
1035
+ mean_auc = row.get("mean_auc")
1036
+ if mean_f1 is None and mean_auc is None:
1037
+ continue
1038
+ totals = algo_totals.get(algo, {"f1": 0.0, "auc": 0.0})
1039
+ counts = algo_counts.get(algo, {"f1": 0, "auc": 0})
1040
+ if mean_f1 is not None:
1041
+ totals["f1"] += float(mean_f1)
1042
+ counts["f1"] += 1
1043
+ if mean_auc is not None:
1044
+ totals["auc"] += float(mean_auc)
1045
+ counts["auc"] += 1
1046
+ algo_totals[algo] = totals
1047
+ algo_counts[algo] = counts
1048
+ global_stats = []
1049
+ for algo, totals in algo_totals.items():
1050
+ counts = algo_counts.get(algo, {"f1": 0, "auc": 0})
1051
+ mean_f1_global = totals["f1"] / counts["f1"] if counts["f1"] else None
1052
+ mean_auc_global = totals["auc"] / counts["auc"] if counts["auc"] else None
1053
+ global_stats.append({
1054
+ "algorithm": algo,
1055
+ "mean_f1_global": mean_f1_global,
1056
+ "mean_auc_global": mean_auc_global,
1057
+ })
1058
+ return jsonify(sanitize_json(global_stats))
1059
+ except Exception as e:
1060
+ print(e)
1061
+ return jsonify({"error": str(e)})
1062
+
1063
+
1064
+ @server.route("/api/algos")
1065
+ def api_algorithms():
1066
+ return jsonify(DISPLAY_COMPLEXITY)
1067
+
1068
+
1069
+ @server.route("/pdfs/<path:filename>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1070
  def serve_pdf(filename):
1071
  return send_from_directory(PDF_DIR, filename)
1072
 
1073
 
1074
  if __name__ == "__main__":
1075
+ port = int(os.environ.get("PORT", 7865))
1076
+ print(f"Loaded {len(DATASET_METADATA)} datasets from {RESULT_DIR}")
1077
  app.run(host="0.0.0.0", port=port, debug=False)
Webapp/templates/global.html CHANGED
@@ -13,14 +13,73 @@
13
  --text-color: #333;
14
  --border-color: #dee2e6;
15
  --hover-color: #f1f1f1;
 
16
  }
17
 
18
  body {
19
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20
  margin: 0;
21
- padding: 20px;
22
  background-color: var(--background-color);
23
  color: var(--text-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
  .container {
@@ -46,20 +105,6 @@
46
  color: var(--secondary-color);
47
  }
48
 
49
- .nav-link {
50
- text-decoration: none;
51
- color: white;
52
- background-color: var(--secondary-color);
53
- padding: 8px 16px;
54
- border-radius: 4px;
55
- font-size: 14px;
56
- transition: background-color 0.2s;
57
- }
58
-
59
- .nav-link:hover {
60
- background-color: #34495e;
61
- }
62
-
63
  .description-box {
64
  background-color: #e8f4fd;
65
  border-left: 4px solid #3498db;
@@ -185,12 +230,6 @@
185
  background-color: var(--primary-color);
186
  }
187
 
188
- .time-detail {
189
- font-size: 0.8em;
190
- color: #666;
191
- margin-top: 2px;
192
- }
193
-
194
  .version-tag {
195
  font-size: 0.8em;
196
  color: #7f8c8d;
@@ -200,67 +239,89 @@
200
  </head>
201
  <body>
202
 
203
- <div class="container">
204
- <header>
205
- <div>
206
- <h1>🌍 Global Algorithm Rankings</h1>
207
- <div id="last-updated" class="version-tag">Data Last Updated: Loading...</div>
208
- </div>
209
- <a href="/" class="nav-link">← Back to Dataset View</a>
210
- </header>
211
-
212
- <div class="description-box">
213
- <h3>About Global Rankings</h3>
214
- <p>
215
- This page provides a comprehensive evaluation of feature selection algorithms across all available datasets.
216
- Algorithms are ranked based on a weighted score combining <strong>Accuracy (F1)</strong>, <strong>Robustness (AUC)</strong>, and <strong>Efficiency (Time)</strong>.
217
- You can adjust the importance of each factor below to customize the ranking criteria.
218
- </p>
219
  </div>
220
 
221
- <div class="weights-control">
222
- <h3>🏆 Scoring Formula: S = α·F1 + β·AUC + γ·TimeScore</h3>
223
- <p style="font-size: 0.9em; color: #666; margin-bottom: 10px;">
224
- Constraint: α + β + γ = 1. TimeScore is normalized (1 = fastest).
 
 
 
 
 
 
 
 
 
 
 
 
225
  </p>
226
-
227
- <div class="sliders-container">
228
- <div class="slider-group">
229
- <label for="weight-a">F1 (α):</label>
230
- <input type="number" id="weight-a" value="0.4" step="0.05" min="0" max="1">
231
- </div>
232
-
233
- <div class="slider-group">
234
- <label for="weight-b">AUC (β):</label>
235
- <input type="number" id="weight-b" value="0.4" step="0.05" min="0" max="1">
236
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- <div class="slider-group">
239
- <label for="weight-c">Time (γ):</label>
240
- <input type="number" id="weight-c" value="0.2" readonly title="Auto-calculated: 1 - α - β">
 
 
 
 
 
 
 
 
 
241
  </div>
242
-
243
- <button class="recalc-btn" onclick="calculateAndRender()">Recalculate Rankings</button>
244
  </div>
245
- </div>
246
 
247
- <div id="loading-indicator" style="text-align: center; color: #666;">Loading global stats...</div>
248
-
249
- <table id="global-table">
250
- <thead>
251
- <tr>
252
- <th data-key="rank">#</th>
253
- <th data-key="algorithm">Algorithm <span class="arrow">↕</span></th>
254
- <th data-key="mean_f1_global">Global F1 <span class="arrow">↕</span></th>
255
- <th data-key="mean_auc_global">Global AUC <span class="arrow">↕</span></th>
256
- <th data-key="mean_time_global">Efficiency (Time) <span class="arrow">↕</span></th>
257
- <th data-key="final_score">Final Score <span class="arrow">↕</span></th>
258
- </tr>
259
- </thead>
260
- <tbody>
261
- <!-- Data rows -->
262
- </tbody>
263
- </table>
264
  </div>
265
 
266
  <script>
@@ -272,7 +333,6 @@
272
  const tableBody = document.querySelector("#global-table tbody");
273
  const weightA = document.getElementById("weight-a");
274
  const weightB = document.getElementById("weight-b");
275
- const weightC = document.getElementById("weight-c");
276
  const lastUpdatedDiv = document.getElementById("last-updated");
277
 
278
  // Fetch datasets info to get latest date
@@ -312,70 +372,30 @@
312
  // Weight auto-adjustment logic
313
  function updateWeights(changedInput) {
314
  let a = parseFloat(weightA.value) || 0;
315
- let b = parseFloat(weightB.value) || 0;
316
 
317
  // Clamp inputs to 0-1
318
  if (a < 0) a = 0; if (a > 1) a = 1;
319
- if (b < 0) b = 0; if (b > 1) b = 1;
320
 
321
  if (changedInput === 'a') {
322
- // If a changes, we try to adjust c first (c = 1 - a - b)
323
- // If 1 - a - b < 0, it means a + b > 1, so we must reduce b
324
- let c = 1 - a - b;
325
- if (c < 0) {
326
- b = Math.max(0, 1 - a); // Reduce b
327
- c = 0; // c becomes 0
328
- }
329
  // Update UI
330
  weightA.value = parseFloat(a.toFixed(2));
331
  weightB.value = parseFloat(b.toFixed(2));
332
- weightC.value = parseFloat(c.toFixed(2));
333
- } else if (changedInput === 'b') {
334
- // If b changes, we try to adjust c first
335
- // If 1 - a - b < 0, we must reduce a
336
- let c = 1 - a - b;
337
- if (c < 0) {
338
- a = Math.max(0, 1 - b); // Reduce a
339
- c = 0;
340
- }
341
- // Update UI
342
- weightA.value = parseFloat(a.toFixed(2));
343
- weightB.value = parseFloat(b.toFixed(2));
344
- weightC.value = parseFloat(c.toFixed(2));
345
  }
346
  }
347
 
348
  weightA.addEventListener('input', () => updateWeights('a'));
349
- weightB.addEventListener('input', () => updateWeights('b'));
350
 
351
  function calculateAndRender() {
352
  const a = parseFloat(weightA.value) || 0;
353
  const b = parseFloat(weightB.value) || 0;
354
- const c = parseFloat(weightC.value) || 0;
355
-
356
- // Find min/max time for normalization
357
- let minTime = Infinity;
358
- let maxTime = -Infinity;
359
- rawData.forEach(d => {
360
- if (d.mean_time_global < minTime) minTime = d.mean_time_global;
361
- if (d.mean_time_global > maxTime) maxTime = d.mean_time_global;
362
- });
363
-
364
- const timeRange = maxTime - minTime;
365
 
366
  // Process data
367
  processedData = rawData.map(d => {
368
- // Time Score: 1 if fast, 0 if slow
369
- let timeScore = 1.0;
370
- if (timeRange > 0.0001) {
371
- timeScore = 1.0 - ((d.mean_time_global - minTime) / timeRange);
372
- }
373
-
374
- const score = (a * d.mean_f1_global) + (b * d.mean_auc_global) + (c * timeScore);
375
 
376
  return {
377
  ...d,
378
- time_score_norm: timeScore,
379
  final_score: score
380
  };
381
  });
@@ -404,7 +424,7 @@
404
  tableBody.innerHTML = "";
405
 
406
  if (processedData.length === 0) {
407
- tableBody.innerHTML = '<tr><td colspan="6" style="text-align:center;">No data available</td></tr>';
408
  return;
409
  }
410
 
@@ -422,10 +442,6 @@
422
  <td><strong>${row.algorithm}</strong> <span style="font-size:0.8em; color:#999">(${row.datasets_count} datasets)</span></td>
423
  <td>${safeFixed(row.mean_f1_global)}</td>
424
  <td>${safeFixed(row.mean_auc_global)}</td>
425
- <td>
426
- <strong>${safeFixed(row.time_score_norm)}</strong>
427
- <div class="time-detail">${safeFixed(row.mean_time_global, 2)}s</div>
428
- </td>
429
  <td>
430
  <strong>${safeFixed(row.final_score)}</strong>
431
  <div class="score-bar"><div class="score-fill" style="width: ${Math.min(row.final_score * 100, 100)}%"></div></div>
@@ -450,7 +466,7 @@
450
  sortDirection *= -1;
451
  } else {
452
  sortKey = key;
453
- sortDirection = (key === 'rank' || key === 'mean_time_global') ? 1 : -1;
454
  }
455
  if (key === 'rank') {
456
  sortKey = 'final_score';
@@ -469,4 +485,4 @@
469
  </script>
470
 
471
  </body>
472
- </html>
 
13
  --text-color: #333;
14
  --border-color: #dee2e6;
15
  --hover-color: #f1f1f1;
16
+ --sidebar-width: 280px;
17
  }
18
 
19
  body {
20
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
  margin: 0;
22
+ padding: 0;
23
  background-color: var(--background-color);
24
  color: var(--text-color);
25
+ display: flex;
26
+ min-height: 100vh;
27
+ }
28
+
29
+ /* Sidebar Styling */
30
+ .sidebar {
31
+ width: var(--sidebar-width);
32
+ background-color: var(--secondary-color);
33
+ color: white;
34
+ position: fixed;
35
+ height: 100vh;
36
+ overflow-y: auto;
37
+ padding: 20px;
38
+ box-sizing: border-box;
39
+ left: 0;
40
+ top: 0;
41
+ z-index: 100;
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 20px;
45
+ }
46
+
47
+ .sidebar h2 {
48
+ font-size: 1.2em;
49
+ margin-bottom: 10px;
50
+ color: #ecf0f1;
51
+ border-bottom: 1px solid #34495e;
52
+ padding-bottom: 5px;
53
+ }
54
+
55
+ .nav-links {
56
+ list-style: none;
57
+ padding: 0;
58
+ margin: 0;
59
+ }
60
+
61
+ .nav-links li a {
62
+ display: block;
63
+ padding: 10px;
64
+ color: #bdc3c7;
65
+ text-decoration: none;
66
+ border-radius: 4px;
67
+ transition: background 0.2s;
68
+ }
69
+
70
+ .nav-links li a:hover, .nav-links li a.active {
71
+ background: rgba(255,255,255,0.1);
72
+ color: white;
73
+ }
74
+
75
+ /* Main Content Styling */
76
+ .main-content {
77
+ margin-left: var(--sidebar-width);
78
+ flex: 1;
79
+ padding: 40px;
80
+ max-width: calc(100% - var(--sidebar-width));
81
+ box-sizing: border-box;
82
+ min-width: 0;
83
  }
84
 
85
  .container {
 
105
  color: var(--secondary-color);
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  .description-box {
109
  background-color: #e8f4fd;
110
  border-left: 4px solid #3498db;
 
230
  background-color: var(--primary-color);
231
  }
232
 
 
 
 
 
 
 
233
  .version-tag {
234
  font-size: 0.8em;
235
  color: #7f8c8d;
 
239
  </head>
240
  <body>
241
 
242
+ <div class="sidebar">
243
+ <div style="text-align: center; margin-bottom: 20px;">
244
+ <h1 style="font-size: 1.5em; margin: 0; color: white;">AutoFS</h1>
245
+ <div style="font-size: 0.8em; color: #bdc3c7;">Feature Selection Leaderboard</div>
 
 
 
 
 
 
 
 
 
 
 
 
246
  </div>
247
 
248
+ <div>
249
+ <h2>Navigation</h2>
250
+ <ul class="nav-links">
251
+ <li><a href="/">📊 Overview</a></li>
252
+ <li><a href="/#main-table">🏆 Leaderboard</a></li>
253
+ <li><a href="/#charts">📈 Charts</a></li>
254
+ <li><a href="/#details">ℹ️ Details</a></li>
255
+ <li><a href="/global" class="active">🌍 Global Rankings</a></li>
256
+ <li><a href="/submit">📤 Submit Data/Method</a></li>
257
+ </ul>
258
+ </div>
259
+
260
+ <div style="margin-top: auto; padding-top: 20px; border-top: 1px solid #34495e;">
261
+ <p style="font-size: 0.8em; color: #bdc3c7; text-align: center;">
262
+ Need help?<br>
263
+ <a href="mailto:support@autofs.com" style="color: var(--primary-color);">Contact Support</a>
264
  </p>
265
+ </div>
266
+ </div>
267
+
268
+ <div class="main-content">
269
+ <div class="container">
270
+ <header>
271
+ <div>
272
+ <h1>🌍 Global Algorithm Rankings</h1>
273
+ <div id="last-updated" class="version-tag">Data Last Updated: Loading...</div>
 
274
  </div>
275
+ <!-- Removed redundant back link as sidebar handles navigation -->
276
+ </header>
277
+
278
+ <div class="description-box">
279
+ <h3>About Global Rankings</h3>
280
+ <p>
281
+ This page provides a comprehensive evaluation of feature selection algorithms across all available datasets.
282
+ Algorithms are ranked based on a weighted score combining <strong>Accuracy (F1)</strong>, <strong>Robustness (AUC)</strong>, and <strong>Efficiency (Time)</strong>.
283
+ You can adjust the importance of each factor below to customize the ranking criteria.
284
+ </p>
285
+ </div>
286
+
287
+ <div class="weights-control">
288
+ <h3>🏆 Scoring Formula: S = α·F1 + β·AUC</h3>
289
+ <p style="font-size: 0.9em; color: #666; margin-bottom: 10px;">
290
+ Constraint: α + β = 1.
291
+ </p>
292
 
293
+ <div class="sliders-container">
294
+ <div class="slider-group">
295
+ <label for="weight-a">F1 (α):</label>
296
+ <input type="number" id="weight-a" value="0.5" step="0.05" min="0" max="1">
297
+ </div>
298
+
299
+ <div class="slider-group">
300
+ <label for="weight-b">AUC (β):</label>
301
+ <input type="number" id="weight-b" value="0.5" readonly title="Auto-calculated: 1 - α">
302
+ </div>
303
+
304
+ <button class="recalc-btn" onclick="calculateAndRender()">Recalculate Rankings</button>
305
  </div>
 
 
306
  </div>
 
307
 
308
+ <div id="loading-indicator" style="text-align: center; color: #666;">Loading global stats...</div>
309
+
310
+ <table id="global-table">
311
+ <thead>
312
+ <tr>
313
+ <th data-key="rank">#</th>
314
+ <th data-key="algorithm">Algorithm <span class="arrow">↕</span></th>
315
+ <th data-key="mean_f1_global">Global F1 <span class="arrow">↕</span></th>
316
+ <th data-key="mean_auc_global">Global AUC <span class="arrow">↕</span></th>
317
+ <th data-key="final_score">Final Score <span class="arrow">↕</span></th>
318
+ </tr>
319
+ </thead>
320
+ <tbody>
321
+ <!-- Data rows -->
322
+ </tbody>
323
+ </table>
324
+ </div>
325
  </div>
326
 
327
  <script>
 
333
  const tableBody = document.querySelector("#global-table tbody");
334
  const weightA = document.getElementById("weight-a");
335
  const weightB = document.getElementById("weight-b");
 
336
  const lastUpdatedDiv = document.getElementById("last-updated");
337
 
338
  // Fetch datasets info to get latest date
 
372
  // Weight auto-adjustment logic
373
  function updateWeights(changedInput) {
374
  let a = parseFloat(weightA.value) || 0;
 
375
 
376
  // Clamp inputs to 0-1
377
  if (a < 0) a = 0; if (a > 1) a = 1;
 
378
 
379
  if (changedInput === 'a') {
380
+ let b = 1 - a;
 
 
 
 
 
 
381
  // Update UI
382
  weightA.value = parseFloat(a.toFixed(2));
383
  weightB.value = parseFloat(b.toFixed(2));
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  }
385
  }
386
 
387
  weightA.addEventListener('input', () => updateWeights('a'));
 
388
 
389
  function calculateAndRender() {
390
  const a = parseFloat(weightA.value) || 0;
391
  const b = parseFloat(weightB.value) || 0;
 
 
 
 
 
 
 
 
 
 
 
392
 
393
  // Process data
394
  processedData = rawData.map(d => {
395
+ const score = (a * d.mean_f1_global) + (b * d.mean_auc_global);
 
 
 
 
 
 
396
 
397
  return {
398
  ...d,
 
399
  final_score: score
400
  };
401
  });
 
424
  tableBody.innerHTML = "";
425
 
426
  if (processedData.length === 0) {
427
+ tableBody.innerHTML = '<tr><td colspan="5" style="text-align:center;">No data available</td></tr>';
428
  return;
429
  }
430
 
 
442
  <td><strong>${row.algorithm}</strong> <span style="font-size:0.8em; color:#999">(${row.datasets_count} datasets)</span></td>
443
  <td>${safeFixed(row.mean_f1_global)}</td>
444
  <td>${safeFixed(row.mean_auc_global)}</td>
 
 
 
 
445
  <td>
446
  <strong>${safeFixed(row.final_score)}</strong>
447
  <div class="score-bar"><div class="score-fill" style="width: ${Math.min(row.final_score * 100, 100)}%"></div></div>
 
466
  sortDirection *= -1;
467
  } else {
468
  sortKey = key;
469
+ sortDirection = (key === 'rank') ? 1 : -1;
470
  }
471
  if (key === 'rank') {
472
  sortKey = 'final_score';
 
485
  </script>
486
 
487
  </body>
488
+ </html>
Webapp/templates/index.html CHANGED
@@ -14,126 +14,187 @@
14
  --border-color: #dee2e6;
15
  --hover-color: #f1f1f1;
16
  --accent-color: #e67e22;
 
17
  }
18
 
19
  body {
20
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
  margin: 0;
22
- padding: 20px;
23
  background-color: var(--background-color);
24
  color: var(--text-color);
 
 
25
  }
26
 
27
- .container {
28
- max-width: 1200px;
29
- margin: 0 auto;
30
- background: white;
 
 
 
 
31
  padding: 20px;
32
- border-radius: 8px;
33
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
34
- }
35
-
36
- header {
37
  display: flex;
38
- justify-content: space-between;
39
- align-items: center;
40
- margin-bottom: 20px;
41
- border-bottom: 2px solid var(--primary-color);
42
- padding-bottom: 10px;
43
  }
44
 
45
- h1 {
46
- margin: 0;
47
- color: var(--secondary-color);
 
 
 
48
  }
49
 
50
- .controls {
51
- display: flex;
 
 
52
  gap: 10px;
53
- align-items: center;
54
  }
55
 
56
- select {
57
- padding: 8px 12px;
58
- border: 1px solid var(--border-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  border-radius: 4px;
60
- font-size: 14px;
61
  }
62
 
63
- /* Info Boxes */
64
- .info-section {
65
- display: flex;
66
- gap: 20px;
67
- margin-bottom: 20px;
68
- flex-wrap: wrap;
69
  }
70
 
71
- .description-box, .metadata-box {
72
- flex: 1;
73
- background-color: #e8f4fd;
74
- border-left: 4px solid #3498db;
75
- padding: 15px;
 
 
 
 
 
 
 
 
 
 
76
  border-radius: 4px;
77
- min-width: 300px;
 
 
 
78
  }
79
 
80
- .metadata-box {
81
- background-color: #fef9e7;
82
- border-left-color: #f1c40f;
 
 
 
83
  }
84
 
85
- h3 {
86
- margin-top: 0;
87
- margin-bottom: 10px;
88
- font-size: 1.1em;
89
- color: var(--secondary-color);
 
90
  }
91
 
92
- p {
93
- margin: 5px 0;
94
- line-height: 1.5;
95
- font-size: 0.95em;
 
 
 
 
96
  }
97
 
98
- .version-tag {
99
- font-size: 0.8em;
 
 
 
 
 
 
 
 
 
100
  color: #7f8c8d;
101
  margin-top: 5px;
102
  }
103
 
104
- /* Filters */
105
- .filters-box {
106
- background-color: #f1f1f1;
107
- padding: 15px;
108
- border-radius: 8px;
109
- margin-bottom: 20px;
110
- border: 1px solid #ddd;
111
  display: flex;
112
  gap: 20px;
113
- align-items: center;
114
  flex-wrap: wrap;
115
  }
116
 
117
- .filter-group {
118
- display: flex;
119
- align-items: center;
120
- gap: 10px;
 
 
 
121
  }
122
 
123
- input[type="range"] {
124
- width: 120px;
125
  }
126
 
127
- .filter-val {
128
- font-weight: bold;
129
- min-width: 40px;
 
 
 
 
130
  }
131
 
132
- /* Table */
133
  table {
134
  width: 100%;
135
  border-collapse: collapse;
136
- margin-top: 10px;
137
  }
138
 
139
  th, td {
@@ -143,55 +204,38 @@
143
  }
144
 
145
  th {
146
- background-color: var(--secondary-color);
147
- color: white;
148
  cursor: pointer;
149
  user-select: none;
150
- position: sticky;
151
- top: 0;
152
  }
153
 
154
  th:hover {
155
- background-color: #34495e;
156
- }
157
-
158
- th .arrow {
159
- font-size: 10px;
160
- margin-left: 5px;
161
- opacity: 0.7;
162
  }
163
-
164
  tr:hover {
165
  background-color: var(--hover-color);
166
  }
167
 
168
- .score-bar {
169
- height: 6px;
170
- background-color: #e9ecef;
171
- border-radius: 3px;
172
- overflow: hidden;
173
- margin-top: 5px;
174
- }
175
-
176
- .score-fill {
177
- height: 100%;
178
- background-color: var(--primary-color);
179
  }
 
180
 
181
  .features-cell {
182
  max-width: 200px;
 
 
 
183
  white-space: nowrap;
184
  overflow: hidden;
185
  text-overflow: ellipsis;
186
- color: #666;
187
- font-size: 0.9em;
188
- cursor: pointer;
189
- }
190
-
191
- .features-cell:hover {
192
- text-decoration: underline;
193
- color: var(--primary-color);
194
  }
 
195
 
196
  /* Charts */
197
  .charts-section {
@@ -203,16 +247,23 @@
203
 
204
  .chart-container {
205
  background: white;
206
- padding: 15px;
207
  border-radius: 8px;
208
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
209
- border: 1px solid #eee;
210
  position: relative;
211
- height: 400px;
 
 
212
  width: 100%;
 
 
 
 
 
 
213
  }
214
 
215
- /* Modal styles */
216
  .modal {
217
  display: none;
218
  position: fixed;
@@ -234,18 +285,7 @@
234
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
235
  }
236
 
237
- .close {
238
- color: #aaa;
239
- float: right;
240
- font-size: 28px;
241
- font-weight: bold;
242
- cursor: pointer;
243
- }
244
-
245
- .close:hover {
246
- color: black;
247
- }
248
-
249
  .feature-tag {
250
  display: inline-block;
251
  background-color: #e1ecf4;
@@ -255,18 +295,12 @@
255
  margin: 2px;
256
  font-size: 0.9em;
257
  }
258
-
259
- .loading {
260
- text-align: center;
261
- padding: 20px;
262
- color: #666;
263
- }
264
 
265
- /* Sidebar for PDF */
266
  .pdf-sidebar {
267
  position: fixed;
268
  top: 0;
269
- right: -50%; /* Hidden by default */
270
  width: 50%;
271
  height: 100%;
272
  background: white;
@@ -276,11 +310,7 @@
276
  display: flex;
277
  flex-direction: column;
278
  }
279
-
280
- .pdf-sidebar.open {
281
- right: 0;
282
- }
283
-
284
  .sidebar-header {
285
  padding: 10px 20px;
286
  background: var(--primary-color);
@@ -289,116 +319,155 @@
289
  justify-content: space-between;
290
  align-items: center;
291
  }
292
-
293
- .sidebar-content {
294
- flex: 1;
295
- padding: 0;
296
- }
297
-
298
- .sidebar-content iframe {
299
- width: 100%;
300
- height: 100%;
301
- border: none;
302
- }
303
-
304
- .algo-link {
305
- color: var(--primary-color);
306
- cursor: pointer;
307
- font-weight: bold;
308
- }
309
- .algo-link:hover {
310
- text-decoration: underline;
311
- }
312
  </style>
313
  </head>
314
  <body>
315
 
316
- <div class="container">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  <header>
318
  <div>
319
- <h1>🏆 AutoFS Leaderboard</h1>
320
- <div id="last-updated" class="version-tag">Data Last Updated: Loading...</div>
321
- </div>
322
- <div style="display:flex; gap:15px; align-items:center;">
323
- <a href="/global" style="text-decoration:none; color:white; background-color:#8e44ad; padding:8px 15px; border-radius:4px; font-size:0.9em;">🌍 Global Rankings</a>
324
- <div class="controls">
325
- <label for="dataset-select">Dataset:</label>
326
- <select id="dataset-select">
327
- <option value="" disabled selected>Loading...</option>
328
- </select>
329
- </div>
330
  </div>
331
  </header>
332
 
333
- <div class="info-section">
 
 
 
 
 
 
 
334
  <div class="description-box">
335
  <h3>About This Dataset</h3>
336
  <p>
337
- This dashboard displays the performance of various feature selection algorithms on the
338
- <strong><span id="desc-dataset-name">Selected</span></strong> dataset.
339
- Compare algorithms based on accuracy (F1), stability (AUC), and computational efficiency.
340
  </p>
341
  </div>
342
  <div class="metadata-box">
343
  <h3>Dataset Metadata</h3>
344
  <p><strong>Name:</strong> <span id="meta-name">-</span></p>
 
345
  <p><strong>Last Updated:</strong> <span id="meta-updated">-</span></p>
346
- <!-- Placeholder for future metadata -->
347
- <p style="color:#888; font-size:0.8em;">(Additional metadata like samples/features not available)</p>
348
  </div>
349
  </div>
350
 
351
- <div class="filters-box">
352
- <h3>🔍 Filters</h3>
353
-
354
- <div class="filter-group">
355
- <label>Min F1 Score:</label>
356
- <input type="range" id="filter-f1" min="0" max="1" step="0.05" value="0">
357
- <span id="val-f1" class="filter-val">0.00</span>
358
- </div>
359
-
360
- <div class="filter-group">
361
- <label>Max Time (s):</label>
362
- <input type="range" id="filter-time" min="1" max="500" step="10" value="500">
363
- <span id="val-time" class="filter-val">500+</span>
364
- </div>
365
-
366
- <div style="margin-left: auto;">
367
- <label style="margin-right:10px; font-weight:bold;">Chart View:</label>
368
- <select id="chart-view-mode" onchange="updateView()">
369
- <option value="overall">Overall (Mean)</option>
370
- <option value="classifiers-f1">F1 by Classifier</option>
371
- <option value="classifiers-auc">AUC by Classifier</option>
372
- </select>
373
  </div>
374
  </div>
375
 
376
- <div id="loading-indicator" class="loading" style="display: none;">Loading data...</div>
377
-
378
- <table id="result-table">
379
- <thead>
380
- <!-- Headers generated dynamically -->
381
- </thead>
382
- <tbody>
383
- <!-- Data rows will be populated here -->
384
- </tbody>
385
- </table>
386
-
387
- <div class="charts-section">
388
  <div class="chart-container">
389
  <h3>📊 Performance Comparison</h3>
 
 
 
 
 
 
 
390
  <canvas id="scoreChart"></canvas>
 
391
  </div>
392
 
393
  <div class="chart-container">
394
  <h3>📉 Pareto Frontier (Trade-off)</h3>
395
- <p style="font-size:0.9em; color:#666; margin-top:-10px;">X: Number of Selected Features (Lower is better) vs Y: F1 Score (Higher is better). Optimal: Top-Left.</p>
396
  <canvas id="paretoChart"></canvas>
397
  </div>
398
  </div>
399
- </div>
400
 
401
- <!-- Modal for details -->
 
 
 
 
 
 
402
  <div id="details-modal" class="modal">
403
  <div class="modal-content">
404
  <span class="close">&times;</span>
@@ -422,14 +491,44 @@
422
  let currentResults = [];
423
  let filteredResults = [];
424
  let allDatasets = [];
425
- let sortDirection = 1; // 1 for asc, -1 for desc
 
426
  let lastSortKey = '';
427
 
 
 
 
 
 
 
428
  // Filter Elements
429
  const filterF1 = document.getElementById('filter-f1');
430
- const filterTime = document.getElementById('filter-time');
 
 
 
 
 
 
431
  const valF1 = document.getElementById('val-f1');
432
- const valTime = document.getElementById('val-time');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
  const VIEW_CONFIG = {
435
  'overall': [
@@ -448,158 +547,418 @@
448
  ]
449
  };
450
 
451
- const tableHead = document.querySelector("#result-table thead");
452
- const tableBody = document.querySelector("#result-table tbody");
453
- const datasetSelect = document.getElementById("dataset-select");
454
- const loadingIndicator = document.getElementById("loading-indicator");
455
  const modal = document.getElementById("details-modal");
456
- const closeModal = document.querySelector(".close");
457
-
458
- // Metadata elements
459
- const metaName = document.getElementById('meta-name');
460
- const metaUpdated = document.getElementById('meta-updated');
461
- const descName = document.getElementById('desc-dataset-name');
462
- const globalUpdated = document.getElementById('last-updated');
463
 
464
- // Close modal
465
- closeModal.onclick = () => modal.style.display = "none";
466
- window.onclick = (event) => {
467
- if (event.target == modal) modal.style.display = "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
469
 
470
- // Chart instances
471
- let scoreChartInstance = null;
472
- let paretoChartInstance = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
  // Filter Logic
 
 
475
  function applyFilters() {
476
  const minF1 = parseFloat(filterF1.value);
477
- const maxTime = parseFloat(filterTime.value);
 
 
 
478
 
479
- valF1.textContent = minF1.toFixed(2);
480
- valTime.textContent = maxTime >= 500 ? "500+" : maxTime + "s";
 
 
 
 
 
481
 
482
  filteredResults = currentResults.filter(r => {
483
  const f1 = r.mean_f1 || 0;
484
- const time = r.time || 0;
485
- return f1 >= minF1 && (maxTime >= 500 || time <= maxTime);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  });
487
 
 
488
  renderTable(filteredResults);
489
  updateCharts(filteredResults);
490
  }
491
 
492
- filterF1.addEventListener('input', applyFilters);
493
- filterTime.addEventListener('input', applyFilters);
 
 
 
 
 
 
 
 
494
 
495
- function updateCharts(results) {
496
- if (!Array.isArray(results)) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
- // Use filtered results for charts too
499
- // Limit to top 20 for bar chart readability
500
- const topResults = results.slice(0, 20);
501
- const labels = topResults.map(r => r.algorithm || 'Unknown');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
 
 
 
 
 
 
 
 
 
 
503
  const viewMode = document.getElementById('chart-view-mode').value;
 
 
504
  let datasets = [];
 
 
505
 
506
  if (viewMode === 'overall') {
 
 
 
507
  datasets = [
508
  {
509
- label: 'Mean F1',
510
- data: topResults.map(r => r.mean_f1 || 0),
511
- backgroundColor: 'rgba(52, 152, 219, 0.7)',
512
- borderColor: 'rgba(52, 152, 219, 1)',
513
- borderWidth: 1
514
  },
515
  {
516
- label: 'Mean AUC',
517
- data: topResults.map(r => r.mean_auc || 0),
518
- backgroundColor: 'rgba(46, 204, 113, 0.7)',
519
- borderColor: 'rgba(46, 204, 113, 1)',
520
- borderWidth: 1
521
  }
522
  ];
 
 
 
 
 
 
 
 
523
  } else if (viewMode === 'classifiers-f1') {
524
  datasets = ['nb', 'svm', 'rf'].map((clf, i) => ({
525
- label: clf.toUpperCase() + ' F1',
526
- data: topResults.map(r => r.metrics?.[clf]?.f1 || 0),
527
- backgroundColor: `hsla(${200 + i*40}, 70%, 60%, 0.7)`,
528
- borderColor: `hsla(${200 + i*40}, 70%, 60%, 1)`,
529
- borderWidth: 1
530
- }));
531
- } else {
532
- datasets = ['nb', 'svm', 'rf'].map((clf, i) => ({
533
- label: clf.toUpperCase() + ' AUC',
534
- data: topResults.map(r => r.metrics?.[clf]?.auc || 0),
535
- backgroundColor: `hsla(${30 + i*40}, 70%, 60%, 0.7)`,
536
- borderColor: `hsla(${30 + i*40}, 70%, 60%, 1)`,
537
- borderWidth: 1
538
  }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  }
540
 
541
- // 1. Performance Chart (Horizontal Bar)
542
- const ctxScore = document.getElementById('scoreChart').getContext('2d');
543
  if (scoreChartInstance) scoreChartInstance.destroy();
544
-
545
- scoreChartInstance = new Chart(ctxScore, {
546
  type: 'bar',
547
- data: { labels: labels, datasets: datasets },
548
- options: {
549
- indexAxis: 'y', // Horizontal
550
- responsive: true,
551
- maintainAspectRatio: false,
552
- scales: {
553
- x: { beginAtZero: true, max: 1.0 },
554
- y: { ticks: { autoSkip: false } }
555
- }
556
- }
557
  });
558
 
559
- // 2. Pareto Frontier Chart (Scatter)
560
- // X: Num Selected Features, Y: Mean F1
561
  const paretoData = results.map(r => ({
562
  x: r.num_features || (r.selected_features ? r.selected_features.length : 0),
563
- y: r.mean_f1 || 0,
564
  algorithm: r.algorithm
565
  }));
566
-
567
- const ctxPareto = document.getElementById('paretoChart').getContext('2d');
568
- if (paretoChartInstance) paretoChartInstance.destroy();
569
-
570
- paretoChartInstance = new Chart(ctxPareto, {
571
  type: 'scatter',
572
  data: {
573
  datasets: [{
574
- label: 'Algorithm Performance',
575
  data: paretoData,
576
- backgroundColor: 'rgba(230, 126, 34, 0.7)', // Orange accent
577
- borderColor: 'rgba(230, 126, 34, 1)',
578
- pointRadius: 6,
579
- pointHoverRadius: 8
580
  }]
581
  },
582
  options: {
583
  responsive: true,
584
  maintainAspectRatio: false,
585
  scales: {
586
- x: {
587
- type: 'linear',
588
- position: 'bottom',
589
- title: { display: true, text: 'Number of Selected Features' }
590
- },
591
- y: {
592
- title: { display: true, text: 'Mean F1 Score' },
593
- min: 0, max: 1
594
- }
595
  },
596
  plugins: {
597
  tooltip: {
598
  callbacks: {
599
- label: function(context) {
600
- const pt = context.raw;
601
- return `${pt.algorithm}: F1=${pt.y.toFixed(4)}, Feats=${pt.x}`;
602
- }
603
  }
604
  }
605
  }
@@ -607,244 +966,41 @@
607
  });
608
  }
609
 
610
- function updateView() {
611
- renderTableHeader();
612
- renderTable(filteredResults);
613
- updateCharts(filteredResults);
614
- }
615
-
616
- function renderTableHeader() {
617
- const viewMode = document.getElementById('chart-view-mode').value;
618
- const config = VIEW_CONFIG[viewMode];
619
-
620
- let headerHTML = `
621
- <tr>
622
- <th>Rank</th>
623
- <th onclick="sortTable('algorithm')">Algorithm <span class="arrow"></span></th>
624
- `;
625
-
626
- config.forEach(col => {
627
- headerHTML += `<th onclick="sortTable('${col.key}')">${col.label} <span class="arrow"></span></th>`;
628
- });
629
-
630
- headerHTML += `
631
- <th onclick="sortTable('time')">Time (s) <span class="arrow"></span></th>
632
- <th onclick="sortTable('selected_features')">Selected Features <span class="arrow"></span></th>
633
- </tr>
634
- `;
635
-
636
- tableHead.innerHTML = headerHTML;
637
- }
638
-
639
- function renderTable(results) {
640
- tableBody.innerHTML = "";
641
-
642
- // Robust data handling
643
- if (!results) { results = []; }
644
- else if (!Array.isArray(results)) {
645
- if (results.data && Array.isArray(results.data)) results = results.data;
646
- else if (results.results && Array.isArray(results.results)) results = results.results;
647
- else if (results.algorithm) results = [results];
648
- else results = [];
649
- }
650
-
651
- if (results.length === 0) {
652
- tableBody.innerHTML = '<tr><td colspan="10" style="text-align:center;">No results found</td></tr>';
653
- return;
654
- }
655
-
656
- const viewMode = document.getElementById('chart-view-mode').value;
657
- const config = VIEW_CONFIG[viewMode];
658
-
659
- results.forEach((row, index) => {
660
- const tr = document.createElement("tr");
661
-
662
- // Helper to get nested property safely
663
- const getVal = (obj, path) => {
664
- return path.split('.').reduce((acc, part) => acc && acc[part], obj);
665
- };
666
-
667
- let metricsHTML = '';
668
- config.forEach(col => {
669
- const val = getVal(row, col.key);
670
- const numVal = (val !== undefined && val !== null) ? Number(val).toFixed(4) : 'N/A';
671
- metricsHTML += `<td>${numVal}</td>`;
672
- });
673
-
674
- // Features
675
- let featCount = row.num_features;
676
- if (featCount === undefined && row.selected_features) featCount = row.selected_features.length;
677
-
678
- let featText = "";
679
- if (Array.isArray(row.selected_features)) {
680
- featText = row.selected_features.join(", ");
681
- } else {
682
- featText = "N/A";
683
- }
684
-
685
- const rank = index + 1;
686
-
687
- tr.innerHTML = `
688
- <td>${rank}</td>
689
- <td class="algo-link" onclick="openPdf('${row.algorithm}')" title="Click to view paper">${row.algorithm || 'Unknown'}</td>
690
- ${metricsHTML}
691
- <td>${row.time ? Number(row.time).toFixed(4) : 'N/A'}</td>
692
- <td class="features-cell" onclick="showDetails('${row.algorithm}', '${featText}')" title="${featText}">
693
- ${featText}
694
- </td>
695
- `;
696
- tableBody.appendChild(tr);
697
- });
698
- }
699
-
700
  function sortTable(key) {
701
- if (lastSortKey === key) {
702
- sortDirection *= -1;
703
- } else {
704
- sortDirection = 1;
705
- lastSortKey = key;
706
- }
707
-
708
- // Helper to get nested value
709
  const getVal = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
710
-
711
  filteredResults.sort((a, b) => {
712
  let valA = getVal(a, key);
713
  let valB = getVal(b, key);
714
-
715
- // Handle array length for selected_features sort
716
  if (key === 'selected_features') {
717
  valA = Array.isArray(valA) ? valA.length : 0;
718
  valB = Array.isArray(valB) ? valB.length : 0;
719
  }
720
-
721
  if (valA === undefined) valA = -Infinity;
722
  if (valB === undefined) valB = -Infinity;
723
-
724
- if (valA < valB) return -1 * sortDirection;
725
- if (valA > valB) return 1 * sortDirection;
726
- return 0;
727
  });
728
-
729
  renderTable(filteredResults);
730
- updateSortArrows(key);
731
- }
732
-
733
- function updateSortArrows(activeKey) {
734
  document.querySelectorAll('th .arrow').forEach(span => span.textContent = '↕');
735
- // Find the th with onclick containing this key
736
- const ths = document.querySelectorAll('th');
737
- ths.forEach(th => {
738
- if (th.getAttribute('onclick').includes(`'${activeKey}'`)) {
739
- th.querySelector('.arrow').textContent = sortDirection === 1 ? '↑' : '↓';
740
- }
741
- });
742
  }
743
 
744
- function showDetails(algo, features) {
745
- document.getElementById("modal-title").innerText = `${algo} - Selected Features`;
746
- const featArray = features.split(", ");
747
- const html = featArray.map(f => `<span class="feature-tag">${f}</span>`).join(" ");
748
- document.getElementById("modal-body").innerHTML = `
749
- <p><strong>Total Selected:</strong> ${featArray.length}</p>
750
- <div style="margin-top:10px; line-height:1.6;">${html}</div>
751
- `;
752
- modal.style.display = "block";
753
- }
754
-
755
- function fetchDatasets() {
756
- fetch("/api/datasets")
757
- .then(res => res.json())
758
- .then(data => {
759
- allDatasets = data;
760
- datasetSelect.innerHTML = "";
761
-
762
- // Sort dates for global updated
763
- const dates = data.map(d => d.last_updated).filter(d => d !== 'Unknown').sort().reverse();
764
- if (dates.length > 0) {
765
- globalUpdated.textContent = `Data Last Updated: ${dates[0]}`;
766
- } else {
767
- globalUpdated.textContent = `Data Last Updated: Unknown`;
768
- }
769
-
770
- data.forEach(ds => {
771
- const option = document.createElement("option");
772
- option.value = ds.name;
773
- option.textContent = ds.name;
774
- datasetSelect.appendChild(option);
775
- });
776
-
777
- // Default selection
778
- if (data.length > 0) {
779
- loadDataset(data[0].name);
780
- }
781
- })
782
- .catch(err => {
783
- console.error("Error loading datasets:", err);
784
- datasetSelect.innerHTML = '<option disabled>Error loading</option>';
785
- });
786
- }
787
-
788
- function loadDataset(name) {
789
- datasetSelect.value = name;
790
- loadingIndicator.style.display = "block";
791
- tableBody.innerHTML = "";
792
-
793
- // Update metadata box
794
- const dsInfo = allDatasets.find(d => d.name === name);
795
- if (dsInfo) {
796
- metaName.textContent = dsInfo.name;
797
- metaUpdated.textContent = dsInfo.last_updated;
798
- descName.textContent = dsInfo.name;
799
- }
800
-
801
- fetch(`/api/results?dataset=${name}`)
802
- .then(res => res.json())
803
- .then(data => {
804
- loadingIndicator.style.display = "none";
805
- currentResults = data;
806
-
807
- // Reset filters on new dataset? Or keep them?
808
- // Let's reset to show all data first, or apply current?
809
- // Applying current is better UX
810
- applyFilters();
811
- renderTableHeader(); // Ensure headers match view mode
812
- })
813
- .catch(err => {
814
- loadingIndicator.style.display = "none";
815
- console.error("Error:", err);
816
- tableBody.innerHTML = '<tr><td colspan="10" style="color:red; text-align:center;">Error loading results</td></tr>';
817
- });
818
- }
819
-
820
- datasetSelect.addEventListener("change", (e) => {
821
- loadDataset(e.target.value);
822
- });
823
-
824
- // PDF Sidebar Logic
825
  function openPdf(algoName) {
826
  if (!algoName) return;
827
- const sidebar = document.getElementById('pdf-sidebar');
828
- const frame = document.getElementById('pdf-frame');
829
-
830
- // Use upper case as observed in file system
831
  const filename = algoName.toUpperCase() + ".pdf";
832
-
833
- frame.src = `/pdfs/${filename}`;
834
- sidebar.classList.add('open');
835
  }
836
-
837
  function closeSidebar() {
838
- const sidebar = document.getElementById('pdf-sidebar');
839
- sidebar.classList.remove('open');
840
- // Clear src after transition to avoid flicker or keep memory usage low
841
- setTimeout(() => {
842
- document.getElementById('pdf-frame').src = "";
843
- }, 300);
844
  }
845
-
846
- document.addEventListener("DOMContentLoaded", fetchDatasets);
847
-
848
  </script>
849
 
850
  </body>
 
14
  --border-color: #dee2e6;
15
  --hover-color: #f1f1f1;
16
  --accent-color: #e67e22;
17
+ --sidebar-width: 280px;
18
  }
19
 
20
  body {
21
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22
  margin: 0;
23
+ padding: 0;
24
  background-color: var(--background-color);
25
  color: var(--text-color);
26
+ display: flex;
27
+ min-height: 100vh;
28
  }
29
 
30
+ /* Sidebar Styling */
31
+ .sidebar {
32
+ width: var(--sidebar-width);
33
+ background-color: var(--secondary-color);
34
+ color: white;
35
+ position: fixed;
36
+ height: 100vh;
37
+ overflow-y: auto;
38
  padding: 20px;
39
+ box-sizing: border-box;
40
+ left: 0;
41
+ top: 0;
42
+ z-index: 100;
 
43
  display: flex;
44
+ flex-direction: column;
45
+ gap: 20px;
 
 
 
46
  }
47
 
48
+ .sidebar h2 {
49
+ font-size: 1.2em;
50
+ margin-bottom: 10px;
51
+ color: #ecf0f1;
52
+ border-bottom: 1px solid #34495e;
53
+ padding-bottom: 5px;
54
  }
55
 
56
+ /* Stats Cards */
57
+ .stats-grid {
58
+ display: grid;
59
+ grid-template-columns: 1fr;
60
  gap: 10px;
 
61
  }
62
 
63
+ .stat-card {
64
+ background: rgba(255,255,255,0.1);
65
+ padding: 10px;
66
+ border-radius: 6px;
67
+ text-align: center;
68
+ }
69
+
70
+ .stat-value {
71
+ font-size: 1.2em;
72
+ font-weight: bold;
73
+ color: var(--accent-color);
74
+ }
75
+
76
+ .stat-label {
77
+ font-size: 0.8em;
78
+ color: #bdc3c7;
79
+ }
80
+
81
+ /* Navigation */
82
+ .nav-links {
83
+ list-style: none;
84
+ padding: 0;
85
+ margin: 0;
86
+ }
87
+
88
+ .nav-links li a {
89
+ display: block;
90
+ padding: 10px;
91
+ color: #bdc3c7;
92
+ text-decoration: none;
93
  border-radius: 4px;
94
+ transition: background 0.2s;
95
  }
96
 
97
+ .nav-links li a:hover {
98
+ background: rgba(255,255,255,0.1);
99
+ color: white;
 
 
 
100
  }
101
 
102
+ /* Sidebar Filters */
103
+ .sidebar-filters label {
104
+ display: block;
105
+ margin-bottom: 5px;
106
+ font-size: 0.9em;
107
+ color: #bdc3c7;
108
+ }
109
+
110
+ .sidebar-input {
111
+ width: 100%;
112
+ margin-bottom: 15px;
113
+ padding: 5px;
114
+ box-sizing: border-box;
115
+ background: rgba(255,255,255,0.9);
116
+ border: none;
117
  border-radius: 4px;
118
+ }
119
+
120
+ input[type="range"] {
121
+ width: 100%;
122
  }
123
 
124
+ .checkbox-group {
125
+ max-height: 150px;
126
+ overflow-y: auto;
127
+ background: rgba(0,0,0,0.2);
128
+ padding: 5px;
129
+ border-radius: 4px;
130
  }
131
 
132
+ .checkbox-item {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 5px;
136
+ margin-bottom: 3px;
137
+ font-size: 0.85em;
138
  }
139
 
140
+ /* Main Content Styling */
141
+ .main-content {
142
+ margin-left: var(--sidebar-width);
143
+ flex: 1;
144
+ padding: 40px;
145
+ max-width: calc(100% - var(--sidebar-width));
146
+ box-sizing: border-box;
147
+ min-width: 0; /* Prevent flex item from overflowing */
148
  }
149
 
150
+ header {
151
+ display: flex;
152
+ justify-content: space-between;
153
+ align-items: center;
154
+ margin-bottom: 20px;
155
+ border-bottom: 2px solid var(--primary-color);
156
+ padding-bottom: 10px;
157
+ }
158
+
159
+ .subtitle {
160
+ font-size: 0.9em;
161
  color: #7f8c8d;
162
  margin-top: 5px;
163
  }
164
 
165
+ /* Info Boxes */
166
+ .info-section {
 
 
 
 
 
167
  display: flex;
168
  gap: 20px;
169
+ margin-bottom: 20px;
170
  flex-wrap: wrap;
171
  }
172
 
173
+ .description-box, .metadata-box {
174
+ flex: 1;
175
+ background-color: white;
176
+ border-left: 4px solid #3498db;
177
+ padding: 15px;
178
+ border-radius: 4px;
179
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
180
  }
181
 
182
+ .metadata-box {
183
+ border-left-color: #f1c40f;
184
  }
185
 
186
+ /* Table */
187
+ .table-container {
188
+ background: white;
189
+ padding: 20px;
190
+ border-radius: 8px;
191
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
192
+ overflow-x: auto;
193
  }
194
 
 
195
  table {
196
  width: 100%;
197
  border-collapse: collapse;
 
198
  }
199
 
200
  th, td {
 
204
  }
205
 
206
  th {
207
+ background-color: #f8f9fa;
208
+ color: var(--secondary-color);
209
  cursor: pointer;
210
  user-select: none;
 
 
211
  }
212
 
213
  th:hover {
214
+ background-color: #e9ecef;
 
 
 
 
 
 
215
  }
216
+
217
  tr:hover {
218
  background-color: var(--hover-color);
219
  }
220
 
221
+ .algo-link {
222
+ color: var(--primary-color);
223
+ cursor: pointer;
224
+ font-weight: bold;
 
 
 
 
 
 
 
225
  }
226
+ .algo-link:hover { text-decoration: underline; }
227
 
228
  .features-cell {
229
  max-width: 200px;
230
+ cursor: pointer;
231
+ }
232
+ .truncate-text {
233
  white-space: nowrap;
234
  overflow: hidden;
235
  text-overflow: ellipsis;
236
+ display: block;
 
 
 
 
 
 
 
237
  }
238
+ .features-cell:hover .truncate-text { text-decoration: underline; color: var(--primary-color); }
239
 
240
  /* Charts */
241
  .charts-section {
 
247
 
248
  .chart-container {
249
  background: white;
250
+ padding: 20px;
251
  border-radius: 8px;
252
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 
253
  position: relative;
254
+ height: 60vh;
255
+ min-height: 400px;
256
+ max-height: 700px;
257
  width: 100%;
258
+ box-sizing: border-box; /* Fix overflow */
259
+ }
260
+
261
+ canvas {
262
+ width: 100% !important;
263
+ height: 100% !important;
264
  }
265
 
266
+ /* Modal */
267
  .modal {
268
  display: none;
269
  position: fixed;
 
285
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
286
  }
287
 
288
+ .close { float: right; font-size: 28px; cursor: pointer; }
 
 
 
 
 
 
 
 
 
 
 
289
  .feature-tag {
290
  display: inline-block;
291
  background-color: #e1ecf4;
 
295
  margin: 2px;
296
  font-size: 0.9em;
297
  }
 
 
 
 
 
 
298
 
299
+ /* PDF Sidebar */
300
  .pdf-sidebar {
301
  position: fixed;
302
  top: 0;
303
+ right: -50%;
304
  width: 50%;
305
  height: 100%;
306
  background: white;
 
310
  display: flex;
311
  flex-direction: column;
312
  }
313
+ .pdf-sidebar.open { right: 0; }
 
 
 
 
314
  .sidebar-header {
315
  padding: 10px 20px;
316
  background: var(--primary-color);
 
319
  justify-content: space-between;
320
  align-items: center;
321
  }
322
+ .sidebar-content { flex: 1; }
323
+ .sidebar-content iframe { width: 100%; height: 100%; border: none; }
324
+
325
+ .loading { text-align: center; padding: 20px; color: #666; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  </style>
327
  </head>
328
  <body>
329
 
330
+ <!-- Sidebar Navigation & Filters -->
331
+ <aside class="sidebar">
332
+ <div style="text-align: center; margin-bottom: 20px;">
333
+ <h1 style="font-size: 1.5em; margin: 0; color: white;">AutoFS</h1>
334
+ <div style="font-size: 0.8em; color: #bdc3c7;">Feature Selection Leaderboard</div>
335
+ </div>
336
+
337
+ <!-- Stats -->
338
+ <div class="stats-grid">
339
+ <div class="stat-card">
340
+ <div class="stat-value" id="stat-count">-</div>
341
+ <div class="stat-label">Methods</div>
342
+ </div>
343
+ <div class="stat-card">
344
+ <div class="stat-value" id="stat-best">-</div>
345
+ <div class="stat-label">Best F1</div>
346
+ </div>
347
+ <div class="stat-card">
348
+ <div class="stat-value" id="stat-updated">-</div>
349
+ <div class="stat-label">Updated</div>
350
+ </div>
351
+ </div>
352
+
353
+ <!-- Navigation -->
354
+ <div>
355
+ <h2>Navigation</h2>
356
+ <ul class="nav-links">
357
+ <li><a href="#overview">📊 Overview</a></li>
358
+ <li><a href="#main-table">🏆 Leaderboard</a></li>
359
+ <li><a href="/#charts">📈 Charts</a></li>
360
+ <li><a href="#details">ℹ️ Details</a></li>
361
+ <li><a href="/global">🌍 Global Rankings</a></li>
362
+ <li><a href="/submit">📤 Submit Data/Method</a></li>
363
+ </ul>
364
+ </div>
365
+
366
+ <!-- Filters -->
367
+ <div class="sidebar-filters">
368
+ <h2>Filters</h2>
369
+
370
+ <label>Dataset:</label>
371
+ <select id="dataset-select" class="sidebar-input">
372
+ <option value="" disabled selected>Loading...</option>
373
+ </select>
374
+
375
+ <label>Min F1 Score: <span id="val-f1" style="color:var(--accent-color)">0.0000</span></label>
376
+ <input type="range" id="filter-f1" min="0" max="1" step="0.0001" value="0">
377
+
378
+ <label>Del. Rate: <span id="val-del-rate" style="color:var(--accent-color)">0% - 100%</span></label>
379
+ <div style="display: flex; gap: 5px;">
380
+ <input type="range" id="filter-del-min" min="0" max="100" value="0" style="width: 50%;">
381
+ <input type="range" id="filter-del-max" min="0" max="100" value="100" style="width: 50%;">
382
+ </div>
383
+
384
+ <label>Max Features: <span id="val-feats" style="color:var(--accent-color)">All</span></label>
385
+ <input type="range" id="filter-feats" min="1" max="100" step="1" value="100">
386
+
387
+ <label>Complexity:</label>
388
+ <select id="filter-complexity" class="sidebar-input" onchange="applyFilters()">
389
+ <option value="all">All Complexities</option>
390
+ <!-- Populated via JS -->
391
+ </select>
392
+
393
+ <label>Algorithms:</label>
394
+ <div id="algo-checkboxes" class="checkbox-group">
395
+ <!-- Populated via JS -->
396
+ </div>
397
+ </div>
398
+ </aside>
399
+
400
+ <!-- Main Content -->
401
+ <main class="main-content">
402
  <header>
403
  <div>
404
+ <h1 style="color: var(--secondary-color); margin: 0;">🏆 Leaderboard Dashboard</h1>
405
+ <div class="subtitle">Comprehensive benchmark of feature selection algorithms across diverse datasets.</div>
 
 
 
 
 
 
 
 
 
406
  </div>
407
  </header>
408
 
409
+ <div class="intro-text" style="background: white; padding: 20px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); color: #555; line-height: 1.6; font-size: 0.95em;">
410
+ <p style="margin-top:0">Feature selection is a critical step in machine learning and data analysis, aimed at <strong>identifying the most relevant subset of features</strong> from a high-dimensional dataset. By eliminating irrelevant or redundant features, feature selection not only <strong>improves model interpretability</strong> but also <strong>enhances predictive performance</strong> and <strong>reduces computational cost</strong>.</p>
411
+ <p>This leaderboard presents a comprehensive comparison of various feature selection algorithms across multiple benchmark datasets. It includes several <strong>information-theoretic and mutual information-based methods</strong>, which quantify the statistical dependency between features and the target variable to rank feature relevance. Mutual information approaches are particularly effective in <strong>capturing both linear and non-linear relationships</strong>, making them suitable for complex datasets where classical correlation-based methods may fail.</p>
412
+ <p>The leaderboard is structured to reflect algorithm performance across different datasets, allowing for an objective assessment of each method’s ability to select informative features. For each method and dataset combination, metrics such as <strong>classification accuracy, F1-score, and area under the ROC curve (AUC)</strong> are reported, providing insights into how the selected features contribute to predictive modeling.</p>
413
+ <p style="margin-bottom:0">By examining this feature selection leaderboard, researchers and practitioners can gain a better understanding of which methods perform consistently well across diverse domains, helping to guide the choice of feature selection strategies in real-world applications. This serves as a valuable resource for both benchmarking and method development in the field of feature selection.</p>
414
+ </div>
415
+
416
+ <div id="overview" class="info-section">
417
  <div class="description-box">
418
  <h3>About This Dataset</h3>
419
  <p>
420
+ Analyzing performance on <strong><span id="desc-dataset-name">Selected</span></strong>.
421
+ Compare F1 scores, AUC stability, and computational efficiency to find the optimal method for your data.
 
422
  </p>
423
  </div>
424
  <div class="metadata-box">
425
  <h3>Dataset Metadata</h3>
426
  <p><strong>Name:</strong> <span id="meta-name">-</span></p>
427
+ <p><strong>Samples:</strong> <span id="meta-samples">-</span> | <strong>Features:</strong> <span id="meta-features">-</span></p>
428
  <p><strong>Last Updated:</strong> <span id="meta-updated">-</span></p>
 
 
429
  </div>
430
  </div>
431
 
432
+ <div id="main-table" style="margin-top: 30px;">
433
+ <h3>📋 Detailed Rankings</h3>
434
+ <div id="loading-indicator" class="loading" style="display: none;">Loading data...</div>
435
+ <div class="table-container">
436
+ <table id="result-table">
437
+ <thead><!-- Headers generated dynamically --></thead>
438
+ <tbody><!-- Data rows --></tbody>
439
+ </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  </div>
441
  </div>
442
 
443
+ <div id="charts" class="charts-section">
 
 
 
 
 
 
 
 
 
 
 
444
  <div class="chart-container">
445
  <h3>📊 Performance Comparison</h3>
446
+ <div style="position: absolute; top: 15px; right: 20px; z-index: 10;">
447
+ <select id="chart-view-mode" onchange="updateView()" style="padding: 5px;">
448
+ <option value="overall">Overall (Mean)</option>
449
+ <option value="classifiers-f1">F1 by Classifier</option>
450
+ <option value="classifiers-auc">AUC by Classifier</option>
451
+ </select>
452
+ </div>
453
  <canvas id="scoreChart"></canvas>
454
+ <div id="score-debug" style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); color:red; display:none; background:rgba(255,255,255,0.9); padding:10px; border:1px solid red;"></div>
455
  </div>
456
 
457
  <div class="chart-container">
458
  <h3>📉 Pareto Frontier (Trade-off)</h3>
459
+ <p style="font-size:0.9em; color:#666; margin-top:-10px;">X: Selected Features vs Y: F1 Score (Top-Left is better)</p>
460
  <canvas id="paretoChart"></canvas>
461
  </div>
462
  </div>
 
463
 
464
+
465
+ <div id="details" style="margin-top: 50px; color: #999; text-align: center; border-top: 1px solid #eee; padding-top: 20px;">
466
+ <p>AutoFS Benchmark Platform &copy; 2026</p>
467
+ </div>
468
+ </main>
469
+
470
+ <!-- Modal -->
471
  <div id="details-modal" class="modal">
472
  <div class="modal-content">
473
  <span class="close">&times;</span>
 
491
  let currentResults = [];
492
  let filteredResults = [];
493
  let allDatasets = [];
494
+ let algoComplexityMap = {};
495
+ let sortDirection = 1;
496
  let lastSortKey = '';
497
 
498
+ // Elements
499
+ const datasetSelect = document.getElementById("dataset-select");
500
+ const loadingIndicator = document.getElementById("loading-indicator");
501
+ const tableHead = document.querySelector("#result-table thead");
502
+ const tableBody = document.querySelector("#result-table tbody");
503
+
504
  // Filter Elements
505
  const filterF1 = document.getElementById('filter-f1');
506
+ const filterFeats = document.getElementById('filter-feats');
507
+ const filterDelMin = document.getElementById('filter-del-min');
508
+ const filterDelMax = document.getElementById('filter-del-max');
509
+ const filterComplexity = document.getElementById('filter-complexity');
510
+ const algoCheckboxes = document.getElementById('algo-checkboxes');
511
+
512
+ // Display Values
513
  const valF1 = document.getElementById('val-f1');
514
+ const valFeats = document.getElementById('val-feats');
515
+ const valDelRate = document.getElementById('val-del-rate');
516
+
517
+ // Stats Elements
518
+ const statCount = document.getElementById('stat-count');
519
+ const statBest = document.getElementById('stat-best');
520
+ const statUpdated = document.getElementById('stat-updated');
521
+
522
+ // Metadata Elements
523
+ const metaName = document.getElementById('meta-name');
524
+ const metaSamples = document.getElementById('meta-samples');
525
+ const metaFeatures = document.getElementById('meta-features');
526
+ const metaUpdated = document.getElementById('meta-updated');
527
+ const descName = document.getElementById('desc-dataset-name');
528
+
529
+ // Charts
530
+ let scoreChartInstance = null;
531
+ let paretoChartInstance = null;
532
 
533
  const VIEW_CONFIG = {
534
  'overall': [
 
547
  ]
548
  };
549
 
550
+ // Modal Logic
 
 
 
551
  const modal = document.getElementById("details-modal");
552
+ document.querySelector(".close").onclick = () => modal.style.display = "none";
553
+ window.onclick = (e) => { if (e.target == modal) modal.style.display = "none"; };
 
 
 
 
 
554
 
555
+ // Initialization
556
+ document.addEventListener("DOMContentLoaded", () => {
557
+ fetchAlgorithms();
558
+ fetchDatasets();
559
+ });
560
+
561
+ function fetchAlgorithms() {
562
+ fetch("/api/algos")
563
+ .then(res => res.json())
564
+ .then(data => {
565
+ algoComplexityMap = data;
566
+ // Populate Complexity Filter
567
+ const complexities = new Set();
568
+ Object.values(data).forEach(c => {
569
+ if (c.time) complexities.add(c.time); // Use time formula as key
570
+ });
571
+ complexities.forEach(c => {
572
+ const opt = document.createElement('option');
573
+ opt.value = c;
574
+ opt.textContent = c;
575
+ filterComplexity.appendChild(opt);
576
+ });
577
+ })
578
+ .catch(console.error);
579
  }
580
 
581
+ function fetchDatasets() {
582
+ fetch("/api/datasets")
583
+ .then(res => res.json())
584
+ .then(data => {
585
+ allDatasets = data;
586
+ datasetSelect.innerHTML = "";
587
+ data.forEach(ds => {
588
+ const opt = document.createElement("option");
589
+ opt.value = ds.name;
590
+ opt.textContent = ds.name;
591
+ datasetSelect.appendChild(opt);
592
+ });
593
+ if (data.length > 0) loadDataset(data[0].name);
594
+ });
595
+ }
596
+
597
+ datasetSelect.addEventListener("change", (e) => loadDataset(e.target.value));
598
+
599
+ function loadDataset(name) {
600
+ loadingIndicator.style.display = "block";
601
+
602
+ // Update Metadata
603
+ const ds = allDatasets.find(d => d.name === name);
604
+ if (ds) {
605
+ metaName.textContent = ds.name;
606
+ metaSamples.textContent = ds.num_samples || 'N/A';
607
+ metaFeatures.textContent = ds.total_features || 'N/A';
608
+ metaUpdated.textContent = ds.last_updated;
609
+ descName.textContent = ds.name;
610
+ statUpdated.textContent = ds.last_updated;
611
+
612
+ // Update Max Features Slider
613
+ if (ds.total_features && ds.total_features !== 'Unavailable') {
614
+ filterFeats.max = ds.total_features;
615
+ filterFeats.value = ds.total_features;
616
+ valFeats.textContent = ds.total_features;
617
+ } else {
618
+ filterFeats.max = 100; // Default
619
+ filterFeats.value = 100;
620
+ valFeats.textContent = "100+";
621
+ }
622
+ }
623
+
624
+ fetch(`/api/results?dataset=${name}`)
625
+ .then(res => res.json())
626
+ .then(data => {
627
+ loadingIndicator.style.display = "none";
628
+ currentResults = data;
629
+
630
+ // Adjust Filter Ranges based on Data
631
+ if (data.length > 0) {
632
+ const f1Scores = data.map(d => d.mean_f1 || 0);
633
+
634
+ const minF1 = Math.min(...f1Scores);
635
+ const maxF1 = Math.max(...f1Scores);
636
+
637
+ // Set F1 Range (sensible defaults)
638
+ // Set min to a bit below the lowest score to maximize resolution
639
+ const safeMin = Math.max(0, Math.floor((Math.min(...f1Scores) - 0.1) * 10) / 10);
640
+ filterF1.min = safeMin;
641
+ filterF1.max = 1;
642
+ filterF1.value = safeMin;
643
+ valF1.textContent = safeMin.toFixed(4);
644
+ }
645
+
646
+ // Populate Algorithm Checkboxes
647
+ const algos = [...new Set(data.map(r => r.algorithm))].sort();
648
+ algoCheckboxes.innerHTML = `
649
+ <div class="checkbox-item">
650
+ <input type="checkbox" id="algo-all" checked onchange="toggleAllAlgos(this)">
651
+ <label for="algo-all" style="font-weight:bold">Select All</label>
652
+ </div>
653
+ `;
654
+ algos.forEach(algo => {
655
+ algoCheckboxes.innerHTML += `
656
+ <div class="checkbox-item">
657
+ <input type="checkbox" value="${algo}" checked class="algo-cb" onchange="applyFilters()">
658
+ <label>${algo}</label>
659
+ </div>
660
+ `;
661
+ });
662
+
663
+ applyFilters();
664
+ renderTableHeader();
665
+ })
666
+ .catch(err => {
667
+ console.error(err);
668
+ loadingIndicator.style.display = "none";
669
+ });
670
+ }
671
+
672
+ function toggleAllAlgos(source) {
673
+ document.querySelectorAll('.algo-cb').forEach(cb => {
674
+ cb.checked = source.checked;
675
+ });
676
+ applyFilters();
677
+ }
678
 
679
  // Filter Logic
680
+ [filterF1, filterFeats, filterDelMin, filterDelMax].forEach(el => el.addEventListener('input', applyFilters));
681
+
682
  function applyFilters() {
683
  const minF1 = parseFloat(filterF1.value);
684
+ const maxFeats = parseFloat(filterFeats.value);
685
+ const minDel = parseInt(filterDelMin.value) / 100;
686
+ const maxDel = parseInt(filterDelMax.value) / 100;
687
+ const selectedComplexity = filterComplexity.value;
688
 
689
+ // Get checked algorithms
690
+ const checkedAlgos = Array.from(document.querySelectorAll('.algo-cb:checked')).map(cb => cb.value);
691
+
692
+ // Update UI Values
693
+ valF1.textContent = minF1.toFixed(4);
694
+ valFeats.textContent = maxFeats;
695
+ valDelRate.textContent = `${(minDel*100).toFixed(0)}% - ${(maxDel*100).toFixed(0)}%`;
696
 
697
  filteredResults = currentResults.filter(r => {
698
  const f1 = r.mean_f1 || 0;
699
+ const feats = r.num_features || (r.selected_features ? r.selected_features.length : 0);
700
+
701
+ // Algo Filter
702
+ if (!checkedAlgos.includes(r.algorithm)) return false;
703
+
704
+ // Metric Filters
705
+ if (f1 < minF1) return false;
706
+ if (feats > maxFeats) return false;
707
+
708
+ // Deletion Rate Check
709
+ const ds = allDatasets.find(d => d.name === datasetSelect.value);
710
+ let totalFeats = 0;
711
+ if (ds && ds.total_features && ds.total_features !== 'Unavailable') {
712
+ totalFeats = parseInt(ds.total_features);
713
+ }
714
+
715
+ if (totalFeats > 0) {
716
+ const delRate = 1.0 - (feats / totalFeats);
717
+ if (delRate < minDel || delRate > maxDel) return false;
718
+ }
719
+
720
+ // Complexity Filter
721
+ if (selectedComplexity !== 'all') {
722
+ const algoInfo = algoComplexityMap[r.algorithm];
723
+ if (!algoInfo || algoInfo.time !== selectedComplexity) return false;
724
+ }
725
+
726
+ return true;
727
  });
728
 
729
+ updateDashboard(filteredResults);
730
  renderTable(filteredResults);
731
  updateCharts(filteredResults);
732
  }
733
 
734
+ function updateDashboard(results) {
735
+ // Update Stats
736
+ statCount.textContent = results.length;
737
+ if (results.length > 0) {
738
+ const best = results.reduce((prev, curr) => (prev.mean_f1 > curr.mean_f1) ? prev : curr, {mean_f1:0, algorithm:'-'});
739
+ statBest.textContent = `${best.algorithm} (${Number(best.mean_f1).toFixed(3)})`;
740
+ } else {
741
+ statBest.textContent = "-";
742
+ }
743
+ }
744
 
745
+ function renderTableHeader() {
746
+ const viewMode = document.getElementById('chart-view-mode').value;
747
+ const config = VIEW_CONFIG[viewMode];
748
+ let html = `<tr>
749
+ <th>Rank</th>
750
+ <th onclick="sortTable('algorithm')">Algorithm <span class="arrow"></span></th>`;
751
+ config.forEach(col => {
752
+ html += `<th onclick="sortTable('${col.key}')">${col.label} <span class="arrow"></span></th>`;
753
+ });
754
+ html += `<th onclick="sortTable('selected_features')">Selected Features <span class="arrow"></span></th>
755
+ </tr>`;
756
+ tableHead.innerHTML = html;
757
+ }
758
+
759
+ function renderTable(results) {
760
+ tableBody.innerHTML = "";
761
+ if (results.length === 0) {
762
+ tableBody.innerHTML = '<tr><td colspan="10" style="text-align:center;">No results match filters</td></tr>';
763
+ return;
764
+ }
765
+
766
+ const viewMode = document.getElementById('chart-view-mode').value;
767
+ const config = VIEW_CONFIG[viewMode];
768
+ const getVal = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
769
+
770
+ // Calculate Best Values (Max for Score, Min for Time/Feats)
771
+ const bestValues = {};
772
+
773
+ // Metrics (Max is best)
774
+ config.forEach(col => {
775
+ const values = results.map(r => Number(getVal(r, col.key) || 0));
776
+ bestValues[col.key] = Math.max(...values);
777
+ });
778
+
779
+ // Features (Min is best)
780
+ const featCounts = results.map(r => Array.isArray(r.selected_features) ? r.selected_features.length : Infinity);
781
+ bestValues['features'] = Math.min(...featCounts);
782
+
783
+ results.forEach((row, index) => {
784
+ const tr = document.createElement("tr");
785
+ let metricsHTML = '';
786
+
787
+ config.forEach(col => {
788
+ const rawVal = getVal(row, col.key);
789
+ const val = (rawVal !== undefined && rawVal !== null) ? Number(rawVal) : 0;
790
+ // Compare formatted values to avoid precision issues
791
+ const isBest = val.toFixed(4) === bestValues[col.key].toFixed(4) && val > 0;
792
+ const style = isBest ? 'font-weight:bold; color:var(--primary-color);' : '';
793
+ metricsHTML += `<td style="${style}">${val.toFixed(4)}</td>`;
794
+ });
795
+
796
+ // Features
797
+ const featText = Array.isArray(row.selected_features) ? row.selected_features.join(", ") : "N/A";
798
+ const featCount = Array.isArray(row.selected_features) ? row.selected_features.length : 0;
799
+ const isBestFeat = featCount === bestValues['features'];
800
+ const featStyle = isBestFeat ? 'font-weight:bold; color:var(--primary-color);' : '';
801
+
802
+ tr.innerHTML = `
803
+ <td>${index + 1}</td>
804
+ <td class="algo-link" onclick="openPdf('${row.algorithm}')">${row.algorithm || 'Unknown'}</td>
805
+ ${metricsHTML}
806
+ <td class="features-cell" onclick="showDetails(${index})" title="${featText}">
807
+ <div class="truncate-text" style="${featStyle}">${featText}</div>
808
+ </td>
809
+ `;
810
+ tableBody.appendChild(tr);
811
+ });
812
+ }
813
+
814
+ function showDetails(index) {
815
+ const row = filteredResults[index];
816
+ if (!row) return;
817
 
818
+ const algo = row.algorithm;
819
+ const features = Array.isArray(row.selected_features) ? row.selected_features.join(", ") : "";
820
+ const metrics = row.metrics || {};
821
+
822
+ document.getElementById("modal-title").innerText = `${algo} - Details`;
823
+ const list = features.split(", ").map(f => `<span class="feature-tag">${f}</span>`).join(" ");
824
+
825
+ // Build Classifier Table
826
+ let classifierTable = `
827
+ <table style="width:100%; margin-top:10px; border-collapse:collapse;">
828
+ <thead>
829
+ <tr style="background:#f8f9fa;">
830
+ <th style="padding:8px; border:1px solid #dee2e6;">Classifier</th>
831
+ <th style="padding:8px; border:1px solid #dee2e6;">F1 Score</th>
832
+ <th style="padding:8px; border:1px solid #dee2e6;">AUC Score</th>
833
+ </tr>
834
+ </thead>
835
+ <tbody>
836
+ `;
837
+
838
+ ['nb', 'svm', 'rf'].forEach(clf => {
839
+ const m = metrics[clf] || {};
840
+ classifierTable += `
841
+ <tr>
842
+ <td style="padding:8px; border:1px solid #dee2e6;">${clf.toUpperCase()}</td>
843
+ <td style="padding:8px; border:1px solid #dee2e6;">${m.f1 ? Number(m.f1).toFixed(4) : '-'}</td>
844
+ <td style="padding:8px; border:1px solid #dee2e6;">${m.auc ? Number(m.auc).toFixed(4) : '-'}</td>
845
+ </tr>
846
+ `;
847
+ });
848
+ classifierTable += `</tbody></table>`;
849
+
850
+ document.getElementById("modal-body").innerHTML = `
851
+ <p><strong>Selected Features (${features ? features.split(", ").length : 0}):</strong></p>
852
+ <div style="line-height:1.6; max-height:200px; overflow-y:auto;">${list}</div>
853
+ <hr>
854
+ <p><strong>Classifier Performance:</strong></p>
855
+ ${classifierTable}
856
+ <hr>
857
+ <p><strong>Complexity:</strong></p>
858
+ <pre style="background:#f4f4f4; padding:10px; border-radius:4px;">${JSON.stringify(algoComplexityMap[algo] || 'Unknown', null, 2)}</pre>
859
+ `;
860
+ modal.style.display = "block";
861
+ }
862
 
863
+ function updateView() {
864
+ renderTableHeader();
865
+ renderTable(filteredResults);
866
+ updateCharts(filteredResults);
867
+ }
868
+
869
+ function updateCharts(results) {
870
+ const topResults = results.slice(0, 20);
871
+ const labels = topResults.map(r => r.algorithm);
872
  const viewMode = document.getElementById('chart-view-mode').value;
873
+
874
+ // Prepare datasets based on viewMode (same as before)
875
  let datasets = [];
876
+ const debugEl = document.getElementById('score-debug');
877
+ if (debugEl) debugEl.style.display = 'none';
878
 
879
  if (viewMode === 'overall') {
880
+ const f1Data = topResults.map(r => r.mean_f1);
881
+ const aucData = topResults.map(r => r.mean_auc);
882
+
883
  datasets = [
884
  {
885
+ label: 'Mean F1',
886
+ data: f1Data,
887
+ backgroundColor: 'rgba(52, 152, 219, 0.7)'
 
 
888
  },
889
  {
890
+ label: 'Mean AUC',
891
+ data: aucData,
892
+ backgroundColor: 'rgba(46, 204, 113, 0.7)'
 
 
893
  }
894
  ];
895
+
896
+ // Debug Check for AUC
897
+ // const validAuc = aucData.some(v => v > 0);
898
+ // if (!validAuc && debugEl) {
899
+ // debugEl.innerHTML = "<strong>Warning:</strong> No AUC data found for these algorithms.<br>Check backend 'metrics.x.auc' or JSON source.";
900
+ // debugEl.style.display = 'block';
901
+ // }
902
+
903
  } else if (viewMode === 'classifiers-f1') {
904
  datasets = ['nb', 'svm', 'rf'].map((clf, i) => ({
905
+ label: clf.toUpperCase(),
906
+ data: topResults.map(r => r.metrics?.[clf]?.f1),
907
+ backgroundColor: `hsla(${200 + i*40}, 70%, 60%, 0.7)`
 
 
 
 
 
 
 
 
 
 
908
  }));
909
+ } else if (viewMode === 'classifiers-auc') {
910
+ datasets = ['nb', 'svm', 'rf'].map((clf, i) => {
911
+ const data = topResults.map(r => r.metrics?.[clf]?.auc);
912
+ // Check if data exists
913
+ // if (data.every(v => !v)) {
914
+ // if (debugEl) {
915
+ // debugEl.innerHTML += `<br>Missing AUC for ${clf.toUpperCase()}`;
916
+ // debugEl.style.display = 'block';
917
+ // }
918
+ // }
919
+ return {
920
+ label: clf.toUpperCase(),
921
+ data: data,
922
+ backgroundColor: `hsla(${100 + i*40}, 70%, 60%, 0.7)`
923
+ };
924
+ });
925
  }
926
 
927
+ // Score Chart
 
928
  if (scoreChartInstance) scoreChartInstance.destroy();
929
+ scoreChartInstance = new Chart(document.getElementById('scoreChart'), {
 
930
  type: 'bar',
931
+ data: { labels, datasets },
932
+ options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false }
 
 
 
 
 
 
 
 
933
  });
934
 
935
+ // Pareto Chart
936
+ if (paretoChartInstance) paretoChartInstance.destroy();
937
  const paretoData = results.map(r => ({
938
  x: r.num_features || (r.selected_features ? r.selected_features.length : 0),
939
+ y: r.mean_f1,
940
  algorithm: r.algorithm
941
  }));
942
+ paretoChartInstance = new Chart(document.getElementById('paretoChart'), {
 
 
 
 
943
  type: 'scatter',
944
  data: {
945
  datasets: [{
946
+ label: 'Algo',
947
  data: paretoData,
948
+ backgroundColor: 'rgba(230, 126, 34, 0.7)'
 
 
 
949
  }]
950
  },
951
  options: {
952
  responsive: true,
953
  maintainAspectRatio: false,
954
  scales: {
955
+ x: { title: {display:true, text:'Num Features'} },
956
+ y: { title: {display:true, text:'Mean F1'} }
 
 
 
 
 
 
 
957
  },
958
  plugins: {
959
  tooltip: {
960
  callbacks: {
961
+ label: ctx => `${ctx.raw.algorithm}: F1=${ctx.raw.y.toFixed(4)}, Feats=${ctx.raw.x}`
 
 
 
962
  }
963
  }
964
  }
 
966
  });
967
  }
968
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
969
  function sortTable(key) {
970
+ if (lastSortKey === key) sortDirection *= -1;
971
+ else { sortDirection = 1; lastSortKey = key; }
972
+
 
 
 
 
 
973
  const getVal = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
974
+
975
  filteredResults.sort((a, b) => {
976
  let valA = getVal(a, key);
977
  let valB = getVal(b, key);
 
 
978
  if (key === 'selected_features') {
979
  valA = Array.isArray(valA) ? valA.length : 0;
980
  valB = Array.isArray(valB) ? valB.length : 0;
981
  }
 
982
  if (valA === undefined) valA = -Infinity;
983
  if (valB === undefined) valB = -Infinity;
984
+ return (valA < valB ? -1 : 1) * sortDirection;
 
 
 
985
  });
986
+
987
  renderTable(filteredResults);
 
 
 
 
988
  document.querySelectorAll('th .arrow').forEach(span => span.textContent = '↕');
989
+ const th = Array.from(document.querySelectorAll('th')).find(th => th.getAttribute('onclick').includes(`'${key}'`));
990
+ if (th) th.querySelector('.arrow').textContent = sortDirection === 1 ? '↑' : '↓';
 
 
 
 
 
991
  }
992
 
993
+ // PDF Sidebar
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
  function openPdf(algoName) {
995
  if (!algoName) return;
 
 
 
 
996
  const filename = algoName.toUpperCase() + ".pdf";
997
+ document.getElementById('pdf-frame').src = `/pdfs/${filename}`;
998
+ document.getElementById('pdf-sidebar').classList.add('open');
 
999
  }
 
1000
  function closeSidebar() {
1001
+ document.getElementById('pdf-sidebar').classList.remove('open');
1002
+ setTimeout(() => document.getElementById('pdf-frame').src = "", 300);
 
 
 
 
1003
  }
 
 
 
1004
  </script>
1005
 
1006
  </body>
Webapp/templates/submit.html ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AutoFS - Submit Data/Method</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #3498db;
10
+ --secondary-color: #2c3e50;
11
+ --background-color: #f8f9fa;
12
+ --text-color: #333;
13
+ --border-color: #dee2e6;
14
+ --hover-color: #f1f1f1;
15
+ --accent-color: #e67e22;
16
+ --sidebar-width: 280px;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
+ margin: 0;
22
+ padding: 0;
23
+ background-color: var(--background-color);
24
+ color: var(--text-color);
25
+ display: flex;
26
+ min-height: 100vh;
27
+ }
28
+
29
+ /* Sidebar Styling (Copied from index.html for consistency) */
30
+ .sidebar {
31
+ width: var(--sidebar-width);
32
+ background-color: var(--secondary-color);
33
+ color: white;
34
+ position: fixed;
35
+ height: 100vh;
36
+ overflow-y: auto;
37
+ padding: 20px;
38
+ box-sizing: border-box;
39
+ left: 0;
40
+ top: 0;
41
+ z-index: 100;
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 20px;
45
+ }
46
+
47
+ .sidebar h2 {
48
+ font-size: 1.2em;
49
+ margin-bottom: 10px;
50
+ color: #ecf0f1;
51
+ border-bottom: 1px solid #34495e;
52
+ padding-bottom: 5px;
53
+ }
54
+
55
+ .nav-links {
56
+ list-style: none;
57
+ padding: 0;
58
+ margin: 0;
59
+ }
60
+
61
+ .nav-links li a {
62
+ display: block;
63
+ padding: 10px;
64
+ color: #bdc3c7;
65
+ text-decoration: none;
66
+ border-radius: 4px;
67
+ transition: background 0.2s;
68
+ }
69
+
70
+ .nav-links li a:hover, .nav-links li a.active {
71
+ background: rgba(255,255,255,0.1);
72
+ color: white;
73
+ }
74
+
75
+ /* Main Content */
76
+ .main-content {
77
+ margin-left: var(--sidebar-width);
78
+ flex: 1;
79
+ padding: 40px;
80
+ max-width: calc(100% - var(--sidebar-width));
81
+ box-sizing: border-box;
82
+ min-width: 0;
83
+ }
84
+
85
+ header {
86
+ margin-bottom: 30px;
87
+ border-bottom: 2px solid var(--primary-color);
88
+ padding-bottom: 10px;
89
+ }
90
+
91
+ .subtitle {
92
+ font-size: 0.9em;
93
+ color: #7f8c8d;
94
+ margin-top: 5px;
95
+ }
96
+
97
+ /* Submission Form Styles */
98
+ .submission-container {
99
+ background: white;
100
+ border-radius: 8px;
101
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
102
+ overflow: hidden;
103
+ max-width: 800px;
104
+ margin: 0 auto;
105
+ }
106
+
107
+ .tabs {
108
+ display: flex;
109
+ background: #f1f1f1;
110
+ border-bottom: 1px solid var(--border-color);
111
+ }
112
+
113
+ .tab-btn {
114
+ flex: 1;
115
+ padding: 15px;
116
+ border: none;
117
+ background: none;
118
+ cursor: pointer;
119
+ font-size: 1.1em;
120
+ font-weight: 600;
121
+ color: #7f8c8d;
122
+ transition: all 0.3s;
123
+ }
124
+
125
+ .tab-btn:hover {
126
+ background: #e9ecef;
127
+ color: var(--secondary-color);
128
+ }
129
+
130
+ .tab-btn.active {
131
+ background: white;
132
+ color: var(--primary-color);
133
+ border-bottom: 3px solid var(--primary-color);
134
+ }
135
+
136
+ .form-content {
137
+ padding: 30px;
138
+ display: none;
139
+ }
140
+
141
+ .form-content.active {
142
+ display: block;
143
+ animation: fadeIn 0.5s;
144
+ }
145
+
146
+ @keyframes fadeIn {
147
+ from { opacity: 0; }
148
+ to { opacity: 1; }
149
+ }
150
+
151
+ .form-group {
152
+ margin-bottom: 20px;
153
+ }
154
+
155
+ .form-group label {
156
+ display: block;
157
+ margin-bottom: 8px;
158
+ font-weight: 600;
159
+ color: var(--secondary-color);
160
+ }
161
+
162
+ .form-group input[type="text"],
163
+ .form-group input[type="email"],
164
+ .form-group input[type="url"],
165
+ .form-group textarea,
166
+ .form-group select {
167
+ width: 100%;
168
+ padding: 10px;
169
+ border: 1px solid var(--border-color);
170
+ border-radius: 4px;
171
+ font-size: 1em;
172
+ box-sizing: border-box;
173
+ transition: border-color 0.2s;
174
+ }
175
+
176
+ .form-group input:focus,
177
+ .form-group textarea:focus,
178
+ .form-group select:focus {
179
+ outline: none;
180
+ border-color: var(--primary-color);
181
+ }
182
+
183
+ .form-group textarea {
184
+ resize: vertical;
185
+ min-height: 100px;
186
+ }
187
+
188
+ .file-upload {
189
+ border: 2px dashed var(--border-color);
190
+ padding: 20px;
191
+ text-align: center;
192
+ border-radius: 4px;
193
+ cursor: pointer;
194
+ transition: border-color 0.2s;
195
+ position: relative;
196
+ }
197
+
198
+ .file-upload:hover {
199
+ border-color: var(--primary-color);
200
+ background: #f8f9fa;
201
+ }
202
+
203
+ .file-upload input[type="file"] {
204
+ position: absolute;
205
+ top: 0;
206
+ left: 0;
207
+ width: 100%;
208
+ height: 100%;
209
+ opacity: 0;
210
+ cursor: pointer;
211
+ }
212
+
213
+ .submit-btn {
214
+ background-color: var(--primary-color);
215
+ color: white;
216
+ border: none;
217
+ padding: 12px 24px;
218
+ font-size: 1.1em;
219
+ border-radius: 4px;
220
+ cursor: pointer;
221
+ transition: background 0.3s;
222
+ display: block;
223
+ width: 100%;
224
+ margin-top: 30px;
225
+ }
226
+
227
+ .submit-btn:hover {
228
+ background-color: #2980b9;
229
+ }
230
+
231
+ .helper-text {
232
+ font-size: 0.85em;
233
+ color: #7f8c8d;
234
+ margin-top: 5px;
235
+ }
236
+
237
+ .success-message {
238
+ display: none;
239
+ background: #d4edda;
240
+ color: #155724;
241
+ padding: 15px;
242
+ border-radius: 4px;
243
+ margin-top: 20px;
244
+ text-align: center;
245
+ border: 1px solid #c3e6cb;
246
+ }
247
+ </style>
248
+ </head>
249
+ <body>
250
+
251
+ <aside class="sidebar">
252
+ <div style="text-align: center; margin-bottom: 20px;">
253
+ <h1 style="font-size: 1.5em; margin: 0; color: white;">AutoFS</h1>
254
+ <div style="font-size: 0.8em; color: #bdc3c7;">Feature Selection Leaderboard</div>
255
+ </div>
256
+
257
+ <div>
258
+ <h2>Navigation</h2>
259
+ <ul class="nav-links">
260
+ <li><a href="/">📊 Overview</a></li>
261
+ <li><a href="/#main-table">🏆 Leaderboard</a></li>
262
+ <li><a href="/#charts">📈 Charts</a></li>
263
+ <li><a href="/#details">ℹ️ Details</a></li>
264
+ <li><a href="/global">🌍 Global Rankings</a></li>
265
+ <li><a href="/submit" class="active">📤 Submit Data/Method</a></li>
266
+ </ul>
267
+ </div>
268
+
269
+ <div style="margin-top: auto; padding-top: 20px; border-top: 1px solid #34495e;">
270
+ <p style="font-size: 0.8em; color: #bdc3c7; text-align: center;">
271
+ Need help?<br>
272
+ <a href="mailto:support@autofs.com" style="color: var(--primary-color);">Contact Support</a>
273
+ </p>
274
+ </div>
275
+ </aside>
276
+
277
+ <main class="main-content">
278
+ <header>
279
+ <h1 style="color: var(--secondary-color); margin: 0;">📤 Submission Center</h1>
280
+ <div class="subtitle">Contribute to the leaderboard by submitting new datasets or feature selection methods.</div>
281
+ </header>
282
+
283
+ <div class="submission-container">
284
+ <div class="tabs">
285
+ <button class="tab-btn active" onclick="switchTab('dataset')">Dataset Submission</button>
286
+ <button class="tab-btn" onclick="switchTab('method')">Method Submission</button>
287
+ </div>
288
+
289
+ <!-- Dataset Submission Form -->
290
+ <div id="dataset-form" class="form-content active">
291
+ <form onsubmit="handleSubmit(event, 'Dataset')">
292
+ <div class="form-group">
293
+ <label>Dataset Name *</label>
294
+ <input type="text" placeholder="e.g., GeneExpression_v2" required>
295
+ </div>
296
+
297
+ <div class="form-group">
298
+ <label>Domain/Category</label>
299
+ <select>
300
+ <option>Biology/Medical</option>
301
+ <option>Image Processing</option>
302
+ <option>Text/NLP</option>
303
+ <option>Finance</option>
304
+ <option>Other</option>
305
+ </select>
306
+ </div>
307
+
308
+ <div class="form-group">
309
+ <label>Description</label>
310
+ <textarea placeholder="Briefly describe the dataset, its origin, and the target variable..." required></textarea>
311
+ </div>
312
+
313
+ <div class="form-group">
314
+ <label>Dataset File (.mat, .csv) *</label>
315
+ <div class="file-upload">
316
+ <input type="file" accept=".mat,.csv,.zip" required onchange="updateFileName(this)">
317
+ <span id="file-name-dataset">Drag & drop or click to upload file</span>
318
+ </div>
319
+ <div class="helper-text">Max file size: 50MB. For larger files, please provide a link below.</div>
320
+ </div>
321
+
322
+ <div class="form-group">
323
+ <label>External Download Link (Optional)</label>
324
+ <input type="url" placeholder="https://...">
325
+ </div>
326
+
327
+ <div class="form-group">
328
+ <label>Contact Email *</label>
329
+ <input type="email" placeholder="researcher@university.edu" required>
330
+ </div>
331
+
332
+ <button type="submit" class="submit-btn">Submit Dataset</button>
333
+ </form>
334
+ </div>
335
+
336
+ <!-- Method Submission Form -->
337
+ <div id="method-form" class="form-content">
338
+ <form onsubmit="handleSubmit(event, 'Method')">
339
+ <div class="form-group">
340
+ <label>Algorithm Name *</label>
341
+ <input type="text" placeholder="e.g., FastCorr-FS" required>
342
+ </div>
343
+
344
+ <div class="form-group">
345
+ <label>Complexity Class (Big O)</label>
346
+ <input type="text" placeholder="e.g., O(n*d^2)">
347
+ </div>
348
+
349
+ <div class="form-group">
350
+ <label>Description & Key Innovation</label>
351
+ <textarea placeholder="Describe the core idea, advantages, and theoretical basis..." required></textarea>
352
+ </div>
353
+
354
+ <div class="form-group">
355
+ <label>Code Implementation (.py, .zip) *</label>
356
+ <div class="file-upload">
357
+ <input type="file" accept=".py,.zip,.rar" required onchange="updateFileName(this)">
358
+ <span id="file-name-method">Drag & drop or click to upload code</span>
359
+ </div>
360
+ </div>
361
+
362
+ <div class="form-group">
363
+ <label>Paper/Preprint Link (Optional)</label>
364
+ <input type="url" placeholder="https://arxiv.org/abs/...">
365
+ </div>
366
+
367
+ <div class="form-group">
368
+ <label>Contact Email *</label>
369
+ <input type="email" placeholder="developer@lab.com" required>
370
+ </div>
371
+
372
+ <button type="submit" class="submit-btn">Submit Method</button>
373
+ </form>
374
+ </div>
375
+
376
+ <div id="success-msg" class="success-message">
377
+ <h3>✅ Submission Received!</h3>
378
+ <p>Thank you for your contribution. Our team will review your submission and update the leaderboard shortly.</p>
379
+ <button onclick="resetForm()" style="background:none; border:none; color:#155724; text-decoration:underline; cursor:pointer;">Submit another</button>
380
+ </div>
381
+ </div>
382
+ </main>
383
+
384
+ <script>
385
+ function switchTab(tab) {
386
+ // Update buttons
387
+ document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
388
+ event.target.classList.add('active');
389
+
390
+ // Update content
391
+ document.querySelectorAll('.form-content').forEach(content => content.classList.remove('active'));
392
+ document.getElementById(tab + '-form').classList.add('active');
393
+
394
+ // Hide success message if visible
395
+ document.getElementById('success-msg').style.display = 'none';
396
+ }
397
+
398
+ function updateFileName(input) {
399
+ const spanId = input.parentElement.querySelector('span').id;
400
+ if (input.files && input.files.length > 0) {
401
+ document.getElementById(spanId).textContent = input.files[0].name;
402
+ document.getElementById(spanId).style.color = 'var(--primary-color)';
403
+ document.getElementById(spanId).style.fontWeight = 'bold';
404
+ }
405
+ }
406
+
407
+ function handleSubmit(e, type) {
408
+ e.preventDefault();
409
+ // Here you would typically send data to backend via fetch()
410
+ // For now, we simulate a successful submission
411
+
412
+ const btn = e.target.querySelector('.submit-btn');
413
+ const originalText = btn.innerText;
414
+
415
+ btn.innerText = 'Submitting...';
416
+ btn.disabled = true;
417
+
418
+ setTimeout(() => {
419
+ document.querySelectorAll('.form-content').forEach(c => c.style.display = 'none');
420
+ document.getElementById('success-msg').style.display = 'block';
421
+
422
+ // Reset button
423
+ btn.innerText = originalText;
424
+ btn.disabled = false;
425
+ e.target.reset();
426
+ const spans = document.querySelectorAll('.file-upload span');
427
+ spans[0].textContent = "Drag & drop or click to upload file";
428
+ spans[1].textContent = "Drag & drop or click to upload code";
429
+ spans.forEach(s => { s.style.color = ''; s.style.fontWeight = ''; });
430
+
431
+ }, 1500);
432
+ }
433
+
434
+ function resetForm() {
435
+ document.getElementById('success-msg').style.display = 'none';
436
+ document.querySelector('.tab-btn.active').click(); // Re-activate current tab to show form
437
+ }
438
+ </script>
439
+
440
+ </body>
441
+ </html>