Private
Public Access
1
0

otw interactive power plot

This commit is contained in:
Sander Roosendaal
2018-10-08 21:18:42 +02:00
parent 3222ced28d
commit b82abace65
4 changed files with 139 additions and 78 deletions

View File

@@ -1,3 +1,4 @@
{% load rowerfilters %}
<h1>Workout</h1> <h1>Workout</h1>
<ul class="cd-accordion-menu animated"> <ul class="cd-accordion-menu animated">
<li class="has-children" id="workout"> <li class="has-children" id="workout">
@@ -5,10 +6,17 @@
<label for="group-workout">Workout</label> <label for="group-workout">Workout</label>
<ul> <ul>
<li id="workout-dashboard"> <li id="workout-dashboard">
{% if user.is_authenticated and workout|may_edit:request %}
<a href="/rowers/workout/{{ workout.id }}/workflow"> <a href="/rowers/workout/{{ workout.id }}/workflow">
<i class="fas fa-tachometer-alt fa-fw"></i>&nbsp;View <i class="fas fa-tachometer-alt fa-fw"></i>&nbsp;View
</a> </a>
{% else %}
<a href="/rowers/workout/{{ workout.id }}/workflow">
<i class="fas fa-tachometer-alt fa-fw"></i>&nbsp;View
</a>
{% endif %}
</li> </li>
{% if user.is_authenticated and workout|may_edit:request %}
<li id="workout-edit"> <li id="workout-edit">
<a href="/rowers/workout/{{ workout.id }}/edit"> <a href="/rowers/workout/{{ workout.id }}/edit">
<i class="fas fa-pencil-alt fa-fw"></i>&nbsp;Edit <i class="fas fa-pencil-alt fa-fw"></i>&nbsp;Edit
@@ -19,6 +27,7 @@
<i class="fas fa-pause fa-fw"></i>&nbsp;Intervals <i class="fas fa-pause fa-fw"></i>&nbsp;Intervals
</a> </a>
</li> </li>
{% endif %}
<li id="workout-stats"> <li id="workout-stats">
<a href="/rowers/workout/{{ workout.id }}/stats"> <a href="/rowers/workout/{{ workout.id }}/stats">
<i class="fal fa-table fa-fw"></i>&nbsp;Statistics <i class="fal fa-table fa-fw"></i>&nbsp;Statistics
@@ -29,11 +38,13 @@
<i class="fas fa-balance-scale fa-fw"></i>&nbsp;Compare <i class="fas fa-balance-scale fa-fw"></i>&nbsp;Compare
</a> </a>
</li> </li>
{% if user.is_authenticated and workout|may_edit:request %}
<li id="workout-delete"> <li id="workout-delete">
<a href="/rowers/workout/{{ workout.id }}/delete"> <a href="/rowers/workout/{{ workout.id }}/delete">
<i class="fas fa-trash-alt fa-fw"></i>&nbsp;Delete <i class="fas fa-trash-alt fa-fw"></i>&nbsp;Delete
</a> </a>
</li> </li>
{% endif %}
</ul> </ul>
</li> </li>
<li class="has-children" id="flexchart"> <li class="has-children" id="flexchart">
@@ -45,13 +56,21 @@
<i class="fas fa-chart-line fa-fw"></i>&nbsp;Flex Chart <i class="fas fa-chart-line fa-fw"></i>&nbsp;Flex Chart
</a> </a>
</li> </li>
{% if workout|water %}
<li id="chart-map"> <li id="chart-map">
<a href="/rowers/workout/{{ workout.id }}/map"> <a href="/rowers/workout/{{ workout.id }}/map">
<i class="fas fa-map-marked-alt fa-fw"></i>&nbsp;Map <i class="fas fa-map-marked-alt fa-fw"></i>&nbsp;Map
</a> </a>
</li> </li>
<li id="chart-otwpower">
<a href="/rowers/workout/{{ workout.id }}/interactiveotwplot">
<i class="fal fa-calculator-alt fa-fw"></i>&nbsp;OTW Power
</a>
</li>
{% endif %}
</ul> </ul>
</li> </li>
{% if user.is_authenticated and workout|may_edit:request %}
<li class="has-children" id="chart"> <li class="has-children" id="chart">
<input type="checkbox" name="group-chart" id="group-chart"> <input type="checkbox" name="group-chart" id="group-chart">
<label for="group-chart">Static Charts</label> <label for="group-chart">Static Charts</label>
@@ -76,11 +95,13 @@
<i class="fas fa-heartbeat fa-fw"></i>&nbsp;Heart Rate (Pie) <i class="fas fa-heartbeat fa-fw"></i>&nbsp;Heart Rate (Pie)
</a> </a>
</li> </li>
{% if workout|water %}
<li id="chart-otwpower"> <li id="chart-otwpower">
<a href="/rowers/workout/{{ workout.id }}/addotwpowerplot"> <a href="/rowers/workout/{{ workout.id }}/addotwpowerplot">
<i class="fas fa-chart-area fa-fw"></i>&nbsp;OTW Power <i class="fas fa-chart-area fa-fw"></i>&nbsp;OTW Power
</a> </a>
</li> </li>
{% endif %}
</ul> </ul>
</li> </li>
<li class="has-children" id="export"> <li class="has-children" id="export">
@@ -128,6 +149,7 @@
<input type="checkbox" name="group-advanced" id="group-advanced"> <input type="checkbox" name="group-advanced" id="group-advanced">
<label for="group-advanced">Advanced</label> <label for="group-advanced">Advanced</label>
<ul> <ul>
{% if workout|water %}
<li id="advanced-wind"> <li id="advanced-wind">
<a href="/rowers/workout/{{ workout.id }}/wind"> <a href="/rowers/workout/{{ workout.id }}/wind">
<i class="fas fa-pennant fa-fw"></i>&nbsp;Wind <i class="fas fa-pennant fa-fw"></i>&nbsp;Wind
@@ -143,9 +165,11 @@
<i class="fas fa-calculator-alt fa-fw"></i>&nbsp;OTW Power <i class="fas fa-calculator-alt fa-fw"></i>&nbsp;OTW Power
</a> </a>
</li> </li>
{% endif %}
<li id="advanced-instroke"> <li id="advanced-instroke">
<a href="/rowers/workout/{{ workout.id }}/instroke"> <a href="/rowers/workout/{{ workout.id }}/instroke">
<i class="fas fa-search-plus fa-fw"></i>&nbsp;In-Stroke Metrics</a></li> <i class="fas fa-search-plus fa-fw"></i>&nbsp;In-Stroke Metrics</a></li>
</ul> </ul>
</li> </li>
{% endif %}
</ul><!-- cd-accordion-menu --> </ul><!-- cd-accordion-menu -->

View File

@@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "newbase.html" %}
{% load staticfiles %} {% load staticfiles %}
{% load rowerfilters %} {% load rowerfilters %}
{% block title %}View Workout {% endblock %} {% block title %}View Workout {% endblock %}
{% block content %} {% block main %}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script> <script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript"> <script async="true" type="text/javascript">
@@ -13,71 +13,38 @@
{{ interactiveplot |safe }} {{ 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, false, 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="workouts" class="grid_12 alpha">
<h1>Interactive Plot</h1> <h1>Interactive Plot</h1>
{% if user.is_authenticated and mayedit %} <ul class="main-content">
<div class="grid_2 alpha"> <li class="grid_4">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p>
</div>
<div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
</p>
</div>
<div class="grid_2">
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/wind">Edit Wind Data</a>
</div>
<div class="grid_2">
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/stream">Edit Stream Data</a>
</div>
<div class="grid_2">
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/otwsetpower">OTW Power</a>
</div>
{% endif %}
</div>
<div id="theplot" class="grid_12 alpha flexplot">
{{ the_div|safe }} {{ the_div|safe }}
</div> </li>
<li class="grid_4">
<div class="grid_12">
<p> <p>
<h3>Notes</h3> <h2>Notes</h2>
<ul> <ul>
<li>Is your erg pace slower than you expected? This may be a sign of room for improvement regarding your technique. An alternative explanation is that your team mates are fatter than they told you! For example, put 80.0 kg if your four consists of 2 70kg guys and 2 90kg guys.</li> <li>
<li>In order to speed up the calculation, we are running the calculation only for every 10th datapoint, using interpolation in between. Some very fine pace shifts may disappear.</li> Is your erg pace slower than you expected? This may be a sign of room for improvement regarding your technique. An alternative explanation is that your team mates are fatter than they told you! For example, put 80.0 kg if your four consists of 2 70kg guys and 2 90kg guys.
<li>While the wind and stream correction is fairly reliable, the OTW to OTE conversion sometimes throws errors. Those data points are omitted and replaced by interpolated values. We are sorry if this messed up some of your plots.</li> </li>
<li>Read more details about the way we calculate things <a href="/rowers/physics"</a>here</a>.</li> <li>
In order to speed up the calculation, we are running the calculation only for every 10th datapoint, using interpolation in between. Some very fine pace shifts may disappear.
</li>
<li>
While the wind and stream correction is fairly reliable, the OTW to OTE conversion sometimes throws errors. Those data points are omitted and replaced by interpolated values. We are sorry if this messed up some of your plots.
</li>
<li>
Read more details about the way we calculate things <a href="/rowers/physics">here</a>.
</li>
</ul> </ul>
</p> </p>
</li>
</div> </ul>
{% endblock %} {% endblock %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}

View File

@@ -13,6 +13,8 @@ from rowers.plannedsessions import (
race_can_register, race_can_submit,race_rower_status race_can_register, race_can_submit,race_rower_status
) )
from rowers.types import otwtypes
def strfdelta(tdelta): def strfdelta(tdelta):
minutes,seconds = divmod(tdelta.seconds,60) minutes,seconds = divmod(tdelta.seconds,60)
tenths = int(tdelta.microseconds/1e5) tenths = int(tdelta.microseconds/1e5)
@@ -48,6 +50,10 @@ def secondstotimestring(tdelta):
return res return res
@register.filter
def water(workout):
return workout.workouttype in otwtypes
@register.filter @register.filter
def ddays(ddelta): def ddays(ddelta):
return ddelta.days+1 return ddelta.days+1
@@ -120,6 +126,18 @@ def is_session_manager(id,user):
return ps.manager == user return ps.manager == user
from rowers.models import checkworkoutuser
@register.filter
def may_edit(workout,request):
mayedit = 0
if request.user == workout.user.user:
mayedit = True
if checkworkoutuser(request.user,workout):
mayedit = True
return mayedit
@register.filter(name='times') @register.filter(name='times')
def times(number): def times(number):
return range(number) return range(number)

View File

@@ -7132,6 +7132,12 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
w = get_workout(id) w = get_workout(id)
r = getrower(request.user) r = getrower(request.user)
mayedit = 0
if request.user == w.user.user:
mayedit=1
if checkworkoutuser(request.user,w):
mayedit=1
if (checkworkoutuser(request.user,w)==False): if (checkworkoutuser(request.user,w)==False):
message = "You are not allowed to edit this workout" message = "You are not allowed to edit this workout"
messages.error(request,message) messages.error(request,message)
@@ -7246,6 +7252,7 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
'otwsetpower.html', 'otwsetpower.html',
{'workout':w, {'workout':w,
'rower':w, 'rower':w,
'mayedit':mayedit,
'active':'nav-workouts', 'active':'nav-workouts',
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
@@ -7255,6 +7262,12 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
@login_required() @login_required()
def instroke_view(request,id=0): def instroke_view(request,id=0):
w = get_workout(id) w = get_workout(id)
r = getrower(request.user)
mayedit = 0
if request.user == w.user.user:
mayedit=1
if checkworkoutuser(request.user,w):
mayedit=1
breadcrumbs = [ breadcrumbs = [
{ {
@@ -7298,6 +7311,7 @@ def instroke_view(request,id=0):
'rower':r, 'rower':r,
'active':'nav-workouts', 'active':'nav-workouts',
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'mayedit':mayedit,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
'instrokemetrics':instrokemetrics, 'instrokemetrics':instrokemetrics,
}) })
@@ -7727,6 +7741,12 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
r = getrower(request.user) r = getrower(request.user)
w = get_workout(id) w = get_workout(id)
mayedit = 0
if request.user == w.user.user:
mayedit=1
if checkworkoutuser(request.user,w):
mayedit=1
breadcrumbs = [ breadcrumbs = [
{ {
'url':'/rowers/list-workouts', 'url':'/rowers/list-workouts',
@@ -7877,6 +7897,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
'workout':w, 'workout':w,
'rower':r, 'rower':r,
'mayedit':mayedit,
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'active':'nav-workouts', 'active':'nav-workouts',
'workstrokesonly':workstrokesonly, 'workstrokesonly':workstrokesonly,
@@ -8555,14 +8576,31 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
# The interactive plot with wind corrected pace for OTW outings # The interactive plot with wind corrected pace for OTW outings
def workout_otwpowerplot_view(request,id=0,message="",successmessage=""): def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
row = get_workout(id) w = get_workout(id)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': str(w.id)
},
{
'url':reverse(workout_otwpowerplot_view,kwargs={'id':id}),
'name': 'Interactive OTW Power Plot'
}
]
# check if user is owner of this workout # check if user is owner of this workout
# create interactive plot # create interactive plot
f1 = row.csvfilename f1 = w.csvfilename
u = row.user.user u = w.user.user
# r = getrower(u) # r = getrower(u)
promember=0 promember=0
@@ -8572,7 +8610,7 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
result = request.user.is_authenticated() and ispromember(request.user) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == w.user.user:
mayedit=1 mayedit=1
# create interactive plot # create interactive plot
@@ -8585,7 +8623,10 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
return render(request, return render(request,
'otwinteractive.html', 'otwinteractive.html',
{'workout':row, {'workout':w,
'rower':r,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
'interactiveplot':script, 'interactiveplot':script,
'the_div':div, 'the_div':div,
@@ -9354,6 +9395,7 @@ def course_upload_view(request):
def workout_add_chart_view(request,id,plotnr=1): def workout_add_chart_view(request,id,plotnr=1):
w = get_workout(id) w = get_workout(id)
r = getrower(request.user)
plotnr = int(plotnr) plotnr = int(plotnr)
@@ -9378,10 +9420,9 @@ def workout_add_chart_view(request,id,plotnr=1):
except KeyError: except KeyError:
request.session['async_tasks'] = [(jobid,'make_plot')] request.session['async_tasks'] = [(jobid,'make_plot')]
try:
url = request.session['referer'] url = reverse(r.defaultlandingpage,kwargs={'id':str(w.id)})
except KeyError:
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -14618,10 +14659,21 @@ class WorkoutDelete(DeleteView):
] ]
mayedit=0
if not self.request.user.is_anonymous():
r = getrower(self.request.user)
result = self.request.user.is_authenticated() and ispromember(self.request.user)
if result:
promember=1
if self.request.user == self.object.user.user:
mayedit=1
context['active'] = 'nav-workouts' context['active'] = 'nav-workouts'
context['rower'] = getrower(self.request.user) context['rower'] = getrower(self.request.user)
context['breadcrumbs'] = breadcrumbs context['breadcrumbs'] = breadcrumbs
context['workout'] = self.object context['workout'] = self.object
context['mayedit'] = mayedit
context['promember'] = promember
return context return context