diff --git a/mood/statistics.py b/mood/statistics.py index de8311d..c0fb954 100644 --- a/mood/statistics.py +++ b/mood/statistics.py @@ -13,7 +13,7 @@ from bokeh.layouts import row, column from holoviews.operation import timeseries from dateutil.relativedelta import relativedelta -from .models import Status, Mood +from .models import Status, Mood, StatusActivity def moodstats(user): hv.extension('bokeh') @@ -151,6 +151,116 @@ def moodpies(user): 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) + +def activitymood(activity): + hv.extension('bokeh') + + tooltips = [ + ('Date', '@date{%F %H:%M}'), + ('Mood', '@name (@value)') + ] + + formatters = { + '@date': 'datetime' + } + + hover = HoverTool(tooltips=tooltips, formatters=formatters) + + pointdict = {"date": [], "value": [], "color": [], "name": []} + + for statusactivity in StatusActivity.objects.filter(activity=activity): + if statusactivity.status.mood: + pointdict["date"].append(statusactivity.status.timestamp) + pointdict["value"].append(statusactivity.status.mood.value) + pointdict["color"].append(statusactivity.status.mood.color) + pointdict["name"].append(statusactivity.status.mood.name) + + pointframe = pd.DataFrame.from_dict(pointdict) + + points = hv.Points(pointframe) + + points.opts( + tools=[hover], color='color', cmap='Category20', + line_color='black', size=25, + width=600, height=400, show_grid=True) + + pointtuples = [(pointdict["date"][i], pointdict["value"][i]) for i in range(len(pointdict["date"]))] + + line = hv.Curve(pointtuples) + + maxval = Mood.objects.filter(user=activity.user).latest("value").value + maxy = maxval + max(maxval * 0.1, 1) + + maxx = timezone.now().timestamp() * 1000 + minx = maxx - (60*60*24*7) * 1000 + + output = points * line * timeseries.rolling(line, rolling_window=7) + output.opts(ylim=(0, maxy), xlim=(minx, maxx)) + + return output + +def activitypies(activity): + hv.extension('bokeh') + + maxdate = timezone.now() + + sa = StatusActivity.objects.filter(activity=activity) + + weekly = dict() + colors = [] + + for mood in Mood.objects.filter(user=activity.user): + weekly[mood.name] = 0 + colors.append(mood.color) + + monthly, yearly = weekly.copy(), weekly.copy() + + for single in sa: + if single.status.mood: + if single.status.timestamp > timezone.now() - relativedelta(weeks=1): + weekly[single.status.mood.name] += 1 + if single.status.timestamp > timezone.now() - relativedelta(months=1): + monthly[single.status.mood.name] += 1 + if single.status.timestamp > timezone.now() - relativedelta(years=1): + yearly[single.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) diff --git a/mood/templates/mood/statistics.html b/mood/templates/mood/statistics.html index 7ea1385..ae79b58 100644 --- a/mood/templates/mood/statistics.html +++ b/mood/templates/mood/statistics.html @@ -46,7 +46,7 @@ 1 year {% for activity, counts in activities.items %} - + {{ activity }} {{ counts.weekly }} {{ counts.monthly }} diff --git a/mood/templates/mood/statistics_activity.html b/mood/templates/mood/statistics_activity.html new file mode 100644 index 0000000..6552f86 --- /dev/null +++ b/mood/templates/mood/statistics_activity.html @@ -0,0 +1,35 @@ +{% extends "frontend/base.html" %} +{% load images %} +{% block "content" %} + +
+ + +
+
+ +
+
Activity stats
+ +
+ +
+ +
+
+
+ +
+
Activity stats
+ +
+ +
+ +
+
+
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/mood/urls.py b/mood/urls.py index a791f51..50f665e 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, MoodPiesView +from .views import StatusListView, StatusViewView, StatusDeleteView, StatusEditView, StatusCreateView, ActivityListView, ActivityEditView, ActivityCreateView, ActivityDeleteView, MoodListView, MoodEditView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView, MoodStatisticsView, MoodCSVView, MoodPlotView, MoodPiesView, ActivityStatisticsView, ActivityPlotView, ActivityPiesView from django.urls import path, include @@ -24,4 +24,7 @@ urlpatterns = [ 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"), + path('statistics/activity//', ActivityStatisticsView.as_view(), name="statistics_activity"), + path('statistics/activity//plot/', ActivityPlotView.as_view(), name="statistics_activity_plot"), + path('statistics/activity//pies/', ActivityPiesView.as_view(), name="statistics_activity_pies"), ] \ No newline at end of file diff --git a/mood/views.py b/mood/views.py index 7b24202..466fca6 100644 --- a/mood/views.py +++ b/mood/views.py @@ -9,7 +9,7 @@ from django.utils.decorators import method_decorator from .models import Status, Activity, Mood, StatusMedia, StatusActivity from .forms import StatusForm -from .statistics import moodstats, activitystats, moodpies +from .statistics import moodstats, activitystats, moodpies, activitymood, activitypies from common.helpers import get_upload_path from common.templatetags.images import hvhtml, bkhtml @@ -405,4 +405,35 @@ class MoodPiesView(LoginRequiredMixin, View): res = HttpResponse(content_type="text/html") res.write(bkhtml(moodpies(request.user))) + return res + +class ActivityStatisticsView(LoginRequiredMixin, TemplateView): + template_name = "mood/statistics_activity.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + activity = get_object_or_404(Activity, user=self.request.user, id=kwargs["id"]) + context["title"] = "Activity Statistics for %s" % activity.name + return context + +class ActivityPlotView(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(hvhtml(activitymood(get_object_or_404(Activity, user=request.user, id=kwargs["id"])))) + return res + +class ActivityPiesView(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(activitypies(get_object_or_404(Activity, user=request.user, id=kwargs["id"])))) return res \ No newline at end of file