From 68d238b9625fba2964075825b1932acfb27281ef Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 5 May 2020 16:08:58 +0200
Subject: [PATCH 1/3] adding first pie chart
---
rowers/interactiveplots.py | 74 +++++++++++++++++++++++++++++++++++
rowers/templates/history.html | 70 +++++++++++++++++++++++++++++++--
rowers/views/analysisviews.py | 68 +++++++++++++++++++++++++++++++-
rowers/views/workoutviews.py | 2 +-
4 files changed, 209 insertions(+), 5 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index ad8e6ba7..a714f4d5 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -169,6 +169,80 @@ def tailwind(bearing,vwind,winddir):
from rowers.dataprep import nicepaceformat,niceformat
from rowers.dataprep import timedeltaconv
+from math import pi
+
+def interactive_hr_piechart(df,rower,title):
+
+ df.sort_values(by='hr',inplace=True)
+ qry = 'hr < {ut2}'.format(ut2=rower.ut2)
+ frac_lut2 = len(df.query(qry))/len(df)
+
+ qry = 'hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
+ frac_ut2 = len(df.query(qry))/len(df)
+
+ qry = 'hr < {at}'.format(ut1=rower.ut1,at=rower.at)
+ frac_ut1 = len(df.query(qry))/len(df)
+
+ qry = 'hr < {tr}'.format(at=rower.at,tr=rower.tr)
+ frac_at = len(df.query(qry))/len(df)
+
+ qry = 'hr < {an}'.format(tr=rower.tr,an=rower.an)
+ frac_tr = len(df.query(qry))/len(df)
+
+ frac_an = 1.
+
+ source_starts = 2*pi*pd.Series([
+ 0,
+ frac_lut2,
+ frac_ut2,
+ frac_ut1,
+ frac_at,
+ frac_tr,
+ ])
+
+ source_ends = 2*pi*pd.Series([
+ frac_lut2,
+ frac_ut2,
+ frac_ut1,
+ frac_at,
+ frac_tr,
+ frac_an,
+ ])
+
+ source_legends = [
+ 'History
- -
-
Future Functionality
+
+ -
+
All workouts
- Watch this space
+
+
+
+
+ | Total Distance | {{ totalsdict|lookup:"distance"}} meters |
+
+
+ | Total Duration | {{ totalsdict|lookup:"duration"}} hours |
+
+
+ | Number of workouts | {{ totalsdict|lookup:"nrworkouts"}} |
+
+
+ | Average heart rate | {{ totalsdict|lookup:"hrmean"}} bpm |
+
+
+ | Maximum heart rate | {{ totalsdict|lookup:"hrmax"}} bpm |
+
+
+ | Average power | {{ totalsdict|lookup:"powermean"}} W |
+
+
+ | Maximum power | {{ totalsdict|lookup:"powermax"}} W |
+
+
+
+
+
+ {{ totalscript|safe }}{{ totaldiv|safe }}
+
+ {% for ddict in typedicts %}
+
+ {{ ddict|lookup:"wtype"}}
+
+
+
+
+
+ | Total Distance | {{ ddict|lookup:"distance"}} meters |
+
+
+ | Total Duration | {{ ddict|lookup:"duration"}} hours |
+
+
+ | Number of workouts | {{ ddict|lookup:"nrworkouts"}} |
+
+
+ | Average heart rate | {{ ddict|lookup:"hrmean"}} bpm |
+
+
+ | Maximum heart rate | {{ ddict|lookup:"hrmax"}} bpm |
+
+
+ | Average power | {{ ddict|lookup:"powermean"}} W |
+
+
+ | Maximum power | {{ ddict|lookup:"powermax"}} W |
+
+
+
+
+
+ {% endfor %}
{% endblock %}
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 2a70d01d..c21ca7b1 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -4654,6 +4654,68 @@ class AlertDelete(DeleteView):
@login_required()
def history_view(request,userid=0):
r = getrequestrower(request,userid=userid)
+ usertimezone = pytz.timezone(r.defaulttimezone)
+
+ activity_enddate = timezone.now()
+ activity_enddate = activity_enddate.replace(hour=23,minute=59,second=59).astimezone(usertimezone)
+ activity_startdate = activity_enddate-datetime.timedelta(days=15)
+ activity_startdate = activity_startdate.replace(hour=0,minute=0,second=0)
+
+ g_workouts = Workout.objects.filter(
+ user=r,
+ startdatetime__gte=activity_startdate,
+ startdatetime__lte=activity_enddate,
+ duplicate=False,
+ privacy='visible'
+ ).order_by("-startdatetime")
+
+ ids = [w.id for w in g_workouts]
+
+ columns = ['hr','power']
+
+ df = getsmallrowdata_db(columns,ids=ids)
+
+ totalmeters,totalhours, totalminutes = get_totals(g_workouts)
+
+ # meters, duration per workout type
+ wtypes = list(set([w.workouttype for w in g_workouts]))
+ listofdicts = []
+
+ for wtype in wtypes:
+ a_workouts = g_workouts.filter(workouttype=wtype)
+ wmeters, whours, wminutes = get_totals(a_workouts)
+ ddict = {}
+ ddict['wtype'] = mytypes.workouttypes_ordered[wtype]
+ ddict['distance'] = wmeters
+ ddict['duration'] = "{whours}:{wminutes:02d}".format(
+ whours=whours,
+ wminutes=wminutes
+ )
+ ddf = getsmallrowdata_db(columns,ids=[w.id for w in a_workouts])
+ ddict['hrmean'] = ddf['hr'].mean().astype(int)
+ ddict['hrmax'] = ddf['hr'].max().astype(int)
+ ddict['powermean'] = ddf['power'].mean().astype(int)
+ ddict['powermax'] = ddf['power'].max().astype(int)
+ ddict['nrworkouts'] = a_workouts.count()
+ listofdicts.append(ddict)
+
+ # interactive hr pie chart
+ totalscript,totaldiv = interactive_hr_piechart(df,r,'All Workouts')
+
+ # interactive power pie chart
+
+ totalsdict = {}
+ totalsdict['duration'] = "{totalhours}:{totalminutes}".format(
+ totalhours=totalhours,
+ totalminutes=totalminutes
+ )
+
+ totalsdict['distance'] = totalmeters
+ totalsdict['powermean'] = df['power'].mean().astype(int)
+ totalsdict['powermax'] = df['power'].max().astype(int)
+ totalsdict['hrmean'] = df['hr'].mean().astype(int)
+ totalsdict['hrmax'] = df['hr'].max().astype(int)
+ totalsdict['nrworkouts'] = g_workouts.count()
breadcrumbs = [
{
@@ -4670,5 +4732,9 @@ def history_view(request,userid=0):
{
'rower':r,
'breadcrumbs':breadcrumbs,
- 'active':'nav-analysis'
+ 'active':'nav-analysis',
+ 'totalsdict':totalsdict,
+ 'typedicts':listofdicts,
+ 'totalscript':totalscript,
+ 'totaldiv':totaldiv,
})
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 105c069b..0163840d 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -1918,7 +1918,7 @@ def workouts_view(request,message='',successmessage='',
g_workouts = Workout.objects.filter(
team=theteam,user=r,
startdatetime__gte=activity_startdate,
- enddatetime__lte=activity_enddate,
+ startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-startdatetime")
From e7b3c7df0fa3058253c14d0d294adfa09e74ec5d Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 5 May 2020 17:58:30 +0200
Subject: [PATCH 2/3] prototype history page
---
rowers/forms.py | 24 +++++++++++++++++++++++
rowers/interactiveplots.py | 2 ++
rowers/templates/history.html | 15 ++++++++++++--
rowers/views/analysisviews.py | 37 ++++++++++++++++++++++++++++++++++-
rowers/views/statements.py | 2 +-
5 files changed, 76 insertions(+), 4 deletions(-)
diff --git a/rowers/forms.py b/rowers/forms.py
index 3532be60..312c6d65 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -143,6 +143,30 @@ class DisqualificationForm(forms.Form):
message = forms.CharField(required=True,widget=forms.Textarea)
+class HistorySelectForm(forms.Form):
+ typeselectchoices = [("All","All")]
+ for wtype,verbose in mytypes.workouttypes_ordered.items():
+ typeselectchoices.append((wtype,verbose))
+ startdate = forms.DateField(
+ initial=timezone.now()-datetime.timedelta(days=15),
+ # widget=SelectDateWidget(years=range(1990,2050)),
+ widget=AdminDateWidget(), #format='%Y-%m-%d'),
+ label='Start Date')
+ enddate = forms.DateField(
+ initial=timezone.now(),
+ widget=AdminDateWidget(), #format='%Y-%m-%d'),
+ label='End Date')
+
+ workouttype = forms.ChoiceField(initial='All',choices=typeselectchoices)
+
+ class Meta:
+ fields = ['startdate','enddate']
+ input_formats=("%Y-%m-%d")
+ dateTimeOptions = {
+ 'format': '%Y-%m-%d',
+ 'autoclose': True,
+ }
+
class MetricsForm(forms.Form):
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
avgpwr = forms.IntegerField(required=False,label='Average Power')
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index a714f4d5..829b7777 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -172,6 +172,8 @@ from rowers.dataprep import timedeltaconv
from math import pi
def interactive_hr_piechart(df,rower,title):
+ if df.empty:
+ return "","Not enough data to make a chart"
df.sort_values(by='hr',inplace=True)
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
diff --git a/rowers/templates/history.html b/rowers/templates/history.html
index 0328be5b..b315eb9f 100644
--- a/rowers/templates/history.html
+++ b/rowers/templates/history.html
@@ -8,7 +8,16 @@
History
- -
+
-
+
+
+ -
All workouts
@@ -39,7 +48,7 @@
- -
+
-
{{ totalscript|safe }}{{ totaldiv|safe }}
{% for ddict in typedicts %}
@@ -74,12 +83,14 @@
+
{% endfor %}
{% endblock %}
+
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index c21ca7b1..aa767ace 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -4654,12 +4654,33 @@ class AlertDelete(DeleteView):
@login_required()
def history_view(request,userid=0):
r = getrequestrower(request,userid=userid)
+
+ form = HistorySelectForm()
+
usertimezone = pytz.timezone(r.defaulttimezone)
activity_enddate = timezone.now()
activity_enddate = activity_enddate.replace(hour=23,minute=59,second=59).astimezone(usertimezone)
activity_startdate = activity_enddate-datetime.timedelta(days=15)
activity_startdate = activity_startdate.replace(hour=0,minute=0,second=0)
+ typeselect = 'All'
+
+ if request.method=='POST':
+ form = HistorySelectForm(request.POST)
+ if form.is_valid():
+ startdate = form.cleaned_data['startdate']
+ enddate = form.cleaned_data['enddate']
+ typeselect = form.cleaned_data['workouttype']
+ activity_startdate = datetime.datetime(
+ startdate.year,startdate.month,startdate.day
+ ).replace(hour=0,minute=0,second=0).astimezone(usertimezone)
+ activity_enddate = datetime.datetime(
+ enddate.year,enddate.month,enddate.day
+ ).replace(hour=23,minute=59,second=59).astimezone(usertimezone)
+
+
+
+
g_workouts = Workout.objects.filter(
user=r,
@@ -4679,6 +4700,13 @@ def history_view(request,userid=0):
# meters, duration per workout type
wtypes = list(set([w.workouttype for w in g_workouts]))
+
+ typechoices = [("All","All")]
+ for wtype in wtypes:
+ typechoices.append((wtype,mytypes.workouttypes_ordered[wtype]))
+
+ form.fields['workouttype'].choices = typechoices
+
listofdicts = []
for wtype in wtypes:
@@ -4699,8 +4727,14 @@ def history_view(request,userid=0):
ddict['nrworkouts'] = a_workouts.count()
listofdicts.append(ddict)
+
# interactive hr pie chart
- totalscript,totaldiv = interactive_hr_piechart(df,r,'All Workouts')
+ if typeselect == 'All':
+ totalscript,totaldiv = interactive_hr_piechart(df,r,'All Workouts')
+ else:
+ a_workouts = g_workouts.filter(workouttype=typeselect)
+ ddf = getsmallrowdata_db(columns,ids=[w.id for w in a_workouts])
+ totalscript, totaldiv = interactive_hr_piechart(ddf,r,mytypes.workouttypes_ordered[typeselect])
# interactive power pie chart
@@ -4737,4 +4771,5 @@ def history_view(request,userid=0):
'typedicts':listofdicts,
'totalscript':totalscript,
'totaldiv':totaldiv,
+ 'form':form,
})
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index a999339f..87da8851 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -74,7 +74,7 @@ from rowers.forms import (
MetricsForm,DisqualificationForm,disqualificationreasons,
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
- VideoAnalysisMetricsForm,SurveyForm,
+ VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
)
from django.urls import reverse, reverse_lazy
From b694b08fcf912f5d860e61e38db667c1e96430a0 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 5 May 2020 18:08:57 +0200
Subject: [PATCH 3/3] better layout
---
rowers/interactiveplots.py | 5 +++--
rowers/templates/history.html | 7 ++++---
rowers/templates/menu_analytics.html | 5 +++++
3 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 829b7777..7fe3049e 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -223,7 +223,7 @@ def interactive_hr_piechart(df,rower,title):
colors = ['gray','yellow','lime','blue','purple','red']
- size=220
+ size=350
TOOLS = 'save'
z = figure(title="HR "+title, x_range=(-1,1), y_range=(-1,1), width=size, height=size,
@@ -236,7 +236,8 @@ def interactive_hr_piechart(df,rower,title):
z.toolbar_location = 'right'
- z.legend.visible = False
+ z.legend.location = 'top_right'
+ #z.legend.visible = False
z.axis.visible = False
z.xgrid.grid_line_color = None
z.ygrid.grid_line_color = None
diff --git a/rowers/templates/history.html b/rowers/templates/history.html
index b315eb9f..89132f1e 100644
--- a/rowers/templates/history.html
+++ b/rowers/templates/history.html
@@ -8,7 +8,8 @@
History
- -
+
-
+
-
- -
+
All workouts
@@ -51,6 +51,7 @@
-
{{ totalscript|safe }}{{ totaldiv|safe }}
+
{% for ddict in typedicts %}
-
{{ ddict|lookup:"wtype"}}
diff --git a/rowers/templates/menu_analytics.html b/rowers/templates/menu_analytics.html
index 9e9c32e3..df8a23c6 100644
--- a/rowers/templates/menu_analytics.html
+++ b/rowers/templates/menu_analytics.html
@@ -90,6 +90,11 @@
Alerts
+ -
+
+ History
+
+