Add graph handling for sleep entry start and end dates that cross midnight.

This commit is contained in:
Christopher Charbonneau Wells 2017-08-25 17:32:46 -04:00
parent 9d0572ba92
commit 2320d31d48
1 changed files with 67 additions and 21 deletions

View File

@ -5,13 +5,14 @@ from collections import OrderedDict
from django.db.models import Count, Case, When
from django.db.models.functions import TruncDate
from django.utils import timezone, duration
from django.utils import timezone
import pandas as pd
import plotly.offline as plotly
import plotly.graph_objs as go
from core.models import DiaperChange, Sleep
from core.utils import duration_string
from .utils import default_graph_layout_options, split_graph_output
@ -58,49 +59,81 @@ def diaperchange_types(child):
def sleep_times(child):
# TODO: Handle dates crossing over midnight!
"""Create a graph showing blocked out period of sleep during each day."""
instances = Sleep.objects.filter(child=child).order_by('start')
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'],
'Asleep {} ({} to {})'.format(
duration_string(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).seconds/60
}
# Adjust end_time for the current entry.
end_time = end_time.replace(
day=start_time.day, hour=23, minute=59, second=0)
duration = end_time - start_time
if start_date not in y_df:
y_df.assign(**{start_date: 0 in range(0, len(y_df.index))})
text_df.assign(**{start_date: 0 in range(0, len(y_df.index))})
last_end_time = start_time.replace(hour=0, minute=0, second=0)
df_index = 0
# Awake time.
y_df.set_value(
df_index, start_date, (start_time - last_end_time).seconds/60,
)
text_df.set_value(
df_index = _add_sleep_entry(
y_df,
text_df,
df_index,
start_date,
'Awake for from {} to {} ({})'.format(
last_end_time.strftime('%I:%M %p'),
start_time.strftime('%I:%M %p'),
duration.duration_string(start_time - last_end_time)
(start_time - last_end_time).seconds/60
)
)
df_index += 1
# Asleep time.
y_df.set_value(df_index, start_date, instance.duration.seconds/60)
text_df.set_value(
df_index = _add_sleep_entry(
y_df,
text_df,
df_index,
start_date,
'Asleep for from {} to {} ({})'.format(
duration.seconds/60,
'Asleep {} ({} to {})'.format(
duration_string(duration),
start_time.strftime('%I:%M %p'),
end_time.strftime('%I:%M %p'),
duration.duration_string(instance.duration)
end_time.strftime('%I:%M %p')
)
)
df_index += 1
last_end_time = end_time
dates = list(y_df)
@ -151,3 +184,16 @@ def sleep_times(child):
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return split_graph_output(output)
def _add_sleep_entry(y_df, text_df, index, column, duration, text=''):
"""Create a duration and text description entry in a DataFrame and return
the next index on success."""
if column not in y_df:
y_df.assign(**{column: 0 in range(0, len(y_df.index))})
text_df.assign(**{column: 0 in range(0, len(text_df.index))})
index = 0
y_df.set_value(index, column, duration)
text_df.set_value(index, column, text)
return index + 1