diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 7f640cee..0fdb77c1 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -596,6 +596,94 @@ def googlemap_chart(lat,lon,name=""): return [script,div] +def interactive_otwcpchart(powerdf,promember=0): + powerdf = powerdf[~(powerdf == 0).any(axis=1)] + # plot tools + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + x_axis_type = 'log' + y_axis_type = 'linear' + + source = ColumnDataSource( + data = powerdf + ) + + # there is no Paul's law for OTW + + # Fit the data to thee parameter CP model + fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + p0 = [500,350,10,8000] + + p1 = p0 + + thesecs = powerdf['Delta'] + theavpower = powerdf['CP'] + + if len(thesecs)>=4: + p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower)) + else: + factor = fitfunc(p0,thesecs.mean())/theavpower.mean() + p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] + + + fitt = pd.Series(10**(4*np.arange(100)/100.)) + + fitpower = fitfunc(p1,fitt) + + message = "" + #if len(fitpower[fitpower<0]) > 0: + # message = "CP model fit didn't give correct results" + + + sourcecomplex = ColumnDataSource( + data = dict( + power = fitpower, + duration = fitt + ) + ) + + # making the plot + plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],1.8*max(thesecs),watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + y_range_name = "watermark", + ) + + plot.circle('Delta','CP',source=source,fill_color='red',size=15, + legend='Power') + plot.xaxis.axis_label = "Duration (seconds)" + plot.yaxis.axis_label = "Power (W)" + + plot.y_range = Range1d(0,1.5*max(theavpower)) + plot.x_range = Range1d(1,2*max(thesecs)) + plot.legend.orientation = "vertical" + + + plot.line('duration','power',source=sourcecomplex,legend="CP Model", + color='green') + + script, div = components(plot) + + return [script,div,p1,message] + def interactive_cpchart(thedistances,thesecs,theavpower, theworkouts,promember=0): diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html new file mode 100644 index 00000000..35b35865 --- /dev/null +++ b/rowers/templates/otwrankings.html @@ -0,0 +1,199 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Workouts{% endblock %} + +{% block content %} + + + + + {{ interactiveplot |safe }} + + + + + +
Summary for {{ theuser.first_name }} {{ theuser.last_name }} + between {{ startdate|date }} and {{ enddate|date }}
+ +Direct link for other users: + 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.
+Use this form to select a different date range:
++ Select start and end date for a date range: +
| Distance | +Duration | +Date | +Avg HR | +Max HR | +Edit | +
|---|---|---|---|---|---|
| {{ workout.distance }} | +{{ workout.duration |durationprint:"%H:%M:%S.%f" }} | +{{ workout.date }} | +{{ workout.averagehr }} | +{{ workout.maxhr }} | ++ {{ workout.name }} | + +
No ranking workouts found
+ {% endif %} + +Add non-ranking piece using the form. The piece will be added in the prediction tables below.
+| Duration | +Power | +
|---|---|
| {{ value }} W | + {% endif %} + {% if key == "duration" %} +{{ value |deltatimeprint }} | + {% endif %} + {% endfor %} +
No ranking pieces found.
' @@ -2983,23 +2982,6 @@ def otwrankings_view(request,theuser=0, t += rankingduration.second t += rankingduration.microsecond/1.e6 - # Paul's model - ratio = paulintercept/paulslope - - u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope - - d = 500*t*np.log(10.) - d = d/(paulslope*lambertw(u)) - d = d.real - - velo = d/t - p = 500./velo - pwr = 2.8*(velo**3) - a = {'distance':int(d), - 'duration':timedeltaconv(t), - 'pace':timedeltaconv(p), - 'power':int(pwr)} - predictions.append(a) # CP model pwr = p1[0]/(1+t/p1[2]) @@ -3008,28 +2990,20 @@ def otwrankings_view(request,theuser=0, if pwr <= 0: pwr = 50. - velo = (pwr/2.8)**(1./3.) - - if np.isnan(velo) or velo <=0: - velo = 1.0 - - d = t*velo - p = 500./velo - a = {'distance':int(d), - 'duration':timedeltaconv(t), - 'pace':timedeltaconv(p), - 'power':int(pwr)} + a = { + 'duration':timedeltaconv(t), + 'power':int(pwr)} cpredictions.append(a) + print cpredictions messages.error(request,message) - return render(request, 'rankings.html', + return render(request, 'otwrankings.html', {'rankingworkouts':theworkouts, 'interactiveplot':script, 'the_div':div, 'predictions':predictions, 'cpredictions':cpredictions, - 'nrdata':len(thedistances), 'form':form, 'dateform':dateform, 'deltaform':deltaform,