From 35fd2450f4c48f354f574da91c30a5f8e52597d9 Mon Sep 17 00:00:00 2001 From: Klaus-Uwe Mitterer Date: Mon, 1 Mar 2021 18:05:14 +0100 Subject: [PATCH] Badly implement mood pie charts --- common/templatetags/images.py | 21 ++++---- mood/models.py | 3 ++ mood/statistics.py | 81 +++++++++++++++++++++++++++-- mood/templates/mood/statistics.html | 15 +++++- mood/urls.py | 3 +- mood/views.py | 28 +++++----- 6 files changed, 121 insertions(+), 30 deletions(-) diff --git a/common/templatetags/images.py b/common/templatetags/images.py index eaa72a1..b481509 100644 --- a/common/templatetags/images.py +++ b/common/templatetags/images.py @@ -20,19 +20,22 @@ def pildata(image): return f"data:img/jpeg;base64,{content}" @register.simple_tag -def hvhtml(hvobject): - bokeh = hv.render(hvobject) +def bkhtml(bkobject, lock_y=False): + if lock_y: + pan_tool = bkobject.select(dict(type=PanTool)) + pan_tool.dimensions = "width" - pan_tool = bokeh.select(dict(type=PanTool)) - pan_tool.dimensions = "width" + zoom_tool = bkobject.select(dict(type=WheelZoomTool)) + zoom_tool.dimensions = "width" - zoom_tool = bokeh.select(dict(type=WheelZoomTool)) - zoom_tool.dimensions = "width" - - html = file_html(bokeh, INLINE) + html = file_html(bkobject, INLINE) html = html.replace("http://localhost:5006/static/extensions/panel/css", "/static/frontend/vendor/panel") - return html + return html + +@register.simple_tag +def hvhtml(hvobject, lock_y=True): + return bkhtml(hv.render(hvobject), lock_y) @register.simple_tag def hvdata(hvobject): diff --git a/mood/models.py b/mood/models.py index b1a0665..c8a30c4 100644 --- a/mood/models.py +++ b/mood/models.py @@ -10,6 +10,9 @@ from colorfield.fields import ColorField from common.helpers import get_upload_path class Mood(models.Model): + class Meta: + ordering = ["-value"] + user = models.ForeignKey(get_user_model(), models.CASCADE) name = models.CharField(max_length=64) icon = models.CharField(default="fas fa-star", max_length=64) diff --git a/mood/statistics.py b/mood/statistics.py index abab4f5..de8311d 100644 --- a/mood/statistics.py +++ b/mood/statistics.py @@ -3,7 +3,13 @@ import pandas as pd from django.utils import timezone +from math import pi + from bokeh.models import HoverTool +from bokeh.io import output_file, show +from bokeh.plotting import figure +from bokeh.transform import cumsum +from bokeh.layouts import row, column from holoviews.operation import timeseries from dateutil.relativedelta import relativedelta @@ -14,7 +20,7 @@ def moodstats(user): tooltips = [ ('Date', '@date{%F %H:%M}'), - ('Value', '@value') + ('Mood', '@name (@value)') ] formatters = { @@ -23,7 +29,7 @@ def moodstats(user): hover = HoverTool(tooltips=tooltips, formatters=formatters) - pointdict = {"date": [], "value": [], "color": []} + pointdict = {"date": [], "value": [], "color": [], "name": []} for status in Status.objects.filter(user=user): @@ -31,6 +37,7 @@ def moodstats(user): pointdict["date"].append(status.timestamp) pointdict["value"].append(status.mood.value) pointdict["color"].append(status.mood.color) + pointdict["name"].append(status.mood.name) pointframe = pd.DataFrame.from_dict(pointdict) @@ -80,4 +87,72 @@ def activitystats(user): if status.timestamp > timezone.now() - relativedelta(weeks=1): output[activity]["weekly"] += 1 - return output \ No newline at end of file + return output + +def moodpies(user): + hv.extension('bokeh') + + maxdate = timezone.now() + + weekly_moods = Status.objects.filter(user=user, timestamp__lte=maxdate, timestamp__gte=maxdate - relativedelta(weeks=1)) + monthly_moods = Status.objects.filter(user=user, timestamp__lte=maxdate, timestamp__gte=maxdate - relativedelta(months=1)) + yearly_moods = Status.objects.filter(user=user, timestamp__lte=maxdate, timestamp__gte=maxdate - relativedelta(years=1)) + + weekly = dict() + colors = [] + + for mood in Mood.objects.filter(user=user): + weekly[mood.name] = 0 + colors.append(mood.color) + + monthly, yearly = weekly.copy(), weekly.copy() + + for status in weekly_moods: + if status.mood: + weekly[status.mood.name] += 1 + + for status in monthly_moods: + if status.mood: + monthly[status.mood.name] += 1 + + for status in yearly_moods: + if status.mood: + yearly[status.mood.name] += 1 + + weekly_data = pd.Series(weekly).reset_index(name='value').rename(columns={'index':'mood'}) + weekly_data['angle'] = weekly_data['value']/weekly_data['value'].sum() * 2*pi + weekly_data['color'] = colors + + weekly_chart = figure(plot_height=350, title="Weekly", toolbar_location=None, + tools="hover", tooltips="@mood: @value") + weekly_chart.axis.visible = False + + weekly_chart.wedge(x=0, y=1, radius=0.4, + start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), + line_color="white", fill_color='color', legend='mood', source=weekly_data) + + monthly_data = pd.Series(monthly).reset_index(name='value').rename(columns={'index':'mood'}) + monthly_data['angle'] = monthly_data['value']/monthly_data['value'].sum() * 2*pi + monthly_data['color'] = colors + + monthly_chart = figure(plot_height=350, title="Monthly", toolbar_location=None, + tools="hover", tooltips="@mood: @value") + monthly_chart.axis.visible = False + + monthly_chart.wedge(x=0, y=1, radius=0.4, + start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), + line_color="white", fill_color='color', legend='mood', source=monthly_data) + + yearly_data = pd.Series(yearly).reset_index(name='value').rename(columns={'index':'mood'}) + yearly_data['angle'] = yearly_data['value']/yearly_data['value'].sum() * 2*pi + yearly_data['color'] = colors + + yearly_chart = figure(plot_height=350, title="Yearly", toolbar_location=None, + tools="hover", tooltips="@mood: @value") + yearly_chart.axis.visible = False + + yearly_chart.wedge(x=0, y=1, radius=0.4, + start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), + line_color="white", fill_color='color', legend='mood', source=yearly_data) + + return column(weekly_chart, monthly_chart, yearly_chart) \ No newline at end of file diff --git a/mood/templates/mood/statistics.html b/mood/templates/mood/statistics.html index b7d2d23..7ea1385 100644 --- a/mood/templates/mood/statistics.html +++ b/mood/templates/mood/statistics.html @@ -1,6 +1,5 @@ {% extends "frontend/base.html" %} {% load images %} -{% load request %} {% block "content" %}
@@ -15,7 +14,18 @@
- + +
+ +
+ +
+
Mood stats
+ +
+ +
+
@@ -48,6 +58,7 @@ + {% endblock %} \ No newline at end of file diff --git a/mood/urls.py b/mood/urls.py index 7f84039..a791f51 100644 --- a/mood/urls.py +++ b/mood/urls.py @@ -1,4 +1,4 @@ -from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView, MoodPlotView +from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView, MoodPlotView, MoodPiesView from django.urls import path, include @@ -23,4 +23,5 @@ urlpatterns = [ path('statistics/', MoodStatisticsView.as_view(), name="statistics"), path('statistics/csv/', MoodCSVView.as_view(), name="statistics_csv"), path('statistics/plot/', MoodPlotView.as_view(), name="statistics_plot"), + path('statistics/pies/', MoodPiesView.as_view(), name="statistics_pies"), ] \ No newline at end of file diff --git a/mood/views.py b/mood/views.py index 12c7471..7b24202 100644 --- a/mood/views.py +++ b/mood/views.py @@ -9,10 +9,10 @@ from django.utils.decorators import method_decorator from .models import Status, Activity, Mood, StatusMedia, StatusActivity from .forms import StatusForm -from .statistics import moodstats, activitystats +from .statistics import moodstats, activitystats, moodpies from common.helpers import get_upload_path -from common.templatetags.images import hvhtml +from common.templatetags.images import hvhtml, bkhtml from msgio.models import NotificationDailySchedule, Notification from dateutil import relativedelta @@ -393,18 +393,16 @@ class MoodPlotView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): res = HttpResponse(content_type="text/html") - startdate = request.GET.get("start") - enddate = request.GET.get("end") - - if enddate: - maxdate = datetime.strptime(enddate, "%Y-%m-%d") - else: - maxdate = timezone.now() - - if startdate: - mindate = datetime.strptime(startdate, "%Y-%m-%d") - else: - mindate = maxdate - relativedelta.relativedelta(weeks=1) - res.write(hvhtml(moodstats(request.user))) + return res + +class MoodPiesView(LoginRequiredMixin, View): + @method_decorator(xframe_options_sameorigin) + def dispatch(self, *args, **kwargs): + return super().dispatch(*args, **kwargs) + + def get(self, request, *args, **kwargs): + res = HttpResponse(content_type="text/html") + + res.write(bkhtml(moodpies(request.user))) return res \ No newline at end of file