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 {% 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

    -
  • +
  • +
    + {% csrf_token %} + + {{ form.as_table }} +
    + +
    +
  • +
  • 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

    -
  • +
  • +

    {% csrf_token %} @@ -16,8 +17,7 @@
    -
  • -
  • +

    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 + +