Added better stats page and tweaked css

This commit is contained in:
Claudio Maggioni (maggicl) 2019-08-06 14:51:29 +02:00
parent 3a359cce40
commit 2680e485cd
7 changed files with 206 additions and 48 deletions

View file

@ -140,3 +140,22 @@ form .card-action button {
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
.chart.scrollable.wrapper {
overflow-x: scroll;
}
.chart.wrapper {
position: relative;
max-width: 100%;
height: 25em;
}
.chart.area-wrapper {
height: 100%;
}
.chart-title {
margin-top: 0;
margin-bottom: 2rem;
}

View file

@ -1,43 +1,144 @@
'use strict'; 'use strict';
// vim: set ts=2 sw=2 tw=80 et: // vim: set ts=2 sw=2 tw=80 et:
$(document).ready(function() { /**
var data = JSON.parse($('#data').text()), * Given an id of a `<script type="application/json">` node, returns the parsed
format = djangoToMomentFmt(django.get_format('SHORT_DATE_FORMAT')); * JSON inside as an object.
var chart = $('#chart'); *
var labels = [], dataset = []; * @param {String} id the id of the node from which to parse
* @return {Object} the object parsed
*/
function parseJSONData(id) {
return JSON.parse(document.getElementById(id).innerHTML);
}
// Set the chart height in em according to length of dataset /**
chart.css("height", "" + (2 * data.length) + "rem"); * Given two arrays, one for labels and one for data, to fill, fill the arrays
// Force height in pixels in order to fix infinite growth resizing bug of * with weekly arrowcount records for the weekly activity chart.
// chart.js *
chart.attr("height", chart.height()); * @param {Array} labels an empty array for labels
* @param {Array} data an empty array for data
data.forEach(function(d) { */
labels.push(moment(d.weekStarts).format(format) + ' - ' + function buildWeeklyDataset(labels, data) {
moment(d.weekEnds).format(format)); parseJSONData('data-weekly').forEach(function(d) {
dataset.push(d.sum_count); labels.unshift(moment(d.weekStarts));
data.unshift(d.sum_count);
}); });
}
var chart = new Chart(chart, { /**
type: 'horizontalBar', * Given two arrays, one for labels and one for data, to fill, fill the arrays
* with cumulative total count records for the cumulative chart.
*
* @param {Array} labels an empty array for labels
* @param {Array} data an empty array for data
*/
function buildCumulativeDataset(labels, data) {
parseJSONData('data-cumulative').forEach(function(d) {
labels.push(moment(d.date));
data.push(d.count_inc);
});
}
/**
* Given a ChartJS object's parent node, sets up the width of the chart
* according to the data given and scrolls to the end of the chart.
*
* @param {Node} wrapper the parent node of the chart.
* @param {number} length how much bars/points are in the chart.
*/
function setUpWrapper(wrapper, length) {
wrapper.css("width", 25 * length + 'px');
// scroll to end of chart
wrapper.parent().scrollLeft(wrapper.width());
}
$(document).ready(function() {
var weeklyChart = $('#chart-weekly'),
weeklyWrapper = weeklyChart.parent();
var weeklyLabels = [], weeklyData = [];
buildWeeklyDataset(weeklyLabels, weeklyData);
var weekly = new Chart(weeklyChart, {
type: 'bar',
data: { data: {
labels: labels, labels: weeklyLabels,
datasets: [{ datasets: [{
label: '# of Arrows', label: gettext('# of Arrows'),
data: dataset, data: weeklyData,
borderWidth: 1 backgroundColor: '#ffc107', // amber
borderWidth: 0
}] }]
}, },
options: { options: {
maintainAspectRatio: false,
scales: { scales: {
xAxes: [{ yAxes: [{
ticks: { ticks: {
beginAtZero: true min: 0,
stepSize: 100,
} }
}] }],
} xAxes: [{
} type: 'time',
distribution: 'linear',
time: {
stepSize: 1,
max: new Date(),
unit: 'month',
}
}],
},
legend: {
display: false,
},
},
});
setUpWrapper(weeklyWrapper, weeklyData.length);
var cumulativeChart = $('#chart-cumulative'),
cumulativeWrapper = cumulativeChart.parent();
var cumulativeLabels = [], cumulativeData = [];
buildCumulativeDataset(cumulativeLabels, cumulativeData);
var weekly = new Chart(cumulativeChart, {
type: 'line',
data: {
labels: cumulativeLabels,
datasets: [{
label: gettext('Yearly arrow count up to the date'),
data: cumulativeData,
steppedLine: true,
backgroundColor: '#009688', // teal
borderWidth: 0
}]
},
options: {
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
min: 0,
}
}],
xAxes: [{
type: 'time',
distribution: 'linear',
bounds: 'ticks',
time: {
stepSize: 1,
min: new Date(new Date().getFullYear(), 0, 1),
max: new Date(),
unit: 'month',
}
}],
},
legend: {
display: false,
},
},
}); });
}); });

View file

@ -14,7 +14,7 @@
rel="stylesheet"> rel="stylesheet">
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-rc.2/css/materialize.min.css"> href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-rc.2/css/materialize.min.css">
<link rel="stylesheet" href="{% static "css/main.css" %}"> <link rel="stylesheet" href="{% static "css/main.css" %}?v=2">
{% block style %}{% endblock %} {% block style %}{% endblock %}
</head> </head>

View file

@ -18,7 +18,7 @@
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url "count_list" %}">{% trans "Counts" %}</a></li> <li><a href="{% url "count_list" %}">{% trans "Counts" %}</a></li>
<li><a href="{% url "target_edit" %}">{% trans "Set yearly target" %}</a></li> <li><a href="{% url "target_edit" %}">{% trans "Set yearly target" %}</a></li>
<li><a href="{% url "stats" %}">{% trans "Weekly stats" %}</a></li> <li><a href="{% url "stats" %}">{% trans "Stats" %}</a></li>
<li class="divider" tabindex="-1"></li> <li class="divider" tabindex="-1"></li>
{% if user.is_superuser %} {% if user.is_superuser %}
<li><a href="{% url "admin:index" %}">{% trans "Admin" %}</a></li> <li><a href="{% url "admin:index" %}">{% trans "Admin" %}</a></li>

View file

@ -4,19 +4,35 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% block title %}{% trans "Weekly stats" %}{% endblock %} {% block title %}{% trans "Stats" %}{% endblock %}
{% block scripts %} {% block scripts %}
<script id="data" type="application/json"> <script id="data-weekly" type="application/json">
{% autoescape off %}{{ weeklyArrows }}{% endautoescape %} {% autoescape off %}{{ data_weekly }}{% endautoescape %}
</script>
<script id="data-cumulative" type="application/json">
{% autoescape off %}{{ data_cumulative }}{% endautoescape %}
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="{% static 'js/dateformat.js' %}"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script src="{% static 'js/weeklystats.js' %}"></script> <script src="{% static 'js/dateformat.js' %}?v=2"></script>
<script src="{% static 'js/weeklystats.js' %}?v=2"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h1 class="center">{% trans "Weekly stats" %}</h1> <h1 class="center">{% trans "Stats" %}</h1>
<canvas id="chart"></canvas> <div class="card-panel">
<h4 class="center chart-title">{% trans "Arrows shot by week" %}</h4>
<div class="chart scrollable wrapper">
<div class="chart area-wrapper">
<canvas id="chart-weekly"></canvas>
</div>
</div>
</div>
<div class="card-panel">
<h4 class="center chart-title">{% trans "Arrows shot this year" %}</h4>
<div class="chart wrapper">
<canvas id="chart-cumulative"></canvas>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -12,7 +12,7 @@ from django.conf import settings
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.db.models.functions import Extract, ExtractWeek from django.db.models.functions import Extract, ExtractWeek
from django.db.models import Sum from django.db.models import Sum, Func
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
import json import json
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
@ -76,12 +76,23 @@ def count_stats(request):
.annotate(sum_count=Sum('count')) \ .annotate(sum_count=Sum('count')) \
.order_by('-isoyear', '-week') .order_by('-isoyear', '-week')
incArrows = ArrowCount.objects \
.filter(user = request.user) \
.filter(date__gte = date(datetime.today().year, 1, 1)) \
.annotate(count_inc=Func(
Sum('count'),
template='%(expressions)s OVER (ORDER BY %(order_by)s)',
order_by="date"
)).values('date', 'count_inc') \
.order_by('date')
for w in weeklyArrows: for w in weeklyArrows:
w['weekStarts'] = tofirstdayinisoweek(w['isoyear'], w['week']) w['weekStarts'] = tofirstdayinisoweek(w['isoyear'], w['week'])
w['weekEnds'] = w['weekStarts'] + timedelta(days=6) w['weekEnds'] = w['weekStarts'] + timedelta(days=6)
return render(request, 'stats.html', { return render(request, 'stats.html', {
'weeklyArrows': json.dumps(list(weeklyArrows), cls=DjangoJSONEncoder) 'data_weekly': json.dumps(list(weeklyArrows), cls=DjangoJSONEncoder),
'data_cumulative': json.dumps(list(incArrows), cls=DjangoJSONEncoder)
}) })
@login_required @login_required

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Arrowcounter\n" "Project-Id-Version: Arrowcounter\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-05 15:54+0200\n" "POT-Creation-Date: 2019-08-06 14:44+0200\n"
"PO-Revision-Date: 2019-07-27 13:00+0200\n" "PO-Revision-Date: 2019-07-27 13:00+0200\n"
"Last-Translator: Claudio Maggioni <maggicl@kolabnow.ch>\n" "Last-Translator: Claudio Maggioni <maggicl@kolabnow.ch>\n"
"Language-Team: Claudio Maggioni <maggicl@kolabnow.ch>\n" "Language-Team: Claudio Maggioni <maggicl@kolabnow.ch>\n"
@ -148,9 +148,9 @@ msgid "Set yearly target"
msgstr "Imposta obiettivo annuale" msgstr "Imposta obiettivo annuale"
#: counter/templates/menu.html:21 counter/templates/stats.html:7 #: counter/templates/menu.html:21 counter/templates/stats.html:7
#: counter/templates/stats.html:20 #: counter/templates/stats.html:23
msgid "Weekly stats" msgid "Stats"
msgstr "Statistiche settimanali" msgstr "Statistiche"
#: counter/templates/menu.html:24 #: counter/templates/menu.html:24
msgid "Admin" msgid "Admin"
@ -173,30 +173,41 @@ msgstr "Accedi"
msgid "Register" msgid "Register"
msgstr "Registrati" msgstr "Registrati"
#: counter/templates/stats.html:25
msgid "Arrows shot by week"
msgstr "Frecce tirate questa settimana"
#: counter/templates/stats.html:33
msgid "Arrows shot this year"
msgstr "Frecce tirate quest'anno"
#: counter/templates/target/edit.html:22 #: counter/templates/target/edit.html:22
msgid "Remove" msgid "Remove"
msgstr "Rimuovi" msgstr "Rimuovi"
#: counter/views.py:116 #: counter/views.py:127
msgid "page is negative or 0" msgid "page is negative or 0"
msgstr "pagina negativa o uguale a 0" msgstr "pagina negativa o uguale a 0"
#: counter/views.py:210 #: counter/views.py:221
msgid "ArrowCount instance not found or from different user" msgid "ArrowCount instance not found or from different user"
msgstr "istanza ArrowCount non trovata o appartenente ad altro utente" msgstr "istanza ArrowCount non trovata o appartenente ad altro utente"
#: counter/views.py:222 #: counter/views.py:233
msgid "mode not valid" msgid "mode not valid"
msgstr "campo 'mode' non valido" msgstr "campo 'mode' non valido"
#: counter/views.py:231 #: counter/views.py:242
msgid "value field is not a number" msgid "value field is not a number"
msgstr "il campo 'value' non è un numero" msgstr "il campo 'value' non è un numero"
#: counter/views.py:247 #: counter/views.py:258
msgid "count is negative or 0" msgid "count is negative or 0"
msgstr "count è negativo o uguale a 0" msgstr "count è negativo o uguale a 0"
#: counter/views.py:255 #: counter/views.py:266
msgid "count too big" msgid "count too big"
msgstr "conteggio troppo alto" msgstr "conteggio troppo alto"
#~ msgid "Weekly stats"
#~ msgstr "Statistiche settimanali"