Basic activity statistics
This commit is contained in:
parent
bedfccb79c
commit
2d09e25df3
5 changed files with 183 additions and 4 deletions
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
35
mood/templates/mood/statistics_activity.html
Normal file
35
mood/templates/mood/statistics_activity.html
Normal 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 %}
|
|
@ -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"),
|
||||||
]
|
]
|
|
@ -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
|
Loading…
Reference in a new issue