Added better stats page and tweaked css
This commit is contained in:
parent
3a359cce40
commit
2680e485cd
7 changed files with 206 additions and 48 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue