From e5cb8b856dc3be56482063d7ce47a53b1f75e86c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 9 Dec 2016 19:49:18 +0100 Subject: [PATCH 1/4] Flex buttons on workouts list --- rowers/interactiveplots.py | 1 + rowers/templates/list_workouts.html | 114 ++++++++++++++-------------- rowers/views.py | 30 +++++--- 3 files changed, 77 insertions(+), 68 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e50b9e93..e1ee9af7 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -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 diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index d3cf848e..900564b4 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -26,77 +26,79 @@

My Workouts

{% if workouts %} - - - - - - - - - - - - - - - - - {% for workout in workouts %} - - - - - {% else %} -{{ workout.name }} - {% endif %} - - - - - - - +
Date Time Name Type Distance Duration Avg HR Max HR Delete Export
{{ workout.date }} {{ workout.starttime }} - {% if user.rower.rowerplan == 'pro' %} -{{ workout.name }} {{ workout.workouttype }} {{ workout.distance }}m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ workout.averagehr }} {{ workout.maxhr }} Delete Export
+ + + + + + + + + + + + + + + +{% for workout in workouts %} + + + + + {% else %} + {{ workout.name }} +{% endif %} + + + + + + + + - - - {% endfor %} - -
Date Time Name Type Distance Duration Avg HR Max HR Delete Export
{{ workout.date }} {{ workout.starttime }} + {% if user.rower.rowerplan == 'pro' %} + {{ workout.name }} {{ workout.workouttype }} {{ workout.distance }}m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ workout.averagehr }} {{ workout.maxhr }} Delete Export Flex
- {% else %} -

No workouts found

- {% endif %} + + + +{% endfor %} + + +{% else %} +

No workouts found

+{% endif %}
-
-
- -
-
- -
-
+
+
+ +
+
+ +
+
{% if workouts.has_previous %} < {% endif %} - + Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}. - + {% if workouts.has_next %} > {% endif %} - - + +
{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index 2c8af572..72da81de 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2688,10 +2688,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] @@ -3634,10 +3644,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, @@ -3724,13 +3734,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 @@ -3742,9 +3751,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) @@ -3754,7 +3763,6 @@ def workout_upload_view(request,message=""): os.remove(f2) except: pass - # recalculate power data if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides': @@ -3772,12 +3780,11 @@ def workout_upload_view(request,message=""): 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) @@ -3798,7 +3805,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, From 7d0d7a0fd5223b6c2762c04867458fe71d915a23 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 11 Dec 2016 20:33:41 +0100 Subject: [PATCH 2/4] added stats for SpeedCoach 2 GPS --- rowers/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 72da81de..3b483290 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -3701,6 +3701,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'): @@ -3773,7 +3777,7 @@ 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() From 0e6fac88b5e83ed615bbc85ec04a29b5c8f84c21 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 13 Dec 2016 10:44:02 +0100 Subject: [PATCH 3/4] NK Tools Force Curve --- rowers/interactiveplots.py | 170 ++++++++++++++++++++++++ rowers/templates/forcecurve_single.html | 57 ++++++++ rowers/urls.py | 1 + rowers/views.py | 32 ++++- 4 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 rowers/templates/forcecurve_single.html diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e1ee9af7..04c2ee23 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -125,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=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' diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html new file mode 100644 index 00000000..2f7e83b1 --- /dev/null +++ b/rowers/templates/forcecurve_single.html @@ -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 }} + + + + + + {{ the_script |safe }} + + + + + + + +

 

+ + + +
+ + {{ the_div|safe }} + +
+ + +{% endblock %} +{% endlocaltime %} diff --git a/rowers/urls.py b/rowers/urls.py index 7c3d8e06..9e2c2e8d 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -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\d+)/histo$',views.workout_histo_view), + url(r'^workout/(?P\d+)/forcecurve$',views.workout_forcecurve_view), url(r'^workout/(?P\d+)/export/c/(?P\w+.*)/s/(?P\w+.*)$',views.workout_export_view), url(r'^workout/(?P\d+)/export/c/(?P\w+.*)$',views.workout_export_view), url(r'^workout/(?P\d+)/export/s/(?P\w+.*)$',views.workout_export_view), diff --git a/rowers/views.py b/rowers/views.py index 3b483290..beda5436 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1390,6 +1390,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) @@ -1406,9 +1435,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] From b59aeeee14338adabd85d8cbe35036fedcaa85ec Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 13 Dec 2016 15:52:21 +0100 Subject: [PATCH 4/4] bug fix edit and adv edit menus --- rowers/views.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rowers/views.py b/rowers/views.py index 72da81de..bb77daa2 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1400,8 +1400,8 @@ def workout_histo_view(request,id=0): 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 if not promember: return HttpResponseRedirect("/rowers/about/") @@ -2608,8 +2608,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' @@ -2853,8 +2853,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 @@ -2889,8 +2889,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)