Merge branch 'develop' into feature/restapi
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
|
||||
from rowingdata import rower as rrower
|
||||
from rowingdata import main as rmain
|
||||
@@ -124,7 +125,177 @@ def tailwind(bearing,vwind,winddir):
|
||||
from rowers.dataprep import nicepaceformat,niceformat
|
||||
from rowers.dataprep import timedeltaconv
|
||||
|
||||
def interactive_forcecurve(theworkouts):
|
||||
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'
|
||||
|
||||
ids = [int(w.id) for w in theworkouts]
|
||||
|
||||
boattype = theworkouts[0].boattype
|
||||
|
||||
columns = ['catch','slip','wash','finish','averageforce',
|
||||
'peakforceangle','peakforce','spm','distance']
|
||||
|
||||
rowdata = dataprep.getsmallrowdata_db(columns,ids=ids)
|
||||
|
||||
catchav = rowdata['catch'].mean()
|
||||
finishav = rowdata['finish'].mean()
|
||||
washav = rowdata['wash'].mean()
|
||||
slipav = rowdata['slip'].mean()
|
||||
peakforceav = rowdata['peakforce'].mean()
|
||||
averageforceav = rowdata['averageforce'].mean()
|
||||
peakforceangleav = rowdata['peakforceangle'].mean()
|
||||
|
||||
x = [catchav,
|
||||
catchav+slipav,
|
||||
peakforceangleav,
|
||||
finishav-washav,
|
||||
finishav]
|
||||
|
||||
thresholdforce = 100 if 'x' in boattype else 200
|
||||
thresholdforce /= 4.45 # N to lbs
|
||||
y = [0,thresholdforce,
|
||||
peakforceav,
|
||||
thresholdforce,0]
|
||||
|
||||
source = ColumnDataSource(
|
||||
data = dict(
|
||||
x = x,
|
||||
y = y,
|
||||
))
|
||||
|
||||
|
||||
source2 = ColumnDataSource(
|
||||
rowdata
|
||||
)
|
||||
|
||||
plot = Figure(tools=TOOLS,
|
||||
toolbar_sticky=False)
|
||||
|
||||
avf = Span(location=averageforceav,dimension='width',line_color='blue',
|
||||
line_dash=[6,6],line_width=2)
|
||||
|
||||
plot.line('x','y',source=source,color="red")
|
||||
|
||||
plot.add_layout(avf)
|
||||
|
||||
plot.xaxis.axis_label = "Angle"
|
||||
plot.yaxis.axis_label = "Force (lbs)"
|
||||
plot.title.text = theworkouts[0].name
|
||||
plot.title.text_font_size=value("1.0em")
|
||||
|
||||
yrange1 = Range1d(start=0,end=200)
|
||||
plot.y_range = yrange1
|
||||
|
||||
xrange1 = Range1d(start=yaxmaxima['catch'],end=yaxmaxima['finish'])
|
||||
plot.x_range = xrange1
|
||||
|
||||
callback = CustomJS(args = dict(
|
||||
source=source,
|
||||
source2=source2,
|
||||
avf=avf,
|
||||
), code="""
|
||||
var data = source.data
|
||||
var data2 = source2.data
|
||||
|
||||
var x = data['x']
|
||||
var y = data['y']
|
||||
var spm1 = data2['spm']
|
||||
var distance1 = data2['distance']
|
||||
|
||||
var thresholdforce = y[1]
|
||||
|
||||
var c = source2.data['catch']
|
||||
var finish = data2['finish']
|
||||
var slip = data2['slip']
|
||||
var wash = data2['wash']
|
||||
var peakforceangle = data2['peakforceangle']
|
||||
var peakforce = data2['peakforce']
|
||||
var averageforce = data2['averageforce']
|
||||
|
||||
var minspm = minspm.value
|
||||
var maxspm = maxspm.value
|
||||
var mindist = mindist.value
|
||||
var maxdist = maxdist.value
|
||||
|
||||
var catchav = 0
|
||||
var finishav = 0
|
||||
var slipav = 0
|
||||
var washav = 0
|
||||
var peakforceangleav = 0
|
||||
var averageforceav = 0
|
||||
var peakforceav = 0
|
||||
var count = 0
|
||||
|
||||
|
||||
for (i=0; i<c.length; i++) {
|
||||
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
|
||||
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
|
||||
catchav += c[i]
|
||||
finishav += finish[i]
|
||||
slipav += slip[i]
|
||||
washav += wash[i]
|
||||
peakforceangleav += peakforceangle[i]
|
||||
averageforceav += averageforce[i]
|
||||
peakforceav += peakforce[i]
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catchav /= count
|
||||
finishav /= count
|
||||
slipav /= count
|
||||
washav /= count
|
||||
peakforceangleav /= count
|
||||
peakforceav /= count
|
||||
averageforceav /= count
|
||||
|
||||
data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav]
|
||||
data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0]
|
||||
|
||||
avf.location = averageforceav
|
||||
|
||||
source.trigger('change');
|
||||
""")
|
||||
|
||||
slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1,
|
||||
title="Min SPM",callback=callback)
|
||||
callback.args["minspm"] = slider_spm_min
|
||||
|
||||
|
||||
slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1,
|
||||
title="Max SPM",callback=callback)
|
||||
callback.args["maxspm"] = slider_spm_max
|
||||
|
||||
distmax = 100+100*int(rowdata['distance'].max()/100.)
|
||||
|
||||
slider_dist_min = Slider(start=0,end=distmax,value=0,step=1,
|
||||
title="Min Distance",callback=callback)
|
||||
callback.args["mindist"] = slider_dist_min
|
||||
|
||||
slider_dist_max = Slider(start=0,end=distmax,value=distmax,
|
||||
step=1,
|
||||
title="Max Distance",callback=callback)
|
||||
callback.args["maxdist"] = slider_dist_max
|
||||
|
||||
layout = layoutrow([layoutcolumn([slider_spm_min,
|
||||
slider_spm_max,
|
||||
slider_dist_min,
|
||||
slider_dist_max,
|
||||
],
|
||||
),
|
||||
plot])
|
||||
|
||||
script, div = components(layout)
|
||||
js_resources = INLINE.render_js()
|
||||
css_resources = INLINE.render_css()
|
||||
|
||||
|
||||
|
||||
return [script,div,js_resources,css_resources]
|
||||
|
||||
|
||||
|
||||
def interactive_histoall(theworkouts):
|
||||
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'
|
||||
|
||||
|
||||
57
rowers/templates/forcecurve_single.html
Normal file
57
rowers/templates/forcecurve_single.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
{% load tz %}
|
||||
|
||||
{% block title %} Force Curve Plot {% endblock %}
|
||||
|
||||
{% localtime on %}
|
||||
{% block content %}
|
||||
|
||||
{{ js_res | safe }}
|
||||
{{ css_res| safe }}
|
||||
|
||||
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/bokeh-widgets-0.12.3.min.js"></script>
|
||||
<script async="true" type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
</script>
|
||||
|
||||
{{ the_script |safe }}
|
||||
|
||||
|
||||
<style>
|
||||
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
|
||||
html, body {height: 100%; margin:5px;}
|
||||
</style>
|
||||
|
||||
<div id="navigation" class="grid_12 alpha">
|
||||
{% if user.is_authenticated and mayedit %}
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ id }}/edit">Edit Workout</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_8 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ id }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
|
||||
|
||||
<div id="theplot" class="grid_12 alpha">
|
||||
|
||||
{{ the_div|safe }}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endlocaltime %}
|
||||
@@ -26,77 +26,79 @@
|
||||
<h1>My Workouts</h1>
|
||||
|
||||
{% if workouts %}
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Date</th>
|
||||
<th> Time</th>
|
||||
<th> Name</th>
|
||||
<th> Type</th>
|
||||
<th> Distance </th>
|
||||
<th> Duration </th>
|
||||
<th> Avg HR </th>
|
||||
<th> Max HR </th>
|
||||
<th> Delete</th>
|
||||
<th> Export</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</tbody>
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td> {{ workout.date }} </td>
|
||||
<td> {{ workout.starttime }} </td>
|
||||
<td>
|
||||
{% if user.rower.rowerplan == 'pro' %}
|
||||
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||
{% else %}
|
||||
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||
{% endif %}
|
||||
<td> {{ workout.workouttype }} </td>
|
||||
<td> {{ workout.distance }}m</td>
|
||||
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||
<td> {{ workout.averagehr }} </td>
|
||||
<td> {{ workout.maxhr }} </td>
|
||||
<td> <a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</td>
|
||||
<td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/export">Export</a> </td>
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Date</th>
|
||||
<th> Time</th>
|
||||
<th> Name</th>
|
||||
<th> Type</th>
|
||||
<th> Distance </th>
|
||||
<th> Duration </th>
|
||||
<th> Avg HR </th>
|
||||
<th> Max HR </th>
|
||||
<th> Delete</th>
|
||||
<th> Export</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</tbody>
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td> {{ workout.date }} </td>
|
||||
<td> {{ workout.starttime }} </td>
|
||||
<td>
|
||||
{% if user.rower.rowerplan == 'pro' %}
|
||||
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||
{% else %}
|
||||
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||
{% endif %}
|
||||
<td> {{ workout.workouttype }} </td>
|
||||
<td> {{ workout.distance }}m</td>
|
||||
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||
<td> {{ workout.averagehr }} </td>
|
||||
<td> {{ workout.maxhr }} </td>
|
||||
<td> <a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</td>
|
||||
<td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/export">Export</a> </td>
|
||||
<td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/flexchart">Flex</a> </td>
|
||||
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found </p>
|
||||
{% endif %}
|
||||
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
|
||||
method="get" accept-charset="utf-8">
|
||||
<div class="grid_3 prefix_1 alpha">
|
||||
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
|
||||
</div>
|
||||
<div class="grid_1 suffix_1 omega">
|
||||
<button class="button blue small" type="submit">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
|
||||
method="get" accept-charset="utf-8">
|
||||
<div class="grid_3 prefix_1 alpha">
|
||||
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
|
||||
</div>
|
||||
<div class="grid_1 suffix_1 omega">
|
||||
<button class="button blue small" type="submit">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="grid_2 prefix_3 omega">
|
||||
<span class="button gray small">
|
||||
{% if workouts.has_previous %}
|
||||
<a class="wh" href="?page={{ workouts.previous_page_number }}"><</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<span>
|
||||
Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}.
|
||||
</span>
|
||||
|
||||
|
||||
{% if workouts.has_next %}
|
||||
<a class="wh" href="?page={{ workouts.next_page_number }}">></a>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -82,6 +82,7 @@ urlpatterns = [
|
||||
url(r'^workout/upload/$',views.workout_upload_view),
|
||||
url(r'^workout/upload/(.+.*)$',views.workout_upload_view),
|
||||
url(r'^workout/(?P<id>\d+)/histo$',views.workout_histo_view),
|
||||
url(r'^workout/(?P<id>\d+)/forcecurve$',views.workout_forcecurve_view),
|
||||
url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.workout_export_view),
|
||||
url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)$',views.workout_export_view),
|
||||
url(r'^workout/(?P<id>\d+)/export/s/(?P<successmessage>\w+.*)$',views.workout_export_view),
|
||||
|
||||
@@ -1394,6 +1394,35 @@ def cum_flex(request,theuser=0,
|
||||
'promember':promember,
|
||||
})
|
||||
|
||||
@user_passes_test(promember,login_url="/",redirect_field_name=None)
|
||||
def workout_forcecurve_view(request,id=0):
|
||||
row = Workout.objects.get(id=id)
|
||||
promember=0
|
||||
mayedit=0
|
||||
if not request.user.is_anonymous():
|
||||
r = Rower.objects.get(user=request.user)
|
||||
result = request.user.is_authenticated() and r.rowerplan=='pro'
|
||||
if result:
|
||||
promember=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
|
||||
if not promember:
|
||||
return HttpResponseRedirect("/rowers/about/")
|
||||
|
||||
script,div,js_resources,css_resources = interactive_forcecurve([row])
|
||||
|
||||
return render(request,
|
||||
'forcecurve_single.html',
|
||||
{
|
||||
'the_script':script,
|
||||
'the_div':div,
|
||||
'js_res': js_resources,
|
||||
'css_res':css_resources,
|
||||
'id':id,
|
||||
'mayedit':mayedit,
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def workout_histo_view(request,id=0):
|
||||
row = Workout.objects.get(id=id)
|
||||
@@ -1410,9 +1439,6 @@ def workout_histo_view(request,id=0):
|
||||
if not promember:
|
||||
return HttpResponseRedirect("/rowers/about/")
|
||||
|
||||
|
||||
|
||||
|
||||
res = interactive_histoall([row])
|
||||
script = res[0]
|
||||
div = res[1]
|
||||
@@ -2612,8 +2638,8 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
result = request.user.is_authenticated() and r.rowerplan=='pro'
|
||||
if result:
|
||||
promember=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
|
||||
|
||||
workouttype = 'ote'
|
||||
@@ -2692,10 +2718,20 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
workstrokesonly = False
|
||||
|
||||
# create interactive plot
|
||||
script,div,js_resources,css_resources = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
|
||||
try:
|
||||
script,div,js_resources,css_resources = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
|
||||
yparam2=yparam2,
|
||||
promember=promember,plottype=plottype,
|
||||
workstrokesonly=workstrokesonly)
|
||||
except ValueError:
|
||||
script,div = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
|
||||
yparam2=yparam2,
|
||||
promember=promember,plottype=plottype,
|
||||
workstrokesonly=workstrokesonly)
|
||||
js_resources = ""
|
||||
css_resources = ""
|
||||
|
||||
|
||||
# script = res[0]
|
||||
# div = res[1]
|
||||
# js_resources = res[2]
|
||||
@@ -2847,8 +2883,8 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
|
||||
result = request.user.is_authenticated() and r.rowerplan=='pro'
|
||||
if result:
|
||||
promember=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
|
||||
|
||||
# create interactive plot
|
||||
@@ -2883,8 +2919,8 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
|
||||
result = request.user.is_authenticated() and r.rowerplan=='pro'
|
||||
if result:
|
||||
promember=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
if request.user == row.user.user:
|
||||
mayedit=1
|
||||
|
||||
# create interactive plot
|
||||
res = interactive_otw_advanced_pace_chart(id,promember=promember)
|
||||
@@ -3638,10 +3674,10 @@ def workout_upload_view(request,message=""):
|
||||
|
||||
f1 = res[0] # file name
|
||||
f2 = res[1] # file name incl media directory
|
||||
|
||||
|
||||
# new
|
||||
fileformat = get_file_type(f2)
|
||||
|
||||
|
||||
if fileformat == 'unknown':
|
||||
message = "We couldn't recognize the file type"
|
||||
url = reverse(workout_upload_view,
|
||||
@@ -3695,6 +3731,10 @@ def workout_upload_view(request,message=""):
|
||||
# handle speed coach GPS 2
|
||||
if (fileformat == 'speedcoach2'):
|
||||
row = SpeedCoach2Parser(f2)
|
||||
try:
|
||||
summary = row.allstats()
|
||||
except:
|
||||
pass
|
||||
|
||||
# handle ErgStick
|
||||
if (fileformat == 'ergstick'):
|
||||
@@ -3728,13 +3768,12 @@ def workout_upload_view(request,message=""):
|
||||
if row == 0:
|
||||
return HttpResponse("Error: CSV Data File Not Found")
|
||||
|
||||
# auto smoothing
|
||||
pace = row.df[' Stroke500mPace (sec/500m)'].values
|
||||
# auto smoothing
|
||||
pace = row.df[' Stroke500mPace (sec/500m)'].values
|
||||
velo = 500./pace
|
||||
|
||||
f = row.df['TimeStamp (sec)'].diff().mean()
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
|
||||
if not 'originalvelo' in row.df:
|
||||
row.df['originalvelo'] = velo
|
||||
|
||||
@@ -3746,9 +3785,9 @@ def workout_upload_view(request,message=""):
|
||||
velo3 = pd.Series(velo2)
|
||||
velo3 = velo3.replace([-np.inf,np.inf],np.nan)
|
||||
velo3 = velo3.fillna(method='ffill')
|
||||
|
||||
pace2 = 500./abs(velo3)
|
||||
|
||||
pace2 = 500./abs(velo3)
|
||||
|
||||
row.df[' Stroke500mPace (sec/500m)'] = pace2
|
||||
|
||||
row.df = row.df.fillna(0)
|
||||
@@ -3758,7 +3797,6 @@ def workout_upload_view(request,message=""):
|
||||
os.remove(f2)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# recalculate power data
|
||||
if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides':
|
||||
@@ -3769,19 +3807,18 @@ def workout_upload_view(request,message=""):
|
||||
except:
|
||||
pass
|
||||
|
||||
if fileformat != 'fit':
|
||||
if fileformat != 'fit' and summary == '':
|
||||
summary = row.summary()
|
||||
summary += '\n'
|
||||
summary += row.intervalstats_painsled()
|
||||
|
||||
averagehr = row.df[' HRCur (bpm)'].mean()
|
||||
maxhr = row.df[' HRCur (bpm)'].max()
|
||||
|
||||
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
@@ -3802,7 +3839,6 @@ def workout_upload_view(request,message=""):
|
||||
if (len(ws) != 0):
|
||||
message = "Warning: This workout probably already exists in the database"
|
||||
|
||||
|
||||
w = Workout(user=r,name=t,date=workoutdate,
|
||||
workouttype=workouttype,
|
||||
duration=duration,distance=totaldist,
|
||||
|
||||
Reference in New Issue
Block a user