Basic activity statistics

This commit is contained in:
Kumi 2021-03-03 08:39:26 +01:00
parent bedfccb79c
commit 2d09e25df3
5 changed files with 183 additions and 4 deletions

View file

@ -13,7 +13,7 @@ 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
from .models import Status, Mood from .models import Status, Mood, StatusActivity
def moodstats(user): def moodstats(user):
hv.extension('bokeh') hv.extension('bokeh')
@ -151,6 +151,116 @@ def moodpies(user):
tools="hover", tooltips="@mood: @value") tools="hover", tooltips="@mood: @value")
yearly_chart.axis.visible = False 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, yearly_chart.wedge(x=0, y=1, radius=0.4,
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color="white", fill_color='color', legend='mood', source=yearly_data) line_color="white", fill_color='color', legend='mood', source=yearly_data)

View file

@ -46,7 +46,7 @@
<th>1 year</th> <th>1 year</th>
</tr> </tr>
{% for activity, counts in activities.items %} {% for activity, counts in activities.items %}
<tr> <tr onclick="window.location.href='activity/{{ activity.id }}/'">
<td style="color:{{ activity.color }};"><i class="{{ activity.icon }}"></i><b> {{ activity }}</b></td> <td style="color:{{ activity.color }};"><i class="{{ activity.icon }}"></i><b> {{ activity }}</b></td>
<td style="text-align: right;">{{ counts.weekly }}</td> <td style="text-align: right;">{{ counts.weekly }}</td>
<td style="text-align: right;">{{ counts.monthly }}</td> <td style="text-align: right;">{{ counts.monthly }}</td>

View file

@ -0,0 +1,35 @@
{% extends "frontend/base.html" %}
{% load images %}
{% 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">Activity stats</h6>
</div>
<!-- Card Body -->
<div class="card-body">
<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">Activity stats</h6>
</div>
<!-- Card Body -->
<div class="card-body">
<iframe id="plot" src="pies/" width="800px" height="450px"></iframe>
</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, 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 from django.urls import path, include
@ -24,4 +24,7 @@ urlpatterns = [
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"), path('statistics/pies/', MoodPiesView.as_view(), name="statistics_pies"),
path('statistics/activity/<int:id>/', ActivityStatisticsView.as_view(), name="statistics_activity"),
path('statistics/activity/<int:id>/plot/', ActivityPlotView.as_view(), name="statistics_activity_plot"),
path('statistics/activity/<int:id>/pies/', ActivityPiesView.as_view(), name="statistics_activity_pies"),
] ]

View file

@ -9,7 +9,7 @@ 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, moodpies from .statistics import moodstats, activitystats, moodpies, activitymood, activitypies
from common.helpers import get_upload_path from common.helpers import get_upload_path
from common.templatetags.images import hvhtml, bkhtml from common.templatetags.images import hvhtml, bkhtml
@ -405,4 +405,35 @@ class MoodPiesView(LoginRequiredMixin, View):
res = HttpResponse(content_type="text/html") res = HttpResponse(content_type="text/html")
res.write(bkhtml(moodpies(request.user))) 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 return res