Private
Public Access
1
0

Merge branch 'release/v9.83'

This commit is contained in:
Sander Roosendaal
2019-04-29 22:31:36 +02:00
12 changed files with 1207 additions and 44 deletions
+1 -1
View File
@@ -157,7 +157,7 @@ ratelim==0.1.6
redis==3.2.1
requests==2.21.0
requests-oauthlib==1.2.0
rowingdata==2.2.7
rowingdata==2.3.1
rowingphysics==0.5.0
rq==1.0
rq-dashboard==0.4.0
+11 -5
View File
@@ -221,12 +221,15 @@ def filter_df(datadf, fieldname, value, largerthan=True):
except KeyError:
return datadf
if largerthan:
mask = datadf[fieldname] < value
else:
mask = datadf[fieldname] >= value
try:
if largerthan:
mask = datadf[fieldname] < value
else:
mask = datadf[fieldname] >= value
datadf.loc[mask, fieldname] = np.nan
datadf.loc[mask, fieldname] = np.nan
except TypeError:
pass
return datadf
@@ -2182,6 +2185,9 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
else:
drivenergy = drivelength * averageforce
if driveenergy.mean() == 0 and driveenergy.std() == 0:
driveenergy = 0*driveenergy+100
distance = rowdatadf.loc[:, 'cum_dist']
velo = 500. / p
+98 -16
View File
@@ -721,6 +721,17 @@ class HistoForm(forms.Form):
histoparam = forms.ChoiceField(choices=parchoices,initial='power',
label='Metric')
class AnalysisOptionsForm(forms.Form):
modality = forms.ChoiceField(choices=workouttypes,
label='Workout Type',
initial='all')
waterboattype = forms.MultipleChoiceField(choices=boattypes,
label='Water Boat Type',
initial = mytypes.waterboattype)
rankingonly = forms.BooleanField(initial=False,
label='Only Ranking Pieces',
required=False)
# form to select modality and boat type for trend flex
class TrendFlexModalForm(forms.Form):
@@ -810,22 +821,6 @@ class PlannedSessionMultipleCloneForm(forms.Form):
)
class BoxPlotChoiceForm(forms.Form):
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
label='Metric')
spmmin = forms.FloatField(initial=15,
required=False,label = 'Min SPM')
spmmax = forms.FloatField(initial=55,
required=False,label = 'Max SPM')
workmin = forms.FloatField(initial=0,
required=False,label = 'Min Work per Stroke')
workmax = forms.FloatField(initial=1500,
required=False,label = 'Max Work per Stroke')
includereststrokes = forms.BooleanField(initial=False,
required=False,
label='Include Rest Strokes')
grouplabels = axlabels.copy()
grouplabels['date'] = 'Date'
grouplabels['workoutid'] = 'Workout'
@@ -842,6 +837,93 @@ from rowers.utils import palettes
palettechoices = tuple((p,p) for p in palettes.keys())
analysischoices = (
('boxplot','Box Chart'),
('trendflex','Trend Flex'),
('histo','Histogram'),
('flexall','Cumulative Flex Chart'),
('stats','Statistics'),
)
class AnalysisChoiceForm(forms.Form):
axchoices = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None']
)
axchoices = dict((x,y) for x,y in axchoices)
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
yaxchoices = dict((x,y) for x,y in yaxchoices)
yaxchoices = list(sorted(yaxchoices.items(), key = lambda x:x[1]))
yaxchoices2 = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
)
yaxchoices2 = dict((x,y) for x,y in yaxchoices2)
yaxchoices2 = list(sorted(yaxchoices2.items(), key = lambda x:x[1]))
function = forms.ChoiceField(choices=analysischoices,initial='boxplot',
label='Analysis')
xaxis = forms.ChoiceField(
choices=axchoices,label='X-Axis',required=True,initial='spm')
yaxis1 = forms.ChoiceField(
choices=yaxchoices,label='Left Axis',required=True,initial='power')
yaxis2 = forms.ChoiceField(
choices=yaxchoices2,label='Right Axis',required=True,initial='None')
plotfield = forms.ChoiceField(choices=parchoices,initial='spm',
label='Metric')
xparam = forms.ChoiceField(choices=parchoicesmultiflex,
initial='hr',
label='X axis')
yparam = forms.ChoiceField(choices=parchoicesmultiflex,
initial='pace',
label='Y axis')
groupby = forms.ChoiceField(choices=groupchoices,initial='spm',
label='Group By')
binsize = forms.FloatField(initial=1,required=False,label = 'Bin Size')
ploterrorbars = forms.BooleanField(initial=False,
required=False,
label='Plot Error Bars')
palette = forms.ChoiceField(choices=palettechoices,
label = 'Color Scheme',
initial='monochrome_blue')
spmmin = forms.FloatField(initial=15,
required=False,label = 'Min SPM')
spmmax = forms.FloatField(initial=55,
required=False,label = 'Max SPM')
workmin = forms.FloatField(initial=0,
required=False,label = 'Min Work per Stroke')
workmax = forms.FloatField(initial=1500,
required=False,label = 'Max Work per Stroke')
includereststrokes = forms.BooleanField(initial=False,
required=False,
label='Include Rest Strokes')
class BoxPlotChoiceForm(forms.Form):
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
label='Metric')
spmmin = forms.FloatField(initial=15,
required=False,label = 'Min SPM')
spmmax = forms.FloatField(initial=55,
required=False,label = 'Max SPM')
workmin = forms.FloatField(initial=0,
required=False,label = 'Min Work per Stroke')
workmax = forms.FloatField(initial=1500,
required=False,label = 'Max Work per Stroke')
includereststrokes = forms.BooleanField(initial=False,
required=False,
label='Include Rest Strokes')
class MultiFlexChoiceForm(forms.Form):
xparam = forms.ChoiceField(choices=parchoicesmultiflex,
+12 -5
View File
@@ -1214,7 +1214,9 @@ def fitnessmetric_chart(fitnessmetrics,user,workoutmode='rower',startdate=None,
return [script,div]
def interactive_histoall(theworkouts,histoparam,includereststrokes):
def interactive_histoall(theworkouts,histoparam,includereststrokes,
spmmin=0,spmmax=55,
workmin=0,workmax=1500):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
ids = [int(w.id) for w in theworkouts]
@@ -1224,15 +1226,21 @@ def interactive_histoall(theworkouts,histoparam,includereststrokes):
rowdata.dropna(axis=0,how='any',inplace=True)
rowdata = dataprep.filter_df(rowdata,'spm',spmmin,largerthan=True)
rowdata = dataprep.filter_df(rowdata,'spm',spmmax,largerthan=False)
rowdata = dataprep.filter_df(rowdata,'driveenergy',workmin,largerthan=True)
rowdata = dataprep.filter_df(rowdata,'driveenergy',workmax,largerthan=False)
if rowdata.empty:
return "","No Valid Data Available","",""
return "","No Valid Data Available"
try:
histopwr = rowdata[histoparam].values
except KeyError:
return "","No data","",""
return "","No data"
if len(histopwr) == 0:
return "","No valid data available","",""
return "","No valid data available"
# throw out nans
histopwr = histopwr[~np.isinf(histopwr)]
@@ -1341,7 +1349,6 @@ def interactive_histoall(theworkouts,histoparam,includereststrokes):
script = ''
div = ''
return [script,div]
def course_map(course):
+1
View File
@@ -11,6 +11,7 @@
<p>Rower: {{ rower.user.first_name }}</p>
<a href="/rowers/user-analysis-select">Be adventurous and try our new Analysis page</a>
{% endblock %}
+76
View File
@@ -0,0 +1,76 @@
{% if stats %}
<h2>Statistics</h2>
<table width="100%" class="listtable">
<thead>
<tr>
<th>Metric</th>
<th>Mean</th>
<th>Minimum</th>
<th>25&#37;</th>
<th>Median</th>
<th>75&#37;</th>
<th>Maximum</th>
<th>Standard Deviation</th>
</tr>
</thead>
<tbody>
{% for key, value in stats.items() %}
<tr>
<td>{{ value.verbosename }}</td>
<td>{{ value.mean|floatformat }}</td>
<td>{{ value.min|floatformat }}</td>
<td>{{ value.firstq|floatformat }}</td>
<td>{{ value.median|floatformat }}</td>
<td>{{ value.thirdq|floatformat }}</td>
<td>{{ value.max|floatformat }}</td>
<td>{{ value.std|floatformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if cordict %}
<h2> Correlation matrix</h2>
<p>This matrix indicates a positive (+) or negative (-) correlation between two parameters. The Spearman correlation coefficient has values between +1 and -1. Positive correlation between two metrics means that if one metric increases, the other value is also likely to increase. Negative is the opposite. The further from zero, the higher the likelyhood.
</p>
<table width="90%" class="cortable">
<thead>
<tr>
<th>&nbsp;</th>
{% for key,value in cordict.items() %}
<th class="rotate"><div><span>{{ key }}</span></div></th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for key, thedict in cordict.items() %}
<tr>
<th> {{ key }}</th>
{% for key2,value in thedict.items() %}
<td>
{% if value > 0.5 %}
<div class="poscor">{{ value|floatformat }}</div>
{% elif value > 0.1 %}
<div class="weakposcor">{{ value|floatformat }}</div>
{% elif value < -0.5 %}
<div class="negcor">{{ value|floatformat }}</div>
{% elif value < -0.1 %}
<div class="weaknegcor">{{ value|floatformat }}</div>
{% else %}
&nbsp;
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
+322
View File
@@ -0,0 +1,322 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Workouts{% endblock %}
{% block main %}
<script>
function toggle(source) {
checkboxes = document.querySelectorAll("input[name='workouts']");
for(var i=0, n=checkboxes.length;i<n;i++) {
checkboxes[i].checked = source.checked;
}
}
</script>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(function() {
// Get the form fields and hidden div
var modality = $("#id_modality");
var hidden = $("#id_waterboattype");
// Hide the fields.
// Use JS to do this in case the user doesn't have JS
// enabled.
hidden.hide();
if (modality.val() == 'water') {
hidden.show();
}
// Setup an event listener for when the state of the
// checkbox changes.
modality.change(function() {
// Check to see if the checkbox is checked.
// If it is, show the fields and populate the input.
// If not, hide the fields.
var Value = modality.val();
if (Value=='water') {
// Show the hidden fields.
hidden.show();
} else {
// Make sure that the hidden fields are indeed
// hidden.
hidden.hide();
// You may also want to clear the value of the
// hidden fields here. Just in case somebody
// shows the fields, enters data to them and then
// unticks the checkbox.
//
// This would do the job:
//
// $("#hidden_field").val("");
}
});
});
</script>
<script>
// script for chart options form
$(function() {
// Get the form fields and hidden div
var functionfield = $("#id_function");
var plotfield = $("#id_plotfield").parent().parent();
var x_param = $("#id_xparam").parent().parent();
var y_param = $("#id_yparam").parent().parent();
var groupby = $("#id_groupby").parent().parent();
var binsize = $("#id_binsize").parent().parent();
var errorbars = $("#id_ploterrorbars").parent().parent();
var palette = $("#id_palette").parent().parent();
var spmmin = $("#id_spmmin").parent().parent();
var spmmax = $("#id_spmmax").parent().parent();
var workmin = $("#id_workmin").parent().parent();
var workmax = $("#id_workmax").parent().parent();
var xaxis = $("#id_xaxis").parent().parent();
var yaxis1 = $("#id_yaxis1").parent().parent();
var yaxis2 = $("#id_yaxis2").parent().parent();
// Hide the fields.
// Use JS to do this in case the user doesn't have JS
// enabled.
plotfield.hide();
x_param.hide();
y_param.hide();
groupby.hide();
errorbars.hide();
palette.hide();
binsize.hide();
xaxis.hide();
yaxis1.hide();
yaxis2.hide();
if (functionfield.val() == 'boxplot') {
plotfield.show();
};
if (functionfield.val() == 'histo') {
plotfield.show()
};
if (functionfield.val() == 'trendflex') {
x_param.show();
y_param.show();
groupby.show();
palette.show();
binsize.show();
errorbars.show();
};
if (functionfield.val() == 'flexall') {
xaxis.show();
yaxis1.show();
yaxis2.show();
}
if (functionfield.val() == 'stats') {
plotfield.hide();
}
// Setup an event listener for when the state of the
// checkbox changes.
functionfield.change(function() {
// Check to see if the checkbox is checked.
// If it is, show the fields and populate the input.
// If not, hide the fields.
var Value = functionfield.val();
if (Value=='boxplot') {
// Show the hidden fields.
plotfield.show();
spmmin.show();
spmmax.show();
workmin.show();
workmax.show();
x_param.hide();
y_param.hide();
groupby.hide();
palette.hide();
binsize.hide();
errorbars.hide();
xaxis.hide();
yaxis1.hide();
yaxis2.hide();
}
else if (Value=='histo') {
plotfield.show();
spmmin.show();
spmmax.show();
workmin.show();
workmax.show();
x_param.hide();
y_param.hide();
groupby.hide();
palette.hide();
binsize.hide();
errorbars.hide();
xaxis.hide();
yaxis1.hide();
yaxis2.hide();
}
else if (Value=='trendflex') {
x_param.show();
y_param.show();
groupby.show();
palette.show();
binsize.show();
errorbars.show();
spmmin.show();
spmmax.show();
workmin.show();
workmax.show();
plotfield.hide();
xaxis.hide();
yaxis1.hide();
yaxis2.hide();
}
else if (Value=='flexall') {
xaxis.show();
yaxis1.show();
yaxis2.show();
x_param.hide();
y_param.hide();
groupby.hide();
spmmin.hide();
spmmax.hide();
workmin.hide();
workmax.hide();
plotfield.hide();
palette.hide();
binsize.hide();
errorbars.hide();
}
else if (Value=='stats') {
xaxis.hide();
yaxis1.hide();
yaxis2.hide();
x_param.hide();
y_param.hide();
groupby.hide();
plotfield.hide();
palette.hide();
binsize.hide();
errorbars.hide();
}
});
});
</script>
<div id="id_css_res">
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.0.4.min.css" type="text/css" />
</div>
<div id="id_js_res">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.0.4.min.js"></script>
</div>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
</div>
<ul class="main-content">
<li class="grid_4">
<div id="id_chart">
{{ the_div|safe }}
</div>
</li>
<li class="grid_4">
<p>You can use the date and search forms to search through all
workouts from this team.</p>
<p>TIP: Agree with your team members to put tags (e.g. '8x500m') in the notes section of
your workouts. That makes it easy to search.</p>
</li>
<li class="grid_2 maxheight">
<form id="searchform" action=""
method="get" accept-charset="utf-8">
{{ searchform }}
<input type="submit" value="GO"></input>
</form>
<form enctype="multipart/form-data" action="" method="post">
{% if workouts %}
<input type="checkbox" onClick="toggle(this)" /> Toggle All<br/>
<table width="100%" class="listtable">
{{ form.as_table }}
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</li>
<li class="grid_2">
<p>Select two or more workouts, set your plot settings below,
and press submit
</p>
{% csrf_token %}
<table>
{{ chartform.as_table }}
</table>
</li>
<li class="grid_2">
<form enctype="multipart/form-data" method="post">
<table>
{{ dateform.as_table }}
</table>
<table>
{{ optionsform.as_table }}
</table>
{% csrf_token %}
<input name='optionsform' type="submit" value="Submit">
</form>
</li>
</ul>
{% endblock %}
{% block scripts %}
{% if request.method == 'POST' %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script>
$(function($) {
console.log('loading script');
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/analysisdata/', function(json) {
var counter=0;
var script = json.script;
var div = json.div;
$("#id_sitready").remove();
$("#id_chart").append(div);
console.log(div);
$("#id_script").append("<script>"+script+"</s"+"cript>");
});
});
</script>
{% endif %}
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}
Binary file not shown.
+5
View File
@@ -223,6 +223,10 @@ urlpatterns = [
re_path(r'^workouts-join-select/user/(?P<userid>\d+)/$',views.workouts_join_select,name='workouts_join_select'),
re_path(r'^user-boxplot-select/user/(?P<userid>\d+)/$',views.user_boxplot_select,name='user_boxplot_select'),
re_path(r'^user-boxplot-select/$',views.user_boxplot_select,name='user_boxplot_select'),
re_path(r'^user-analysis-select/(?P<function>\w.*)/user/(?P<userid>\d+)/$',views.analysis_new,name='analysis_new'),
re_path(r'^user-analysis-select/(?P<function>\w.*)/$',views.analysis_new,name='analysis_new'),
re_path(r'^user-analysis-select/user/(?P<userid>\d+)/$',views.analysis_new,name='analysis_new'),
re_path(r'^user-analysis-select/$',views.analysis_new,name='analysis_new'),
# re_path(r'^user-multiflex-select/user/(?P<userid>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.user_multiflex_select,name='user_multiflex_select'),
re_path(r'^user-multiflex-select/user/(?P<userid>\d+)/$',views.user_multiflex_select,name='user_multiflex_select'),
# re_path(r'^user-multiflex-select/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.user_multiflex_select,name='user_multiflex_select'),
@@ -259,6 +263,7 @@ urlpatterns = [
# re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'),
# re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/$',views.cum_flex,name='cum_flex'),
re_path(r'^analysisdata/$',views.analysis_view_data,name='analysis_view_data'),
re_path(r'^flexall/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexall/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexalldata/$',views.cum_flex_data,name='cum_flex_data'),
+674 -12
View File
@@ -5,6 +5,663 @@ from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rowers.views.statements import *
from jinja2 import Template,Environment,FileSystemLoader
def floatformat(x,prec=2):
return '{x}'.format(x=round(x,prec))
env = Environment(loader = FileSystemLoader(["rowers/templates"]))
env.filters['floatformat'] = floatformat
from django.contrib.staticfiles import finders
# generic Analysis view -
defaultoptions = {
'includereststrokes': False,
'workouttypes':['rower','dynamic','slides'],
'waterboattype': mytypes.waterboattype,
'rankingonly': False,
'function':'boxplot'
}
@user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher",
redirect_field_name=None)
def analysis_new(request,userid=0,function='boxplot'):
r = getrequestrower(request, userid=userid)
user = r.user
userid = user.id
if 'options' in request.session:
options = request.session['options']
else:
options=defaultoptions
options['userid'] = userid
try:
workouttypes = options['workouttypes']
except KeyError:
workouttypes = ['rower','dynamic','slides']
try:
rankingonly = options['rankingonly']
except KeyError:
rankingonly = False
try:
includereststrokes = options['includereststrokes']
except KeyError:
includereststrokes = False
if 'startdate' in request.session:
startdate = iso8601.parse_date(request.session['startdate'])
if 'enddate' in request.session:
enddate = iso8601.parse_date(request.session['enddate'])
workstrokesonly = not includereststrokes
waterboattype = mytypes.waterboattype
if request.method == 'POST':
thediv = get_call()
dateform = DateRangeForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
request.session['startdate'] = startdatestring
request.session['enddate'] = enddatestring
optionsform = AnalysisOptionsForm(request.POST)
if optionsform.is_valid():
for key, value in optionsform.cleaned_data.items():
options[key] = value
modality = optionsform.cleaned_data['modality']
waterboattype = optionsform.cleaned_data['waterboattype']
if modality == 'all':
modalities = [m[0] for m in mytypes.workouttypes]
else:
modalities = [modality]
if modality != 'water':
waterboattype = [b[0] for b in mytypes.boattypes]
if 'rankingonly' in optionsform.cleaned_data:
rankingonly = optionsform.cleaned_data['rankingonly']
else:
rankingonly = False
options['modalities'] = modalities
options['waterboattype'] = waterboattype
chartform = AnalysisChoiceForm(request.POST)
if chartform.is_valid():
for key, value in chartform.cleaned_data.items():
options[key] = value
form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
selectedworkouts = cd['workouts']
ids = [int(w.id) for w in selectedworkouts]
options['ids'] = ids
else:
ids = []
options['ids'] = ids
else:
thediv = ''
dateform = DateRangeForm(initial={
'startdate':startdate,
'enddate':enddate,
})
if 'modalities' in request.session:
modalities = request.session['modalities']
if len(modalities) > 1:
modality = 'all'
else:
modality = modalities[0]
else:
modalities = [m[0] for m in mytypes.workouttypes]
modality = 'all'
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype:
negtypes.append(b[0])
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
if enddate < startdate:
s = enddate
enddate = startdate
startdate = s
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype:
negtypes.append(b[0])
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities,
).order_by(
"-date", "-starttime"
).exclude(boattype__in=negtypes)
if rankingonly:
workouts = workouts.exclude(rankingpiece=False)
query = request.GET.get('q')
if query:
query_list = query.split()
workouts = workouts.filter(
reduce(operator.and_,
(Q(name__icontains=q) for q in query_list)) |
reduce(operator.and_,
(Q(notes__icontains=q) for q in query_list))
)
searchform = SearchForm(initial={'q':query})
else:
searchform = SearchForm()
if request.method != 'POST':
form = WorkoutMultipleCompareForm()
chartform = AnalysisChoiceForm()
selectedworkouts = Workout.objects.none()
else:
selectedworkouts = Workout.objects.filter(id__in=ids)
form.fields["workouts"].queryset = workouts | selectedworkouts
optionsform = AnalysisOptionsForm(initial={
'modality':modality,
'waterboattype':waterboattype,
'rankingonly':rankingonly,
})
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
request.session['startdate'] = startdatestring
request.session['enddate'] = enddatestring
request.session['options'] = options
breadcrumbs = [
{
'url':'/rowers/analysis',
'name':'Analysis'
},
{
'url':reverse('analysis_new',kwargs={'userid':userid}),
'name': 'Analysis Select'
},
]
return render(request, 'user_analysis_select.html',
{'workouts': workouts,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'rower':r,
'breadcrumbs':breadcrumbs,
'theuser':user,
'the_div':thediv,
'form':form,
'active':'nav-analysis',
'chartform':chartform,
'searchform':searchform,
'optionsform':optionsform,
'teams':get_my_teams(request.user),
})
def trendflexdata(workouts, options,userid=0):
includereststrokes = options['includereststrokes']
palette = options['palette']
groupby = options['groupby']
binsize = options['binsize']
xparam = options['xparam']
yparam = options['yparam']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ploterrorbars = options['ploterrorbars']
ids = options['ids']
workstrokesonly = not includereststrokes
labeldict = {
int(w.id): w.__str__() for w in workouts
}
fieldlist,fielddict = dataprep.getstatsfields()
fieldlist = [xparam,yparam,groupby,
'workoutid','spm','driveenergy',
'workoutstate']
# prepare data frame
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
if xparam == groupby:
datadf['groupby'] = datadf[xparam]
groupy = 'groupby'
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
datadf = dataprep.filter_df(datadf,'spm',spmmin,
largerthan=True)
datadf = dataprep.filter_df(datadf,'spm',spmmax,
largerthan=False)
datadf = dataprep.filter_df(datadf,'driveenergy',workmin,
largerthan=True)
datadf = dataprep.filter_df(datadf,'driveneergy',workmax,
largerthan=False)
datadf.dropna(axis=0,how='any',inplace=True)
datemapping = {
w.id:w.date for w in workouts
}
datadf['date'] = datadf['workoutid']
datadf['date'].replace(datemapping,inplace=True)
today = datetime.date.today()
datadf['days ago'] = map(lambda x : x.days, datadf.date - today)
if groupby != 'date':
try:
bins = np.arange(datadf[groupby].min()-binsize,
datadf[groupby].max()+binsize,
binsize)
groups = datadf.groupby(pd.cut(datadf[groupby],bins,labels=False))
except ValueError:
messages.error(
request,
"Unable to compete. Probably not enough data selected"
)
url = reverse(user_multiflex_select)
return HttpResponseRedirect(url)
else:
bins = np.arange(datadf['days ago'].min()-binsize,
datadf['days ago'].max()+binsize,
binsize,
)
groups = datadf.groupby(pd.cut(datadf['days ago'], bins,
labels=False))
xvalues = groups.mean()[xparam]
yvalues = groups.mean()[yparam]
xerror = groups.std()[xparam]
yerror = groups.std()[yparam]
groupsize = groups.count()[xparam]
mask = groupsize <= min([0.01*groupsize.sum(),0.2*groupsize.mean()])
xvalues.loc[mask] = np.nan
yvalues.loc[mask] = np.nan
xerror.loc[mask] = np.nan
yerror.loc[mask] = np.nan
groupsize.loc[mask] = np.nan
xvalues.dropna(inplace=True)
yvalues.dropna(inplace=True)
xerror.dropna(inplace=True)
yerror.dropna(inplace=True)
groupsize.dropna(inplace=True)
if len(groupsize) == 0:
messages.error(request,'No data in selection')
url = reverse(user_multiflex_select)
return HttpResponseRedirect(url)
else:
groupsize = 30.*np.sqrt(groupsize/float(groupsize.max()))
df = pd.DataFrame({
xparam:xvalues,
yparam:yvalues,
'x':xvalues,
'y':yvalues,
'xerror':xerror,
'yerror':yerror,
'groupsize':groupsize,
})
if yparam == 'pace':
df['y'] = dataprep.paceformatsecs(df['y']/1.0e3)
aantal = len(df)
if groupby != 'date':
try:
df['groupval'] = groups.mean()[groupby]
df['groupval'].loc[mask] = np.nan
groupcols = df['groupval']
except ValueError:
df['groupval'] = groups.mean()[groupby].fillna(value=0)
df['groupval'].loc[mask] = np.nan
groupcols = df['groupval']
except KeyError:
messages.error(request,'Data selection error')
url = reverse(user_multiflex_select)
return HttpResponseRedirect(url)
else:
try:
dates = groups.min()[groupby]
dates.loc[mask] = np.nan
dates.dropna(inplace=True)
df['groupval'] = [x.strftime("%Y-%m-%d") for x in dates]
df['groupval'].loc[mask] = np.nan
groupcols = 100.*np.arange(aantal)/float(aantal)
except AttributeError:
df['groupval'] = groups.mean()['days ago'].fillna(value=0)
groupcols = 100.*np.arange(aantal)/float(aantal)
groupcols = (groupcols-groupcols.min())/(groupcols.max()-groupcols.min())
if aantal == 1:
groupcols = np.array([1.])
colors = range_to_color_hex(groupcols,palette=palette)
df['color'] = colors
clegendx = np.arange(0,1.2,.2)
legcolors = range_to_color_hex(clegendx,palette=palette)
if groupby != 'date':
clegendy = df['groupval'].min()+clegendx*(df['groupval'].max()-df['groupval'].min())
else:
clegendy = df.index.min()+clegendx*(df.index.max()-df.index.min())
colorlegend = zip(range(6),clegendy,legcolors)
if userid == 0:
extratitle = ''
else:
u = User.objects.get(id=userid)
extratitle = ' '+u.first_name+' '+u.last_name
script,div = interactive_multiflex(df,xparam,yparam,
groupby,
extratitle=extratitle,
ploterrorbars=ploterrorbars,
binsize=binsize,
colorlegend=colorlegend,
spmmin=spmmin,spmmax=spmmax,
workmin=workmin,workmax=workmax)
scripta= script.split('\n')[2:-1]
script = ''.join(scripta)
return(script,div)
def flexalldata(workouts, options):
includereststrokes = options['includereststrokes']
xparam = options['xaxis']
yparam1 = options['yaxis1']
yparam2 = options['yaxis2']
promember=True
workstrokesonly = not includereststrokes
res = interactive_cum_flex_chart2(workouts, xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
promember=promember,
workstrokesonly=workstrokesonly,
)
script = res[0]
div = res[1]
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script,div)
def histodata(workouts, options):
includereststrokes = options['includereststrokes']
plotfield = options['plotfield']
function = options['function']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
workstrokesonly = not includereststrokes
script, div = interactive_histoall(workouts,plotfield,includereststrokes,
spmmin=spmmin,spmmax=spmmax,workmin=workmin,workmax=workmax)
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script,div)
def statsdata(workouts, options):
includereststrokes = options['includereststrokes']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ids = options['ids']
userid = options['userid']
plotfield = options['plotfield']
function = options['function']
workstrokesonly = not includereststrokes
ids = [w.id for w in workouts]
datamapping = {
w.id:w.date for w in workouts
}
fieldlist,fielddict = dataprep.getstatsfields()
# prepare data frame
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
# Create stats
stats = {}
fielddict.pop('workoutstate')
fielddict.pop('workoutid')
for field,verbosename in fielddict.items():
thedict = {
'mean':datadf[field].mean(),
'min': datadf[field].min(),
'std': datadf[field].std(),
'max': datadf[field].max(),
'median': datadf[field].median(),
'firstq':datadf[field].quantile(q=0.25),
'thirdq':datadf[field].quantile(q=0.75),
'verbosename':verbosename,
}
stats[field] = thedict
# Create a dict with correlation values
cor = datadf.corr(method='spearman')
cor.fillna(value=0,inplace=True)
cordict = {}
for field1,verbosename in fielddict.items():
thedict = {}
for field2,verbosename in fielddict.items():
try:
thedict[field2] = cor.loc[field1,field2]
except KeyError:
thedict[field2] = 0
cordict[field1] = thedict
context = {
'stats':stats,
'cordict':cordict,
}
htmly = env.get_template('statsdiv.html')
html_content = htmly.render(context)
return('',html_content)
def boxplotdata(workouts,options):
includereststrokes = options['includereststrokes']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ids = options['ids']
userid = options['userid']
plotfield = options['plotfield']
function = options['function']
workstrokesonly = not includereststrokes
labeldict = {
int(w.id): w.__str__() for w in workouts
}
datemapping = {
w.id:w.date for w in workouts
}
fieldlist,fielddict = dataprep.getstatsfields()
fieldlist = [plotfield,'workoutid','spm','driveenergy',
'workoutstate']
ids = [w.id for w in workouts]
# prepare data frame
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
datadf = dataprep.filter_df(datadf,'spm',spmmin,
largerthan=True)
datadf = dataprep.filter_df(datadf,'spm',spmmax,
largerthan=False)
datadf = dataprep.filter_df(datadf,'driveenergy',workmin,
largerthan=True)
datadf = dataprep.filter_df(datadf,'driveneergy',workmax,
largerthan=False)
datadf.dropna(axis=0,how='any',inplace=True)
datadf['workoutid'].replace(datemapping,inplace=True)
datadf.rename(columns={"workoutid":"date"},inplace=True)
datadf = datadf.sort_values(['date'])
if userid == 0:
extratitle = ''
else:
u = User.objects.get(id=userid)
extratitle = ' '+u.first_name+' '+u.last_name
script,div = interactive_boxchart(datadf,plotfield,
extratitle=extratitle,
spmmin=spmmin,spmmax=spmmax,workmin=workmin,workmax=workmax)
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script,div)
@user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher",
redirect_field_name=None)
def analysis_view_data(request,userid=0):
if 'options' in request.session:
options = request.session['options']
else:
options = defaultoptions
if userid==0:
userid = request.user.id
workouts = []
ids = options['ids']
function = options['function']
if not ids:
return JSONResponse({
"script":'',
"div":'No data found'
})
for id in ids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist:
pass
if function == 'boxplot':
script, div = boxplotdata(workouts,options)
elif function == 'trendflex':
script, div = trendflexdata(workouts, options,userid=userid)
elif function == 'histo':
script, div = histodata(workouts, options)
elif function == 'flexall':
script,div = flexalldata(workouts,options)
elif function == 'stats':
script,div = statsdata(workouts,options)
else:
script = ''
div = 'Unknown analysis functions'
return JSONResponse({
"script":script,
"div":div,
})
# Histogram for a date/time range
@user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher",
@@ -2412,6 +3069,8 @@ def multiflex_data(request,userid=0,
'ploterrorbars':False,
}):
def_options = options
if 'options' in request.session:
options = request.session['options']
@@ -2436,16 +3095,16 @@ def multiflex_data(request,userid=0,
userid = request.user.id
palette = options['palette']
groupby = options['groupby']
binsize = options['binsize']
xparam = options['xparam']
yparam = options['yparam']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ids = options['ids']
palette = keyvalue_get_default('palette',options, def_options)
groupby = keyvalue_get_default('groupby',options, def_options)
binsize = keyvalue_get_default('binsize',options, def_options)
xparam = keyvalue_get_default('xparam',options, def_options)
yparam = keyvalue_get_default('yparam',options, def_options)
spmmin = keyvalue_get_default('spmmin',options, def_options)
spmmax = keyvalue_get_default('spmmax',options, def_options)
workmin = keyvalue_get_default('workmin',options, def_options)
workmax = keyvalue_get_default('workmax',options, def_options)
ids = keyvalue_get_default('ids',options, def_options)
workouts = []
@@ -2496,7 +3155,10 @@ def multiflex_data(request,userid=0,
datadf['date'].replace(datemapping,inplace=True)
today = datetime.date.today()
datadf['days ago'] = map(lambda x : x.days, datadf.date - today)
try:
datadf['days ago'] = map(lambda x : x.days, datadf.date - today)
except TypeError:
datadf['days ago'] = 0
if groupby != 'date':
try:
@@ -2769,7 +3431,7 @@ def multiflex_view(request,userid=0,
options['spmmax'] = spmmax
options['workmin'] = workmin
options['workmax'] = workmax
options['ids'] = ids
options['idso'] = ids
request.session['options'] = options
+1
View File
@@ -80,6 +80,7 @@ from rowers.forms import (
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm,
TrendFlexModalForm,WorkoutSplitForm,WorkoutJoinParamForm,
AnalysisOptionsForm, AnalysisChoiceForm,
PlannedSessionMultipleCloneForm,SessionDateShiftForm,
)
from rowers.models import (
+6 -5
View File
@@ -1798,11 +1798,12 @@ def workout_downloadwind_view(request,id=0,
windspeed = winddata[0]
windbearing = winddata[1]
message = winddata[2]
try:
row.notes += "\n"+message
except TypeError:
if message and row.notes:
row.notes += message
if message is not None:
try:
row.notes += "\n"+message
except TypeError:
if message is not None and row.notes is not None:
row.notes += message
row.save()
rowdata.add_wind(windspeed,windbearing)