animation ; fix the displaying and calculating tmrw
This commit is contained in:
@@ -1,20 +1,29 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Quick Problem Platform</title>
|
<title>Quick Problem Platform</title>
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||||||
<style>
|
<style>
|
||||||
/* Minimal inline CSS to support full-width explanation and popout */
|
/* Popout explanation */
|
||||||
#rankingExplanation {
|
#rankingExplanation {
|
||||||
display:none;
|
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
transition: all 0.35s ease;
|
max-height: 0;
|
||||||
}
|
opacity: 0;
|
||||||
#rankInfoBtn.active { color: #2563eb; }
|
overflow: hidden;
|
||||||
</style>
|
transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
#rankingExplanation.active {
|
||||||
|
max-height: 800px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
#rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; }
|
||||||
|
#rankInfoBtn.active { transform: rotate(90deg); }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -43,7 +52,9 @@
|
|||||||
<!-- Leaderboard -->
|
<!-- Leaderboard -->
|
||||||
<section class="card" id="leaderboardSection">
|
<section class="card" id="leaderboardSection">
|
||||||
<div class="leaderboard-head">
|
<div class="leaderboard-head">
|
||||||
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
|
<h2 style="font-size:1.1rem;margin:0">Leaderboard
|
||||||
|
<span id="rankInfoBtn" title="How ranking works">ℹ️</span>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="leaderboard-controls">
|
<div class="leaderboard-controls">
|
||||||
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
|
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
|
||||||
@@ -57,7 +68,7 @@
|
|||||||
<table class="leaderboard-table">
|
<table class="leaderboard-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sortable" data-sort="rank">Rank <span id="rankInfoBtn" title="How ranking works" style="cursor:pointer;">ℹ️</span></th>
|
<th class="sortable" data-sort="rank">Rank</th>
|
||||||
<th class="sortable" data-sort="user">User</th>
|
<th class="sortable" data-sort="user">User</th>
|
||||||
<th class="sortable" data-sort="problem">Problem</th>
|
<th class="sortable" data-sort="problem">Problem</th>
|
||||||
<th class="sortable" data-sort="runtime">Runtime (s)</th>
|
<th class="sortable" data-sort="runtime">Runtime (s)</th>
|
||||||
@@ -94,18 +105,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Ranking explanation popout -->
|
<!-- Ranking explanation -->
|
||||||
<section class="card" id="rankingExplanation">
|
<section class="card" id="rankingExplanation">
|
||||||
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
|
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
|
||||||
<p>
|
<p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
|
||||||
The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank.
|
|
||||||
It considers multiple metrics for each submission:
|
|
||||||
</p>
|
|
||||||
<ul style="margin-left: 15px;">
|
<ul style="margin-left: 15px;">
|
||||||
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
|
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
|
||||||
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
|
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Each metric is normalized against the best in the leaderboard, and the overall score is calculated as:</p>
|
<p>Overall score is calculated as:</p>
|
||||||
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
|
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
|
||||||
<code>
|
<code>
|
||||||
runtimeScore = yourRuntime / bestRuntime<br>
|
runtimeScore = yourRuntime / bestRuntime<br>
|
||||||
@@ -113,24 +121,22 @@
|
|||||||
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
|
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<p>Lower overall scores are better. If two scores are equal, the earlier submission ranks higher.</p>
|
<p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inline JS -->
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Problem search
|
// Problem search
|
||||||
const problemSearch = document.getElementById('problemSearch');
|
const problemSearch = document.getElementById('problemSearch');
|
||||||
const problemsContainer = document.getElementById('problemsContainer');
|
const problemsContainer = document.getElementById('problemsContainer');
|
||||||
const problemItems = problemsContainer.querySelectorAll('.problem-item');
|
const problemItems = problemsContainer?.querySelectorAll('.problem-item') || [];
|
||||||
|
|
||||||
problemSearch?.addEventListener('input', () => {
|
problemSearch?.addEventListener('input', () => {
|
||||||
const term = problemSearch.value.toLowerCase();
|
const term = problemSearch.value.toLowerCase();
|
||||||
problemItems.forEach(item => {
|
problemItems.forEach(item => {
|
||||||
const name = item.dataset.name.toLowerCase();
|
const name = item.dataset.name?.toLowerCase()||'';
|
||||||
const desc = item.dataset.desc?.toLowerCase() || '';
|
const desc = item.dataset.desc?.toLowerCase()||'';
|
||||||
item.style.display = (name.includes(term) || desc.includes(term)) ? '' : 'none';
|
item.style.display = (name.includes(term) || desc.includes(term)) ? '' : 'none';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -139,86 +145,75 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const problemFilter = document.getElementById('problemFilter');
|
const problemFilter = document.getElementById('problemFilter');
|
||||||
const runtimeFilter = document.getElementById('runtimeFilter');
|
const runtimeFilter = document.getElementById('runtimeFilter');
|
||||||
const leaderboardBody = document.getElementById('leaderboardBody');
|
const leaderboardBody = document.getElementById('leaderboardBody');
|
||||||
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
|
||||||
const sortableHeaders = document.querySelectorAll('.sortable');
|
const sortableHeaders = document.querySelectorAll('.sortable');
|
||||||
|
|
||||||
let currentSort = { column: null, direction: 'asc' };
|
function calculateOverallRank() {
|
||||||
|
const rows = Array.from(leaderboardBody.querySelectorAll('tr')).filter(r => r.style.display !== 'none');
|
||||||
|
const runtimes = rows.map(r => parseFloat(r.dataset.runtime)||Infinity);
|
||||||
|
const memories = rows.map(r => parseFloat(r.dataset.memory)||Infinity);
|
||||||
|
const minRuntime = Math.min(...runtimes);
|
||||||
|
const minMemory = Math.min(...memories);
|
||||||
|
rows.forEach(row => {
|
||||||
|
const runtimeScore = (parseFloat(row.dataset.runtime)||Infinity)/minRuntime;
|
||||||
|
const memoryScore = (parseFloat(row.dataset.memory)||Infinity)/minMemory;
|
||||||
|
row.dataset.overallScore = runtimeScore*0.7 + memoryScore*0.3;
|
||||||
|
});
|
||||||
|
rows.sort((a,b)=>parseFloat(a.dataset.overallScore)-parseFloat(b.dataset.overallScore));
|
||||||
|
rows.forEach((row,i)=>row.cells[0].textContent = i+1);
|
||||||
|
}
|
||||||
|
|
||||||
function filterLeaderboard() {
|
function filterLeaderboard() {
|
||||||
const problemTerm = problemFilter.value.toLowerCase();
|
const problemTerm = problemFilter.value.toLowerCase();
|
||||||
const runtimeType = runtimeFilter.value;
|
const runtimeType = runtimeFilter.value;
|
||||||
|
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
leaderboardRows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const problem = row.dataset.problem.toLowerCase();
|
const problem = row.dataset.problem.toLowerCase();
|
||||||
const runtime = parseFloat(row.dataset.runtime);
|
const runtime = parseFloat(row.dataset.runtime);
|
||||||
const showProblem = problem.includes(problemTerm);
|
let show = problem.includes(problemTerm);
|
||||||
|
if(runtimeType==='best'){
|
||||||
let showRuntime = true;
|
const userRows = rows.filter(r=>r.dataset.user===row.dataset.user && r.dataset.problem===row.dataset.problem);
|
||||||
if(runtimeType === 'best') {
|
show = show && runtime===Math.min(...userRows.map(r=>parseFloat(r.dataset.runtime)));
|
||||||
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem);
|
} else if(runtimeType==='worst'){
|
||||||
const best = Math.min(...userProblemRows.map(r=>parseFloat(r.dataset.runtime)));
|
const userRows = rows.filter(r=>r.dataset.user===row.dataset.user && r.dataset.problem===row.dataset.problem);
|
||||||
showRuntime = runtime === best;
|
show = show && runtime===Math.max(...userRows.map(r=>parseFloat(r.dataset.runtime)));
|
||||||
} else if(runtimeType === 'worst') {
|
|
||||||
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem);
|
|
||||||
const worst = Math.max(...userProblemRows.map(r=>parseFloat(r.dataset.runtime)));
|
|
||||||
showRuntime = runtime === worst;
|
|
||||||
}
|
}
|
||||||
|
row.style.display = show?'':'none';
|
||||||
row.style.display = (showProblem && showRuntime) ? '' : 'none';
|
|
||||||
});
|
});
|
||||||
|
calculateOverallRank();
|
||||||
}
|
}
|
||||||
|
|
||||||
problemFilter?.addEventListener('input', filterLeaderboard);
|
problemFilter?.addEventListener('input', filterLeaderboard);
|
||||||
runtimeFilter?.addEventListener('change', filterLeaderboard);
|
runtimeFilter?.addEventListener('change', filterLeaderboard);
|
||||||
|
|
||||||
function getCellValue(row, column) {
|
function getCellValue(row, column){
|
||||||
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort===column);
|
const index = Array.from(document.querySelectorAll('th')).findIndex(th=>th.dataset.sort===column);
|
||||||
let val = row.cells[index].textContent.trim();
|
let val = row.cells[index]?.textContent?.trim();
|
||||||
if(['runtime','memory','rank'].includes(column)) return parseFloat(val)||0;
|
if(['runtime','memory','rank'].includes(column)) return parseFloat(val)||0;
|
||||||
if(column==='timestamp') return new Date(val).getTime();
|
if(column==='timestamp') return new Date(val).getTime();
|
||||||
return val.toLowerCase();
|
return val.toLowerCase();
|
||||||
}
|
}
|
||||||
|
function compareValues(a,b,direction){
|
||||||
function compareValues(a,b,direction) {
|
|
||||||
if(typeof a==='number' && typeof b==='number') return direction==='asc'?a-b:b-a;
|
if(typeof a==='number' && typeof b==='number') return direction==='asc'?a-b:b-a;
|
||||||
if(a<b) return direction==='asc'?-1:1;
|
if(a<b) return direction==='asc'?-1:1;
|
||||||
if(a>b) return direction==='asc'?1:-1;
|
if(a>b) return direction==='asc'?1:-1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
function sortLeaderboard(column,direction){
|
||||||
function calculateOverallRank() {
|
|
||||||
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
const runtimes = rows.map(r=>parseFloat(r.dataset.runtime)||Infinity);
|
if(column==='rank'){
|
||||||
const memories = rows.map(r=>parseFloat(r.dataset.memory)||Infinity);
|
|
||||||
const minRuntime = Math.min(...runtimes);
|
|
||||||
const minMemory = Math.min(...memories);
|
|
||||||
|
|
||||||
rows.forEach(row=>{
|
|
||||||
const runtimeScore = (parseFloat(row.dataset.runtime)||Infinity)/minRuntime;
|
|
||||||
const memoryScore = (parseFloat(row.dataset.memory)||Infinity)/minMemory;
|
|
||||||
const score = runtimeScore*0.7 + memoryScore*0.3;
|
|
||||||
row.dataset.overallScore = score;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortLeaderboard(column,direction) {
|
|
||||||
let rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
|
||||||
if(column==='rank') {
|
|
||||||
calculateOverallRank();
|
calculateOverallRank();
|
||||||
rows.sort((a,b)=>parseFloat(a.dataset.overallScore)-parseFloat(b.dataset.overallScore));
|
|
||||||
rows.forEach((row,index)=>row.cells[0].textContent = index+1);
|
|
||||||
} else {
|
} else {
|
||||||
rows.sort((a,b)=>compareValues(getCellValue(a,column),getCellValue(b,column),direction));
|
rows.sort((a,b)=>compareValues(getCellValue(a,column),getCellValue(b,column),direction));
|
||||||
|
rows.forEach(r=>leaderboardBody.appendChild(r));
|
||||||
}
|
}
|
||||||
rows.forEach(row=>leaderboardBody.appendChild(row));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentSort={column:null,direction:'asc'};
|
||||||
sortableHeaders.forEach(header=>{
|
sortableHeaders.forEach(header=>{
|
||||||
header.addEventListener('click',()=>{
|
header.addEventListener('click',()=>{
|
||||||
const column = header.dataset.sort;
|
const column=header.dataset.sort;
|
||||||
sortableHeaders.forEach(h=>h.classList.remove('sort-asc','sort-desc'));
|
sortableHeaders.forEach(h=>h.classList.remove('sort-asc','sort-desc'));
|
||||||
if(currentSort.column===column) currentSort.direction = currentSort.direction==='asc'?'desc':'asc';
|
if(currentSort.column===column) currentSort.direction=currentSort.direction==='asc'?'desc':'asc';
|
||||||
else { currentSort.column=column; currentSort.direction='asc'; }
|
else { currentSort.column=column; currentSort.direction='asc'; currentSort.column=column; }
|
||||||
header.classList.add(`sort-${currentSort.direction}`);
|
header.classList.add(`sort-${currentSort.direction}`);
|
||||||
sortLeaderboard(column,currentSort.direction);
|
sortLeaderboard(column,currentSort.direction);
|
||||||
});
|
});
|
||||||
@@ -228,14 +223,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const rankInfoBtn = document.getElementById('rankInfoBtn');
|
const rankInfoBtn = document.getElementById('rankInfoBtn');
|
||||||
const rankingExplanation = document.getElementById('rankingExplanation');
|
const rankingExplanation = document.getElementById('rankingExplanation');
|
||||||
rankInfoBtn?.addEventListener('click',()=>{
|
rankInfoBtn?.addEventListener('click',()=>{
|
||||||
if(rankingExplanation.style.display==='none' || rankingExplanation.style.display==='') {
|
rankingExplanation.classList.toggle('active');
|
||||||
rankingExplanation.style.display='block';
|
rankInfoBtn.classList.toggle('active');
|
||||||
rankInfoBtn.classList.add('active');
|
|
||||||
} else {
|
|
||||||
rankingExplanation.style.display='none';
|
|
||||||
rankInfoBtn.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initial calculation
|
||||||
|
calculateOverallRank();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user