Implement statistics in Javascript only to soon replace this with HoloViews

This commit is contained in:
Kumi 2021-02-21 11:24:25 +01:00
parent 6b1ff42f2f
commit 67a6be0e3c
7 changed files with 175 additions and 6 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
{% include "frontend/header_admin.html" with title=title %} {% include "frontend/header_admin.html" with title=title styles=styles %}
{% include "frontend/sidebar.html" with title=title %} {% include "frontend/sidebar.html" with title=title %}
<!-- Content Wrapper --> <!-- Content Wrapper -->
@ -74,4 +74,4 @@
</div> </div>
</div> </div>
{% include "frontend/footer.html" %} {% include "frontend/footer.html" with scripts=scripts %}

View file

@ -0,0 +1,18 @@
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 120px;
height: 56px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}

View file

@ -0,0 +1,95 @@
const queryString = window.location.search;
var margin = { top: 30, right: 120, bottom: 30, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
tooltip = { width: 100, height: 100, x: 10, y: -30 };
var parseDate = d3.timeParse("%Y-%m-%d %H:%M"),
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(","),
dateFormatter = d3.timeFormat("%d.%m.%y %H:%M");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var xAxis = d3.axisBottom(x)
.tickFormat(dateFormatter);
var yAxis = d3.axisLeft(y)
.tickFormat(d3.format("s"))
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.likes); });
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.csv("csv/" + queryString).then(function(data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = d.value;
});
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain([data[0].date, data[data.length - 1].date]);
y.domain(d3.extent(data, function(d) { return d.value; }));
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// add the dots with tooltips
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); })
.on("mouseover", function(event,d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(dateFormatter(d.date) + "<br/>" + d.value)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
});

View file

@ -0,0 +1,22 @@
{% extends "frontend/base.html" %}
{% block "content" %}
<div class="row">
<!-- Entry details -->
<div class="col-xl-8 col-lg-7">
<div class="card shadow mb-4">
<!-- Card Header - Dropdown -->
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Mood stats</h6>
</div>
<!-- Card Body -->
<div class="card-body">
<div id="my_dataviz"></div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,4 +1,4 @@
from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView
from django.urls import path, include from django.urls import path, include
@ -20,4 +20,6 @@ urlpatterns = [
path('notification/<int:id>/edit/', NotificationEditView.as_view(), name="notification_edit"), path('notification/<int:id>/edit/', NotificationEditView.as_view(), name="notification_edit"),
path('notification/<int:id>/delete/', NotificationDeleteView.as_view(), name="notification_delete"), path('notification/<int:id>/delete/', NotificationDeleteView.as_view(), name="notification_delete"),
path('notification/new/', NotificationCreateView.as_view(), name="notification_create"), path('notification/new/', NotificationCreateView.as_view(), name="notification_create"),
path('statistics/', MoodStatisticsView.as_view(), name="statistics"),
path('statistics/csv/', MoodCSVView.as_view(), name="statistics_csv"),
] ]

View file

@ -1,8 +1,9 @@
from django.views.generic import TemplateView, ListView, UpdateView, DetailView, CreateView, DeleteView from django.views.generic import TemplateView, ListView, UpdateView, DetailView, CreateView, DeleteView, View
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect, HttpResponse
from django.utils import timezone
from .models import Status, Activity, Mood, StatusMedia, StatusActivity from .models import Status, Activity, Mood, StatusMedia, StatusActivity
from .forms import StatusForm from .forms import StatusForm
@ -10,6 +11,8 @@ from .forms import StatusForm
from common.helpers import get_upload_path from common.helpers import get_upload_path
from msgio.models import NotificationDailySchedule, Notification from msgio.models import NotificationDailySchedule, Notification
from dateutil import relativedelta
class StatusListView(LoginRequiredMixin, ListView): class StatusListView(LoginRequiredMixin, ListView):
template_name = "mood/status_list.html" template_name = "mood/status_list.html"
model = Status model = Status
@ -316,3 +319,30 @@ class NotificationDeleteView(LoginRequiredMixin, DeleteView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy("mood:notification_list") return reverse_lazy("mood:notification_list")
class MoodStatisticsView(LoginRequiredMixin, TemplateView):
template_name = "mood/statistics.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Statistics"
context["scripts"] = ["frontend/vendor/d3/d3.min.js", "mood/statistics.js"]
context["styles"] = ["mood/statistics.css"]
return context
class MoodCSVView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/csv")
res["content-disposition"] = 'filename="mood.csv"'
maxage = timezone.now()
minage = maxage - relativedelta.relativedelta(weeks=1)
output = "date,value"
for status in Status.objects.filter(user=request.user, timestamp__gte=minage, timestamp__lte=maxage):
date = status.timestamp.strftime("%Y-%m-%d %H:%M")
output += f"\n{date},{status.mood.value}"
res.write(output)
return res