Private
Public Access
1
0

hr pie v1

This commit is contained in:
2024-03-24 14:30:16 +01:00
parent e80fa1e4f9
commit 2fee1fcdb6
10 changed files with 226 additions and 688 deletions

View File

@@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
datadict = { datadict = {
'<{ut2}'.format(ut2=hrzones[1]): frac_lut2, '&lt;{ut2}'.format(ut2=hrzones[1]): frac_lut2,
'{ut2}'.format(ut2=hrzones[1]): frac_ut2, '{ut2}'.format(ut2=hrzones[1]): frac_ut2,
'{ut1}'.format(ut1=hrzones[2]): frac_ut1, '{ut1}'.format(ut1=hrzones[2]): frac_ut1,
'{at}'.format(at=hrzones[3]): frac_at, '{at}'.format(at=hrzones[3]): frac_at,
@@ -299,7 +299,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
data['angle'] = data['value']/data['value'].sum() * 2*pi data['angle'] = data['value']/data['value'].sum() * 2*pi
data['color'] = colors data['color'] = colors
data['zone'] = [ data['zone'] = [
'<{ut2}'.format(ut2=hrzones[1]), '&lt;{ut2}'.format(ut2=hrzones[1]),
'{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]),
'{ut1}'.format(ut1=hrzones[2]), '{ut1}'.format(ut1=hrzones[2]),
'{at}'.format(at=hrzones[3]), '{at}'.format(at=hrzones[3]),
@@ -309,24 +309,14 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']]) data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
TOOLS = 'save,hover' data_dict = data.to_dict("records")
chart_data = {
z = figure(title="HR "+title, x_range=(-0.5, 1), height=375, 'data': data_dict,
tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime", 'title': "HR "+ title
) }
z.wedge(x=0, y=1, radius=0.4,
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color='white', fill_color='color', source=data, legend_group='zone')
z.axis.axis_label = None
z.axis.visible = False
z.grid.grid_line_color = None
z.outline_line_color = None
z.toolbar_location = 'right'
return components(z)
script, div = get_chart("/hrpie", chart_data, debug=True)
return script, div
def pretty_timedelta(secs): def pretty_timedelta(secs):
hours, remainder = divmod(secs, 3600) hours, remainder = divmod(secs, 3600)

View File

@@ -102,14 +102,11 @@
</li> </li>
{% endif %} {% endif %}
<li class="grid_2"> <li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
{{ the_div|safe }} {{ the_div|safe }}
{{ interactiveplot |safe }}
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -6,12 +6,9 @@
{% block main %} {% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
@@ -24,6 +21,7 @@
</li> </li>
</ul> </ul>
{{ interactiveplot |safe }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -12,6 +12,7 @@
<div id="id_js_res"> <div id="id_js_res">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script> <script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</div> </div>
<script async="true" type="text/javascript"> <script async="true" type="text/javascript">
@@ -52,35 +53,35 @@
<input type="submit" value="Submit"/> <input type="submit" value="Submit"/>
</form> </form>
</p> </p>
</li> </li>
<li class="grid_2"> <li class="grid_2">
<h2>All workouts</h2> <h2>All workouts</h2>
<p> <p>
<table class="listtable shortpadded"> <table class="listtable shortpadded">
<tbody> <tbody>
<tr> <tr>
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td> <td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
</tr> </tr>
<tr> <tr>
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td> <td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td>
</tr> </tr>
<tr> <tr>
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td> <td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
</tr> </tr>
<tr> <tr>
<td>Average heart rate</td><td><span id="total_hr"></span> bpm</td> <td>Average heart rate</td><td><span id="total_hr"></span> bpm</td>
</tr> </tr>
<tr> <tr>
<td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td> <td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td>
</tr> </tr>
<tr> <tr>
<td>Average power</td><td><span id="total_power"></span> W</td> <td>Average power</td><td><span id="total_power"></span> W</td>
</tr> </tr>
<tr> <tr>
<td>Maximum power</td><td><span id="total_maxpower"></span> W</td> <td>Maximum power</td><td><span id="total_maxpower"></span> W</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</p> </p>
</li> </li>
@@ -96,40 +97,40 @@
{{ totaldiv|safe }} {{ totaldiv|safe }}
</div> </div>
</li> </li>
{% for ddict in typedicts %} {% for ddict in typedicts %}
<li class="grid_1"> <li class="grid_1">
<h2>{{ ddict|lookup:"wtype"}}</h2> <h2>{{ ddict|lookup:"wtype"}}</h2>
<p> <p>
<table class="listtable shortpadded"> <table class="listtable shortpadded">
<tbody> <tbody>
<tr> <tr>
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td> <td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
</tr> </tr>
<tr> <tr>
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td> <td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td>
</tr> </tr>
<tr> <tr>
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td> <td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
</tr> </tr>
<tr> <tr>
<td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td> <td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td>
</tr> </tr>
<tr> <tr>
<td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td> <td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td>
</tr> </tr>
<tr> <tr>
<td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td> <td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td>
</tr> </tr>
<tr> <tr>
<td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td> <td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</p> </p>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -142,40 +143,39 @@
<script> <script>
$(function($) { $(function($) {
console.log('loading script for chart'); console.log('loading script for chart');
var ed = '{{ senddate|date:"Y-m-d" }}' var ed = '{{ senddate|date:"Y-m-d" }}'
console.log('End',ed); console.log('End',ed);
console.log(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}') console.log(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}')
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}', function(json) { $.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}', function(json) {
var script = json.script; var script = json.script;
var div = json.div; var div = json.div;
var totalsdict = json.totalsdict var totalsdict = json.totalsdict
var listofdicts = json.listofdicts var listofdicts = json.listofdicts
var activities_script = json.activities_script var activities_script = json.activities_script
var activities_chart = json.activities_chart var activities_chart = json.activities_chart
$("#id_sitready").remove(); $("#id_sitready").remove();
$("#id_chart").append(div); $("#id_chart").append(div);
// $("#id_script").append("<s"+"cript>"+script+"</s"+"cript>"); $("#id_script").append(script);
$("#id_script").append(script); $("#id_activities").append(activities_chart)
$("#id_activities").append(activities_chart) $("#activities_script").append(activities_script)
$("#activities_script").append(activities_script) $("#total_hr").append(totalsdict.hrmean);
$("#total_hr").append(totalsdict.hrmean); $("#total_maxhr").append(totalsdict.hrmax);
$("#total_maxhr").append(totalsdict.hrmax); $("#total_power").append(totalsdict.powermean);
$("#total_power").append(totalsdict.powermean); $("#total_maxpower").append(totalsdict.powermax);
$("#total_maxpower").append(totalsdict.powermax); listofdicts.forEach(function(item){
listofdicts.forEach(function(item){ var id = "#"+item.id+"_hr";
var id = "#"+item.id+"_hr"; $(id).append(item.hrmean);
$(id).append(item.hrmean); id = "#"+item.id+"_hrmax";
id = "#"+item.id+"_hrmax"; $(id).append(item.hrmax);
$(id).append(item.hrmax); id = "#"+item.id+"_power";
id = "#"+item.id+"_power"; $(id).append(item.powermean);
$(id).append(item.powermean); id = "#"+item.id+"_powermax";
id = "#"+item.id+"_powermax"; $(id).append(item.powermax);
$(id).append(item.powermax); })
}) });
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -23,7 +23,7 @@
</li> </li>
<li class="grid_2"> <li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript"> <script async="true" type="text/javascript">
Bokeh.set_log_level("info"); Bokeh.set_log_level("info");
</script> </script>

View File

@@ -8,26 +8,26 @@
{% block scripts %} {% block scripts %}
{% include "monitorjobs.html" %} {% include "monitorjobs.html" %}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script> <script>
$( function() { $( function() {
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range'); console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
$( "#slider-range" ).slider({ $( "#slider-range" ).slider({
range: true, range: true,
min: 0, min: 0,
max: {{ maxminutes }}, max: {{ maxminutes }},
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ], values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
slide: function( event, ui ) { slide: function( event, ui ) {
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " ); $( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
$("#id_activeminutesmin").val(ui.values[0]); $("#id_activeminutesmin").val(ui.values[0]);
$("#id_activeminutesmax").val(ui.values[1]); $("#id_activeminutesmax").val(ui.values[1]);
} }
}); });
$( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) + $( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) +
" min - " + $( "#slider-range" ).slider( "values", 1 ) + " min "); " min - " + $( "#slider-range" ).slider( "values", 1 ) + " min ");
} ); } );
</script> </script>
{% endblock %} {% endblock %}
{% block main %} {% block main %}
@@ -42,78 +42,75 @@
{% endif %} {% endif %}
</p> </p>
<p> <p>
<table width=100%> <table width=100%>
<tr> <tr>
<th>Name</th><td>{{ workout.name }}</td> <th>Name</th><td>{{ workout.name }}</td>
</tr><tr> </tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td> <th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr> </tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td> <th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr> </tr><tr>
<th>Public link to this workout</th> <th>Public link to this workout</th>
<td> <td>
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a> <a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
<td> <td>
</tr> </tr>
</table> </table>
</p> </p>
<h1>Edit Workout Interval Data</h1> <h1>Edit Workout Interval Data</h1>
<p> <p>
Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest
intervals. To save your work, press the Save button above the updated summary. intervals. To save your work, press the Save button above the updated summary.
</p> </p>
<ul class="main-content"> <ul class="main-content">
<li class="grid_2"> <li class="grid_2">
<h1>Updated Summary</h1> <h1>Updated Summary</h1>
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post"> <form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}"> <input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
<input type="hidden" name="nrintervals" value={{ nrintervals }}> <input type="hidden" name="nrintervals" value={{ nrintervals }}>
{% for key,value in formvalues.items %} {% for key,value in formvalues.items %}
<input type="hidden" name="{{ key }}" value="{{ value|safe }}"> <input type="hidden" name="{{ key }}" value="{{ value|safe }}">
{% endfor %} {% endfor %}
<p> <p>
<input type="submit" value="Save"> <input type="submit" value="Save">
</p> </p>
<span> <span>
<a href="">Reset to last saved</a> <a href="">Reset to last saved</a>
&nbsp; &nbsp;
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a> <a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
</span> </span>
</form> </form>
<p> <p>
<pre> <pre>
{{ intervalstats }} {{ intervalstats }}
</pre> </pre>
</p> </p>
</li> </li>
<li class="grid_2"> <li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info"); {{ the_div |safe }}
</script> {{ interactiveplot |safe }}
{{ interactiveplot |safe }} </li>
<li class="grid_2">
{{ the_div |safe }} <h1>Feeling lucky?</h1>
</li> <p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
<li class="grid_2"> autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
<h1>Feeling lucky?</h1> section below, where you can edit it.
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not <form enctype="multipart/form-data" method="post">
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand <input type="hidden" name="ruptures" value="ruptures">
section below, where you can edit it. <table width=100%>
<form enctype="multipart/form-data" method="post"> {{ ruptureform.as_table }}
<input type="hidden" name="ruptures" value="ruptures"> </table>
<table width=100%> {% csrf_token %}
{{ ruptureform.as_table }} <input class="button" type="submit" value="I'm feeling lucky">
</table> </form>
{% csrf_token %} </p>
<input class="button" type="submit" value="I'm feeling lucky"> </li>
</form> <li class="grid_2">
</p>
</li>
<li class="grid_2">
<h1>Interval Shorthand</h1> <h1>Interval Shorthand</h1>
<p> <p>
See the how-to <a href="#howto">at the bottom of this page</a> for details on how to use this form. See the how-to <a href="#howto">at the bottom of this page</a> for details on how to use this form.
@@ -125,26 +122,26 @@
{% csrf_token %} {% csrf_token %}
<input class="button" type="submit" value="Update"> <input class="button" type="submit" value="Update">
</form> </form>
</li> </li>
<li class="grid_2"> <li class="grid_2">
<h1>Intervals by Power/Pace</h1> <h1>Intervals by Power/Pace</h1>
<p>With this form, you can specify a power or pace level. Everything faster/harder than the <p>With this form, you can specify a power or pace level. Everything faster/harder than the
specified pace/power will become a work interval. Everything slower will become a rest specified pace/power will become a work interval. Everything slower will become a rest
interval. Use the slider to limit the active range. Everything outside the active range will interval. Use the slider to limit the active range. Everything outside the active range will
become rest (warming up and cooling down). become rest (warming up and cooling down).
</p> </p>
<form ecntype="multipart/form-data" method="post"> <form ecntype="multipart/form-data" method="post">
<div id="slider-range"></div> <div id="slider-range"></div>
<p> <p>
<label for="amount">Active Range:</label> <label for="amount">Active Range:</label>
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;"> <input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p> </p>
<table> <table>
{{ powerupdateform.as_table }} {{ powerupdateform.as_table }}
</table> </table>
{% csrf_token %} {% csrf_token %}
<input class="button" type="submit" value="Submit"> <input class="button" type="submit" value="Submit">
</form> </form>
@@ -153,11 +150,11 @@
<h1 id="howto">Interval Shorthand How-To</h1> <h1 id="howto">Interval Shorthand How-To</h1>
<p>This is a quick way to enter the intervals using a special mini-language.</p> <p>This is a quick way to enter the intervals using a special mini-language.</p>
<p>You enter something like <em>8x500m/3min</em>, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.</p> <p>You enter something like <em>8x500m/3min</em>, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.</p>
<p>Special characters are <em>x</em> (times), <em>+</em> and <em>/</em> (denotes a rest interval), as well as <em>(</em> and <em>)</em>. Units are <em>min</em> (minutes), <em>sec</em> (seconds), <em>m</em> (meters) and <em>km</em> (km). </p> <p>Special characters are <em>x</em> (times), <em>+</em> and <em>/</em> (denotes a rest interval), as well as <em>(</em> and <em>)</em>. Units are <em>min</em> (minutes), <em>sec</em> (seconds), <em>m</em> (meters) and <em>km</em> (km). </p>
<p>A typical interval is described as "<b>10min/5min</b>", with the work part before the "<b>/</b>" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "<b>1km</b>" or "<b>1000m</b>". The basic units can be combined with "<b>+</b>" and "<b>Nx</b>". You can use parentheses as in the example below.</p> <p>A typical interval is described as "<b>10min/5min</b>", with the work part before the "<b>/</b>" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "<b>1km</b>" or "<b>1000m</b>". The basic units can be combined with "<b>+</b>" and "<b>Nx</b>". You can use parentheses as in the example below.</p>
<p>Here are a few examples</p> <p>Here are a few examples</p>
<table class="listtable" width=100%> <table class="listtable" width=100%>
<tr> <tr>
@@ -182,32 +179,32 @@
</tr> </tr>
</table> </table>
</li> </li>
{% if courses %} {% if courses %}
<li> <li>
<h1>Interval by Course</h1> <h1>Interval by Course</h1>
<p> <p>
This functionality allows you to record a time on a set course that you've rowed during the workout. This functionality allows you to record a time on a set course that you've rowed during the workout.
The summary will be updated to show time on course, and you can compare this with other The summary will be updated to show time on course, and you can compare this with other
attempts. attempts.
</p> </p>
<p> <p>
{% if rower.share_course_results %} {% if rower.share_course_results %}
You are currently sharing your course results with all Rowsandall users. You are currently sharing your course results with all Rowsandall users.
Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results. Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results.
{% else %} {% else %}
You are currently hiding your course results (except for your participation in online challenges). You are currently hiding your course results (except for your participation in online challenges).
Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results. Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results.
{% endif %} {% endif %}
</p> </p>
<form ecntype="multipart/form-data" method="post"> <form ecntype="multipart/form-data" method="post">
<table> <table>
{{ courseselectform.as_table }} {{ courseselectform.as_table }}
</table> </table>
{% csrf_token %} {% csrf_token %}
<input class="button" type="submit" value="Select Course"> <input class="button" type="submit" value="Select Course">
</form> </form>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
{% endblock %} {% endblock %}

View File

@@ -98,14 +98,11 @@
</li> </li>
{% endif %} {% endif %}
<li class="grid_2"> <li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script> <script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
{{ the_div|safe }} {{ the_div|safe }}
{{ interactiveplot |safe }}
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -443,9 +443,6 @@ urlpatterns = [
views.trainingzones_view_data, name="trainingzones_view_data"), views.trainingzones_view_data, name="trainingzones_view_data"),
re_path(r'^trainingzones/data/$', views.trainingzones_view_data, re_path(r'^trainingzones/data/$', views.trainingzones_view_data,
name="trainingzones_view_data"), name="trainingzones_view_data"),
re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',
views.rankings_view2, name='rankings_view2'),
re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'),
re_path(r'^analysisdata/user/(?P<userid>\d+)/$', views.analysis_view_data, re_path(r'^analysisdata/user/(?P<userid>\d+)/$', views.analysis_view_data,
name='analysis_view_data'), name='analysis_view_data'),
re_path(r'^analysisdata/$', views.analysis_view_data, re_path(r'^analysisdata/$', views.analysis_view_data,
@@ -470,8 +467,6 @@ urlpatterns = [
views.workout_upload_view, name='workout_upload_view'), views.workout_upload_view, name='workout_upload_view'),
re_path(r'^workout/upload/$', views.workout_upload_view, re_path(r'^workout/upload/$', views.workout_upload_view,
name='workout_upload_view'), name='workout_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view,
name='workout_histo_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view,
name='workout_forcecurve_view'), name='workout_forcecurve_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/(?P<analysis>\d+)/$', views.workout_forcecurve_view, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/(?P<analysis>\d+)/$', views.workout_forcecurve_view,

View File

@@ -1382,398 +1382,6 @@ def ajax_agegrouprecords(request,
) )
# Show ranking distances including predicted paces
@login_required()
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def rankings_view2(request, userid=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays > 0: # pragma: no cover
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "": # pragma: no cover
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "": # pragma: no cover
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
if userid == 0:
userid = request.user.id
else:
lastupdated = "1900-01-01"
promember = 0
r = getrequestrower(request, userid=userid)
theuser = r.user
wcdurations = []
wcpower = []
lastupdated = "1900-01-01"
userid = 0
if 'options' in request.session:
options = request.session['options']
try:
wcdurations = options['wcdurations']
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError: # pragma: no cover
pass
try:
userid = options['userid']
except KeyError: # pragma: no cover
userid = 0
else:
options = {}
lastupdatedtime = arrow.get(lastupdated).timestamp()
current_time = arrow.utcnow().timestamp()
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else: # pragma: no cover
recalc = False
options['userid'] = theuser.id
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 0
agerecords = CalcAgePerformance.objects.filter(
age=age,
sex=r.sex,
weightcategory=r.weightcategory)
if len(agerecords) == 0:
recalc = True
wcpower = []
wcdurations = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
options['wcpower'] = wcpower
options['wcdurations'] = wcdurations
if theuser:
options['userid'] = theuser.id
request.session['options'] = options
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember = 1
# get all indoor rows in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
dateform = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if startdate > enddate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
dateform = DateRangeForm()
deltaform = DeltaDaysForm()
else:
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
# get all 2k (if any) - this rower, in date range
try:
r = getrower(theuser)
except Rower.DoesNotExist: # pragma: no cover
r = 0
uu = theuser
# test to fix bug
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
startdate = arrow.get(startdate).datetime
enddate = arrow.get(enddate).datetime
thedistances = []
theworkouts = []
thesecs = []
rankingdistances.sort()
rankingdurations.sort()
for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(
user=r, distance=rankingdistance,
workouttype__in=['rower', 'dynamic', 'slides'],
rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('duration')
if workouts:
thedistances.append(rankingdistance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-6*workouts[0].duration.microsecond
thesecs.append(timesecs)
for rankingduration in rankingdurations:
workouts = Workout.objects.filter(
user=r, duration=rankingduration,
workouttype='rower',
rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('-distance')
if workouts:
thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-5*workouts[0].duration.microsecond
thesecs.append(timesecs)
thedistances = np.array(thedistances)
thesecs = np.array(thesecs)
thevelos = thedistances/thesecs
theavpower = 2.8*(thevelos**3)
# create interactive plot
if len(thedistances) != 0:
res = interactive_cpchart(
r, thedistances, thesecs, theavpower,
theworkouts, promember=promember,
wcdurations=wcdurations, wcpower=wcpower
)
script = res[0]
div = res[1]
paulslope = res[2]
paulintercept = res[3]
p1 = res[4]
message = res[5]
else:
script = ''
div = '<p>No ranking pieces found.</p>'
paulslope = 1
paulintercept = 1
p1 = [1, 1, 1, 1]
message = ""
if request.method == 'POST' and "piece" in request.POST: # pragma: no cover
form = PredictedPieceForm(request.POST)
if form.is_valid():
value = form.cleaned_data['value']
hourvalue, value = divmod(value, 60)
if hourvalue >= 24:
hourvalue = 23
pieceunit = form.cleaned_data['pieceunit']
if pieceunit == 'd':
rankingdistances.append(value)
else:
rankingdurations.append(datetime.time(
minute=int(value), hour=int(hourvalue)))
else:
form = PredictedPieceForm()
rankingdistances.sort()
rankingdurations.sort()
predictions = []
cpredictions = []
for rankingdistance in rankingdistances:
# Paul's model
p = paulslope*np.log10(rankingdistance)+paulintercept
velo = 500./p
t = rankingdistance/velo
pwr = 2.8*(velo**3)
try:
pwr = int(pwr)
except (ValueError, AttributeError): # pragma: no cover
pwr = 0
a = {'distance': rankingdistance,
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
predictions.append(a)
# CP model -
pwr2 = p1[0]/(1+t/p1[2])
pwr2 += p1[1]/(1+t/p1[3])
if pwr2 <= 0: # pragma: no cover
pwr2 = 50.
velo2 = (pwr2/2.8)**(1./3.)
if np.isnan(velo2) or velo2 <= 0: # pragma: no cover
velo2 = 1.0
t2 = rankingdistance/velo2
pwr3 = p1[0]/(1+t2/p1[2])
pwr3 += p1[1]/(1+t2/p1[3])
if pwr3 <= 0: # pragma: no cover
pwr3 = 50.
velo3 = (pwr3/2.8)**(1./3.)
if np.isnan(velo3) or velo3 <= 0: # pragma: no cover
velo3 = 1.0
t3 = rankingdistance/velo3
p3 = 500./velo3
a = {'distance': rankingdistance,
'duration': timedeltaconv(t3),
'pace': timedeltaconv(p3),
'power': int(pwr3)}
cpredictions.append(a)
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
t += rankingduration.second
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)
try:
a = {'distance': int(d),
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
predictions.append(a)
except: # pragma: no cover
pass
# CP model
pwr = p1[0] / (1 + t / p1[2])
pwr += p1[1] / (1 + t / p1[3])
if pwr <= 0: # pragma: no cover
pwr = 50.
velo = (pwr / 2.8)**(1. / 3.)
if np.isnan(velo) or velo <= 0: # pragma: no cover
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)
if recalc:
wcdurations = []
wcpower = []
durations = [1, 4, 30, 60]
distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=r.sex,
weightcategory=r.weightcategory
).values()
)
)
jsondf = df.to_json()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf, distances, durations, age, r.sex, r.weightcategory)
try:
request.session['async_tasks'] += [(job.id, 'agegrouprecords')]
except KeyError:
request.session['async_tasks'] = [(job.id, 'agegrouprecords')]
messages.error(request, message)
return render(request, 'rankings.html',
{'rankingworkouts': theworkouts,
'interactiveplot': script,
'the_div': div,
'predictions': predictions,
'cpredictions': cpredictions,
'nrdata': len(thedistances),
'form': form,
'dateform': dateform,
'deltaform': deltaform,
'id': theuser,
'theuser': uu,
'rower': r,
'active': 'nav-analysis',
'age': age,
'sex': r.sex,
'recalc': recalc,
'weightcategory': r.weightcategory,
'startdate': startdate,
'enddate': enddate,
'teams': get_my_teams(request.user),
})
@login_required() @login_required()
def otecp_toadmin_view(request, theuser=0, def otecp_toadmin_view(request, theuser=0,
startdate=timezone.now() - datetime.timedelta(days=365), startdate=timezone.now() - datetime.timedelta(days=365),

View File

@@ -630,50 +630,6 @@ def otw_use_gps(request, id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Show Stroke power histogram for a workout
@login_required()
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_histo_view(request, id=0):
w = get_workoutuser(id, request)
r = getrequestrower(request)
mayedit = 0
if w.user == r:
mayedit = 1
res = interactive_histoall([w], 'power', False)
script = res[0]
div = res[1]
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_histo_view', kwargs={'id': id}),
'name': 'Histogram'
}
]
return render(request,
'histo_single.html',
{'interactiveplot': script,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'workout': w,
'rower': r,
'the_div': div,
'id': id,
'mayedit': mayedit,
'teams': get_my_teams(request.user),
})
# add a workout manually # add a workout manually
@login_required() @login_required()