Private
Public Access
1
0

strava style chart v 1

This commit is contained in:
Sander Roosendaal
2020-11-04 22:59:58 +01:00
parent 62219655f5
commit c418799e47
6 changed files with 432 additions and 17 deletions

View File

@@ -1518,6 +1518,7 @@ class VirtualRaceSelectForm(forms.Form):
choices = get_countries(),initial='All'
)
class FlexOptionsForm(forms.Form):
includereststrokes = forms.BooleanField(initial=True, required = False,
label='Include Rest Strokes')
@@ -1540,24 +1541,80 @@ class ForceCurveOptionsForm(forms.Form):
label='Individual Stroke Chart Type')
axchoices = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None']
)
axchoices = dict((x,y) for x,y in axchoices)
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
yaxchoices = dict((x,y) for x,y in yaxchoices)
yaxchoices = list(sorted(yaxchoices.items(), key = lambda x:x[1]))
yaxchoices2 = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
)
yaxchoices2 = dict((x,y) for x,y in yaxchoices2)
yaxchoices2 = list(sorted(yaxchoices2.items(), key = lambda x:x[1]))
class StravaChartForm(forms.Form):
xaxischoices = (
('cumdist','Distance'),
('time','Time')
)
xaxis = forms.ChoiceField(
choices = xaxischoices,label='X-Axis',required=True)
yaxis1 = forms.ChoiceField(
choices=yaxchoices,label='First Chart',required=True)
yaxis2 = forms.ChoiceField(
choices=yaxchoices2,label='Second Chart',required=True)
yaxis3 = forms.ChoiceField(
choices=yaxchoices,label='Third Chart',required=True)
yaxis4 = forms.ChoiceField(
choices=yaxchoices2,label='Fourth Chart',required=True)
def __init__(self,request,*args,**kwargs):
extrametrics = kwargs.pop('extrametrics',[])
super(StravaChartForm, self).__init__(*args, **kwargs)
rower = Rower.objects.get(user=request.user)
axchoicespro = (
('',ax[1]) if ax[4] == 'pro' and ax[0] else (ax[0],ax[1]) for ax in axes
)
axchoicesbasicx = []
axchoicesbasicy = []
for ax in axes:
if ax[4] != 'pro' and ax[0] != 'cumdist':
if ax[0] != 'None':
axchoicesbasicx.insert(0,(ax[0],ax[1]))
if ax[0] not in ['cumdist','distance','time']:
axchoicesbasicy.insert(0,(ax[0],ax[1]))
else:
if ax[0] != 'None':
axchoicesbasicx.insert(0,('None',ax[1]+' (PRO)'))
if ax[0] not in ['cumdist','distance','time']:
axchoicesbasicy.insert(0,('None',ax[1]+' (PRO)'))
if not user_is_not_basic(rower.user):
self.fields['xaxis'].choices = axchoicesbasicx
self.fields['yaxis1'].choices = axchoicesbasicy
self.fields['yaxis2'].choices = axchoicesbasicy
self.fields['yaxis3'].choices = axchoicesbasicy
self.fields['yaxis4'].choices = axchoicesbasicy
class FlexAxesForm(forms.Form):
axchoices = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None']
)
axchoices = dict((x,y) for x,y in axchoices)
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
yaxchoices = dict((x,y) for x,y in yaxchoices)
yaxchoices = list(sorted(yaxchoices.items(), key = lambda x:x[1]))
yaxchoices2 = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
)
yaxchoices2 = dict((x,y) for x,y in yaxchoices2)
yaxchoices2 = list(sorted(yaxchoices2.items(), key = lambda x:x[1]))
xaxis = forms.ChoiceField(
choices=axchoices,label='X-Axis',required=True)

View File

@@ -4412,6 +4412,213 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
return [script,div,js_resources,css_resources]
def interactive_flexchart_stacked(id,r,xparam='time',
yparam1='pace',
yparam2='power',
yparam3='hr',
yparam4='spm',
mode='erg'):
columns = [xparam,yparam1,yparam2,
'ftime','distance','fpace',
'power','hr','spm','driveenergy',
'time','pace','workoutstate']
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True,
workstrokesonly=False)
if r.usersmooth > 1:
for column in columns:
try:
if metricsdicts[column]['maysmooth']:
nrsteps = int(log2(r.usersmooth))
for i in range(nrsteps):
rowdata[column] = stravastuff.ewmovingaverage(rowdata[column],5)
except KeyError:
pass
if len(rowdata)<2:
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],
doclean=False,
workstrokesonly=False)
row = Workout.objects.get(id=id)
if rowdata.empty:
return "","No valid data",'',''
try:
tseconds = rowdata.loc[:,'time']
except KeyError:
return '','No time data - cannot make flex plot','',''
try:
rowdata['x1'] = rowdata.loc[:,xparam]
rowmin = rowdata[xparam].min()
except KeyError:
rowdata['x1'] = 0*rowdata.loc[:,'time']
try:
rowdata['y1'] = rowdata.loc[:,yparam1]
rowmin = rowdata[yparam1].min()
except KeyError:
rowdata['y1'] = 0*rowdata.loc[:,'time']
rowdata[yparam1] = rowdata['y1']
try:
rowdata['y2'] = rowdata.loc[:,yparam2]
rowmin = rowdata[yparam2].min()
except KeyError:
rowdata['y2'] = 0*rowdata.loc[:,'time']
rowdata[yparam2] = rowdata['y2']
try:
rowdata['y3'] = rowdata.loc[:,yparam3]
rowmin = rowdata[yparam3].min()
except KeyError:
rowdata['y3'] = 0*rowdata.loc[:,'time']
rowdata[yparam3] = rowdata['y3']
try:
rowdata['y4'] = rowdata.loc[:,yparam4]
rowmin = rowdata[yparam4].min()
except KeyError:
rowdata['y4'] = 0*rowdata.loc[:,'time']
rowdata[yparam4] = rowdata['y4']
if xparam=='time':
xaxmax = tseconds.max()
xaxmin = tseconds.min()
elif xparam=='distance' or xparam=='cumdist':
xaxmax = rowdata['x1'].max()
xaxmin = rowdata['x1'].min()
else:
try:
xaxmax = get_yaxmaxima(r,xparam,mode)
xaxmin = get_yaxminima(r,xparam,mode)
except KeyError:
xaxmax = rowdata['x1'].max()
xaxmin = rowdata['x1'].min()
x_axis_type = 'linear'
y1_axis_type = 'linear'
y2_axis_type = 'linear'
y3_axis_type = 'linear'
y4_axis_type = 'linear'
if xparam == 'time':
x_axis_type = 'datetime'
if yparam1 == 'pace':
y1_axis_type = 'datetime'
if yparam2 == 'pace':
y2_axis_type = 'datetime'
if yparam3 == 'pace':
y3_axis_type = 'datetime'
if yparam4 == 'pace':
y4_axis_type = 'datetime'
try:
rowdata['xname'] = axlabels[xparam]
except KeyError:
rowdata['xname'] = xparam
try:
rowdata['yname1'] = axlabels[yparam1]
except KeyError:
rowdata['yname1'] = yparam1
try:
rowdata['yname2'] = axlabels[yparam2]
except KeyError:
rowdata['yname2'] = yparam2
try:
rowdata['yname3'] = axlabels[yparam3]
except KeyError:
rowdata['yname3'] = yparam3
try:
rowdata['yname4'] = axlabels[yparam4]
except KeyError:
rowdata['yname4'] = yparam4
# prepare data
source = ColumnDataSource(
rowdata
)
plot1 = Figure(x_axis_type=x_axis_type,y_axis_type=y1_axis_type,plot_width=920,plot_height=150)
plot2 = Figure(x_axis_type=x_axis_type,y_axis_type=y2_axis_type,plot_width=920,plot_height=150)
plot3 = Figure(x_axis_type=x_axis_type,y_axis_type=y3_axis_type,plot_width=920,plot_height=150)
plot4 = Figure(x_axis_type=x_axis_type,y_axis_type=y4_axis_type,plot_width=920,plot_height=150)
y1min = get_yaxminima(r,yparam1,mode)
y2min = get_yaxminima(r,yparam2,mode)
y3min = get_yaxminima(r,yparam3,mode)
y4min = get_yaxminima(r,yparam4,mode)
y1max = get_yaxmaxima(r,yparam1,mode)
y2max = get_yaxmaxima(r,yparam2,mode)
y3max = get_yaxmaxima(r,yparam3,mode)
y4max = get_yaxmaxima(r,yparam4,mode)
plot1.y_range = Range1d(start=y1min,end=y1max)
plot2.y_range = Range1d(start=y2min,end=y2max)
plot3.y_range = Range1d(start=y3min,end=y3max)
plot4.y_range = Range1d(start=y4min,end=y4max)
if yparam1 == 'pace':
plot1.yaxis[0].formatter = DatetimeTickFormatter(
seconds = ["%S"],
minutes = ["%M"]
)
plot1.y_range = Range1d(y1min,y1max)
if yparam2 == 'pace':
plot2.yaxis[0].formatter = DatetimeTickFormatter(
seconds = ["%S"],
minutes = ["%M"]
)
plot2.y_range = Range1d(y2min,y2max)
if yparam3 == 'pace':
plot3.yaxis[0].formatter = DatetimeTickFormatter(
seconds = ["%S"],
minutes = ["%M"]
)
plot3.y_range = Range1d(y3min,y3max)
if yparam4 == 'pace':
plot4.yaxis[0].formatter = DatetimeTickFormatter(
seconds = ["%S"],
minutes = ["%M"]
)
plot4.y_range = Range1d(y4min,y4max)
plot1.line('x1','y1',source=source,color="cyan")
plot2.line('x1','y2',source=source,color="red")
plot3.line('x1','y3',source=source,color="green")
plot4.line('x1','y4',source=source,color="blue")
layout = layoutcolumn([
plot1,
plot2,
plot3,
plot4,
])
layout.sizing_mode = 'scale_width'
script, div = components(layout)
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
return script,div,js_resources,css_resources
def interactive_flex_chart2(id,r,promember=0,

View File

@@ -0,0 +1,66 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% load tz %}
{% block title %} Flexible Plot {% endblock %}
{% localtime on %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.0.4.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/flexchart/"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/flexchart/"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>Flexible Chart</h1>
<ul class="main-content">
<li class="grid_4">
<div id="theplot">
{{ the_div|safe }}
</div>
</li>
<li class="grid_2">
<form enctype="multipart/form-data"
action=""
method="post">
{% csrf_token %}
<table>
{{ chartform.as_table }}
</table>
<table>
{{ optionsform.as_table }}
</table>
<p>
<input name="chartform" type="submit"
value="Update Chart">
</p>
</form>
</li>
</ul>
{% endblock %}
{% endlocaltime %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}

View File

@@ -688,6 +688,7 @@ urlpatterns = [
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/(?P<plottype>\w+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchartstacked/$',views.workout_flexchart_stacked_view,name='workout_flexchart_stacked_view'),
# re_path(r'^workout/compare/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>[\w\ ]+.*)/(?P<plottype>[\w\ ]+.*)/$',views.workout_comparison_view2),
# re_path(r'^workout/compare/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>[\w\ ]+.*)/$',views.workout_comparison_view2),
re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'),

View File

@@ -76,6 +76,7 @@ from rowers.forms import (
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
StravaChartForm,
)
from django.urls import reverse, reverse_lazy

View File

@@ -3938,7 +3938,90 @@ def workout_flexchart3_view(request,*args,**kwargs):
'maxfav':maxfav,
})
@login_required()
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_flexchart_stacked_view(request,*args,**kwargs):
try:
id = kwargs['id']
except KeyError:
raise Http404("Invalid workout number")
workout = get_workout(id)
r = getrequestrower(request)
xparam = 'time'
yparam1 = 'pace'
yparam2 = 'power'
yparam3 = 'hr'
yparam4 = 'spm'
if request.method == 'POST':
flexaxesform = StravaChartForm(request,request.POST)
if flexaxesform.is_valid():
cd = flexaxesform.cleaned_data
xparam = cd['xaxis']
yparam1 = cd['yaxis1']
yparam2 = cd['yaxis2']
yparam3 = cd['yaxis3']
yparam4 = cd['yaxis4']
(
script, div, js_resources, css_resources
) = interactive_flexchart_stacked(
encoder.decode_hex(id),r,xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
yparam3=yparam3,
yparam4=yparam4,
mode=workout.workouttype,
)
initial = {
'xaxis':xparam,
'yaxis1':yparam1,
'yaxis2':yparam2,
'yaxis3':yparam3,
'yaxis4':yparam4,
}
flexaxesform = StravaChartForm(request,initial=initial,
)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': workout.name
},
{
'url':reverse('workout_flexchart_stacked_view',kwargs=kwargs),
'name': 'Stacked Flex Chart'
}
]
return render(request,
'flexchartstacked.html',
{
'the_script':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'workout':workout,
'chartform':flexaxesform,
'js_res':js_resources,
'css_res':css_resources,
'id':id,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'yparam3':yparam3,
'yparam4':yparam4,
}
)
# The interactive plot with wind corrected pace for OTW outings
def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):