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 }}
+
+
+ -
+
+
+ -
+
+ Explanation
+
+
+ {% if bestworkouts %}
+ Marker Workouts
+ -
+
+
+
+ | Date |
+ Workout |
+ Gold Medal Score |
+ Duration |
+
+
+
+ {% for w in bestworkouts %}
+
+ | {{ w.date }} |
+
+ {{ w.name }}
+ |
+
+ {{ w.goldmedalstandard|floatformat:"0" }} %
+ |
+
+ {{ w.goldmedalseconds|secondstotimestring }}
+ |
+
+ {% endfor %}
+
+
+
+ {% 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,
})