Skip to content

Commit

Permalink
new page for user's ranking/score curve
Browse files Browse the repository at this point in the history
  • Loading branch information
jpy794 committed Sep 20, 2023
1 parent 242b33c commit 01ea5f1
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 0 deletions.
3 changes: 3 additions & 0 deletions frontend/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
</li>
{% endif %}
{% if user_ %}
<li class="pure-menu-item">
<a class="pure-button" href="{% url 'score' %}">分数</a>
</li>
<li class="pure-menu-item">
<a class="pure-button" href="{% url 'profile' %}">个人信息</a>
</li>
Expand Down
184 changes: 184 additions & 0 deletions frontend/templates/score.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
{% extends 'base.html' %}
{% load static %}

{% block js %}
{{ block.super }}
<script src="{% static 'vue.min.js' %}"></script>
<script src="{% static 'axios.min.js' %}"></script>
<script src="{% static 'chart.umd.min.js' %}"></script>
<script src="{% static 'moment.min.js' %}"></script>
<script src="{% static 'chartjs-adapter-moment.min.js' %}"></script>
<script>
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
</script>
{% endblock %}

{% block content %}
{% verbatim %}
<div id="app">
<h1>总排名</h1>
<div id="rank-chart-text">正在加载</div>
<canvas id="rank-chart" style="width: 100%; height: 400px"></canvas>
<h1>分数</h1>
<div id="score-chart-text">正在加载</div>
<canvas id="score-chart" style="width: 100%; height: 400px"></canvas>
</div>
{% endverbatim %}
{{ user_.json|json_script:'json-user' }}
<script>
const app = new Vue({
el: '#app',
data: {
user: JSON.parse(document.getElementById('json-user').textContent),
},
async created() {
await this.refresh();
},
methods: {
async refresh() {
const timeRange = await this.getTimeRange();
if (!timeRange) {
document.getElementById('rank-chart-text').innerHTML = '比赛尚未开始';
document.getElementById('score-chart-text').innerHTML = '比赛尚未开始';
return;
}
this.timeRange = timeRange;
const { data } = await axios.get('/data/core.json');
this.coreData = data;
this.scoreMap = this.buildScoreMap();
const rankHistory = this.generateRankHistory();
this.drawChart(rankHistory, 'rank-chart', '#2ECC71', true);
document.getElementById('rank-chart-text').innerHTML = '';
const scoreHistory = this.generateScoreHistory();
this.drawChart(scoreHistory, 'score-chart', '#3498DB');
document.getElementById('score-chart-text').innerHTML = '';
},
buildScoreMap() {
let challenges = {};
for (const c of this.coreData.challenges) {
challenges[c.id] = {};
for (const f of c.flags) {
challenges[c.id][f.id] = f.score;
}
}
return challenges;
},
generateScoreHistory() {
let score = 0;
let history = [];
for (const i of this.coreData.submissions) {
if (i.user !== this.user.pk) {
continue;
}
score += this.scoreMap[i.challenge][i.flag];
history.push({ time: i.time, data: score });
}
history.unshift({
time: this.timeRange.starttime,
data: 0,
});
history.push({
time: this.timeRange.endtime,
data: score,
});
return history;
},
generateRankHistory() {
let scores = [];
for (const u of this.coreData.users) {
scores.push({ user: u.id, score: 0, time: this.timeRange.starttime });
}
let history = [];
for (const i of this.coreData.submissions) {
const idx = scores.findIndex((s) => s.user == i.user);
scores[idx].score += this.scoreMap[i.challenge][i.flag];
scores[idx].time = i.time;
const user_ = scores.find((s) => s.user == this.user.pk);
const rank = scores.filter(({ user, score, time }) => {
return score > user_.score || (score == user_.score && new Date(user_.time) > new Date(time));
}).length + 1;
if (history.length == 0 || history.slice(-1)[0].data != rank) {
history.push({ time: i.time, data: rank });
}
}
history.unshift({
time: this.timeRange.starttime,
data: 1,
});
history.push({
time: this.timeRange.endtime,
data: history.slice(-1)[0].data,
});
return history;
},
async getTimeRange() {
const { data: { value: triggers } } = await axios.post('/admin/trigger/', { method: 'get_all' });

let starttime = triggers.find(i => i.can_submit);
if (!starttime || new Date(starttime.time) > new Date()) {
return null;
} else {
starttime = new Date(starttime.time);
}
let last_starttime = [...triggers].reverse().find(i => i.can_submit);
let endtime = [...triggers].reverse().find(i => !i.can_submit);
if (!endtime || new Date(endtime.time) > new Date() || new Date(endtime.time) < new Date(last_starttime.time)) {
endtime = new Date();
} else {
endtime = new Date(endtime.time);
}
return {
starttime,
endtime,
}
},
drawChart(history, chartId, color, flipYAxis = false) {
const points = history.map(({ time, data }) => ({ x: new Date(time), y: data }));
const datasets = [{
label: this.user.display_name,
data: points,
stepped: true,
fill: false,
backgroundColor: color,
borderColor: color,
borderWidth: 2,
radius: 2,
hoverRadius: 3,
}];

new Chart(document.getElementById(chartId).getContext('2d'), {
type: 'line',
data: {
datasets,
},
options: {
hover: {
mode: 'x',
},
responsive: false,
scales: {
x: {
type: 'time',
ticks: {
minRotation: 50,
},
time: {
unit: 'hour',
displayFormats: {
hour: "MM-DD HH:mm",
},
tooltipFormat: "YYYY-MM-DD HH:mm:ss",
},
},
y: {
reverse: flipYAxis,
}
},
},
});
}
}
});
</script>
{% endblock %}
1 change: 1 addition & 0 deletions frontend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
path('error/', views.ErrorView.as_view()),
path('data/core.json', views.CoreDataView.as_view(), name='coredata'),
path('challenge/<int:challenge_id>/', views.ChallengeURLView.as_view(), name='challenge_url'),
path('score/', views.ScoreView.as_view(), name='score'),

path('profile/ustc/', views.UstcProfileView.as_view(), name='ustcprofile'),

Expand Down
8 changes: 8 additions & 0 deletions frontend/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ def get(self, request, challenge_id):
url = challenge.get_and_log_url_orig().replace('{token}', quote(user.token))
return redirect(url)

class ScoreView(View):
def get(self, request):
try:
User.test_authenticated(Context.from_request(request))
except LoginRequired:
return redirect('hub')
context = Context.from_request(request)
return TemplateResponse(request, 'score.html')

class UstcProfileView(View):
def check(self):
Expand Down

0 comments on commit 01ea5f1

Please sign in to comment.