hr pie v1
This commit is contained in:
@@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
|
||||
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
||||
|
||||
datadict = {
|
||||
'<{ut2}'.format(ut2=hrzones[1]): frac_lut2,
|
||||
'<{ut2}'.format(ut2=hrzones[1]): frac_lut2,
|
||||
'{ut2}'.format(ut2=hrzones[1]): frac_ut2,
|
||||
'{ut1}'.format(ut1=hrzones[2]): frac_ut1,
|
||||
'{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['color'] = colors
|
||||
data['zone'] = [
|
||||
'<{ut2}'.format(ut2=hrzones[1]),
|
||||
'<{ut2}'.format(ut2=hrzones[1]),
|
||||
'{ut2}'.format(ut2=hrzones[1]),
|
||||
'{ut1}'.format(ut1=hrzones[2]),
|
||||
'{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']])
|
||||
|
||||
TOOLS = 'save,hover'
|
||||
|
||||
z = figure(title="HR "+title, x_range=(-0.5, 1), height=375,
|
||||
tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime",
|
||||
)
|
||||
|
||||
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)
|
||||
data_dict = data.to_dict("records")
|
||||
chart_data = {
|
||||
'data': data_dict,
|
||||
'title': "HR "+ title
|
||||
}
|
||||
|
||||
script, div = get_chart("/hrpie", chart_data, debug=True)
|
||||
return script, div
|
||||
|
||||
def pretty_timedelta(secs):
|
||||
hours, remainder = divmod(secs, 3600)
|
||||
|
||||
@@ -102,14 +102,11 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="grid_2">
|
||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
||||
<script async="true" type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
</script>
|
||||
|
||||
{{ interactiveplot |safe }}
|
||||
<script src="https://d3js.org/d3.v6.js"></script>
|
||||
|
||||
{{ the_div|safe }}
|
||||
{{ interactiveplot |safe }}
|
||||
|
||||
|
||||
</li>
|
||||
<li class="grid_4">
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
|
||||
{% block main %}
|
||||
|
||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
||||
<script async="true" type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
</script>
|
||||
<script src="https://d3js.org/d3.v6.js"></script>
|
||||
|
||||
|
||||
{{ interactiveplot |safe }}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +21,7 @@
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
{{ interactiveplot |safe }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<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-widgets-3.1.1.min.js"></script>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
</div>
|
||||
|
||||
<script async="true" type="text/javascript">
|
||||
@@ -52,35 +53,35 @@
|
||||
<input type="submit" value="Submit"/>
|
||||
</form>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<h2>All workouts</h2>
|
||||
|
||||
|
||||
<p>
|
||||
<table class="listtable shortpadded">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average heart rate</td><td><span id="total_hr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average power</td><td><span id="total_power"></span> W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum power</td><td><span id="total_maxpower"></span> W</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average heart rate</td><td><span id="total_hr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average power</td><td><span id="total_power"></span> W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum power</td><td><span id="total_maxpower"></span> W</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</li>
|
||||
@@ -96,40 +97,40 @@
|
||||
{{ totaldiv|safe }}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
{% for ddict in typedicts %}
|
||||
<li class="grid_1">
|
||||
<h2>{{ ddict|lookup:"wtype"}}</h2>
|
||||
|
||||
|
||||
<p>
|
||||
<table class="listtable shortpadded">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -142,40 +143,39 @@
|
||||
|
||||
<script>
|
||||
$(function($) {
|
||||
console.log('loading script for chart');
|
||||
var ed = '{{ senddate|date:"Y-m-d" }}'
|
||||
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 }}')
|
||||
$.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 div = json.div;
|
||||
var totalsdict = json.totalsdict
|
||||
var listofdicts = json.listofdicts
|
||||
var activities_script = json.activities_script
|
||||
var activities_chart = json.activities_chart
|
||||
$("#id_sitready").remove();
|
||||
$("#id_chart").append(div);
|
||||
// $("#id_script").append("<s"+"cript>"+script+"</s"+"cript>");
|
||||
$("#id_script").append(script);
|
||||
$("#id_activities").append(activities_chart)
|
||||
$("#activities_script").append(activities_script)
|
||||
$("#total_hr").append(totalsdict.hrmean);
|
||||
$("#total_maxhr").append(totalsdict.hrmax);
|
||||
$("#total_power").append(totalsdict.powermean);
|
||||
$("#total_maxpower").append(totalsdict.powermax);
|
||||
listofdicts.forEach(function(item){
|
||||
var id = "#"+item.id+"_hr";
|
||||
$(id).append(item.hrmean);
|
||||
id = "#"+item.id+"_hrmax";
|
||||
$(id).append(item.hrmax);
|
||||
id = "#"+item.id+"_power";
|
||||
$(id).append(item.powermean);
|
||||
id = "#"+item.id+"_powermax";
|
||||
$(id).append(item.powermax);
|
||||
})
|
||||
});
|
||||
console.log('loading script for chart');
|
||||
var ed = '{{ senddate|date:"Y-m-d" }}'
|
||||
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 }}')
|
||||
$.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 div = json.div;
|
||||
var totalsdict = json.totalsdict
|
||||
var listofdicts = json.listofdicts
|
||||
var activities_script = json.activities_script
|
||||
var activities_chart = json.activities_chart
|
||||
$("#id_sitready").remove();
|
||||
$("#id_chart").append(div);
|
||||
$("#id_script").append(script);
|
||||
$("#id_activities").append(activities_chart)
|
||||
$("#activities_script").append(activities_script)
|
||||
$("#total_hr").append(totalsdict.hrmean);
|
||||
$("#total_maxhr").append(totalsdict.hrmax);
|
||||
$("#total_power").append(totalsdict.powermean);
|
||||
$("#total_maxpower").append(totalsdict.powermax);
|
||||
listofdicts.forEach(function(item){
|
||||
var id = "#"+item.id+"_hr";
|
||||
$(id).append(item.hrmean);
|
||||
id = "#"+item.id+"_hrmax";
|
||||
$(id).append(item.hrmax);
|
||||
id = "#"+item.id+"_power";
|
||||
$(id).append(item.powermean);
|
||||
id = "#"+item.id+"_powermax";
|
||||
$(id).append(item.powermax);
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</li>
|
||||
<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>
|
||||
|
||||
@@ -8,26 +8,26 @@
|
||||
{% block scripts %}
|
||||
{% include "monitorjobs.html" %}
|
||||
<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/ui/1.12.1/jquery-ui.js"></script>
|
||||
<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>
|
||||
$( function() {
|
||||
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
|
||||
$( "#slider-range" ).slider({
|
||||
range: true,
|
||||
min: 0,
|
||||
max: {{ maxminutes }},
|
||||
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
|
||||
slide: function( event, ui ) {
|
||||
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
|
||||
$("#id_activeminutesmin").val(ui.values[0]);
|
||||
$("#id_activeminutesmax").val(ui.values[1]);
|
||||
}
|
||||
});
|
||||
$( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) +
|
||||
" min - " + $( "#slider-range" ).slider( "values", 1 ) + " min ");
|
||||
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
|
||||
$( "#slider-range" ).slider({
|
||||
range: true,
|
||||
min: 0,
|
||||
max: {{ maxminutes }},
|
||||
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
|
||||
slide: function( event, ui ) {
|
||||
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
|
||||
$("#id_activeminutesmin").val(ui.values[0]);
|
||||
$("#id_activeminutesmax").val(ui.values[1]);
|
||||
}
|
||||
});
|
||||
$( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) +
|
||||
" min - " + $( "#slider-range" ).slider( "values", 1 ) + " min ");
|
||||
} );
|
||||
</script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
@@ -42,78 +42,75 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<table width=100%>
|
||||
<tr>
|
||||
<th>Name</th><td>{{ workout.name }}</td>
|
||||
</tr><tr>
|
||||
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
||||
</tr><tr>
|
||||
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
||||
</tr><tr>
|
||||
<th>Public link to this workout</th>
|
||||
<td>
|
||||
<table width=100%>
|
||||
<tr>
|
||||
<th>Name</th><td>{{ workout.name }}</td>
|
||||
</tr><tr>
|
||||
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
||||
</tr><tr>
|
||||
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
||||
</tr><tr>
|
||||
<th>Public link to this workout</th>
|
||||
<td>
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
|
||||
<td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
<td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
|
||||
<h1>Edit Workout Interval Data</h1>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<ul class="main-content">
|
||||
<li class="grid_2">
|
||||
<h1>Updated Summary</h1>
|
||||
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
|
||||
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
|
||||
{% for key,value in formvalues.items %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
|
||||
{% endfor %}
|
||||
<p>
|
||||
<input type="submit" value="Save">
|
||||
</p>
|
||||
<span>
|
||||
<a href="">Reset to last saved</a>
|
||||
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
|
||||
</span>
|
||||
</form>
|
||||
<p>
|
||||
<pre>
|
||||
{{ intervalstats }}
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
||||
<script async="true" type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
</script>
|
||||
|
||||
{{ interactiveplot |safe }}
|
||||
|
||||
{{ the_div |safe }}
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<h1>Feeling lucky?</h1>
|
||||
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
|
||||
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
|
||||
section below, where you can edit it.
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
<input type="hidden" name="ruptures" value="ruptures">
|
||||
<table width=100%>
|
||||
{{ ruptureform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="I'm feeling lucky">
|
||||
</form>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<li class="grid_2">
|
||||
<h1>Updated Summary</h1>
|
||||
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
|
||||
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
|
||||
{% for key,value in formvalues.items %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
|
||||
{% endfor %}
|
||||
<p>
|
||||
<input type="submit" value="Save">
|
||||
</p>
|
||||
<span>
|
||||
<a href="">Reset to last saved</a>
|
||||
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
|
||||
</span>
|
||||
</form>
|
||||
<p>
|
||||
<pre>
|
||||
{{ intervalstats }}
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<script src="https://d3js.org/d3.v6.js"></script>
|
||||
|
||||
{{ the_div |safe }}
|
||||
{{ interactiveplot |safe }}
|
||||
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<h1>Feeling lucky?</h1>
|
||||
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
|
||||
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
|
||||
section below, where you can edit it.
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
<input type="hidden" name="ruptures" value="ruptures">
|
||||
<table width=100%>
|
||||
{{ ruptureform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="I'm feeling lucky">
|
||||
</form>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<h1>Interval Shorthand</h1>
|
||||
<p>
|
||||
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 %}
|
||||
<input class="button" type="submit" value="Update">
|
||||
</form>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<h1>Intervals by Power/Pace</h1>
|
||||
|
||||
|
||||
<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
|
||||
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>
|
||||
|
||||
|
||||
<form ecntype="multipart/form-data" method="post">
|
||||
<div id="slider-range"></div>
|
||||
<p>
|
||||
<label for="amount">Active Range:</label>
|
||||
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
|
||||
</p>
|
||||
<div id="slider-range"></div>
|
||||
<p>
|
||||
<label for="amount">Active Range:</label>
|
||||
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
|
||||
</p>
|
||||
<table>
|
||||
{{ powerupdateform.as_table }}
|
||||
</table>
|
||||
|
||||
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="Submit">
|
||||
</form>
|
||||
@@ -153,11 +150,11 @@
|
||||
<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>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>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>
|
||||
<table class="listtable" width=100%>
|
||||
<tr>
|
||||
@@ -182,32 +179,32 @@
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
{% if courses %}
|
||||
<li>
|
||||
<h1>Interval by Course</h1>
|
||||
<p>
|
||||
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
|
||||
attempts.
|
||||
</p>
|
||||
<p>
|
||||
{% if rower.share_course_results %}
|
||||
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.
|
||||
{% else %}
|
||||
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.
|
||||
{% endif %}
|
||||
</p>
|
||||
<form ecntype="multipart/form-data" method="post">
|
||||
<table>
|
||||
{{ courseselectform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="Select Course">
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if courses %}
|
||||
<li>
|
||||
<h1>Interval by Course</h1>
|
||||
<p>
|
||||
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
|
||||
attempts.
|
||||
</p>
|
||||
<p>
|
||||
{% if rower.share_course_results %}
|
||||
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.
|
||||
{% else %}
|
||||
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.
|
||||
{% endif %}
|
||||
</p>
|
||||
<form ecntype="multipart/form-data" method="post">
|
||||
<table>
|
||||
{{ courseselectform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="Select Course">
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -98,14 +98,11 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="grid_2">
|
||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
||||
<script async="true" type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
</script>
|
||||
|
||||
{{ interactiveplot |safe }}
|
||||
<script src="https://d3js.org/d3.v6.js"></script>
|
||||
|
||||
{{ the_div|safe }}
|
||||
{{ interactiveplot |safe }}
|
||||
|
||||
|
||||
</li>
|
||||
<li class="grid_4">
|
||||
|
||||
@@ -443,9 +443,6 @@ urlpatterns = [
|
||||
views.trainingzones_view_data, name="trainingzones_view_data"),
|
||||
re_path(r'^trainingzones/data/$', views.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,
|
||||
name='analysis_view_data'),
|
||||
re_path(r'^analysisdata/$', views.analysis_view_data,
|
||||
@@ -470,8 +467,6 @@ urlpatterns = [
|
||||
views.workout_upload_view, name='workout_upload_view'),
|
||||
re_path(r'^workout/upload/$', views.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,
|
||||
name='workout_forcecurve_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/(?P<analysis>\d+)/$', views.workout_forcecurve_view,
|
||||
|
||||
@@ -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()
|
||||
def otecp_toadmin_view(request, theuser=0,
|
||||
startdate=timezone.now() - datetime.timedelta(days=365),
|
||||
|
||||
@@ -630,50 +630,6 @@ def otw_use_gps(request, id=0):
|
||||
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
|
||||
@login_required()
|
||||
|
||||
Reference in New Issue
Block a user