From b26f7df37313e94869440a8c95548d035371aa45 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 6 Jul 2017 09:37:45 +0200
Subject: [PATCH 1/4] bug checking box plot select older workouts
---
rowers/views.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/rowers/views.py b/rowers/views.py
index 4a580263..a1f76391 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -3480,6 +3480,8 @@ def boxplot_view(request,userid=0,
datadf.dropna(axis=0,how='any',inplace=True)
+ print datadf.info()
+
datadf['workoutid'].replace(datemapping,inplace=True)
datadf.rename(columns={"workoutid":"date"},inplace=True)
datadf = datadf.sort_values(['date'])
@@ -3565,6 +3567,8 @@ def boxplot_view(request,userid=0,
datadf.dropna(axis=0,how='any',inplace=True)
+ print datadf.info()
+
datadf['workoutid'].replace(datemapping,inplace=True)
datadf.rename(columns={"workoutid":"date"},inplace=True)
datadf = datadf.sort_values(['date'])
From cde3eaf60595ccf8d193a20e5262c7824aefdcea Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 6 Jul 2017 09:57:20 +0200
Subject: [PATCH 2/4] start of multiflex
---
rowers/interactiveplots.py | 20 ++++
rowers/templates/analysis.html | 14 ++-
rowers/templates/user_multiflex_select.html | 118 ++++++++++++++++++++
rowers/urls.py | 4 +
rowers/views.py | 91 +++++++++++++++
5 files changed, 246 insertions(+), 1 deletion(-)
create mode 100644 rowers/templates/user_multiflex_select.html
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index e8b5ec77..a8e3f38a 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -69,7 +69,27 @@ watermarkw = 184
watermarkh = 35
watermarkanchor = 'bottom_right'
+def errorbar(fig, x, y, xerr=None, yerr=None, color='red',
+ point_kwargs={}, error_kwargs={}):
+ fig.circle(x, y, color=color, **point_kwargs)
+
+ if xerr:
+ x_err_x = []
+ x_err_y = []
+ for px, py, err in zip(x, y, xerr):
+ x_err_x.append((px - err, px + err))
+ x_err_y.append((py, py))
+ fig.multi_line(x_err_x, x_err_y, color=color, **error_kwargs)
+
+ if yerr:
+ y_err_x = []
+ y_err_y = []
+ for px, py, err in zip(x, y, yerr):
+ y_err_x.append((px, px))
+ y_err_y.append((py - err, py + err))
+ fig.multi_line(y_err_x, y_err_y, color=color, **error_kwargs)
+
def tailwind(bearing,vwind,winddir):
""" Calculates head-on head/tailwind in direction of rowing
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index 4eae9987..2aeca53a 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -95,7 +95,7 @@
Analyse power vs piece duration to make predictions.
-
+
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
Multi Compare
@@ -107,6 +107,18 @@
Compare many workouts
+
+
+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
+ Multi Flex
+ {% else %}
+ Multi Flex
+ {% endif %}
+
+
+ Select workouts and make X-Y charts of averages over various metrics
+
+
diff --git a/rowers/templates/user_multiflex_select.html b/rowers/templates/user_multiflex_select.html
new file mode 100644
index 00000000..2033faf9
--- /dev/null
+++ b/rowers/templates/user_multiflex_select.html
@@ -0,0 +1,118 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workouts{% endblock %}
+
+{% block content %}
+
+
+
+ {% if team %}
+
+ {% include "teambuttons.html" with teamid=team.id team=team %}
+
+{% endif %}
+
+
+ {% if theuser %}
+
{{ theuser.first_name }}'s Workouts
+ {% else %}
+ {{ user.first_name }}'s Workouts
+ {% endif %}
+
+
+ {% if user.is_authenticated and user|is_manager %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index edf1f23d..9c415836 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -129,6 +129,10 @@ urlpatterns = [
url(r'^user-boxplot-select/user/(?P
\d+)/$',views.user_boxplot_select),
url(r'^user-boxplot-select/(?P\w+.*)/(?P\w+.*)$',views.user_boxplot_select),
url(r'^user-boxplot-select/$',views.user_boxplot_select),
+ url(r'^user-multiflex-select/user/(?P\d+)/(?P\w+.*)/(?P\w+.*)$',views.user_multiflex_select),
+ url(r'^user-multiflex-select/user/(?P\d+)/$',views.user_multiflex_select),
+ url(r'^user-multiflex-select/(?P\w+.*)/(?P\w+.*)$',views.user_multiflex_select),
+ url(r'^user-multiflex-select/$',views.user_multiflex_select),
url(r'^list-graphs/$',views.graphs_view),
url(r'^(?P\d+)/ote-bests/(?P\w+.*)/(?P\w+.*)$',views.rankings_view),
url(r'^(?P\d+)/ote-bests/(?P\d+)$',views.rankings_view),
diff --git a/rowers/views.py b/rowers/views.py
index a1f76391..63562b39 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -3324,6 +3324,97 @@ def multi_compare_view(request):
url = reverse(workouts_view)
return HttpResponseRedirect(url)
+# Box plots
+@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
+def user_multiflex_select(request,
+ startdatestring="",
+ enddatestring="",
+ message='',
+ successmessage='',
+ startdate=timezone.now()-datetime.timedelta(days=30),
+ enddate=timezone.now()+datetime.timedelta(days=1),
+ userid=0):
+
+ if userid == 0:
+ user = request.user
+ else:
+ user = User.objects.get(id=userid)
+
+
+ r = getrower(user)
+
+ if 'startdate' in request.session:
+ startdate = iso8601.parse_date(request.session['startdate'])
+
+
+ if 'enddate' in request.session:
+ enddate = iso8601.parse_date(request.session['enddate'])
+
+
+ if request.method == 'POST':
+ dateform = DateRangeForm(request.POST)
+ if dateform.is_valid():
+ startdate = dateform.cleaned_data['startdate']
+ enddate = dateform.cleaned_data['enddate']
+ else:
+ dateform = DateRangeForm(initial={
+ 'startdate':startdate,
+ 'enddate':enddate,
+ })
+
+ startdate = datetime.datetime.combine(startdate,datetime.time())
+ enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
+ enddate = enddate+datetime.timedelta(days=1)
+
+ if startdatestring:
+ startdate = iso8601.parse_date(startdatestring)
+ if enddatestring:
+ enddate = iso8601.parse_date(enddatestring)
+
+ if enddate < startdate:
+ s = enddate
+ enddate = startdate
+ startdate = s
+
+
+ workouts = Workout.objects.filter(user=r,
+ startdatetime__gte=startdate,
+ startdatetime__lte=enddate).order_by("-date", "-starttime")
+
+ 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))
+ )
+
+ form = WorkoutMultipleCompareForm()
+ form.fields["workouts"].queryset = workouts
+
+ chartform = BoxPlotChoiceForm()
+
+ messages.info(request,successmessage)
+ messages.error(request,message)
+
+ startdatestring = startdate.strftime('%Y-%m-%d')
+ enddatestring = enddate.strftime('%Y-%m-%d')
+ request.session['startdate'] = startdatestring
+ request.session['enddate'] = enddatestring
+
+ return render(request, 'user_boxplot_select.html',
+ {'workouts': workouts,
+ 'dateform':dateform,
+ 'startdate':startdate,
+ 'enddate':enddate,
+ 'theuser':user,
+ 'form':form,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+
# Box plots
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def user_boxplot_select(request,
From 7eacc17fd1d04af281cb2053adec7240b32373f8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 6 Jul 2017 13:19:46 +0200
Subject: [PATCH 3/4] multi flex chart
---
rowers/forms.py | 31 +++
rowers/interactiveplots.py | 147 +++++++++--
rowers/templates/multiflex.html | 70 +++++
rowers/templates/user_multiflex_select.html | 10 +-
rowers/urls.py | 3 +
rowers/views.py | 273 +++++++++++++++++++-
6 files changed, 503 insertions(+), 31 deletions(-)
create mode 100644 rowers/templates/multiflex.html
diff --git a/rowers/forms.py b/rowers/forms.py
index 0566c38c..3763b132 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -298,6 +298,7 @@ formaxlabels = axlabels.copy()
formaxlabels.pop('None')
parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
+
class BoxPlotChoiceForm(forms.Form):
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
label='Metric')
@@ -313,6 +314,36 @@ class BoxPlotChoiceForm(forms.Form):
includereststrokes = forms.BooleanField(initial=False,
required=False,
label='Include Rest Strokes')
+
+grouplabels = axlabels.copy()
+grouplabels['date'] = 'Date'
+grouplabels['workoutid'] = 'Workout'
+grouplabels.pop('None')
+grouplabels.pop('time')
+groupchoices = list(sorted(grouplabels.items(), key = lambda x:x[1]))
+
+class MultiFlexChoiceForm(forms.Form):
+ xparam = forms.ChoiceField(choices=parchoices,initial='spm',
+ label='X axis')
+ yparam = forms.ChoiceField(choices=parchoices,initial='power',
+ label='Y axis')
+ groupby = forms.ChoiceField(choices=groupchoices,initial='date',
+ label='Group By')
+ binsize = forms.FloatField(initial=1,required=False,label = 'Bin Size')
+ 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')
+ ploterrorbars = forms.BooleanField(initial=False,
+ required=False,
+ label='Plot Error Bars')
+ includereststrokes = forms.BooleanField(initial=False,
+ required=False,
+ label='Include Rest Strokes')
class ChartParamChoiceForm(forms.Form):
plotchoices = (
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index a8e3f38a..eb524261 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -69,27 +69,46 @@ watermarkw = 184
watermarkh = 35
watermarkanchor = 'bottom_right'
-def errorbar(fig, x, y, xerr=None, yerr=None, color='red',
+def errorbar(fig, x, y, source=ColumnDataSource(),
+ xerr=False, yerr=False, color='red',
point_kwargs={}, error_kwargs={}):
- fig.circle(x, y, color=color, **point_kwargs)
-
- if xerr:
- x_err_x = []
- x_err_y = []
- for px, py, err in zip(x, y, xerr):
- x_err_x.append((px - err, px + err))
- x_err_y.append((py, py))
- fig.multi_line(x_err_x, x_err_y, color=color, **error_kwargs)
+ fig.circle(x, y, source=source, name='data',color=color, **point_kwargs)
- if yerr:
- y_err_x = []
- y_err_y = []
- for px, py, err in zip(x, y, yerr):
- y_err_x.append((px, px))
- y_err_y.append((py - err, py + err))
- fig.multi_line(y_err_x, y_err_y, color=color, **error_kwargs)
-
+ xvalues = source.data[x]
+ yvalues = source.data[y]
+
+ xerrvalues = source.data['xerror']
+ yerrvalues = source.data['yerror']
+
+
+ try:
+ a = xvalues[0]+1
+ if xerr:
+ x_err_x = []
+ x_err_y = []
+ for px, py, err in zip(xvalues, yvalues, xerrvalues):
+ x_err_x.append((px - err, px + err))
+ x_err_y.append((py, py))
+ fig.multi_line(x_err_x, x_err_y, color=color,
+ name='xerr',
+ **error_kwargs)
+ except TypeError:
+ pass
+
+ try:
+ a = yvalues[0]+1
+ if yerr:
+ y_err_x = []
+ y_err_y = []
+ for px, py, err in zip(xvalues, yvalues, yerrvalues):
+ y_err_x.append((px, px))
+ y_err_y.append((py - err, py + err))
+ fig.multi_line(y_err_x, y_err_y, color=color,
+ name='yerr',**error_kwargs)
+ except TypeError:
+ pass
+
def tailwind(bearing,vwind,winddir):
""" Calculates head-on head/tailwind in direction of rowing
@@ -1144,6 +1163,98 @@ def interactive_chart(id=0,promember=0):
return [script,div]
+def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
+ ploterrorbars=False):
+ if datadf.empty:
+ return ['','No non-zero data in selection
']
+
+
+ xparamname = axlabels[xparam]
+ yparamname = axlabels[yparam]
+
+ if xparam=='distance':
+ xaxmax = datadf['x1'].max()
+ xaxmin = datadf['x1'].min()
+ else:
+ xaxmax = yaxmaxima[xparam]
+ xaxmin = yaxminima[xparam]
+
+ x_axis_type = 'linear'
+ y_axis_type = 'linear'
+ if xparam == 'time':
+ x_axis_type = 'datetime'
+ if yparam == 'pace':
+ y_axis_type = 'datetime'
+
+ source = ColumnDataSource(
+ datadf,
+ )
+
+
+ TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,hover'
+
+ plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
+ tools=TOOLS,
+ toolbar_location="above",
+ toolbar_sticky=False)
+ # add watermark
+ 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",
+ )
+
+ errorbar(plot,xparam,yparam,source=source,
+ xerr=ploterrorbars,
+ yerr=ploterrorbars,
+ point_kwargs={
+ 'line_color':None,
+ 'legend':yparamname,
+ 'size':10,
+ })
+
+ plot.xaxis.axis_label = axlabels[xparam]
+ plot.yaxis.axis_label = axlabels[yparam]
+
+
+ yrange1 = Range1d(start=yaxminima[yparam],end=yaxmaxima[yparam])
+ plot.y_range = yrange1
+
+ xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam])
+ plot.x_range = xrange1
+
+ if yparam == 'pace':
+ plot.yaxis[0].formatter = DatetimeTickFormatter(
+ seconds = ["%S"],
+ minutes = ["%M"]
+ )
+
+ hover = plot.select(dict(type=HoverTool))
+
+ if groupby != 'date':
+ hover.tooltips = OrderedDict([
+ (groupby,'@groupval{1.1}'),
+ ])
+ else:
+ hover.tooltips = OrderedDict([
+ (groupby,'@groupval'),
+ ])
+
+ hover.mode = 'mouse'
+
+ script,div = components(plot)
+
+
+ return [script,div]
+
def interactive_cum_flex_chart2(theworkouts,promember=0,
xparam='spm',
yparam1='power',
diff --git a/rowers/templates/multiflex.html b/rowers/templates/multiflex.html
new file mode 100644
index 00000000..62666379
--- /dev/null
+++ b/rowers/templates/multiflex.html
@@ -0,0 +1,70 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}View Comparison {% endblock %}
+
+{% block content %}
+
+
+
+
+{{ interactiveplot |safe }}
+
+
+
+
+
+
+
Multi Flex Chart
+
+
+ {{ the_div|safe }}
+
+
+
+
+
+
+ You can use the form above to change the metric or filter the data.
+ Set Min SPM and Max SPM to select only strokes in a certain range of
+ stroke rates.
+ Set Work per Stroke to a minimum value to remove "paddle" strokes or turns.
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/user_multiflex_select.html b/rowers/templates/user_multiflex_select.html
index 2033faf9..0b0b8642 100644
--- a/rowers/templates/user_multiflex_select.html
+++ b/rowers/templates/user_multiflex_select.html
@@ -36,7 +36,7 @@
{% else %}
@@ -48,9 +48,9 @@