diff --git a/rowers/forms.py b/rowers/forms.py index 7429ca3c..f22cda74 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1218,6 +1218,10 @@ class AnalysisChoiceForm(forms.Form): cpfit = forms.ChoiceField(choices=cpfitchoices, label = 'Model Fit',initial='data',required=False) + cpoverlay = forms.BooleanField(initial=False, + label='Overlay World Record Performance', + required=False) + piece = forms.IntegerField(initial=4,label='Ranking Piece (minutes)', required=False) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8e5287cf..5004b7ad 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3304,7 +3304,8 @@ def interactive_agegroupcpchart(age,normalized=False): def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', - title='',type='water'): + title='',type='water', + wcpower=[],wcdurations=[],cpoverlay=False): powerdf = powerdf[~(powerdf == 0).any(axis=1)] # plot tools @@ -3356,6 +3357,34 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', workouts = powerdf['workout'] urls = powerdf['url'] + # add world class + wcpower = pd.Series(wcpower) + wcdurations = pd.Series(wcdurations) + + + # fitting WC data to three parameter CP model + if len(wcdurations)>=4: + 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 + p1wc, success = optimize.leastsq(errfunc, p0[:], + args = (wcdurations,wcpower)) + else: + p1wc = None + + if p1wc is not None and cpoverlay: + fitpowerwc = fitfunc(p1wc,fitt) + fitpowerexcellent = 0.7*fitfunc(p1wc,fitt) + fitpowergood = 0.6*fitfunc(p1wc,fitt) + fitpowerfair = 0.5*fitfunc(p1wc,fitt) + fitpoweraverage = 0.4*fitfunc(p1wc,fitt) + + else: + fitpowerwc = 0*fitpower + fitpowerexcellent = 0*fitpower + fitpowergood = 0*fitpower + fitpowerfair = 0*fitpower + fitpoweraverage = 0*fitpower + sourcecomplex = ColumnDataSource( data = dict( @@ -3364,6 +3393,11 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', duration = fitt/60., ftime = ftime, workout = workouts, + fitpowerwc = fitpowerwc, + fitpowerexcellent = fitpowerexcellent, + fitpowergood = fitpowergood, + fitpowerfair = fitpowerfair, + fitpoweraverage = fitpoweraverage, url = urls, ) ) @@ -3424,6 +3458,7 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', ('Power (W)','@CP{int}'), ('Power (W) upper','@CPmax{int}'), ('Workout','@workout'), + ('World Class','@fitpowerwc{int}') ]) hover.mode = 'mouse' @@ -3437,6 +3472,27 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', plot.line('duration','CPmax',source=sourcecomplex,legend_label="CP Model", color='red') + if p1wc is not None: + plot.line('duration','fitpowerwc',source=sourcecomplex, + legend_label="World Class", + color='darkgoldenrod',line_dash='dotted') + + plot.line('duration','fitpowerexcellent',source=sourcecomplex, + legend_label="90% percentile", + color='goldenrod',line_dash='dotted') + + plot.line('duration','fitpowergood',source=sourcecomplex, + legend_label="75% percentile", + color='sandybrown',line_dash='dotted') + + plot.line('duration','fitpowerfair',source=sourcecomplex, + legend_label="50% percentile", + color='rosybrown',line_dash='dotted') + + plot.line('duration','fitpoweraverage',source=sourcecomplex, + legend_label="25% percentile", + color='tan',line_dash='dotted') + script, div = components(plot) return [script,div,p1,ratio,message] diff --git a/rowers/templates/user_analysis_select.html b/rowers/templates/user_analysis_select.html index 5d05f318..f098f78d 100644 --- a/rowers/templates/user_analysis_select.html +++ b/rowers/templates/user_analysis_select.html @@ -91,6 +91,7 @@ var reststrokes = $("#id_includereststrokes").parent().parent(); var piece = $("#id_piece").parent().parent(); var cpfit = $("#id_cpfit").parent().parent(); + var cpoverlay = $("#id_cpoverlay").parent().parent(); // Hide the fields. @@ -113,6 +114,7 @@ spmmin.hide(); spmmax.hide(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); if (functionfield.val() == 'boxplot') { @@ -153,6 +155,7 @@ if (functionfield.val() == 'cp') { cpfit.show(); piece.show(); + cpoverlay.show(); } @@ -182,6 +185,7 @@ plottype.hide(); reststrokes.show(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); } else if (Value=='histo') { @@ -202,6 +206,7 @@ plottype.hide(); reststrokes.show(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); } @@ -223,6 +228,7 @@ plottype.hide(); reststrokes.show(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); } @@ -244,6 +250,7 @@ errorbars.hide(); reststrokes.show(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); } else if (Value=='stats') { @@ -260,6 +267,7 @@ plottype.hide(); reststrokes.show(); cpfit.hide(); + cpoverlay.hide(); piece.hide(); } else if (Value=='compare') { @@ -280,6 +288,7 @@ errorbars.hide(); piece.hide(); cpfit.hide(); + cpoverlay.hide(); reststrokes.show(); @@ -302,6 +311,7 @@ plottype.hide(); reststrokes.hide(); cpfit.show(); + cpoverlay.hide(); piece.show(); } }); @@ -372,6 +382,126 @@ + {% if worldclass %} + {% if age and sex != 'not specified' %} +
The dashed lines are based on the + Concept2 + rankings for your age ({{ age }}), gender ({{ sex }}) + and weight category ({{ weightcategory }}). World class means within 5% of + + World Record in terms + of power. + The percentile lines are estimates of where the percentiles + of the Concept2 rankings historically are for those of exactly + your age, gender and weight class. +
++ For rowing on the water, the results are corrected for the expected + difference in power between the Concept2 indoor rower and power + values on the water. +
+| + 100m + + | +
| + 500m + + | +
| + 1000m + + | +
| + 2000m + + | +
| + 5000m + + | +
| + 6000m + + | +
| + 10000m + + | +
| + Half Marathon + + | +
| + Full Marathon + + | +
| + 1 minute + + | +
| + 4 minutes + + | +
| + 30 minutes + + | +
| + 1 hour + + | +