strava style chart v 1
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
66
rowers/templates/flexchartstacked.html
Normal file
66
rowers/templates/flexchartstacked.html
Normal 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>
|
||||
{% 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 %}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=""):
|
||||
|
||||
Reference in New Issue
Block a user