def diaperchange_amounts(instances): """ Create a graph showing daily diaper change amounts over time. :param instances: a QuerySet of DiaperChange instances. :returns: a tuple of the the graph's html and javascript. """ totals = {} for instance in instances: time_local = timezone.localtime(instance.time) date = time_local.date() if date not in totals.keys(): totals[date] = 0 totals[date] += instance.amount or 0 amounts = [round(amount, 2) for amount in totals.values()] trace = go.Bar( name=_("Diaper change amount"), x=list(totals.keys()), y=amounts, hoverinfo="text", textposition="outside", text=amounts, ) layout_args = utils.default_graph_layout_options() layout_args["title"] = _("<b>Diaper Change Amounts</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Change amount") 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 diaperchange_lifetimes(changes): """ Create a graph showing how long diapers last (time between changes). :param changes: a QuerySet of Diaper Change instances. :returns: a tuple of the the graph's html and javascript. """ changes = changes.order_by("time") durations = [] last_change = changes.first() for change in changes[1:]: duration = change.time - last_change.time if duration.seconds > 0: durations.append(duration) last_change = change trace = go.Box( y=[round(d.seconds / 3600, 2) for d in durations], name=_("Changes"), jitter=0.3, pointpos=-1.8, boxpoints="all", ) layout_args = utils.default_graph_layout_options() layout_args["height"] = 800 layout_args["title"] = _("<b>Diaper Lifetimes</b>") layout_args["yaxis"]["title"] = _("Time between changes (hours)") layout_args["yaxis"]["zeroline"] = False layout_args["yaxis"]["dtick"] = 1 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 weight_weight(objects): """ Create a graph showing weight over time. :param objects: a QuerySet of Weight instances. :returns: a tuple of the the graph's html and javascript. """ objects = objects.order_by('-date') trace = go.Scatter( name='Weight', x=list(objects.values_list('date', flat=True)), y=list(objects.values_list('weight', flat=True)), fill='tozeroy', ) layout_args = utils.default_graph_layout_options() layout_args['barmode'] = 'stack' layout_args['title'] = '<b>Weight</b>' layout_args['xaxis']['title'] = 'Date' layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args['yaxis']['title'] = 'Weight' 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 diaperchange_lifetimes(changes): """ Create a graph showing how long diapers last (time between changes). :param changes: a QuerySet of Diaper Change instances. :returns: a tuple of the the graph's html and javascript. """ changes = changes.order_by('time') durations = [] last_change = changes.first() for change in changes[1:]: duration = change.time - last_change.time if duration.seconds > 0: durations.append(duration) last_change = change trace = go.Box(y=[round(d.seconds / 3600, 2) for d in durations], name=_('Changes'), jitter=0.3, pointpos=-1.8, boxpoints='all') layout_args = utils.default_graph_layout_options() layout_args['height'] = 800 layout_args['title'] = _('<b>Diaper Lifetimes</b>') layout_args['yaxis']['title'] = _('Time between changes (hours)') layout_args['yaxis']['zeroline'] = False layout_args['yaxis']['dtick'] = 1 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 height_change(objects): """ Create a graph showing height over time. :param objects: a QuerySet of Height instances. :returns: a tuple of the the graph's html and javascript. """ objects = objects.order_by("-date") trace = go.Scatter( name=_("Height"), x=list(objects.values_list("date", flat=True)), y=list(objects.values_list("height", flat=True)), fill="tozeroy", ) layout_args = utils.default_graph_layout_options() layout_args["barmode"] = "stack" layout_args["title"] = _("<b>Height</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Height") 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 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_avg = go.Scatter( name=_('Average duration'), line=dict(shape='spline'), x=list(totals.values_list('date', flat=True)), y=[td.seconds/60 for td in averages], hoverinfo='text', text=[_duration_string_ms(td) for td in averages] ) trace_count = go.Scatter( name=_('Total feedings'), mode='markers', x=list(totals.values_list('date', flat=True)), y=list(totals.values_list('count', flat=True)), yaxis='y2', hoverinfo='y' ) layout_args = utils.default_graph_layout_options() 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)') layout_args['yaxis2'] = dict(layout_args['yaxis']) layout_args['yaxis2']['title'] = _('Number of feedings') layout_args['yaxis2']['overlaying'] = 'y' layout_args['yaxis2']['side'] = 'right' fig = go.Figure({ 'data': [trace_avg, trace_count], 'layout': go.Layout(**layout_args) }) output = plotly.plot(fig, output_type='div', include_plotlyjs=False) return utils.split_graph_output(output)
def sleep_totals(instances): """ Create a graph showing total time sleeping for each day. :param instances: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ totals = {} for instance in instances: start = timezone.localtime(instance.start) end = timezone.localtime(instance.end) if start.date() not in totals.keys(): totals[start.date()] = timezone.timedelta(seconds=0) if end.date() not in totals.keys(): totals[end.date()] = timezone.timedelta(seconds=0) # Account for dates crossing midnight. if start.date() != end.date(): totals[start.date()] += (end.replace( year=start.year, month=start.month, day=start.day, hour=23, minute=59, second=59, ) - start) totals[end.date()] += end - start.replace(year=end.year, month=end.month, day=end.day, hour=0, minute=0, second=0) else: totals[start.date()] += instance.duration trace = go.Bar( name=_("Total sleep"), x=list(totals.keys()), y=[td.seconds / 3600 for td in totals.values()], hoverinfo="text", textposition="outside", text=[_duration_string_short(td) for td in totals.values()], ) layout_args = utils.default_graph_layout_options() layout_args["barmode"] = "stack" layout_args["title"] = _("<b>Sleep Totals</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Hours of sleep") 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 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_avg = go.Scatter( name=_("Average duration"), line=dict(shape="spline"), x=list(totals.values_list("date", flat=True)), y=[td.seconds / 60 for td in averages], hoverinfo="text", text=[_duration_string_ms(td) for td in averages], ) trace_count = go.Scatter( name=_("Total feedings"), mode="markers", x=list(totals.values_list("date", flat=True)), y=list(totals.values_list("count", flat=True)), yaxis="y2", hoverinfo="y", ) layout_args = utils.default_graph_layout_options() 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)") layout_args["yaxis2"] = dict(layout_args["yaxis"]) layout_args["yaxis2"]["title"] = _("Number of feedings") layout_args["yaxis2"]["overlaying"] = "y" layout_args["yaxis2"]["side"] = "right" fig = go.Figure({ "data": [trace_avg, trace_count], "layout": go.Layout(**layout_args) }) output = plotly.plot(fig, output_type="div", include_plotlyjs=False) return utils.split_graph_output(output)
def tummytime_duration(instances): """ Create a graph showing total duration of tummy time instances per day. :param instances: a QuerySet of TummyTime 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") ) sums = [] for total in totals: sums.append(total["sum"]) trace_avg = go.Bar( name=_("Total duration"), x=list(totals.values_list("date", flat=True)), y=[td.seconds / 60 for td in sums], hoverinfo="text", text=[_duration_string_ms(td) for td in sums], ) trace_count = go.Scatter( name=_("Number of sessions"), mode="markers", x=list(totals.values_list("date", flat=True)), y=list(totals.values_list("count", flat=True)), yaxis="y2", hoverinfo="y", ) layout_args = utils.default_graph_layout_options() layout_args["title"] = _("<b>Total Tummy Time Durations</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Total duration (minutes)") layout_args["yaxis2"] = dict(layout_args["yaxis"]) layout_args["yaxis2"]["title"] = _("Number of sessions") layout_args["yaxis2"]["overlaying"] = "y" layout_args["yaxis2"]["side"] = "right" fig = go.Figure( {"data": [trace_avg, trace_count], "layout": go.Layout(**layout_args)} ) output = plotly.plot(fig, output_type="div", include_plotlyjs=False) return utils.split_graph_output(output)
def sleep_totals(instances): """ Create a graph showing total time sleeping for each day. :param instances: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ totals = {} for instance in instances: start = timezone.localtime(instance.start) end = timezone.localtime(instance.end) if start.date() not in totals.keys(): totals[start.date()] = timezone.timedelta(seconds=0) if end.date() not in totals.keys(): totals[end.date()] = timezone.timedelta(seconds=0) # Account for dates crossing midnight. if start.date() != end.date(): totals[start.date()] += end.replace(year=start.year, month=start.month, day=start.day, hour=23, minute=59, second=59) - start totals[end.date()] += end - start.replace(year=end.year, month=end.month, day=end.day, hour=0, minute=0, second=0) else: totals[start.date()] += instance.duration trace = go.Bar(name='Total sleep', x=list(totals.keys()), y=[td.seconds / 3600 for td in totals.values()], hoverinfo='text', textposition='outside', text=[_duration_string_short(td) for td in totals.values()]) layout_args = utils.default_graph_layout_options() layout_args['barmode'] = 'stack' layout_args['title'] = '<b>Sleep Totals</b>' layout_args['xaxis']['title'] = 'Date' layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args['yaxis']['title'] = 'Hours of sleep' 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 diaperchange_types(changes): """ Create a graph showing types of totals for diaper changes. :param changes: a QuerySet of Diaper Change instances. :returns: a tuple of the the graph's html and javascript. """ changes = changes.annotate(date=TruncDate('time'))\ .values('date') \ .annotate(wet_count=Count(Case(When(wet=True, then=1)))) \ .annotate(solid_count=Count(Case(When(solid=True, then=1)))) \ .annotate(total=Count('id')) \ .order_by('-date') solid_trace = go.Scatter( mode='markers', name=_('Solid'), x=list(changes.values_list('date', flat=True)), y=list(changes.values_list('solid_count', flat=True)), ) wet_trace = go.Scatter( mode='markers', name=_('Wet'), x=list(changes.values_list('date', flat=True)), y=list(changes.values_list('wet_count', flat=True)) ) total_trace = go.Scatter( name=_('Total'), x=list(changes.values_list('date', flat=True)), y=list(changes.values_list('total', flat=True)) ) layout_args = utils.default_graph_layout_options() layout_args['barmode'] = 'stack' layout_args['title'] = _('<b>Diaper Change Types</b>') layout_args['xaxis']['title'] = _('Date') layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args['yaxis']['title'] = _('Number of changes') fig = go.Figure({ 'data': [solid_trace, wet_trace, total_trace], 'layout': go.Layout(**layout_args) }) output = plotly.plot( fig, output_type='div', include_plotlyjs=False, config={'locale': get_language()} ) return utils.split_graph_output(output)
def diaperchange_types(changes): """ Create a graph showing types of totals for diaper changes. :param changes: a QuerySet of Diaper Change instances. :returns: a tuple of the the graph's html and javascript. """ changes = (changes.annotate( date=TruncDate("time")).values("date").annotate( wet_count=Count(Case(When(wet=True, then=1)))).annotate( solid_count=Count(Case(When(solid=True, then=1)))).annotate( total=Count("id")).order_by("-date")) solid_trace = go.Scatter( mode="markers", name=_("Solid"), x=list(changes.values_list("date", flat=True)), y=list(changes.values_list("solid_count", flat=True)), ) wet_trace = go.Scatter( mode="markers", name=_("Wet"), x=list(changes.values_list("date", flat=True)), y=list(changes.values_list("wet_count", flat=True)), ) total_trace = go.Scatter( name=_("Total"), x=list(changes.values_list("date", flat=True)), y=list(changes.values_list("total", flat=True)), ) layout_args = utils.default_graph_layout_options() layout_args["barmode"] = "stack" layout_args["title"] = _("<b>Diaper Change Types</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Number of changes") fig = go.Figure({ "data": [solid_trace, wet_trace, total_trace], "layout": go.Layout(**layout_args), }) output = plotly.plot( fig, output_type="div", include_plotlyjs=False, config={"locale": get_language()}, ) return utils.split_graph_output(output)
def tummytime_duration(instances): """ Create a graph showing total duration of tummy time instances per day. :param instances: a QuerySet of TummyTime 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') sums = [] for total in totals: sums.append(total['sum']) trace_avg = go.Bar(name=_('Total duration'), x=list(totals.values_list('date', flat=True)), y=[td.seconds / 60 for td in sums], hoverinfo='text', text=[_duration_string_ms(td) for td in sums]) trace_count = go.Scatter(name=_('Number of sessions'), mode='markers', x=list(totals.values_list('date', flat=True)), y=list(totals.values_list('count', flat=True)), yaxis='y2', hoverinfo='y') layout_args = utils.default_graph_layout_options() layout_args['title'] = _('<b>Total Tummy Time Durations</b>') layout_args['xaxis']['title'] = _('Date') layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args['yaxis']['title'] = _('Total duration (minutes)') layout_args['yaxis2'] = dict(layout_args['yaxis']) layout_args['yaxis2']['title'] = _('Number of sessions') layout_args['yaxis2']['overlaying'] = 'y' layout_args['yaxis2']['side'] = 'right' fig = go.Figure({ 'data': [trace_avg, trace_count], 'layout': go.Layout(**layout_args) }) output = plotly.plot(fig, output_type='div', include_plotlyjs=False) return utils.split_graph_output(output)
def diaperchange_amounts(instances): """ Create a graph showing daily diaper change amounts over time. :param instances: a QuerySet of DiaperChange instances. :returns: a tuple of the the graph's html and javascript. """ totals = {} for instance in instances: time_local = timezone.localtime(instance.time) date = time_local.date() if date not in totals.keys(): totals[date] = 0 totals[date] += instance.amount or 0 amounts = [round(amount, 2) for amount in totals.values()] trace = go.Bar( name=_('Diaper change amount'), x=list(totals.keys()), y=amounts, hoverinfo='text', textposition='outside', text=amounts ) layout_args = utils.default_graph_layout_options() layout_args['title'] = _('<b>Diaper Change Amounts</b>') layout_args['xaxis']['title'] = _('Date') layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args['yaxis']['title'] = _('Change amount') 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 sleep_pattern(instances): """ Create a graph showing blocked out periods of sleep during each day. :param instances: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ # TODO: Simplify this using the bar charts "base" property. y_df = pd.DataFrame() text_df = pd.DataFrame() last_end_time = None adjustment = None df_index = 0 for instance in instances: start_time = timezone.localtime(instance.start) end_time = timezone.localtime(instance.end) start_date = start_time.date().isoformat() duration = instance.duration # Check if the previous entry crossed midnight (see below). if adjustment: # Fake (0) entry to keep the color switching logic working. df_index = _add_sleep_entry(y_df, text_df, 0, adjustment['column'], 0) # Real adjustment entry. df_index = _add_sleep_entry( y_df, text_df, df_index, adjustment['column'], adjustment['duration'].seconds / 60, 'Asleep {} ({} to {})'.format( duration_string(adjustment['duration']), adjustment['start_time'].strftime('%I:%M %p'), adjustment['end_time'].strftime('%I:%M %p'))) last_end_time = timezone.localtime(adjustment['end_time']) adjustment = None # If the dates do not match, set up an adjustment for the next day. if end_time.date() != start_time.date(): adj_start_time = end_time.replace(hour=0, minute=0, second=0) adjustment = { 'column': end_time.date().isoformat(), 'start_time': adj_start_time, 'end_time': end_time, 'duration': end_time - adj_start_time } # Adjust end_time for the current entry. end_time = end_time.replace(year=start_time.year, month=start_time.month, day=start_time.day, hour=23, minute=59, second=0) duration = end_time - start_time if start_date not in y_df: last_end_time = start_time.replace(hour=0, minute=0, second=0) # Awake time. df_index = _add_sleep_entry(y_df, text_df, df_index, start_date, (start_time - last_end_time).seconds / 60) # Asleep time. df_index = _add_sleep_entry( y_df, text_df, df_index, start_date, duration.seconds / 60, 'Asleep {} ({} to {})'.format(duration_string(duration), start_time.strftime('%I:%M %p'), end_time.strftime('%I:%M %p'))) # Update the previous entry duration if an offset change occurred. # This can happen when an entry crosses a daylight savings time change. if start_time.utcoffset() != end_time.utcoffset(): diff = start_time.utcoffset() - end_time.utcoffset() duration -= timezone.timedelta(seconds=diff.seconds) y_df.set_value(df_index - 1, start_date, duration.seconds / 60) last_end_time = end_time dates = list(y_df) traces = [] color = 'rgba(255, 255, 255, 0)' for index, row in y_df.iterrows(): traces.append( go.Bar( x=dates, y=row, text=text_df.ix[index], hoverinfo='text', marker={'color': color}, showlegend=False, )) if color == 'rgba(255, 255, 255, 0)': color = 'rgb(35, 110, 150)' else: color = 'rgba(255, 255, 255, 0)' layout_args = utils.default_graph_layout_options() layout_args['margin']['b'] = 100 layout_args['barmode'] = 'stack' layout_args['hovermode'] = 'closest' layout_args['title'] = '<b>Sleep Pattern</b>' layout_args['height'] = 800 layout_args['xaxis']['title'] = 'Date' layout_args['xaxis']['tickangle'] = -65 layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() start = timezone.localtime().strptime('12:00 AM', '%I:%M %p') ticks = OrderedDict() ticks[0] = start.strftime('%I:%M %p') for i in range(30, 60 * 24, 30): ticks[i] = (start + timezone.timedelta(minutes=i)).strftime('%I:%M %p') layout_args['yaxis']['title'] = 'Time of day' layout_args['yaxis']['rangemode'] = 'tozero' layout_args['yaxis']['tickmode'] = 'array' layout_args['yaxis']['tickvals'] = list(ticks.keys()) layout_args['yaxis']['ticktext'] = list(ticks.values()) layout_args['yaxis']['tickfont'] = {'size': 10} fig = go.Figure({'data': traces, 'layout': go.Layout(**layout_args)}) output = plotly.plot(fig, output_type='div', include_plotlyjs=False) return utils.split_graph_output(output)
def sleep_pattern(sleeps): """ Create a graph showing blocked out periods of sleep during each day. :param sleeps: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ last_end_time = None adjustment = None first_day = timezone.localtime(sleeps.first().start) last_day = timezone.localtime(sleeps.last().end) days = _init_days(first_day, last_day) for sleep in sleeps: start_time = timezone.localtime(sleep.start) end_time = timezone.localtime(sleep.end) start_date = start_time.date().isoformat() end_date = end_time.date().isoformat() duration = sleep.duration # Check if the previous entry crossed midnight (see below). if adjustment: _add_adjustment(adjustment, days) last_end_time = timezone.localtime(adjustment["end_time"]) adjustment = None # If the dates do not match, set up an adjustment for the next day. if end_time.date() != start_time.date(): adj_start_time = end_time.replace(hour=0, minute=0, second=0) adjustment = { "column": end_date, "start_time": adj_start_time, "end_time": end_time, "duration": end_time - adj_start_time, } # Adjust end_time for the current entry. end_time = end_time.replace( year=start_time.year, month=start_time.month, day=start_time.day, hour=23, minute=59, second=0, ) duration = end_time - start_time if not last_end_time: last_end_time = start_time.replace(hour=0, minute=0, second=0) # Awake time. days[start_date].append({ "time": (start_time - last_end_time).seconds / 60, "label": None }) # Asleep time. days[start_date].append({ "time": duration.seconds / 60, "label": _format_label(duration, start_time, end_time), }) # Update the previous entry duration if an offset change occurred. # This can happen when an entry crosses a daylight savings time change. if start_time.utcoffset() != end_time.utcoffset(): diff = start_time.utcoffset() - end_time.utcoffset() duration -= timezone.timedelta(seconds=diff.seconds) yesterday = end_time - timezone.timedelta(days=1) yesterday = yesterday.date().isoformat() days[yesterday][len(days[yesterday]) - 1] = { "time": duration.seconds / 60, "label": _format_label(duration, start_time, end_time), } last_end_time = end_time # Handle any left over adjustment (if the last entry crossed midnight). if adjustment: _add_adjustment(adjustment, days) # Create dates for x-axis using a 12:00:00 time to ensure correct # positioning of bars (covering entire day). dates = [] for time in list(days.keys()): dates.append("{} 12:00:00".format(time)) traces = [] color = AWAKE_COLOR # Set iterator and determine maximum iteration for dates. i = 0 max_i = 0 for date_times in days.values(): max_i = max(len(date_times), max_i) while i < max_i: y = {} text = {} for date in days.keys(): try: y[date] = days[date][i]["time"] text[date] = days[date][i]["label"] except IndexError: y[date] = None text[date] = None i += 1 traces.append( go.Bar( x=dates, y=list(y.values()), hovertext=list(text.values()), # `hoverinfo` is deprecated but if we use the new `hovertemplate` # the "filler" areas for awake time get a hover that says "null" # and there is no way to prevent this currently with Plotly. hoverinfo="text", marker={"color": color}, showlegend=False, )) if color == AWAKE_COLOR: color = ASLEEP_COLOR else: color = AWAKE_COLOR layout_args = utils.default_graph_layout_options() layout_args["margin"]["b"] = 100 layout_args["barmode"] = "stack" layout_args["bargap"] = 0 layout_args["hovermode"] = "closest" layout_args["title"] = _("<b>Sleep Pattern</b>") layout_args["height"] = 800 layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["tickangle"] = -65 layout_args["xaxis"]["tickformat"] = "%b %e\n%Y" layout_args["xaxis"]["ticklabelmode"] = "period" layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() start = timezone.localtime().strptime("12:00 AM", "%I:%M %p") ticks = OrderedDict() ticks[0] = start.strftime("%I:%M %p") for i in range(0, 60 * 24, 30): ticks[i] = formats.time_format(start + timezone.timedelta(minutes=i), "TIME_FORMAT") layout_args["yaxis"]["title"] = _("Time of day") layout_args["yaxis"]["range"] = [24 * 60, 0] layout_args["yaxis"]["tickmode"] = "array" layout_args["yaxis"]["tickvals"] = list(ticks.keys()) layout_args["yaxis"]["ticktext"] = list(ticks.values()) layout_args["yaxis"]["tickfont"] = {"size": 10} fig = go.Figure({"data": traces, "layout": go.Layout(**layout_args)}) output = plotly.plot(fig, output_type="div", include_plotlyjs=False) return utils.split_graph_output(output)
def sleep_pattern(instances): """ Create a graph showing blocked out periods of sleep during each day. :param instances: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ times = {} labels = {} last_end_time = None adjustment = None for instance in instances: start_time = timezone.localtime(instance.start) end_time = timezone.localtime(instance.end) start_date = start_time.date().isoformat() end_date = end_time.date().isoformat() duration = instance.duration # Ensure that lists are initialized for the start and end date (as they # may be different dates). if start_date not in times: times[start_date] = [] if start_date not in labels: labels[start_date] = [] if end_date not in times: times[end_date] = [] if end_date not in labels: labels[end_date] = [] # Check if the previous entry crossed midnight (see below). if adjustment: _add_adjustment(adjustment, times, labels) last_end_time = timezone.localtime(adjustment['end_time']) adjustment = None # If the dates do not match, set up an adjustment for the next day. if end_time.date() != start_time.date(): adj_start_time = end_time.replace(hour=0, minute=0, second=0) adjustment = { 'column': end_time.date().isoformat(), 'start_time': adj_start_time, 'end_time': end_time, 'duration': end_time - adj_start_time } # Adjust end_time for the current entry. end_time = end_time.replace(year=start_time.year, month=start_time.month, day=start_time.day, hour=23, minute=59, second=0) duration = end_time - start_time if not last_end_time: last_end_time = start_time.replace(hour=0, minute=0, second=0) # Awake time. times[start_date].append((start_time - last_end_time).seconds / 60) labels[start_date].append(None) # Asleep time. times[start_date].append(duration.seconds / 60) labels[start_date].append(_format_label(duration, start_time, end_time)) # Update the previous entry duration if an offset change occurred. # This can happen when an entry crosses a daylight savings time change. if start_time.utcoffset() != end_time.utcoffset(): diff = start_time.utcoffset() - end_time.utcoffset() duration -= timezone.timedelta(seconds=diff.seconds) yesterday = (end_time - timezone.timedelta(days=1)) yesterday = yesterday.date().isoformat() times[yesterday][len(times[yesterday]) - 1] = duration.seconds / 60 labels[yesterday][len(times[yesterday]) - 1] = _format_label( duration, start_time, end_time) last_end_time = end_time # Handle any left over adjustment (if the last entry crossed midnight). if adjustment: _add_adjustment(adjustment, times, labels) # Create dates for x-axis using a 12:00:00 time to ensure correct # positioning of bars (covering entire day). dates = [] for time in list(times.keys()): dates.append('{} 12:00:00'.format(time)) traces = [] color = 'rgba(255, 255, 255, 0)' # Set iterator and determine maximum iteration for dates. i = 0 max_i = 0 for date_times in times.values(): max_i = max(len(date_times), max_i) while i < max_i: y = {} text = {} for date in times.keys(): try: y[date] = times[date][i] text[date] = labels[date][i] except IndexError: y[date] = None text[date] = None i += 1 traces.append( go.Bar( x=dates, y=list(y.values()), hovertext=list(text.values()), # `hoverinfo` is deprecated but if we use the new `hovertemplate` # the "filler" areas for awake time get a hover that says "null" # and there is no way to prevent this currently with Plotly. hoverinfo='text', marker={'color': color}, showlegend=False, )) if color == 'rgba(255, 255, 255, 0)': color = 'rgb(35, 110, 150)' else: color = 'rgba(255, 255, 255, 0)' layout_args = utils.default_graph_layout_options() layout_args['margin']['b'] = 100 layout_args['barmode'] = 'stack' layout_args['bargap'] = 0 layout_args['hovermode'] = 'closest' layout_args['title'] = _('<b>Sleep Pattern</b>') layout_args['height'] = 800 layout_args['xaxis']['title'] = _('Date') layout_args['xaxis']['tickangle'] = -65 layout_args['xaxis']['tickformat'] = '%b %e\n%Y' layout_args['xaxis']['ticklabelmode'] = 'period' layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() start = timezone.localtime().strptime('12:00 AM', '%I:%M %p') ticks = OrderedDict() ticks[0] = start.strftime('%I:%M %p') for i in range(0, 60 * 24, 30): ticks[i] = formats.time_format(start + timezone.timedelta(minutes=i), 'TIME_FORMAT') layout_args['yaxis']['title'] = _('Time of day') layout_args['yaxis']['range'] = [24 * 60, 0] layout_args['yaxis']['tickmode'] = 'array' layout_args['yaxis']['tickvals'] = list(ticks.keys()) layout_args['yaxis']['ticktext'] = list(ticks.values()) layout_args['yaxis']['tickfont'] = {'size': 10} fig = go.Figure({'data': traces, 'layout': go.Layout(**layout_args)}) output = plotly.plot(fig, output_type='div', include_plotlyjs=False) return utils.split_graph_output(output)
def sleep_pattern(instances): """ Create a graph showing blocked out periods of sleep during each day. :param instances: a QuerySet of Sleep instances. :returns: a tuple of the the graph's html and javascript. """ times = {} labels = {} last_end_time = None adjustment = None for instance in instances: start_time = timezone.localtime(instance.start) end_time = timezone.localtime(instance.end) start_date = start_time.date().isoformat() duration = instance.duration if start_date not in times: times[start_date] = [] if start_date not in labels: labels[start_date] = [] # Check if the previous entry crossed midnight (see below). if adjustment: # Fake (0) entry to keep the color switching logic working. times[adjustment['column']].append(0) labels[adjustment['column']].append(0) # Real adjustment entry. times[adjustment['column']].append(adjustment['duration'].seconds / 60) labels[adjustment['column']].append( _format_label(adjustment['duration'], adjustment['start_time'], adjustment['end_time'])) last_end_time = timezone.localtime(adjustment['end_time']) adjustment = None # If the dates do not match, set up an adjustment for the next day. if end_time.date() != start_time.date(): adj_start_time = end_time.replace(hour=0, minute=0, second=0) adjustment = { 'column': end_time.date().isoformat(), 'start_time': adj_start_time, 'end_time': end_time, 'duration': end_time - adj_start_time } # Adjust end_time for the current entry. end_time = end_time.replace(year=start_time.year, month=start_time.month, day=start_time.day, hour=23, minute=59, second=0) duration = end_time - start_time if not last_end_time: last_end_time = start_time.replace(hour=0, minute=0, second=0) # Awake time. times[start_date].append((start_time - last_end_time).seconds / 60) labels[start_date].append(None) # Asleep time. times[start_date].append(duration.seconds / 60) labels[start_date].append(_format_label(duration, start_time, end_time)) # Update the previous entry duration if an offset change occurred. # This can happen when an entry crosses a daylight savings time change. if start_time.utcoffset() != end_time.utcoffset(): diff = start_time.utcoffset() - end_time.utcoffset() duration -= timezone.timedelta(seconds=diff.seconds) yesterday = (end_time - timezone.timedelta(days=1)) yesterday = yesterday.date().isoformat() times[yesterday][len(times[yesterday]) - 1] = duration.seconds / 60 labels[yesterday][len(times[yesterday]) - 1] = _format_label( duration, start_time, end_time) last_end_time = end_time dates = list(times.keys()) traces = [] color = 'rgba(255, 255, 255, 0)' # Set iterator and determine maximum iteration for dates. i = 0 max_i = 0 for date_times in times.values(): max_i = max(len(date_times), max_i) while i < max_i: y = {} text = {} for date in times.keys(): try: y[date] = times[date][i] text[date] = labels[date][i] except IndexError: y[date] = None text[date] = None i += 1 traces.append( go.Bar( x=dates, y=list(y.values()), text=list(text.values()), hoverinfo='text', marker={'color': color}, marker_line_width=0, showlegend=False, )) if color == 'rgba(255, 255, 255, 0)': color = 'rgb(35, 110, 150)' else: color = 'rgba(255, 255, 255, 0)' layout_args = utils.default_graph_layout_options() layout_args['margin']['b'] = 100 layout_args['barmode'] = 'stack' layout_args['bargap'] = 0 layout_args['hovermode'] = 'closest' layout_args['title'] = _('<b>Sleep Pattern</b>') layout_args['height'] = 800 layout_args['xaxis']['title'] = _('Date') layout_args['xaxis']['tickangle'] = -65 layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() start = timezone.localtime().strptime('12:00 AM', '%I:%M %p') ticks = OrderedDict() ticks[0] = start.strftime('%I:%M %p') for i in range(30, 60 * 24, 30): ticks[i] = (start + timezone.timedelta(minutes=i)).strftime('%I:%M %p') layout_args['yaxis']['title'] = _('Time of day') layout_args['yaxis']['range'] = [0, 24 * 60] layout_args['yaxis']['tickmode'] = 'array' layout_args['yaxis']['tickvals'] = list(ticks.keys()) layout_args['yaxis']['ticktext'] = list(ticks.values()) layout_args['yaxis']['tickfont'] = {'size': 10} fig = go.Figure({'data': traces, 'layout': go.Layout(**layout_args)}) output = plotly.plot(fig, output_type='div', include_plotlyjs=False) return utils.split_graph_output(output)
def feeding_amounts(instances): """ Create a graph showing daily feeding amounts over time. :param instances: a QuerySet of Feeding instances. :returns: a tuple of the the graph's html and javascript. """ feeding_types, feeding_types_desc = map( list, zip(*models.Feeding._meta.get_field("type").choices)) total_idx = len(feeding_types) + 1 # +1 for aggregate total totals_list = list() for i in range(total_idx): totals_list.append({}) for instance in instances: end = timezone.localtime(instance.end) date = end.date() if date not in totals_list[total_idx - 1].keys(): for item in totals_list: item[date] = 0 feeding_idx = feeding_types.index(instance.type) totals_list[feeding_idx][date] += instance.amount or 0 totals_list[total_idx - 1][date] += instance.amount or 0 zeros = [0 for a in totals_list[total_idx - 1].values()] # sum each feeding type for graph amounts_array = [] for i in range(total_idx): amounts_array.append([round(a, 2) for a in totals_list[i].values()]) traces = [] for i in range(total_idx - 1): for x in amounts_array[i]: if x != 0: # Only include if it has non zero values traces.append( go.Bar( name=str(feeding_types_desc[i]), x=list(totals_list[total_idx - 1].keys()), y=amounts_array[i], text=amounts_array[i], hovertemplate=str(feeding_types_desc[i]), )) break traces.append( go.Bar( name=_("Total"), x=list(totals_list[total_idx - 1].keys()), y=zeros, hoverinfo="text", textposition="outside", text=amounts_array[total_idx - 1], showlegend=False, )) layout_args = utils.default_graph_layout_options() layout_args["title"] = _("<b>Total Feeding Amount by Type</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Feeding amount") fig = go.Figure({"data": traces, "layout": go.Layout(**layout_args)}) fig.update_layout(barmode="stack") output = plotly.plot(fig, output_type="div", include_plotlyjs=False) return utils.split_graph_output(output)
def pumping_amounts(objects): """ Create a graph showing pumping amounts over time. :param instances: a QuerySet of Pumping instances. :returns: a tuple of the the graph's html and javascript. """ objects = objects.order_by("time") # We need to find date totals for annotations at the end curr_date = "" date_totals = {} for object in objects: date_s = timezone.localtime(object.time) date_s = str(date_s.date()) if curr_date != date_s: date_totals[date_s] = 0.0 curr_date = date_s date_totals[date_s] += object.amount dates = [] # Single array for each bar amounts = [] # Array of arrays containing amounts index_x, index_y = 0, -1 for object in objects: date_s = timezone.localtime(object.time) date_s = str(date_s.date()) if date_s not in dates: dates.append(date_s) index_y += 1 index_x = 0 if len(amounts) == 0 or len(amounts) <= index_x: amounts.append([0] * len(date_totals.keys())) amounts[index_x][index_y] = object.amount index_x += 1 traces = [] for i in range(0, len(amounts) - 1): traces.append( go.Bar( name="Amount", x=dates, y=amounts[i], text=amounts[i], hovertemplate=amounts[i], showlegend=False, )) layout_args = utils.default_graph_layout_options() layout_args["title"] = _("<b>Total Pumping Amount</b>") layout_args["xaxis"]["title"] = _("Date") layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date() layout_args["yaxis"]["title"] = _("Pumping Amount") total_labels = [{ "x": x, "y": total * 1.1, "text": str(total), "showarrow": False } for x, total in zip(list(dates), date_totals.values())] fig = go.Figure({"data": traces, "layout": go.Layout(**layout_args)}) fig.update_layout(barmode="stack", annotations=total_labels) output = plotly.plot(fig, output_type="div", include_plotlyjs=False) return utils.split_graph_output(output)