Badly implement mood pie charts
This commit is contained in:
parent
57fa6c5d7e
commit
f80b909c6e
6 changed files with 121 additions and 30 deletions
|
@ -20,19 +20,22 @@ def pildata(image):
|
||||||
return f"data:img/jpeg;base64,{content}"
|
return f"data:img/jpeg;base64,{content}"
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def hvhtml(hvobject):
|
def bkhtml(bkobject, lock_y=False):
|
||||||
bokeh = hv.render(hvobject)
|
if lock_y:
|
||||||
|
pan_tool = bkobject.select(dict(type=PanTool))
|
||||||
|
pan_tool.dimensions = "width"
|
||||||
|
|
||||||
pan_tool = bokeh.select(dict(type=PanTool))
|
zoom_tool = bkobject.select(dict(type=WheelZoomTool))
|
||||||
pan_tool.dimensions = "width"
|
zoom_tool.dimensions = "width"
|
||||||
|
|
||||||
zoom_tool = bokeh.select(dict(type=WheelZoomTool))
|
html = file_html(bkobject, INLINE)
|
||||||
zoom_tool.dimensions = "width"
|
|
||||||
|
|
||||||
html = file_html(bokeh, INLINE)
|
|
||||||
html = html.replace("http://localhost:5006/static/extensions/panel/css", "/static/frontend/vendor/panel")
|
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
|
@register.simple_tag
|
||||||
def hvdata(hvobject):
|
def hvdata(hvobject):
|
||||||
|
|
|
@ -10,6 +10,9 @@ from colorfield.fields import ColorField
|
||||||
from common.helpers import get_upload_path
|
from common.helpers import get_upload_path
|
||||||
|
|
||||||
class Mood(models.Model):
|
class Mood(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-value"]
|
||||||
|
|
||||||
user = models.ForeignKey(get_user_model(), models.CASCADE)
|
user = models.ForeignKey(get_user_model(), models.CASCADE)
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
icon = models.CharField(default="fas fa-star", max_length=64)
|
icon = models.CharField(default="fas fa-star", max_length=64)
|
||||||
|
|
|
@ -3,7 +3,13 @@ import pandas as pd
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from math import pi
|
||||||
|
|
||||||
from bokeh.models import HoverTool
|
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 holoviews.operation import timeseries
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
@ -14,7 +20,7 @@ def moodstats(user):
|
||||||
|
|
||||||
tooltips = [
|
tooltips = [
|
||||||
('Date', '@date{%F %H:%M}'),
|
('Date', '@date{%F %H:%M}'),
|
||||||
('Value', '@value')
|
('Mood', '@name (@value)')
|
||||||
]
|
]
|
||||||
|
|
||||||
formatters = {
|
formatters = {
|
||||||
|
@ -23,7 +29,7 @@ def moodstats(user):
|
||||||
|
|
||||||
hover = HoverTool(tooltips=tooltips, formatters=formatters)
|
hover = HoverTool(tooltips=tooltips, formatters=formatters)
|
||||||
|
|
||||||
pointdict = {"date": [], "value": [], "color": []}
|
pointdict = {"date": [], "value": [], "color": [], "name": []}
|
||||||
|
|
||||||
|
|
||||||
for status in Status.objects.filter(user=user):
|
for status in Status.objects.filter(user=user):
|
||||||
|
@ -31,6 +37,7 @@ def moodstats(user):
|
||||||
pointdict["date"].append(status.timestamp)
|
pointdict["date"].append(status.timestamp)
|
||||||
pointdict["value"].append(status.mood.value)
|
pointdict["value"].append(status.mood.value)
|
||||||
pointdict["color"].append(status.mood.color)
|
pointdict["color"].append(status.mood.color)
|
||||||
|
pointdict["name"].append(status.mood.name)
|
||||||
|
|
||||||
pointframe = pd.DataFrame.from_dict(pointdict)
|
pointframe = pd.DataFrame.from_dict(pointdict)
|
||||||
|
|
||||||
|
@ -80,4 +87,72 @@ def activitystats(user):
|
||||||
if status.timestamp > timezone.now() - relativedelta(weeks=1):
|
if status.timestamp > timezone.now() - relativedelta(weeks=1):
|
||||||
output[activity]["weekly"] += 1
|
output[activity]["weekly"] += 1
|
||||||
|
|
||||||
return output
|
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)
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "frontend/base.html" %}
|
{% extends "frontend/base.html" %}
|
||||||
{% load images %}
|
{% load images %}
|
||||||
{% load request %}
|
|
||||||
{% block "content" %}
|
{% block "content" %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -15,7 +14,18 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Card Body -->
|
<!-- Card Body -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<iframe id="plot" src="plot/{% querystring %}" width="800px" height="450px"></iframe>
|
<iframe id="plot" src="plot/" width="800px" height="450px"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<iframe id="plot" src="pies/" width="800px" height="450px"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -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
|
from django.urls import path, include
|
||||||
|
|
||||||
|
@ -23,4 +23,5 @@ urlpatterns = [
|
||||||
path('statistics/', MoodStatisticsView.as_view(), name="statistics"),
|
path('statistics/', MoodStatisticsView.as_view(), name="statistics"),
|
||||||
path('statistics/csv/', MoodCSVView.as_view(), name="statistics_csv"),
|
path('statistics/csv/', MoodCSVView.as_view(), name="statistics_csv"),
|
||||||
path('statistics/plot/', MoodPlotView.as_view(), name="statistics_plot"),
|
path('statistics/plot/', MoodPlotView.as_view(), name="statistics_plot"),
|
||||||
|
path('statistics/pies/', MoodPiesView.as_view(), name="statistics_pies"),
|
||||||
]
|
]
|
|
@ -9,10 +9,10 @@ from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
from .models import Status, Activity, Mood, StatusMedia, StatusActivity
|
from .models import Status, Activity, Mood, StatusMedia, StatusActivity
|
||||||
from .forms import StatusForm
|
from .forms import StatusForm
|
||||||
from .statistics import moodstats, activitystats
|
from .statistics import moodstats, activitystats, moodpies
|
||||||
|
|
||||||
from common.helpers import get_upload_path
|
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 msgio.models import NotificationDailySchedule, Notification
|
||||||
|
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
|
@ -393,18 +393,16 @@ class MoodPlotView(LoginRequiredMixin, View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
res = HttpResponse(content_type="text/html")
|
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)))
|
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
|
return res
|
Loading…
Reference in a new issue