Merge branch 'feature/instroke' into develop
This commit is contained in:
+10
-1950
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -148,6 +148,26 @@ class InstantPlanSelectForm(forms.Form):
|
|||||||
self.fields['enddate'].initial = timezone.now(
|
self.fields['enddate'].initial = timezone.now(
|
||||||
)+datetime.timedelta(days=instantplan.duration)
|
)+datetime.timedelta(days=instantplan.duration)
|
||||||
|
|
||||||
|
# Instroke Metrics interactive chart form
|
||||||
|
class InstrokeForm(forms.Form):
|
||||||
|
metric = forms.ChoiceField(label='metric',choices=(('a','a'),('b','b')))
|
||||||
|
individual_curves = forms.BooleanField(label='individual curves',initial=False,
|
||||||
|
required=False)
|
||||||
|
spm_min = forms.IntegerField(initial=15,label='SPM Min',widget=HiddenInput)
|
||||||
|
spm_max = forms.IntegerField(initial=45,label='SPM Max',widget=HiddenInput)
|
||||||
|
activeminutesmin = forms.IntegerField(
|
||||||
|
required=False, initial=0, widget=forms.HiddenInput())
|
||||||
|
activeminutesmax = forms.IntegerField(
|
||||||
|
required=False, initial=0, widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs): # pragma: no cover
|
||||||
|
choices = kwargs.pop('choices', [])
|
||||||
|
super(InstrokeForm, self).__init__(*args, **kwargs)
|
||||||
|
if len(choices) > 0:
|
||||||
|
choices = [(choice, choice) for choice in choices]
|
||||||
|
self.fields['metric'].choices = choices
|
||||||
|
self.fields['metric'].initial = choices[0]
|
||||||
|
|
||||||
|
|
||||||
# Video Analysis creation form
|
# Video Analysis creation form
|
||||||
class VideoAnalysisCreateForm(forms.Form):
|
class VideoAnalysisCreateForm(forms.Form):
|
||||||
|
|||||||
@@ -4070,6 +4070,133 @@ def interactive_streamchart(id=0, promember=0):
|
|||||||
|
|
||||||
return [script, div]
|
return [script, div]
|
||||||
|
|
||||||
|
def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
||||||
|
activeminutesmin, activeminutesmax,
|
||||||
|
individual_curves):
|
||||||
|
|
||||||
|
|
||||||
|
df_pos = (df+abs(df))/2.
|
||||||
|
df_min = -(-df+abs(-df))/2.
|
||||||
|
|
||||||
|
mean_vals = df.median()
|
||||||
|
q75 = df_pos.quantile(q=0.75).replace(0,np.nan)
|
||||||
|
q25 = df_pos.quantile(q=0.25).replace(0,np.nan)
|
||||||
|
q75min = df_min.quantile(q=0.75).replace(0,np.nan)
|
||||||
|
q25min = df_min.quantile(q=0.25).replace(0,np.nan)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
xvals = np.arange(len(mean_vals))
|
||||||
|
|
||||||
|
df_plot = pd.DataFrame({
|
||||||
|
'x':xvals,
|
||||||
|
'median':mean_vals,
|
||||||
|
'high':q75,
|
||||||
|
'low':q75min,
|
||||||
|
'high 2':q25min,
|
||||||
|
'low 2': q25,
|
||||||
|
})
|
||||||
|
|
||||||
|
df_plot['high'].update(df_plot.pop('high 2'))
|
||||||
|
df_plot['low'].update(df_plot.pop('low 2'))
|
||||||
|
df_plot.interpolate(axis=1,inplace=True)
|
||||||
|
|
||||||
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
||||||
|
plot = Figure(plot_width=920,tools=TOOLS,
|
||||||
|
toolbar_location='above',
|
||||||
|
toolbar_sticky=False)
|
||||||
|
|
||||||
|
plot.sizing_mode = 'stretch_both'
|
||||||
|
|
||||||
|
plot.title.text = str(workout) + ' - ' + metric
|
||||||
|
|
||||||
|
# add watermark
|
||||||
|
watermarkurl = "/static/img/logo7.png"
|
||||||
|
|
||||||
|
watermarkrange = Range1d(start=0, end=1)
|
||||||
|
watermarkalpha = 0.6
|
||||||
|
watermarkx = 0.99
|
||||||
|
watermarky = 0.01
|
||||||
|
watermarkw = 184
|
||||||
|
watermarkh = 35
|
||||||
|
watermarkanchor = 'bottom_right'
|
||||||
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
||||||
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
||||||
|
|
||||||
|
plot.image_url([watermarkurl], watermarkx, watermarky,
|
||||||
|
watermarkw, watermarkh,
|
||||||
|
global_alpha=watermarkalpha,
|
||||||
|
w_units='screen',
|
||||||
|
h_units='screen',
|
||||||
|
anchor=watermarkanchor,
|
||||||
|
dilate=True,
|
||||||
|
x_range_name="watermark",
|
||||||
|
y_range_name="watermark",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
source = ColumnDataSource(
|
||||||
|
df_plot
|
||||||
|
)
|
||||||
|
|
||||||
|
TIPS = OrderedDict([
|
||||||
|
('x','@x'),
|
||||||
|
('median','@median'),
|
||||||
|
('high','@high'),
|
||||||
|
('low','@low')
|
||||||
|
])
|
||||||
|
|
||||||
|
hover = plot.select(type=HoverTool)
|
||||||
|
hover.tooltips = TIPS
|
||||||
|
|
||||||
|
s = 'SPM: {spm_min} - {spm_max}'.format(
|
||||||
|
spm_min = spm_min,
|
||||||
|
spm_max = spm_max,
|
||||||
|
)
|
||||||
|
|
||||||
|
label = Label(x=50, y=450, x_units='screen',y_units='screen',
|
||||||
|
text=s,
|
||||||
|
background_fill_alpha=0.7,
|
||||||
|
background_fill_color='white',
|
||||||
|
text_color='black',
|
||||||
|
)
|
||||||
|
|
||||||
|
s2 = 'Time: {activeminutesmin} - {activeminutesmax} min'.format(
|
||||||
|
activeminutesmin=activeminutesmin,
|
||||||
|
activeminutesmax=activeminutesmax
|
||||||
|
)
|
||||||
|
|
||||||
|
label2 = Label(x=50,y=400, x_units='screen', y_units='screen',
|
||||||
|
text=s2,
|
||||||
|
background_fill_alpha=0.7,
|
||||||
|
background_fill_color='white',
|
||||||
|
text_color='black',
|
||||||
|
)
|
||||||
|
|
||||||
|
plot.add_layout(label)
|
||||||
|
plot.add_layout(label2)
|
||||||
|
|
||||||
|
if individual_curves:
|
||||||
|
for index,row in df.iterrows():
|
||||||
|
plot.line(xvals,row,color='lightgray',line_width=1)
|
||||||
|
else:
|
||||||
|
plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5)
|
||||||
|
|
||||||
|
plot.line('x','median',source=source,legend_label='median',color="black",
|
||||||
|
line_width=3)
|
||||||
|
|
||||||
|
medrange = mean_vals.max()-mean_vals.min()
|
||||||
|
yrange = Range1d(start=mean_vals.min()-0.2*medrange,
|
||||||
|
end=mean_vals.max()+0.2*medrange,)
|
||||||
|
plot.y_range = yrange
|
||||||
|
|
||||||
|
plot.add_tools(HoverTool(tooltips=TIPS))
|
||||||
|
|
||||||
|
script, div = components(plot)
|
||||||
|
|
||||||
|
return (script, div)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def interactive_chart(id=0, promember=0, intervaldata={}):
|
def interactive_chart(id=0, promember=0, intervaldata={}):
|
||||||
|
|
||||||
|
|||||||
+40
-2
@@ -14,7 +14,8 @@ from django.core.wsgi import get_wsgi_application
|
|||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
Workout, GeoPolygon, GeoPoint, GeoCourse,
|
Workout, GeoPolygon, GeoPoint, GeoCourse,
|
||||||
VirtualRaceResult, CourseTestResult, Rower
|
VirtualRaceResult, CourseTestResult, Rower,
|
||||||
|
GraphImage,
|
||||||
)
|
)
|
||||||
|
|
||||||
import math
|
import math
|
||||||
@@ -45,6 +46,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
from time import strftime
|
||||||
|
|
||||||
from scipy import optimize
|
from scipy import optimize
|
||||||
from scipy.signal import savgol_filter
|
from scipy.signal import savgol_filter
|
||||||
@@ -103,7 +105,7 @@ from rowers.emails import htmlstrip
|
|||||||
from rowers import mytypes
|
from rowers import mytypes
|
||||||
|
|
||||||
|
|
||||||
from rowers.dataprep import (
|
from rowers.dataroutines import (
|
||||||
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
|
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
|
||||||
#update_workout_field_sql,
|
#update_workout_field_sql,
|
||||||
update_agegroup_db, update_strokedata,
|
update_agegroup_db, update_strokedata,
|
||||||
@@ -297,6 +299,42 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
|
|||||||
|
|
||||||
return sums, sa, results
|
return sums, sa, results
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def instroke_static(w, metric, debug=False, **kwargs):
|
||||||
|
f1 = w.csvfilename[6:-4]
|
||||||
|
rowdata = rdata(csvfile=w.csvfilename)
|
||||||
|
|
||||||
|
|
||||||
|
timestr = strftime("%Y%m%d-%H%M%S")
|
||||||
|
imagename = f1+timestr+'.png'
|
||||||
|
fullpathimagename = 'static/plots/'+imagename
|
||||||
|
r = w.user
|
||||||
|
fig1 = rowdata.get_plot_instroke(metric)
|
||||||
|
canvas = FigureCanvas(fig1)
|
||||||
|
canvas.print_figure('static/plots/'+imagename)
|
||||||
|
plt.close(fig1)
|
||||||
|
fig1.clf()
|
||||||
|
|
||||||
|
try:
|
||||||
|
width, height = Image.open(fullpathimagename).size
|
||||||
|
except:
|
||||||
|
width = 1200
|
||||||
|
height = 600
|
||||||
|
|
||||||
|
imgs = GraphImage.objects.filter(workout=w)
|
||||||
|
if imgs.count() < 7:
|
||||||
|
i = GraphImage(workout=w,
|
||||||
|
creationdatetime=timezone.now(),
|
||||||
|
filename=fullpathimagename,
|
||||||
|
width=width, height=height)
|
||||||
|
|
||||||
|
i.save()
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover
|
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover
|
||||||
|
|||||||
@@ -42,14 +42,18 @@
|
|||||||
{% if instrokemetrics %}
|
{% if instrokemetrics %}
|
||||||
{% for metric in instrokemetrics %}
|
{% for metric in instrokemetrics %}
|
||||||
<p>
|
<p>
|
||||||
<a class="button blue small" href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
|
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
|
||||||
</p>
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/interactive/">NEW: Dynamic</a>
|
||||||
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Unfortunately, this workout doesn't have any in stroke metrics</p>
|
<p>Unfortunately, this workout doesn't have any in stroke metrics</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Advanced Features {% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type='text/javascript'
|
||||||
|
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||||
|
</script>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>jQuery UI Slider - Range slider</title>
|
||||||
|
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||||
|
<link rel="stylesheet" href="/resources/demos/style.css">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function submit_form() {
|
||||||
|
console.log("form changed");
|
||||||
|
var frm = $("#instrokeform");
|
||||||
|
var data = new FormData(frm[0]);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url:"/rowers/workout/{{ workout.id|encode }}/instroke/interactive/",
|
||||||
|
type: "POST",
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
data: data,
|
||||||
|
dataType: 'json',
|
||||||
|
|
||||||
|
success: function(data) {
|
||||||
|
console.log(data);
|
||||||
|
$("#id_script").replaceWith('<div id="id_script">'+data.script+'</d'+'iv>');
|
||||||
|
$("#id_chart").replaceWith('<div id="id_chart">'+data.div+'</d'+'iv>');
|
||||||
|
$("#ds").replaceWith('<div id="ds">'+data.ds+'</d'+'iv>');
|
||||||
|
$("#dd").replaceWith('<div id="dd">'+data.dd+'</d'+'iv>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
||||||
|
function csrfSafeMethod(method) {
|
||||||
|
return(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||||
|
}
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#instrokeform").on('change', function(evt) {
|
||||||
|
submit_form();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$( function() {
|
||||||
|
$( "#slider-range" ).slider({
|
||||||
|
range: true,
|
||||||
|
min: 0,
|
||||||
|
max: 60,
|
||||||
|
values: [ {{ spm_min }}, {{ spm_max }} ],
|
||||||
|
slide: function( event, ui ) {
|
||||||
|
$( "#amountspm" ).val(ui.values[ 0 ] + " - " + ui.values[ 1 ] );
|
||||||
|
$( "#id_spm_min" ).val( ui.values[ 0 ]);
|
||||||
|
$( "#id_spm_max" ).val(ui.values[ 1 ] );
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
$( "#amountspm" ).val($( "#slider-range" ).slider( "values", 0 ) +
|
||||||
|
" - " + $( "#slider-range" ).slider( "values", 1 ));
|
||||||
|
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$("#instrokeform").mouseup(function() {
|
||||||
|
submit_form();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$( function() {
|
||||||
|
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
|
||||||
|
$( "#slider-timerange" ).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-timerange" ).slider( "values", 0 ) +
|
||||||
|
" min - " + $( "#slider-timerange" ).slider( "values", 1 ) + " min ");
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
||||||
|
|
||||||
|
<script async="true" type="text/javascript">
|
||||||
|
Bokeh.set_log_level("info");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="id_script">
|
||||||
|
{{ the_script |safe }}
|
||||||
|
</div>
|
||||||
|
<div id="ds">
|
||||||
|
{{ ds |safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>In Stroke Metrics</h1>
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>This is an experimental prototype. It's behavior is not guaranteed. Response may be slow.
|
||||||
|
</li>
|
||||||
|
<li class="grid_4" >
|
||||||
|
<form id="instrokeform" enctype="multipart/form-data" action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<div id="slider-range"></div>
|
||||||
|
<p>
|
||||||
|
<label for="amountspm">Active Range:</label>
|
||||||
|
<input type="text" id="amountspm" readonly style="border:0; color:#1c75bc; font-weight:bold;">
|
||||||
|
</p>
|
||||||
|
<div id="slider-timerange"></div>
|
||||||
|
<p>
|
||||||
|
<label for="amount">Active Range:</label>
|
||||||
|
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
|
||||||
|
</p>
|
||||||
|
<input name='form' class="button" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="grid_4">
|
||||||
|
<div id="id_chart" class="flexplot">
|
||||||
|
{{ the_div|safe }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid_4">
|
||||||
|
<div id="dd" class="flexplot">
|
||||||
|
{{ dd|safe }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workout.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -249,6 +249,8 @@ urlpatterns = [
|
|||||||
path('404/', TemplateView.as_view(template_name='404.html'), name='404'),
|
path('404/', TemplateView.as_view(template_name='404.html'), name='404'),
|
||||||
path('400/', TemplateView.as_view(template_name='400.html'), name='400'),
|
path('400/', TemplateView.as_view(template_name='400.html'), name='400'),
|
||||||
path('403/', TemplateView.as_view(template_name='403.html'), name='403'),
|
path('403/', TemplateView.as_view(template_name='403.html'), name='403'),
|
||||||
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/instroke/interactive/$',
|
||||||
|
views.instroke_chart_interactive, name='instroke_chart_interactive'),
|
||||||
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
|
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
|
||||||
name='workouts_summaries_email_view'),
|
name='workouts_summaries_email_view'),
|
||||||
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),
|
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ from rowers.forms import (
|
|||||||
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
|
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
|
||||||
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
|
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
|
||||||
TrainingPlanBillingForm, InstantPlanSelectForm,
|
TrainingPlanBillingForm, InstantPlanSelectForm,
|
||||||
TrainingZonesForm,
|
TrainingZonesForm, InstrokeForm
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@@ -239,6 +239,7 @@ from rowers.rows import handle_uploaded_file, handle_uploaded_image
|
|||||||
from rowers.plannedsessions import *
|
from rowers.plannedsessions import *
|
||||||
from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv
|
from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv
|
||||||
from rowers.tasks import (
|
from rowers.tasks import (
|
||||||
|
instroke_static,
|
||||||
fetch_rojabo_session,
|
fetch_rojabo_session,
|
||||||
handle_sendemail_unrecognized, handle_sendemailnewcomment,
|
handle_sendemail_unrecognized, handle_sendemailnewcomment,
|
||||||
handle_request_post,
|
handle_request_post,
|
||||||
|
|||||||
+127
-32
@@ -2904,36 +2904,8 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
|
|||||||
instrokemetrics = rowdata.get_instroke_columns()
|
instrokemetrics = rowdata.get_instroke_columns()
|
||||||
|
|
||||||
if metric in instrokemetrics:
|
if metric in instrokemetrics:
|
||||||
f1 = w.csvfilename[6:-4]
|
job = myqueue(queuelow,
|
||||||
timestr = strftime("%Y%m%d-%H%M%S")
|
instroke_static,w, metric)
|
||||||
imagename = f1+timestr+'.png'
|
|
||||||
fullpathimagename = 'static/plots/'+imagename
|
|
||||||
u = w.user.user
|
|
||||||
r = getrower(u)
|
|
||||||
fig1 = rowdata.get_plot_instroke(metric)
|
|
||||||
canvas = FigureCanvas(fig1)
|
|
||||||
canvas.print_figure('static/plots/'+imagename)
|
|
||||||
plt.close(fig1)
|
|
||||||
fig1.clf()
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
try:
|
|
||||||
width, height = Image.open(fullpathimagename).size
|
|
||||||
except:
|
|
||||||
width = 1200
|
|
||||||
height = 600
|
|
||||||
|
|
||||||
imgs = GraphImage.objects.filter(workout=w)
|
|
||||||
if imgs.count() < 7:
|
|
||||||
i = GraphImage(workout=w,
|
|
||||||
creationdatetime=timezone.now(),
|
|
||||||
filename=fullpathimagename,
|
|
||||||
width=width, height=height)
|
|
||||||
|
|
||||||
i.save()
|
|
||||||
else:
|
|
||||||
messages.error(
|
|
||||||
request, 'You have reached the maximum number of static images for this workout. Delete an image first')
|
|
||||||
|
|
||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
url = reverse(r.defaultlandingpage,
|
url = reverse(r.defaultlandingpage,
|
||||||
@@ -2943,9 +2915,132 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
|
|||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
|
||||||
|
def instroke_chart_interactive(request, id=0):
|
||||||
|
|
||||||
|
is_ajax = request_is_ajax(request)
|
||||||
|
|
||||||
|
w = get_workoutuser(id, request)
|
||||||
|
|
||||||
|
rowdata = rrdata(csvfile=w.csvfilename)
|
||||||
|
instrokemetrics = rowdata.get_instroke_columns()
|
||||||
|
|
||||||
|
form = InstrokeForm(choices=instrokemetrics)
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
div = get_call()
|
||||||
|
|
||||||
|
metric = instrokemetrics[0]
|
||||||
|
spm_min = 15
|
||||||
|
spm_max = 45
|
||||||
|
|
||||||
|
activeminutesmax = int(rowdata.duration/60.)
|
||||||
|
activeminutesmin = 0
|
||||||
|
maxminutes = activeminutesmax
|
||||||
|
individual_curves = False
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = InstrokeForm(request.POST,choices=instrokemetrics)
|
||||||
|
if form.is_valid():
|
||||||
|
metric = form.cleaned_data['metric']
|
||||||
|
spm_min = form.cleaned_data['spm_min']
|
||||||
|
spm_max = form.cleaned_data['spm_max']
|
||||||
|
activeminutesmin = form.cleaned_data['activeminutesmin']
|
||||||
|
activeminutesmax = form.cleaned_data['activeminutesmax']
|
||||||
|
individual_curves = form.cleaned_data['individual_curves']
|
||||||
|
|
||||||
|
|
||||||
|
activesecondsmin = 60.*activeminutesmin
|
||||||
|
activesecondsmax = 60.*activeminutesmax
|
||||||
|
|
||||||
|
data = rowdata.get_instroke_data(
|
||||||
|
metric,
|
||||||
|
spm_min=spm_min,
|
||||||
|
spm_max=spm_max,
|
||||||
|
activeminutesmin=activeminutesmin,
|
||||||
|
activeminutesmax=activeminutesmax,
|
||||||
|
)
|
||||||
|
|
||||||
|
script, div = instroke_interactive_chart(
|
||||||
|
data, metric, w,
|
||||||
|
spm_min,
|
||||||
|
spm_max,
|
||||||
|
activeminutesmin,
|
||||||
|
activeminutesmax,
|
||||||
|
individual_curves,
|
||||||
|
)
|
||||||
|
|
||||||
|
# change to range spm_min to spm_max
|
||||||
|
vals, units, typ = rowdata.updateinterval_range(
|
||||||
|
' Cadence (stokes/min)', spm_min, spm_max,
|
||||||
|
debug=False, smoothwindow=2.,
|
||||||
|
activewindow=[activesecondsmin, activesecondsmax])
|
||||||
|
|
||||||
|
intervalstats = rowdata.allstats()
|
||||||
|
itime, idist, itype = rowdata.intervalstats_values()
|
||||||
|
|
||||||
|
intervaldata = {
|
||||||
|
'itime': itime,
|
||||||
|
'idist': idist,
|
||||||
|
'itype': itype,
|
||||||
|
'selector': '',
|
||||||
|
'normp': 0,
|
||||||
|
'normv': 0,
|
||||||
|
}
|
||||||
|
ds, dd = interactive_chart(encoder.decode_hex(
|
||||||
|
id), promember=1, intervaldata=intervaldata)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url': '/rowers/list-workouts/',
|
||||||
|
'name': 'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': get_workout_default_page(request, id),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': reverse('instroke_view', kwargs={'id': id}),
|
||||||
|
'name': 'In-Stroke Metrics'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
if is_ajax:
|
||||||
|
response = json.dumps({
|
||||||
|
'script': script,
|
||||||
|
'div': div,
|
||||||
|
'ds': ds,
|
||||||
|
'dd': dd,
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponse(response, content_type='application/json')
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'instroke_interactive.html',
|
||||||
|
{
|
||||||
|
'workout': w,
|
||||||
|
'rower': w.user,
|
||||||
|
'active': 'nav-workouts',
|
||||||
|
'breadcrumbs': breadcrumbs,
|
||||||
|
'teams': get_my_teams(request.user),
|
||||||
|
'instrokemetrics': instrokemetrics,
|
||||||
|
'form':form,
|
||||||
|
'the_script': script,
|
||||||
|
'the_div': div,
|
||||||
|
'spm_min': spm_min,
|
||||||
|
'spm_max': spm_max,
|
||||||
|
'ds': ds,
|
||||||
|
'dd': dd,
|
||||||
|
'activeminutesmin': activeminutesmin,
|
||||||
|
'activeminutesmax': activeminutesmax,
|
||||||
|
'maxminutes': maxminutes,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# erase column
|
# erase column
|
||||||
|
|
||||||
|
|
||||||
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
|
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
|
||||||
def workout_erase_column_view(request, id=0, column=''):
|
def workout_erase_column_view(request, id=0, column=''):
|
||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
|
|||||||
Reference in New Issue
Block a user