Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add submission activity graph to users page; #236 #1480

Merged
merged 1 commit into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions judge/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction
from django.db.models import Count, Max, Min
from django.db.models.fields import DateField
from django.db.models.functions import Cast, ExtractYear
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
Expand Down Expand Up @@ -182,6 +184,23 @@ def get_context_data(self, **kwargs):
ratio = (max_ever - max_user) / (max_ever - min_ever) if max_ever != min_ever else 1.0
context['max_graph'] = max_user + ratio * delta
context['min_graph'] = min_user + ratio * delta - delta

submissions = (
self.object.submission_set
.annotate(date_only=Cast('date', DateField()))
.values('date_only').annotate(cnt=Count('id'))
)

context['submission_data'] = mark_safe(json.dumps({
date_counts['date_only'].isoformat(): date_counts['cnt'] for date_counts in submissions
}))
context['submission_metadata'] = mark_safe(json.dumps({
'min_year': (
self.object.submission_set
.annotate(year_only=ExtractYear('date'))
.aggregate(min_year=Min('year_only'))['min_year']
),
}))
return context


Expand Down
2 changes: 0 additions & 2 deletions resources/table.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@import "vars";

$table_header_rounding: 6px;

.h-scrollable-table {
overflow-x: auto;
}
Expand Down
99 changes: 99 additions & 0 deletions resources/users.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "vars";

#content-left {
&.users {
flex: unset;
Expand Down Expand Up @@ -263,3 +265,100 @@ a.edit-profile {
color: white;
}
}

#submission-activity {
#submission-activity-actions {
text-align: center;
#prev-year-action, #next-year-action {
font-size: 1.75em;
}
#year {
font-size: 1.25em;
color: #444;
}
}

#submission-activity-display {
border: 1px solid $border_gray;
border-radius: $table_header_rounding;

.info-bar {
display: flex;
justify-content: space-between;

.info-table {
width: 15%;
min-width: 130px;

.info-table-text {
width: 8%;
}
}
}

.info-text {
font-size: 0.75em;
line-height: 1;
font-weight: 100;
color: #444;
}

#submission-total-count {
align-self: center;
padding-left: 8%;
font-size: 0.85em;
}

@media(max-width: 1000px) {
#submission-total-count {
padding-left: 5px;
}
}

table {
width: 100%;
padding: 5px;

th.submission-date-col {
width: 8%;
}

@media (max-width: 1000px) {
th.submission-date-col {
display: none;
}
}
td {
border-radius: 20%;

div {
margin-top: 100%;
}

&.activity-label {
position: relative;
white-space: nowrap;
}

&.activity-blank {
background-color: white;
}
&.activity-0 {
background-color: #ddd;
}
&.activity-1 {
background-color: #9be9a8;
}
&.activity-2 {
background-color: #40c463;
}
&.activity-3 {
background-color: #2f9c4c;
}
&.activity-4 {
background-color: #216e39;
}
}
}
}
}
2 changes: 2 additions & 0 deletions resources/vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ $announcement_red: #ae0000;
$base_font_size: 14px;
$widget_border_radius: 4px;

$table_header_rounding: 6px;

$monospace-fonts: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
188 changes: 187 additions & 1 deletion templates/user/user-about.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,69 @@ <h4>{{ _('About') }}</h4>
<br>
{% endif %}

<h4 id="submission-activity-header"></h4>
<div id="submission-activity" style="display: none;">
<div id="submission-activity-actions">
<a href="javascript:void(0)" id="prev-year-action">&laquo;</a>
&nbsp;<span id="year"></span>&nbsp;
<a href="javascript:void(0)" id="next-year-action">&raquo;</a>
</div>
<div id="submission-activity-display">
<table id="submission-activity-table">
<tr id="submission-0">
<th class="submission-date-col info-text">
{{ _('Mon') }}
</th>
</tr>
<tr id="submission-1">
<th class="submission-date-col info-text">
{{ _('Tues') }}
</th>
</tr>
<tr id="submission-2">
<th class="submission-date-col info-text">
{{ _('Wed') }}
</th>
</tr>
<tr id="submission-3">
<th class="submission-date-col info-text">
{{ _('Thurs') }}
</th>
</tr>
<tr id="submission-4">
<th class="submission-date-col info-text">
{{ _('Fri') }}
</th>
</tr>
<tr id="submission-5">
<th class="submission-date-col info-text">
{{ _('Sat') }}
</th>
</tr>
<tr id="submission-6">
<th class="submission-date-col info-text">
{{ _('Sun') }}
</th>
</tr>
</table>
<div class="info-bar">
<span id="submission-total-count" class="info-text">
</span>
<table class="info-table">
<tr>
<th class="info-table-text info-text">{{ _('Less') }}</th>
<td class="activity-0"><div></div></td>
<td class="activity-1"><div></div></td>
<td class="activity-2"><div></div></td>
<td class="activity-3"><div></div></td>
<td class="activity-4"><div></div></td>
<th class="info-table-text info-text">{{ _('More') }}</th>
<tr>
</table>
</div>
</div>
</div>

{% if rating %}
<h4>Rating history</h4>
<div id="rating-chart">
Expand All @@ -67,10 +130,133 @@ <h4>Rating history</h4>
{% include "mathjax-load.html" %}
{% endif %}

<script type="text/javascript">
var submission_activity = {{ submission_data }};
var metadata = {{ submission_metadata }};
const activity_levels = 5; // 5 levels of activity

$(function () {
var active_tooltip = null;

function display_tooltip(where) {
if (active_tooltip !== null) {
active_tooltip.removeClass(['tooltipped', 'tooltipped-e', 'tooltipped-w']).removeAttr('aria-label');
}
if (where !== null) {
var day_num = parseInt(where.attr('data-day'));
var tooltip_direction = day_num < 183 ? 'tooltipped-e' : 'tooltipped-w';
where.addClass(['tooltipped', tooltip_direction])
.attr('aria-label', where.attr('data-submission-activity'));
}
active_tooltip = where;
}

function install_tooltips () {
display_tooltip(null);
$('.activity-label').each(function () {
var link = $(this);
link.hover(
function(e) {
display_tooltip(link);
},
function(e) {
display_tooltip(null);
}
);
});
}

var current_year = new Date().getFullYear();
var $div = $('#submission-activity');

function draw_contribution(year) {
$div.find('#submission-activity-table td').remove();
$div.find('#year').attr('data-year', year);
$div.find('#prev-year-action').css('display', year > metadata.min_year ? '' : 'none');
$div.find('#next-year-action').css('display', year < current_year ? '' : 'none');
var current_weekday = 0;
var start_day = new Date(year, 0, 1)
var end_day = new Date(year + 1, 0, 0);
if (year == current_year) {
end_day = new Date();
start_day = new Date(end_day.getFullYear() - 1, end_day.getMonth(), end_day.getDate() + 1);
$div.find('#year').text("{{ _('past year') }}");
} else {
$div.find('#year').text(year);
}
var days = [];
for (var day = start_day, day_num = 1; day <= end_day; day.setDate(day.getDate() + 1), day_num++) {
var isodate = day.toISOString().split('T')[0];
days.push({
date: new Date(day),
weekday: day.getDay(),
day_num: day_num,
activity: submission_activity[isodate] || 0,
});
}

var sum_activity = days.map(obj => obj.activity).reduce((a, b) => a + b, 0);
$div.find('#submission-total-count').text(
ngettext("%(cnt)d total submission", "%(cnt)d total submissions", sum_activity)
.replace("%(cnt)d", sum_activity)
)
if (year == current_year) {
$('#submission-activity-header').text(
ngettext("%(cnt)d submission in the last year", "%(cnt)d submissions in the last year", sum_activity)
.replace("%(cnt)d", sum_activity)
)
}

var max_activity = Math.max.apply(null, days.map(obj => obj.activity));
var diff = max_activity / (activity_levels - 1);
var activity_breakdown = [...Array(activity_levels).keys()].map(x => diff * x);

for (; current_weekday < days[0].weekday; current_weekday++) {
$div.find('#submission-' + current_weekday)
.append($('<td>').addClass('activity-blank').append('<div>'));
}

days.forEach(obj => {
var level = activity_breakdown.findIndex(x => x >= obj.activity);
var text =
ngettext("%(cnt)d submission on %(date)s", "%(cnt)d submissions on %(date)s", obj.activity)
.replace('%(cnt)d', obj.activity)
.replace(
'%(date)s',
obj.date.toLocaleDateString(
"{{ LANGUAGE_CODE }}",
{month: 'short', year: 'numeric', day: 'numeric'}
)
)

$div.find('#submission-' + obj.weekday)
.append(
$('<td>').addClass(['activity-label', 'activity-' + level])
.attr('data-submission-activity', text)
.attr('data-day', obj.day_num)
.append('<div>')
);
});

install_tooltips();
}

$('#prev-year-action').click(function () {
draw_contribution(parseInt($div.find('#year').attr('data-year')) - 1);
});
$('#next-year-action').click(function () {
draw_contribution(parseInt($div.find('#year').attr('data-year')) + 1);
});

draw_contribution(current_year);
$('#submission-activity').css('display', '');
});
</script>

{% if ratings %}
<script type="text/javascript" src="{{ static('libs/chart.js/Chart.js') }}"></script>
<script type="text/javascript">
var rating_history = {{rating_data}};
var rating_history = {{ rating_data }};

$.each(rating_history, function (index, item) {
item.x = new Date(item.timestamp);
Expand Down