From 832d6f88786471a0534290cf4e3269722cd36400 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 8 Jun 2017 14:19:21 +0200 Subject: [PATCH] OTW power CP graph working! --- rowers/interactiveplots.py | 14 +++- rowers/templates/analysis.html | 20 +++++ rowers/templates/otwrankings.html | 118 ++++++++++++++++-------------- rowers/views.py | 62 +++++++++------- 4 files changed, 130 insertions(+), 84 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 0fdb77c1..0e2e4d23 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -608,6 +608,10 @@ def interactive_otwcpchart(powerdf,promember=0): x_axis_type = 'log' y_axis_type = 'linear' + deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x)) + powerdf['ftime'] = niceformat(deltas) + + source = ColumnDataSource( data = powerdf ) @@ -668,7 +672,7 @@ def interactive_otwcpchart(powerdf,promember=0): ) plot.circle('Delta','CP',source=source,fill_color='red',size=15, - legend='Power') + legend='Power Data') plot.xaxis.axis_label = "Duration (seconds)" plot.yaxis.axis_label = "Power (W)" @@ -676,6 +680,14 @@ def interactive_otwcpchart(powerdf,promember=0): plot.x_range = Range1d(1,2*max(thesecs)) plot.legend.orientation = "vertical" + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Duration ','@ftime'), + ('Power (W)','@CP{int}'), + ]) + + hover.mode = 'mouse' plot.line('duration','power',source=sourcecomplex,legend="CP Model", color='green') diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index 2dc2cf43..9fd83289 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -77,8 +77,28 @@ +
+
+

 

+
+
+
+

+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} + OTW Ranking Pieces + {% else %} + OTW Ranking Pieces + {% endif %} +

+

+ Analyse power vs piece duration to make predictions. +

+
+ +
+ {% endblock %} diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html index 35b35865..a78330e4 100644 --- a/rowers/templates/otwrankings.html +++ b/rowers/templates/otwrankings.html @@ -67,9 +67,12 @@ https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}

-

The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.

- -

This page will evolve and try to give you guidance on where to improve.

+

The table gives the OTW efforts you marked as Ranking Piece. + The graph shows the best segments from those pieces, plotted as + average power (over the segment) vs the duration of the segment/ + In other words: How long you can hold that power. +

+

At the bottom of the page, you will find predictions derived from the model.

Use this form to select a different date range:

@@ -98,6 +101,16 @@
+ + +
+ +

Critical Power Plot

+ + {{ the_div|safe }} + +
+

Ranking Piece Results

@@ -109,6 +122,7 @@ Distance Duration + Avg Power Date Avg HR Max HR @@ -118,8 +132,9 @@ {% for workout in rankingworkouts %} - {{ workout.distance }} + {{ workout.distance }} m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} + {{ avgpower|lookup:workout.id }} W {{ workout.date }} {{ workout.averagehr }} {{ workout.maxhr }} @@ -137,63 +152,56 @@
- -
- -

Critical Power Plot

- - {{ the_div|safe }} - -
-
-

Pace predictions for Ranking Pieces

+

Pace predictions for Ranking Pieces

-

Add non-ranking piece using the form. The piece will be added in the prediction tables below.

-
-
- {{ form.value }} {{ form.pieceunit }} - - {% csrf_token %} -
-
- - -
+

Add non-ranking piece using the form. The piece will be added in the prediction tables below.

-
- No Paul Data -
-
-

CP Model

- - - - - - - - - {% for pred in cpredictions %} - - {% for key, value in pred.items %} - {% if key == "power" %} - - {% endif %} - {% if key == "duration" %} - - {% endif %} - {% endfor %} - - {% endfor %} - -
Duration Power
{{ value }} W {{ value |deltatimeprint }}
+
+ + + + + + + + + {% for pred in cpredictions %} + + {% for key, value in pred.items %} + {% if key == "power" %} + + {% endif %} + {% if key == "duration" %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
Duration Power
{{ value }} W {{ value |deltatimeprint }}
-
+
+ +
+
+ {{ form.value }} {{ form.pieceunit }} + + {% csrf_token %} +
+
+ minutes +
+
+ + +
+ +
{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index 85f321aa..0c5ae939 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -292,14 +292,11 @@ def iscoachmember(user): return result def getrower(user): - if not user.is_anonymous(): - try: - r = Rower.objects.get(user=user) - except Rower.DoesNotExist: - r = Rower(user=user) - r.save() - else: - raise PermissionDenied("You need to log in to use this function") + try: + r = Rower.objects.get(user=user) + except Rower.DoesNotExist: + r = Rower(user=user) + r.save() return r @@ -2900,8 +2897,10 @@ def otwrankings_view(request,theuser=0, thesecs.append(timesecs) - - maxt = pd.Series(thesecs).max() + if len(thesecs) != 0: + maxt = pd.Series(thesecs).max() + else: + maxt = 1000. maxlog10 = np.log10(maxt) logarr = np.arange(100)*maxlog10/100. @@ -2912,11 +2911,16 @@ def otwrankings_view(request,theuser=0, delta = [] cpvalue = [] + avgpower = {} dfgrouped = df.groupby(['workoutid']) for id,group in dfgrouped: tt = group['time'] ww = group['power'] + try: + avgpower[id] = int(ww.mean()) + except ValueError: + avgpower[id] = '---' if not np.isnan(ww.mean()): length = len(ww) dt = [] @@ -2946,6 +2950,7 @@ def otwrankings_view(request,theuser=0, for d in logarr: delta.append(d) + print avgpower dt = pd.Series(delta,name='Delta') cpvalue = pd.Series(cpvalue,name='CP') @@ -2955,13 +2960,15 @@ def otwrankings_view(request,theuser=0, 'CP':cpvalue, }) - powerdf.sort_values(['Delta','CP'],ascending=[1,0]) - powerdf.drop_duplicates(subset='Delta',keep='first') + powerdf = powerdf[powerdf['CP']>0] + powerdf.dropna(axis=0,inplace=True) + powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True) + powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True) # create interactive plot if len(powerdf) !=0 : - res = interactive_otwcpchart(powerdf) + res = interactive_otwcpchart(powerdf,promember=promember) script = res[0] div = res[1] p1 = res[2] @@ -2979,16 +2986,12 @@ def otwrankings_view(request,theuser=0, if request.method == 'POST' and "piece" in request.POST: form = PredictedPieceForm(request.POST) - if form.is_valid(): - value = form.cleaned_data['value'] - hourvalue,value = divmod(value,60) - if hourvalue >= 24: - hourvalue = 23 - pieceunit = form.cleaned_data['pieceunit'] - if pieceunit == 'd': - rankingdistances.append(value) - else: - rankingdurations.append(datetime.time(minute=value,hour=hourvalue)) + clean = form.is_valid() + value = form.cleaned_data['value'] + hourvalue,value = divmod(value,60) + if hourvalue >= 24: + hourvalue = 23 + rankingdurations.append(datetime.time(minute=value,hour=hourvalue)) else: form = PredictedPieceForm() @@ -3012,13 +3015,15 @@ def otwrankings_view(request,theuser=0, if pwr <= 0: pwr = 50. - a = { - 'duration':timedeltaconv(t), - 'power':int(pwr)} - cpredictions.append(a) + if not np.isnan(pwr): + a = { + 'duration':timedeltaconv(t), + 'power':int(pwr)} + cpredictions.append(a) - print cpredictions + del form.fields["pieceunit"] + messages.error(request,message) return render(request, 'otwrankings.html', {'rankingworkouts':theworkouts, @@ -3026,6 +3031,7 @@ def otwrankings_view(request,theuser=0, 'the_div':div, 'predictions':predictions, 'cpredictions':cpredictions, + 'avgpower':avgpower, 'form':form, 'dateform':dateform, 'deltaform':deltaform,