Add a basic bar chart showing average feeding durations over time.

This commit is contained in:
Christopher Charbonneau Wells 2017-11-29 21:28:54 -05:00
parent 78b61f7f80
commit 4272c9b075
8 changed files with 114 additions and 0 deletions

View File

@ -5,6 +5,7 @@ from .base import *
SECRET_KEY = 'CHANGE ME'
DEBUG = True
TIME_ZONE = 'America/New_York'
# Database

View File

@ -16,6 +16,7 @@
<div class="dropdown-menu" aria-labelledby="reports-dropdown">
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-types-child' object.slug %}">Diaper Change Types</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}">Diaper Lifetimes</a>
<a class="dropdown-item" href="{% url 'reports:report-feeding-duration-child' object.slug %}">Feeding Durations (Average)</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-pattern-child' object.slug %}">Sleep Pattern</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">Sleep Totals</a>
<a class="dropdown-item" href="{% url 'reports:report-weight-weight-child' object.slug %}">Weight</a>

View File

@ -1,5 +1,6 @@
from .diaperchange_lifetimes import diaperchange_lifetimes # NOQA
from .diaperchange_types import diaperchange_types # NOQA
from .feeding_duration import feeding_duration # NOQA
from .sleep_pattern import sleep_pattern # NOQA
from .sleep_totals import sleep_totals # NOQA
from .weight_weight import weight_weight # NOQA

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models import Count, Sum
from django.db.models.functions import TruncDate
import plotly.offline as plotly
import plotly.graph_objs as go
from core.utils import duration_parts
from reports import utils
def feeding_duration(instances):
"""
Create a graph showing average duration of feeding instances over time.
This function originally used the Avg() function from django.db.models but
for some reason it was returning None any time the exact count of entries
was equal to seven.
:param instances: a QuerySet of Feeding instances.
:returns: a tuple of the the graph's html and javascript.
"""
totals = instances.annotate(date=TruncDate('start')) \
.values('date') \
.annotate(count=Count('id')) \
.annotate(sum=Sum('duration')) \
.order_by('-date')
averages = []
for total in totals:
averages.append(total['sum']/total['count'])
trace = go.Bar(
name='Average duration',
x=list(totals.values_list('date', flat=True)),
y=[td.seconds/60 for td in averages],
hoverinfo='text',
textposition='outside',
text=[_duration_string_minutes(td) for td in averages]
)
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = '<b>Average Feeding Durations</b>'
layout_args['xaxis']['title'] = 'Date'
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = 'Average duration (minutes)'
fig = go.Figure({
'data': [trace],
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output)
def _duration_string_minutes(duration):
"""
Format a "short" duration string with only minutes precision. This is
intended to fit better in smaller spaces on a graph.
:returns: a string of the form Xm.
"""
h, m, s = duration_parts(duration)
return '{}m'.format(m)

View File

@ -0,0 +1,13 @@
{% extends 'reports/report_base.html' %}
{% block title %}Average Feeding Durations - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Average Feeding Durations</li>
{% endblock %}
{% block javascript %}
{{ block.super }}
{{ javascript|safe }}
{% endblock %}

View File

@ -40,6 +40,9 @@ class ViewsTestCase(TestCase):
page = self.c.get('{}/changes/types/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/feeding/duration/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/sleep/pattern/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/sleep/totals/'.format(base_url))

View File

@ -13,6 +13,10 @@ urlpatterns = [
views.DiaperChangeTypesChildReport.as_view(),
name='report-diaperchange-types-child'),
url(r'^children/(?P<slug>[^/.]+)/reports/feeding/duration/$',
views.FeedingDurationChildReport.as_view(),
name='report-feeding-duration-child'),
url(r'^children/(?P<slug>[^/.]+)/reports/sleep/pattern/$',
views.SleepPatternChildReport.as_view(),
name='report-sleep-pattern-child'),

View File

@ -47,6 +47,30 @@ class DiaperChangeTypesChildReport(PermissionRequiredMixin, DetailView):
return context
class FeedingDurationChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of feeding durations over time.
"""
model = models.Child
permission_required = ('core.view_child',)
template_name = 'reports/feeding_duration.html'
def __init__(self):
super(FeedingDurationChildReport, self).__init__()
self.html = ''
self.javascript = ''
def get_context_data(self, **kwargs):
context = super(FeedingDurationChildReport, self).get_context_data(
**kwargs)
child = context['object']
instances = models.Feeding.objects.filter(child=child)
if instances:
context['html'], context['javascript'] = \
graphs.feeding_duration(instances)
return context
class SleepPatternChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of sleep pattern comparing sleep to wake times by day.