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

+
+
+
+ {% csrf_token %} + + {{ chartform.as_table }} +
+
+

+ +

+
+
+
+
+ + + +
+ {{ 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

+
+
+
+ + {% if theuser %} +
+ {% else %} + + {% endif %} + + + {{ dateform.as_table }} +
+ {% csrf_token %} +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+ + +
+
+ + + {% if workouts %} + + + + {{ form.as_table }} +
+{% else %} +

No workouts found

+{% endif %} +
+
+

Warning: You are on an experimental part of the site. Use at your own risk.

+

Select two or more workouts on the left, set your plot settings below, + and press submit"

+ {% csrf_token %} + + {{ chartform.as_table }} +
+
+

+ +

+
+
+

You can use the date and search forms above to search through all + workouts from this team.

+

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.

+
+
+
+ + +{% 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='',