diff --git a/rowers/forms.py b/rowers/forms.py
index d5a1715d..69b6870f 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -297,6 +297,9 @@ 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')
class ChartParamChoiceForm(forms.Form):
plotchoices = (
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index 4cdbf511..2dc2cf43 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -64,11 +64,15 @@
-
- Pro Feature 3
+
+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
+ Box Chart
+ {% else %}
+ Box Chart
+ {% endif %}
- Reserved for future functionality.
+ BETA: Box Chart Statistics of stroke metrics over a date range
diff --git a/rowers/templates/boxplot.html b/rowers/templates/boxplot.html
new file mode 100644
index 00000000..ce250dd2
--- /dev/null
+++ b/rowers/templates/boxplot.html
@@ -0,0 +1,63 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}View Comparison {% endblock %}
+
+{% block content %}
+
+
+
+
+{{ interactiveplot |safe }}
+
+
+
+
+
+
+
Box Chart
+
+
+
+
+
+
+
+ {{ the_div|safe }}
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/user_boxplot_select.html b/rowers/templates/user_boxplot_select.html
new file mode 100644
index 00000000..155cd3d9
--- /dev/null
+++ b/rowers/templates/user_boxplot_select.html
@@ -0,0 +1,89 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workouts{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% include "teambuttons.html" with teamid=team.id %}
+
+
+
+
{{ team.name }} Team Workouts
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 952b6175..d7b8f857 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -124,6 +124,10 @@ urlpatterns = [
url(r'^team-compare-select/team/(?P\d+)/$',views.team_comparison_select),
url(r'^team-compare-select/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/$',views.team_comparison_select),
+ url(r'^user-boxplot-select/team/(?P\d+)/(?P\w+.*)/(?P\w+.*)$',views.user_boxplot_select),
+ 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'^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),
@@ -220,6 +224,7 @@ urlpatterns = [
url(r'^workout/(?P\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
url(r'^workout/(?P\d+)/tpuploadw/$',views.workout_tp_upload_view),
url(r'^multi-compare$',views.multi_compare_view),
+ url(r'^user-boxplot$',views.boxplot_view),
url(r'^me/teams/$',views.rower_teams_view),
url(r'^team/(?P\d+)/$',views.team_view),
url(r'^team/(?P\d+)/edit$',views.team_edit_view),
diff --git a/rowers/views.py b/rowers/views.py
index def43cda..506fb65a 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -34,7 +34,7 @@ from rowers.forms import (
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
- FusionMetricChoiceForm,
+ FusionMetricChoiceForm,BoxPlotChoiceForm,
)
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
@@ -2847,6 +2847,8 @@ def team_comparison_select(request,
'teams':get_my_teams(request.user),
})
+
+# Team comparison
@login_required()
def multi_compare_view(request):
promember=0
@@ -2926,6 +2928,181 @@ 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_boxplot_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)
+
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExist:
+ raise Http404("Rower doesn't exist")
+
+ 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)
+
+ 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),
+ })
+
+@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
+def boxplot_view(request,userid=0,
+ options={
+ 'includereststrokes':False,
+ }):
+
+ if 'options' in request.session:
+ options = request.session['options']
+
+ includereststrokes = options['includereststrokes']
+ workstrokesonly = not includereststrokes
+
+ if request.method == 'POST' and 'workouts' in request.POST:
+ form = WorkoutMultipleCompareForm(request.POST)
+ chartform = BoxPlotChoiceForm(request.POST)
+ if form.is_valid() and chartform.is_valid():
+ cd = form.cleaned_data
+ workouts = cd['workouts']
+ plotfield = chartform.cleaned_data['yparam']
+ ids = [int(w.id) for w in workouts]
+ request.session['ids'] = ids
+
+ labeldict = {
+ int(w.id): w.__unicode__() for w in workouts
+ }
+
+
+ datemapping = {
+ w.id:w.date for w in workouts
+ }
+
+
+
+ fieldlist,fielddict = dataprep.getstatsfields()
+ fieldlist = [plotfield,'workoutid']
+
+ # prepare data frame
+ datadf = dataprep.read_cols_df_sql(ids,fieldlist)
+
+ datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
+ datadf['workoutid'].replace(datemapping,inplace=True)
+ datadf.rename(columns={"workoutid":"date"},inplace=True)
+ datadf = datadf.sort_values(['date'])
+ script,div = interactive_boxchart(datadf,plotfield)
+
+
+ return render(request,'boxplot.html',
+ {'interactiveplot':script,
+ 'the_div':div,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+ else:
+ return HttpResponse("Form is not valid")
+ if request.method == 'POST' and 'ids' in request.session:
+ chartform = BoxPlotChoiceForm(request.POST)
+ if chartform.is_valid():
+ plotfield = chartform.cleaned_data['yparam']
+ ids = request.session['ids']
+ request.session['ids'] = ids
+ workouts = [Workout.objects.get(id=id) for id in ids]
+
+ labeldict = {
+ int(w.id): w.__unicode__() for w in workouts
+ }
+
+ datemapping = {
+ w.id:w.date for w in workouts
+ }
+
+ fieldlist,fielddict = dataprep.getstatsfields()
+ fieldlist = [plotfield,'workoutid']
+
+ # prepare data frame
+ datadf = dataprep.read_cols_df_sql(ids,fieldlist)
+
+ datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
+ datadf['workoutid'].replace(datemapping,inplace=True)
+ datadf.rename(columns={"workoutid":"date"},inplace=True)
+ datadf = datadf.sort_values(['date'])
+ script,div = interactive_boxchart(datadf,plotfield)
+
+
+ return render(request,'boxplot.html',
+ {'interactiveplot':script,
+ 'the_div':div,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+ else:
+ return HttpResponse("invalid form")
+ else:
+ url = reverse(workouts_view)
+ return HttpResponseRedirect(url)
+
# List Workouts
@login_required()
def workouts_view(request,message='',successmessage='',