From b84ba9d49a555a2933f2b5ad456bf4754b27ed9e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 3 Jan 2021 20:34:40 +0100 Subject: [PATCH 1/2] First version of gold medal chart --- rowers/interactiveplots.py | 179 ++++++++++++++++++++++++++ rowers/templates/goldmedalscores.html | 142 ++++++++++++++++++++ rowers/urls.py | 3 + rowers/views/analysisviews.py | 68 +++++++++- 4 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 rowers/templates/goldmedalscores.html diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1809be10..adc404eb 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -123,6 +123,29 @@ def newtestpowerid(x): return x['id'] +def newtestpowerdate(x): + try: + if np.isnan(x['testpower']): + return np.nan + except (AttributeError,TypeError): + return np.nan + + return x['date'] + +def all_goldmedalstandards(workouts,startdate,enddate): + dates = [] + testpowers = [] + testduration = [] + + for w in workouts: + goldmedalstandard, goldmedalseconds = dataprep.workout_goldmedalstandard(w) + if goldmedalseconds > 60: + dates.append(arrow.get(w.date).datetime) + testpowers.append(goldmedalstandard) + testduration.append(goldmedalseconds) + + return dates,testpowers,testduration + def build_goldmedalstandards(workouts,kfitness): dates = [] testpower = [] @@ -1778,6 +1801,162 @@ def getfatigues( return fatigues,fitnesses,dates,testpower,testduration,impulses +def goldmedalscorechart(user,startdate=None,enddate=None): + # to avoid data mess later on + startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0) + enddate = enddate+datetime.timedelta(days=1) + enddate = arrow.get(enddate).datetime.replace(hour=0,minute=0,second=0,microsecond=0) + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + workouts = Workout.objects.filter(user=user.rower,date__gte=startdate, + date__lte=enddate, + workouttype__in=mytypes.rowtypes, + duplicate=False) + + # marker workouts + dates,testpower,testduration,fatigues,fitnesses,impulses, outids = build_goldmedalstandards( + workouts,42 + ) + + df = pd.DataFrame({ + 'date':dates, + 'testpower':testpower, + 'testduration':testduration, + }) + df.sort_values(['date'],inplace=True) + + df['testdup'] = df['testpower'].shift(1) + df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1) + #df['date'] = df.apply(lambda x: newtestpowerdate(x), axis=1) + + mask = df['testpower'].isnull() + dates = df.mask(mask)['date'].dropna().values + testpower = df.mask(mask)['testpower'].dropna().values + + # all workouts + alldates,alltestpower,allduration = all_goldmedalstandards(workouts,startdate,enddate) + + nrdays = (enddate-startdate).days + + td = [] + markerscore = [] + score = [] + markerduration = [] + duration = [] + + previous = 0 + + for i in range(len(dates)): + dd = str(dates[i]) + td.append(arrow.get(dd).datetime) + markerscore.append(testpower[i]) + markerduration.append(testduration[i]) + score.append(np.nan) + duration.append(np.nan) + + for i in range(len(alldates)): + td.append(arrow.get(alldates[i]).datetime) + markerscore.append(np.nan) + score.append(alltestpower[i]) + markerduration.append(np.nan) + duration.append(allduration[i]) + + for i in range(nrdays+1): + td.append(arrow.get(startdate+datetime.timedelta(days=i)).datetime) + markerscore.append(np.nan) + score.append(np.nan) + markerduration.append(np.nan) + duration.append(np.nan) + + df = pd.DataFrame({ + 'markerscore':markerscore, + 'markerduration':markerduration, + 'score':score, + 'duration':duration, + 'date':td, + }) + + + df.sort_values(['date'],inplace=True) + + df = df.groupby(['date']).max() + df['date'] = df.index.values + + + source = ColumnDataSource( + data = dict( + markerscore = df['markerscore'], + score = df['score'], + markerduration = df['markerduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)), + duration = df['duration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)), + date = df['date'], + fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')), + ) + ) + + plot = Figure(tools=TOOLS,x_axis_type='datetime', + plot_width=900,plot_height=600, + toolbar_location='above', + toolbar_sticky=False) + + # add watermark + watermarkurl = "/static/img/logo7.png" + watermarksource = ColumnDataSource(dict( + url = [watermarkurl],)) + + 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", + ) + + plot.xaxis.axis_label = 'Date' + plot.yaxis.axis_label = 'Gold Medal Score' + + plot.circle('date','score',source=source,fill_color='blue', + size=10, + legend_label='Workouts') + + plot.circle('date','markerscore',source=source,fill_color='red', + size=10, + legend_label='Marker Workouts') + + plot.legend.location = "top_left" + + plot.x_range = Range1d( + startdate,enddate+datetime.timedelta(days=5), + ) + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Marker','@markerscore{int}'), + ('Test', '@markerduration'), + ('Score','@score{int}'), + ('Duration', '@duration'), + ('Date','@fdate'), + ]) + + script, div = components(plot) + + return script, div + def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, metricchoice='trimp',doform=False,dofatigue=False, showtests=False): diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html new file mode 100644 index 00000000..82388984 --- /dev/null +++ b/rowers/templates/goldmedalscores.html @@ -0,0 +1,142 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Gold Medal Score {% endblock %} + +{% block scripts %} + + +{% endblock %} + +{% block main %} + + + + + +
+ {{ chartscript |safe }} +
+ + + +{% if rower.user %} +

Gold Medal Scores for {{ rower.user.first_name }}

+{% else %} +

Gold Medal Scores for {{ user.first_name }}

+{% endif %} + + +
    +
  • +
    + {{ the_div|safe }} +
    +
  • +
  • +
    + + {{ form.as_table }} +
    + {% csrf_token %} + +
    +
  • +
  • +

    + Explanation +

    +
  • + {% if bestworkouts %} +

    Marker Workouts

    +
  • + + + + + + + + + + + {% for w in bestworkouts %} + + + + + + + {% endfor %} + +
    DateWorkoutGold Medal ScoreDuration
    {{ w.date }} + {{ w.name }} + + {{ w.goldmedalstandard|floatformat:"0" }} % + + {{ w.goldmedalseconds|secondstotimestring }} +
    +
  • + {% endif %} +
+ + + +{% endblock %} + + + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 23f431d9..3dc3771a 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -355,6 +355,9 @@ urlpatterns = [ re_path(r'^fitness-fit/$',views.fitness_from_cp_view,name='fitness_from_cp_view'), re_path(r'^fitness-fit/user/(?P\d+)/$',views.fitness_from_cp_view,name='fitness_from_cp_view'), re_path(r'^fitness-fit/user/(?P\d+)/(?P\w+.*)/$',views.fitness_from_cp_view,name='fitness_from_cp_view'), + re_path(r'^goldmedalscores/$',views.goldmedalscores_view,name='goldmedalscores_view'), + re_path(r'^goldmedalscores/user/(?P\d+)/$',views.goldmedalscores_view,name='goldmedalscores_view'), + re_path(r'^goldmedalscores/user/(?P\d+)/(?P\w+.*)/$',views.goldmedalscores_view,name='goldmedalscores_view'), re_path(r'^performancemanager/$',views.performancemanager_view,name='performancemanager_view'), re_path(r'^performancemanager/user/(?P\d+)/$',views.performancemanager_view,name='performancemanager_view'), re_path(r'^performancemanager/user/(?P\d+)/(?P\w+.*)/$',views.performancemanager_view,name='performancemanager_view'), diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index f879150c..d26d91ec 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1543,6 +1543,70 @@ def fitnessmetric_view(request,userid=0,mode='rower', 'form':form, }) +@user_passes_test(ispromember, login_url="/rowers/paidplans", + message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", + redirect_field_name=None) +def goldmedalscores_view(request,userid=0, + startdate=timezone.now()-timezone.timedelta(days=365), + enddate=timezone.now()): + + is_ajax = False + if request.is_ajax(): + is_ajax = True + + therower = getrequestrower(request,userid=userid) + theuser = therower.user + + + if request.method == 'POST': + form = DateRangeForm(request.POST) + if form.is_valid(): + startdate = form.cleaned_data['startdate'] + enddate = form.cleaned_data['enddate'] + if startdate > enddate: + s = enddate + enddate = startdate + startdate = s + else: + form = DateRangeForm(initial={ + 'startdate':startdate, + 'enddate':enddate, + }) + + script, div = goldmedalscorechart( + theuser,startdate=startdate,enddate=enddate, + ) + + breadcrumbs = [ + { + 'url':'/rower/analysis', + 'name':'Analysis', + }, + { + 'url':reverse(goldmedalscores_view), + 'name': 'Gold Medal Scores' + } + ] + + if is_ajax: + response = json.dumps({ + 'script':script, + 'div':div, + }) + + return(HttpResponse(response,content_type='application/json')) + + return render(request,'goldmedalscores.html', + { + 'rower':therower, + 'active':'nav-analysis', + 'chartscript':script, + 'breadcrumbs':breadcrumbs, + 'the_div':div, + 'form':form, + }) + + @user_passes_test(ispromember, login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @@ -1606,8 +1670,8 @@ def performancemanager_view(request,userid=0,mode='rower', 'name':'Analysis' }, { - 'url':reverse('fitnessmetric_view'), - 'name': 'Power Progress' + 'url':reverse('performancemanager_view'), + 'name': 'Performance Manager' } ] From 6e3d4a705147d73a1e805e07e1d1c2cbe8821e92 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 4 Jan 2021 17:20:16 +0100 Subject: [PATCH 2/2] finalizing mvp of goldmedalscores chart --- rowers/interactiveplots.py | 10 ++++++++-- rowers/templates/goldmedalscores.html | 2 +- rowers/views/analysisviews.py | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index adc404eb..d8b0dc06 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1820,6 +1820,7 @@ def goldmedalscorechart(user,startdate=None,enddate=None): ) df = pd.DataFrame({ + 'id':outids, 'date':dates, 'testpower':testpower, 'testduration':testduration, @@ -1830,10 +1831,15 @@ def goldmedalscorechart(user,startdate=None,enddate=None): df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1) #df['date'] = df.apply(lambda x: newtestpowerdate(x), axis=1) + + + mask = df['testpower'].isnull() dates = df.mask(mask)['date'].dropna().values testpower = df.mask(mask)['testpower'].dropna().values + outids = df.mask(mask)['id'].dropna().unique() + # all workouts alldates,alltestpower,allduration = all_goldmedalstandards(workouts,startdate,enddate) @@ -1937,7 +1943,7 @@ def goldmedalscorechart(user,startdate=None,enddate=None): size=10, legend_label='Marker Workouts') - plot.legend.location = "top_left" + plot.legend.location = "bottom_left" plot.x_range = Range1d( startdate,enddate+datetime.timedelta(days=5), @@ -1955,7 +1961,7 @@ def goldmedalscorechart(user,startdate=None,enddate=None): script, div = components(plot) - return script, div + return script, div,outids def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, metricchoice='trimp',doform=False,dofatigue=False, diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index 82388984..0b4d4dfe 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -99,8 +99,8 @@

{% if bestworkouts %} -

Marker Workouts

  • +

    Marker Workouts

    diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index d26d91ec..4db01213 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1573,10 +1573,12 @@ def goldmedalscores_view(request,userid=0, 'enddate':enddate, }) - script, div = goldmedalscorechart( + script, div, ids = goldmedalscorechart( theuser,startdate=startdate,enddate=enddate, ) + bestworkouts = Workout.objects.filter(id__in=ids).order_by('date') + breadcrumbs = [ { 'url':'/rower/analysis', @@ -1604,6 +1606,7 @@ def goldmedalscores_view(request,userid=0, 'breadcrumbs':breadcrumbs, 'the_div':div, 'form':form, + 'bestworkouts':bestworkouts, })