| from typing import Dict, List, Optional |
|
|
| import pandas as pd |
|
|
|
|
| def dataframe_to_html( |
| df: pd.DataFrame, |
| column_label_map: Optional[Dict[str, str]] = None, |
| dimension_metrics: Optional[List[str]] = None, |
| primary_metric: Optional[str] = None, |
| ) -> str: |
| if df.empty: |
| return "<div class='no-data'>No data available</div>" |
|
|
| column_label_map = column_label_map or {} |
| dimension_metrics = dimension_metrics or [] |
| headers = df.columns.tolist() |
|
|
| html = """ |
| <div class="academic-table-container"> |
| <table class="academic-table"> |
| <thead> |
| <tr> |
| """ |
|
|
| for header in headers: |
| display_header = column_label_map.get(header, header) |
| if header == "Model": |
| html += f'<th class="model-header">{display_header}</th>' |
| elif header == "entry_type": |
| html += f'<th class="entry-type-header">{display_header}</th>' |
| elif header == "Rank": |
| html += f'<th class="rank-header">{display_header}</th>' |
| elif primary_metric and header == primary_metric: |
| html += f'<th class="primary-metric" title="Primary Ranking Metric">{display_header}</th>' |
| elif header in dimension_metrics: |
| html += f'<th class="dimension-metric-header" title="Dimension Metric">{display_header}</th>' |
| else: |
| html += f"<th>{display_header}</th>" |
|
|
| html += """ |
| </tr> |
| </thead> |
| <tbody> |
| """ |
|
|
| for row_idx, (_, row) in enumerate(df.iterrows()): |
| row_class = "even-row" if row_idx % 2 == 0 else "odd-row" |
| html += f'<tr class="{row_class}">' |
|
|
| for col in headers: |
| cell_value = row[col] |
| if col == "Model": |
| cell_class = "model-cell" |
| elif col == "entry_type": |
| cell_class = "entry-type-cell" |
| elif col == "Rank": |
| cell_class = "rank-cell" |
| elif primary_metric and col == primary_metric: |
| cell_class = "primary-metric-cell" |
| elif col in dimension_metrics: |
| cell_class = "dimension-metric-cell" |
| else: |
| cell_class = "regular-cell" |
|
|
| cell_content = str(cell_value) |
| if col == "Rank": |
| rank_class = "" |
| if cell_content == "1": |
| rank_class = " rank-1" |
| elif cell_content == "2": |
| rank_class = " rank-2" |
| elif cell_content == "3": |
| rank_class = " rank-3" |
| html += f'<td class="{cell_class}{rank_class}">{cell_content}</td>' |
| elif col == "entry_type": |
| entry_class = "" |
| entry_lower = cell_content.lower() |
| if "baseline" in entry_lower: |
| entry_class = " baseline-entry" |
| elif "submission" in entry_lower: |
| entry_class = " competition-entry" |
| html += f'<td class="{cell_class}{entry_class}">{cell_content}</td>' |
| elif "**" in cell_content: |
| html += f'<td class="{cell_class} best-score">{cell_content.replace("**", "")}</td>' |
| elif "<u>" in cell_content: |
| html += f'<td class="{cell_class} second-best">{cell_content.replace("<u>", "").replace("</u>", "")}</td>' |
| else: |
| html += f'<td class="{cell_class}">{cell_content}</td>' |
|
|
| html += "</tr>" |
|
|
| html += """ |
| </tbody> |
| </table> |
| </div> |
| """ |
| return html |
|
|
|
|
| def get_academic_css() -> str: |
| return """ |
| /* 艺术化标题样式 */ |
| #title, .wa-title { |
| text-align: center; |
| font-size: 3.25em !important; |
| margin-bottom: 40px !important; |
| font-family: 'Segoe UI', 'Arial', sans-serif; |
| font-weight: 900; |
| letter-spacing: -0.5px; |
| line-height: 1.06; |
| padding: 36px 30px; |
| border-radius: 15px; |
| background: linear-gradient(145deg, #ffffff, #f5f7fa); |
| box-shadow: |
| 0 10px 40px rgba(41, 128, 185, 0.2), |
| inset 0 1px 0 rgba(255, 255, 255, 0.8); |
| position: relative; |
| overflow: hidden; |
| transition: all 0.3s ease; |
| } |
| |
| #title:hover, .wa-title:hover { |
| box-shadow: |
| 0 15px 50px rgba(41, 128, 185, 0.3), |
| inset 0 1px 0 rgba(255, 255, 255, 0.9); |
| } |
| |
| #title h1, .wa-title h1 { |
| margin: 0; |
| padding: 0; |
| background: linear-gradient( |
| 90deg, |
| #3498db 0%, |
| #2ecc71 25%, |
| #f1c40f 50%, |
| #e74c3c 75%, |
| #9b59b6 100% |
| ); |
| background-size: 400% 100%; |
| -webkit-background-clip: text; |
| background-clip: text; |
| -webkit-text-fill-color: transparent; |
| text-fill-color: transparent; |
| animation: gradientShift 8s ease infinite; |
| position: relative; |
| z-index: 2; |
| text-shadow: |
| 2px 2px 0px rgba(0, 0, 0, 0.05), |
| 4px 4px 0px rgba(0, 0, 0, 0.03); |
| filter: drop-shadow(0 5px 15px rgba(52, 152, 219, 0.3)); |
| } |
| |
| @keyframes gradientShift { |
| 0%, 100% { background-position: 0% 50%; } |
| 50% { background-position: 100% 50%; } |
| } |
| |
| #title .subtitle, .wa-title .subtitle { |
| font-size: 0.9em !important; |
| color: #34495e; |
| margin-top: 18px; |
| font-weight: 600; |
| display: block; |
| line-height: 1.45; |
| font-style: normal; |
| max-width: 1080px; |
| margin-left: auto; |
| margin-right: auto; |
| background: linear-gradient( |
| 90deg, |
| #e74c3c 0%, |
| #f39c12 20%, |
| #2ecc71 40%, |
| #3498db 60%, |
| #9b59b6 80%, |
| #e74c3c 100% |
| ); |
| background-size: 300% 100%; |
| -webkit-background-clip: text; |
| background-clip: text; |
| -webkit-text-fill-color: transparent; |
| text-fill-color: transparent; |
| animation: subtitleShift 10s ease infinite; |
| padding: 12px 24px; |
| border-radius: 8px; |
| background-color: rgba(255, 255, 255, 0.9); |
| box-shadow: |
| inset 0 0 0 1px rgba(52, 152, 219, 0.1), |
| 0 4px 12px rgba(0, 0, 0, 0.05); |
| font-family: 'Georgia', 'Times New Roman', serif; |
| text-transform: none; |
| letter-spacing: 0.5px; |
| } |
| |
| @keyframes subtitleShift { |
| 0%, 100% { background-position: 0% 50%; } |
| 50% { background-position: 100% 50%; } |
| } |
| |
| #title::before, .wa-title::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 8px; |
| background: linear-gradient(90deg, |
| #3498db, #2ecc71, #f1c40f, #e74c3c, #9b59b6, #3498db); |
| background-size: 400% 100%; |
| animation: gradientShift 12s linear infinite; |
| } |
| |
| #title::after, .wa-title::after { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: |
| radial-gradient(circle at 10% 20%, rgba(52, 152, 219, 0.08) 0%, transparent 50%), |
| radial-gradient(circle at 90% 80%, rgba(46, 204, 113, 0.08) 0%, transparent 50%), |
| radial-gradient(circle at 50% 50%, rgba(155, 89, 182, 0.05) 0%, transparent 50%); |
| pointer-events: none; |
| z-index: 1; |
| } |
| |
| #title .emoji, .wa-title .emoji { |
| font-size: 1.2em; |
| vertical-align: middle; |
| margin-right: 15px; |
| display: inline-block; |
| background: linear-gradient( |
| 90deg, |
| #3498db 0%, |
| #2ecc71 25%, |
| #f1c40f 50%, |
| #e74c3c 75%, |
| #9b59b6 100% |
| ); |
| background-size: 400% 100%; |
| -webkit-background-clip: text; |
| background-clip: text; |
| -webkit-text-fill-color: transparent; |
| text-fill-color: transparent; |
| animation: gradientShift 8s ease infinite, float 3s ease-in-out infinite; |
| filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1)); |
| position: relative; |
| z-index: 2; |
| } |
| |
| @keyframes float { |
| 0%, 100% { transform: translateY(0px); } |
| 50% { transform: translateY(-5px); } |
| } |
| |
| .academic-table-container { |
| margin: 20px 0; |
| overflow-x: auto; |
| border-radius: 12px; |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08); |
| background: white; |
| } |
| |
| .academic-table { |
| width: 100%; |
| border-collapse: collapse; |
| font-family: 'Segoe UI', Arial, sans-serif; |
| font-size: 14px; |
| background: white; |
| } |
| |
| .academic-table thead { |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
| } |
| |
| .academic-table th { |
| padding: 15px 12px; |
| text-align: center; |
| font-weight: 700; |
| color: #2c3e50; |
| border-bottom: 2px solid #dee2e6; |
| border-right: 1px solid #e9ecef; |
| white-space: nowrap; |
| } |
| |
| .academic-table th:last-child { |
| border-right: none; |
| } |
| |
| .academic-table td { |
| padding: 12px 10px; |
| text-align: center; |
| border-bottom: 1px solid #f1f3f4; |
| border-right: 1px solid #f8f9fa; |
| transition: background-color 0.2s ease; |
| } |
| |
| .academic-table td:last-child { |
| border-right: none; |
| } |
| |
| .academic-table tbody tr:hover { |
| background-color: #f8f9ff !important; |
| } |
| |
| .academic-table tbody tr.even-row { |
| background-color: #ffffff; |
| } |
| |
| .academic-table tbody tr.odd-row { |
| background-color: #fafbfc; |
| } |
| |
| .model-header, .model-cell { |
| text-align: left !important; |
| min-width: 200px; |
| font-weight: 600; |
| } |
| |
| .entry-type-header, .entry-type-cell { |
| min-width: 160px; |
| text-align: center !important; |
| white-space: nowrap; |
| } |
| |
| .entry-type-cell { |
| font-weight: 700; |
| } |
| |
| .entry-type-cell.baseline-entry { |
| color: #8e5a00; |
| background-color: #fff7e6; |
| } |
| |
| .entry-type-cell.competition-entry { |
| color: #0b5d3b; |
| background-color: #eafaf1; |
| } |
| |
| .rank-header, .rank-cell { |
| width: 80px; |
| font-weight: 700; |
| color: #495057; |
| } |
| |
| .rank-cell.rank-1 { |
| color: #e67e22; |
| font-weight: 800; |
| font-size: 15px; |
| } |
| |
| .rank-cell.rank-2 { |
| color: #95a5a6; |
| font-weight: 700; |
| font-size: 14px; |
| } |
| |
| .rank-cell.rank-3 { |
| color: #d35400; |
| font-weight: 700; |
| font-size: 14px; |
| } |
| |
| .primary-metric { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
| color: white !important; |
| font-weight: 800; |
| } |
| |
| .primary-metric-cell { |
| background-color: rgba(102, 126, 234, 0.06); |
| font-weight: 700; |
| color: #5a67d8; |
| } |
| |
| .dimension-metric-header { |
| background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%) !important; |
| color: white !important; |
| font-weight: 700; |
| } |
| |
| .dimension-metric-cell { |
| background-color: rgba(79, 172, 254, 0.05); |
| color: #3182ce; |
| font-weight: 600; |
| } |
| |
| .best-score { |
| font-weight: 800 !important; |
| color: #27ae60 !important; |
| background-color: #eafaf1 !important; |
| position: relative; |
| } |
| |
| .best-score::after { |
| content: "🏆"; |
| position: absolute; |
| right: 5px; |
| top: 50%; |
| transform: translateY(-50%); |
| font-size: 10px; |
| } |
| |
| .second-best { |
| color: #f39c12 !important; |
| background-color: #fef9e7 !important; |
| font-weight: 700 !important; |
| text-decoration: underline; |
| text-decoration-color: #f39c12; |
| text-decoration-thickness: 2px; |
| text-underline-offset: 2px; |
| } |
| |
| .no-data, .placeholder { |
| padding: 40px; |
| text-align: center; |
| color: #6c757d; |
| font-size: 16px; |
| font-style: italic; |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
| border-radius: 12px; |
| border: 2px dashed #dee2e6; |
| } |
| |
| @media (max-width: 1200px) { |
| #title, .wa-title { |
| font-size: 2.7em !important; |
| padding: 30px 24px; |
| } |
| |
| #title .subtitle, .wa-title .subtitle { |
| font-size: 0.8em !important; |
| } |
| } |
| |
| @media (max-width: 768px) { |
| #title, .wa-title { |
| font-size: 2.1em !important; |
| padding: 24px 18px; |
| } |
| |
| #title .subtitle, .wa-title .subtitle { |
| font-size: 0.72em !important; |
| padding: 10px 16px; |
| } |
| } |
| """ |
|
|