otw-bests somehow working
This commit is contained in:
@@ -596,6 +596,94 @@ def googlemap_chart(lat,lon,name=""):
|
|||||||
return [script,div]
|
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,
|
def interactive_cpchart(thedistances,thesecs,theavpower,
|
||||||
theworkouts,promember=0):
|
theworkouts,promember=0):
|
||||||
|
|
||||||
|
|||||||
199
rowers/templates/otwrankings.html
Normal file
199
rowers/templates/otwrankings.html
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workouts{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
|
||||||
|
<script async="true" type="text/javascript">
|
||||||
|
Bokeh.set_log_level("info");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ interactiveplot |safe }}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Set things up to resize the plot on a window resize. You can play with
|
||||||
|
// the arguments of resize_width_height() to change the plot's behavior.
|
||||||
|
var plot_resize_setup = function () {
|
||||||
|
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
|
||||||
|
var plot = Bokeh.index[plotid];
|
||||||
|
var plotresizer = function() {
|
||||||
|
// arguments: use width, use height, maintain aspect ratio
|
||||||
|
plot.resize_width_height(true, true, false);
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', plotresizer);
|
||||||
|
plotresizer();
|
||||||
|
};
|
||||||
|
window.addEventListener('load', plot_resize_setup);
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
|
||||||
|
html, body {height: 100%; margin:5px;}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="title" class="grid_12 alpha">
|
||||||
|
<div class="grid_10 alpha">
|
||||||
|
{% if theuser %}
|
||||||
|
<h3>{{ theuser.first_name }}'s Ranking Pieces</h3>
|
||||||
|
{% else %}
|
||||||
|
<h3>{{ user.first_name }}'s Ranking Pieces</h3>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
{% if user.is_authenticated and user|is_manager %}
|
||||||
|
<div class="grid_2 alpha dropdown">
|
||||||
|
<button class="grid_2 alpha button green small dropbtn">
|
||||||
|
Change Rower
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
{% for member in user|team_members %}
|
||||||
|
<a class="button green small" href="/rowers/{{ member.id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">{{ member.first_name }} {{ member.last_name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="summary" class="grid_6 alpha">
|
||||||
|
<p>Summary for {{ theuser.first_name }} {{ theuser.last_name }}
|
||||||
|
between {{ startdate|date }} and {{ enddate|date }}</p>
|
||||||
|
|
||||||
|
<p>Direct link for other users:
|
||||||
|
<a href="/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.</p>
|
||||||
|
|
||||||
|
<p>This page will evolve and try to give you guidance on where to improve.</p>
|
||||||
|
</div>
|
||||||
|
<div id="form" class="grid_6 omega">
|
||||||
|
<p>Use this form to select a different date range:</p>
|
||||||
|
<p>
|
||||||
|
Select start and end date for a date range:
|
||||||
|
<div class="grid_4 alpha">
|
||||||
|
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ dateform.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
|
||||||
|
</div>
|
||||||
|
<div class="grid_4 alpha">
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
Or use the last {{ deltaform }} days.
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input name='datedelta' class="button green" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_12 alpha">
|
||||||
|
|
||||||
|
<h2>Ranking Piece Results</h2>
|
||||||
|
|
||||||
|
{% if rankingworkouts %}
|
||||||
|
|
||||||
|
<table width="70%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Distance</th>
|
||||||
|
<th> Duration</th>
|
||||||
|
<th> Date</th>
|
||||||
|
<th> Avg HR </th>
|
||||||
|
<th> Max HR </th>
|
||||||
|
<th> Edit</th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for workout in rankingworkouts %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ workout.distance }} </td>
|
||||||
|
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||||
|
<td> {{ workout.date }} </td>
|
||||||
|
<td> {{ workout.averagehr }} </td>
|
||||||
|
<td> {{ workout.maxhr }} </td>
|
||||||
|
<td>
|
||||||
|
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> No ranking workouts found </p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="theplot" class="grid_12 alpha">
|
||||||
|
|
||||||
|
<h2>Critical Power Plot</h2>
|
||||||
|
|
||||||
|
{{ the_div|safe }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="predictions" class="grid_12 alpha">
|
||||||
|
<h2>Pace predictions for Ranking Pieces</h2>
|
||||||
|
|
||||||
|
<p>Add non-ranking piece using the form. The piece will be added in the prediction tables below. </p>
|
||||||
|
<div class="grid_4 alpha">
|
||||||
|
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||||
|
{{ form.value }} {{ form.pieceunit }}
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 suffix_6 omega">
|
||||||
|
<input name="piece" class="button green"
|
||||||
|
formaction="/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
|
||||||
|
type="submit" value="Add">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="paul" class="grid_6 alpha">
|
||||||
|
No Paul Data
|
||||||
|
</div>
|
||||||
|
<div id="cpmodel" class="grid_6 omega">
|
||||||
|
<h3>CP Model</h3>
|
||||||
|
<table width="70%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Duration</th>
|
||||||
|
<th> Power </th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pred in cpredictions %}
|
||||||
|
<tr>
|
||||||
|
{% for key, value in pred.items %}
|
||||||
|
{% if key == "power" %}
|
||||||
|
<td> {{ value }} W </td>
|
||||||
|
{% endif %}
|
||||||
|
{% if key == "duration" %}
|
||||||
|
<td> {{ value |deltatimeprint }} </td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -2938,15 +2938,14 @@ def otwrankings_view(request,theuser=0,
|
|||||||
|
|
||||||
|
|
||||||
# create interactive plot
|
# create interactive plot
|
||||||
if len(thedistances) !=0 :
|
if len(powerdf) !=0 :
|
||||||
res = interactive_cpchart(thedistances,thesecs,theavpower,
|
res = interactive_otwcpchart(powerdf)
|
||||||
theworkouts,promember=promember)
|
|
||||||
script = res[0]
|
script = res[0]
|
||||||
div = res[1]
|
div = res[1]
|
||||||
paulslope = res[2]
|
p1 = res[2]
|
||||||
paulintercept = res[3]
|
paulslope = 1
|
||||||
p1 = res[4]
|
paulintercept = 1
|
||||||
message = res[5]
|
message = res[3]
|
||||||
else:
|
else:
|
||||||
script = ''
|
script = ''
|
||||||
div = '<p>No ranking pieces found.</p>'
|
div = '<p>No ranking pieces found.</p>'
|
||||||
@@ -2983,23 +2982,6 @@ def otwrankings_view(request,theuser=0,
|
|||||||
t += rankingduration.second
|
t += rankingduration.second
|
||||||
t += rankingduration.microsecond/1.e6
|
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
|
# CP model
|
||||||
pwr = p1[0]/(1+t/p1[2])
|
pwr = p1[0]/(1+t/p1[2])
|
||||||
@@ -3008,28 +2990,20 @@ def otwrankings_view(request,theuser=0,
|
|||||||
if pwr <= 0:
|
if pwr <= 0:
|
||||||
pwr = 50.
|
pwr = 50.
|
||||||
|
|
||||||
velo = (pwr/2.8)**(1./3.)
|
a = {
|
||||||
|
'duration':timedeltaconv(t),
|
||||||
if np.isnan(velo) or velo <=0:
|
'power':int(pwr)}
|
||||||
velo = 1.0
|
|
||||||
|
|
||||||
d = t*velo
|
|
||||||
p = 500./velo
|
|
||||||
a = {'distance':int(d),
|
|
||||||
'duration':timedeltaconv(t),
|
|
||||||
'pace':timedeltaconv(p),
|
|
||||||
'power':int(pwr)}
|
|
||||||
cpredictions.append(a)
|
cpredictions.append(a)
|
||||||
|
|
||||||
|
print cpredictions
|
||||||
|
|
||||||
messages.error(request,message)
|
messages.error(request,message)
|
||||||
return render(request, 'rankings.html',
|
return render(request, 'otwrankings.html',
|
||||||
{'rankingworkouts':theworkouts,
|
{'rankingworkouts':theworkouts,
|
||||||
'interactiveplot':script,
|
'interactiveplot':script,
|
||||||
'the_div':div,
|
'the_div':div,
|
||||||
'predictions':predictions,
|
'predictions':predictions,
|
||||||
'cpredictions':cpredictions,
|
'cpredictions':cpredictions,
|
||||||
'nrdata':len(thedistances),
|
|
||||||
'form':form,
|
'form':form,
|
||||||
'dateform':dateform,
|
'dateform':dateform,
|
||||||
'deltaform':deltaform,
|
'deltaform':deltaform,
|
||||||
|
|||||||
Reference in New Issue
Block a user