Skip to content

Commit 31344ad

Browse files
committed
Use adaptive buckets for the submission graph
We determine a suitable unit based on the contest length and some min/max bucket bounds. We convert all submission times in seconds to this unit and determine nice locations to put ticks on the x-axis.
1 parent 6209e3c commit 31344ad

File tree

1 file changed

+46
-21
lines changed

1 file changed

+46
-21
lines changed

webapp/templates/jury/analysis/contest_overview.html.twig

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,6 @@ nv.addGraph(function() {
365365
// x-axis is contest time
366366
// y axis is # of submissions
367367
368-
var max_submissions_per_minute = 0;
369-
370368
var submission_stats = [
371369
{% for result in ['correct', 'wrong-answer', 'timelimit', 'run-error', 'compiler-error', 'no-output'] %}
372370
{
@@ -377,20 +375,44 @@ var submission_stats = [
377375
{% endfor %}
378376
];
379377
380-
var submissions = [
378+
const contest_start_time = {{ current_contest.starttime }};
379+
const submissions = [
381380
{% for submission in submissions %}
382381
{
383382
result: "{{ submission.result }}",
384383
submittime: {{ submission.submittime }},
385-
starttime: {{ current_contest.starttime }}
386384
}{{ loop.last ? '' : ',' }}
387385
{% endfor %}
388386
];
389387
390-
var contest_duration_minutes = Math.ceil(({{ current_contest.endtime }} - {{ current_contest.starttime }}) / 60);
388+
const min_bucket_count = 30;
389+
const max_bucket_count = 301;
390+
const units = [
391+
{'name': 'seconds', 'convert': 1, 'step': 60},
392+
{'name': 'minutes', 'convert': 60, 'step': 15},
393+
{'name': 'hours', 'convert': 60*60, 'step': 6},
394+
{'name': 'days', 'convert': 60*60*24, 'step': 7},
395+
{'name': 'weeks', 'convert': 60*60*24*7, 'step': 1},
396+
{'name': 'years', 'convert': 60*60*24*365, 'step': 1}
397+
];
398+
let unit = units[0];
399+
400+
let contest_duration = {{ (current_contest.endtime - current_contest.starttime) | round(0, 'ceil') }};
401+
for (let u of units) {
402+
const new_duration = Math.ceil(contest_duration / u.convert);
403+
if (new_duration > min_bucket_count) {
404+
unit = u;
405+
} else {
406+
break;
407+
}
408+
}
409+
contest_duration = Math.ceil(contest_duration / unit.convert);
410+
const bucket_count = Math.min(contest_duration + 1, max_bucket_count);
411+
// Make sure buckets have whole unit
412+
const seconds_per_bucket = Math.ceil(contest_duration / (bucket_count - 1)) * unit.convert;
391413
392414
submission_stats.forEach(stat => {
393-
stat.values = Array.from({ length: contest_duration_minutes + 1 }, (_, i) => [i, 0]);
415+
stat.values = Array.from({ length: bucket_count }, (_, i) => [i * seconds_per_bucket / unit.convert, 0]);
394416
});
395417
396418
const statMap = submission_stats.reduce((map, stat) => {
@@ -399,27 +421,30 @@ const statMap = submission_stats.reduce((map, stat) => {
399421
}, {});
400422
401423
submissions.forEach(submission => {
402-
let submission_minute = Math.floor((submission.submittime - submission.starttime) / 60);
403-
let stat = statMap[submission.result];
404-
if (stat && submission_minute >= 0 && submission_minute < contest_duration_minutes) {
405-
stat.values[submission_minute][1]++;
424+
const submission_bucket = Math.floor((submission.submittime - contest_start_time) / seconds_per_bucket);
425+
const stat = statMap[submission.result];
426+
if (stat && submission_bucket >= 0 && submission_bucket < bucket_count) {
427+
stat.values[submission_bucket][1]++;
406428
}
407429
});
408430
409-
for (let minute = 0; minute <= contest_duration_minutes; minute++) {
410-
let this_minute_submission_nums = 0;
431+
let max_submissions_per_bucket = 1
432+
for (let bucket = 0; bucket < bucket_count; bucket++) {
433+
let sum = 0;
411434
submission_stats.forEach(stat => {
412-
this_minute_submission_nums += stat.values[minute][1];
435+
sum += stat.values[bucket][1];
413436
});
414-
max_submissions_per_minute = Math.max(max_submissions_per_minute, this_minute_submission_nums);
437+
max_submissions_per_bucket = Math.max(max_submissions_per_bucket, sum);
415438
}
416439
417-
// Pick a nice round tickDelta and tickValues
418-
var tickDelta = 15;
419-
while (contest_duration_minutes / tickDelta > 15) {
420-
tickDelta *= 2;
421-
}
422-
var tickValues = Array.from({ length: Math.ceil(contest_duration_minutes / tickDelta) + 1 }, (_, i) => i * tickDelta);
440+
// Pick a nice round tickDelta and tickValues based on the step size of units.
441+
// We want whole values in the unit, and the ticks MUST match a corresponding bucket otherwise the resulting
442+
// coordinate will be NaN.
443+
const convert_factor = seconds_per_bucket / unit.convert;
444+
const maxTicks = Math.min(bucket_count, contest_duration / unit.step, min_bucket_count)
445+
const tickDelta = convert_factor * Math.ceil(contest_duration / (maxTicks * convert_factor));
446+
const ticks = Math.floor(contest_duration / tickDelta) + 1;
447+
const tickValues = Array.from({ length: ticks }, (_, i) => i * tickDelta);
423448
424449
nv.addGraph(function() {
425450
var chart = nv.models.multiBarChart()
@@ -436,7 +461,7 @@ nv.addGraph(function() {
436461
.reduceXTicks(false)
437462
;
438463
chart.xAxis //Chart x-axis settings
439-
.axisLabel('Contest Time(minutes)')
464+
.axisLabel(`Contest Time (${unit.name})`)
440465
.ticks(tickValues.length)
441466
.tickValues(tickValues)
442467
.tickFormat(d3.format('d'));

0 commit comments

Comments
 (0)