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 84aaf34ec0
commit 49ae5ef7d9
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 %}
<!-- Content Wrapper -->
@ -74,4 +74,4 @@
</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
@ -20,4 +20,6 @@ urlpatterns = [
path('notification/<int:id>/edit/', NotificationEditView.as_view(), name="notification_edit"),
path('notification/<int:id>/delete/', NotificationDeleteView.as_view(), name="notification_delete"),
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.shortcuts import get_object_or_404
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 .forms import StatusForm
@ -10,6 +11,8 @@ from .forms import StatusForm
from common.helpers import get_upload_path
from msgio.models import NotificationDailySchedule, Notification
from dateutil import relativedelta
class StatusListView(LoginRequiredMixin, ListView):
template_name = "mood/status_list.html"
model = Status
@ -315,4 +318,31 @@ class NotificationDeleteView(LoginRequiredMixin, DeleteView):
return HttpResponseRedirect(success_url)
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