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 ad8e6ba7..7fe3049e 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -169,6 +169,83 @@ 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): + 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) + 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

    + +
  • +

    +

    + {% csrf_token %} + + {{ form.as_table }} +
    + +
    +

    +

    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 %} + {% block sidebar %} {% include 'menu_analytics.html' %} {% endblock %} 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 + +
  • diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 2a70d01d..aa767ace 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4655,6 +4655,102 @@ class AlertDelete(DeleteView): 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, + 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])) + + typechoices = [("All","All")] + for wtype in wtypes: + typechoices.append((wtype,mytypes.workouttypes_ordered[wtype])) + + form.fields['workouttype'].choices = typechoices + + 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 + 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 + + 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 = [ { 'url':'rowers/analysis', @@ -4670,5 +4766,10 @@ def history_view(request,userid=0): { 'rower':r, 'breadcrumbs':breadcrumbs, - 'active':'nav-analysis' + 'active':'nav-analysis', + 'totalsdict':totalsdict, + '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 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")