From c418799e477dad1c066a9fd1cc85773797770246 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 4 Nov 2020 22:59:58 +0100
Subject: [PATCH 1/7] strava style chart v 1
---
rowers/forms.py | 91 +++++++++--
rowers/interactiveplots.py | 207 +++++++++++++++++++++++++
rowers/templates/flexchartstacked.html | 66 ++++++++
rowers/urls.py | 1 +
rowers/views/statements.py | 1 +
rowers/views/workoutviews.py | 83 ++++++++++
6 files changed, 432 insertions(+), 17 deletions(-)
create mode 100644 rowers/templates/flexchartstacked.html
diff --git a/rowers/forms.py b/rowers/forms.py
index d9c7da0c..dba009c4 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -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)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index c271506c..3297a83c 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -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,
diff --git a/rowers/templates/flexchartstacked.html b/rowers/templates/flexchartstacked.html
new file mode 100644
index 00000000..d6468570
--- /dev/null
+++ b/rowers/templates/flexchartstacked.html
@@ -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 }}
+
+
+
+
+
+{{ the_script |safe }}
+
+
+ {% if workout|previousworkout:rower.user %}
+ Previous
+ {% endif %}
+ {% if workout|nextworkout:rower.user %}
+ Next
+ {% endif %}
+
+
+
+Flexible Chart
+
+
+ -
+
+ {{ the_div|safe }}
+
+
+ -
+
+
+
+
+{% endblock %}
+{% endlocaltime %}
+
+{% block sidebar %}
+{% include 'menu_workout.html' %}
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 236f8e4d..520aeffa 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -688,6 +688,7 @@ urlpatterns = [
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/flexchart/(?P\w+.*)/(?P[\w\ ]+.*)/(?P[\w\ ]+.*)/(?P\w+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/flexchart/(?P\w+.*)/(?P[\w\ ]+.*)/(?P[\w\ ]+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/flexchart/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
+ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/flexchartstacked/$',views.workout_flexchart_stacked_view,name='workout_flexchart_stacked_view'),
# re_path(r'^workout/compare/(?P\d+)/(?P\d+)/(?P\w+.*)/(?P[\w\ ]+.*)/(?P[\w\ ]+.*)/$',views.workout_comparison_view2),
# re_path(r'^workout/compare/(?P\d+)/(?P\d+)/(?P\w+.*)/(?P[\w\ ]+.*)/$',views.workout_comparison_view2),
re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'),
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 665abf4e..fc2cdb13 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -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
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index fd91c6ef..d63c85c6 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -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=""):
From 17477fe43a972e33706beeed967048b9bc32e540 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 5 Nov 2020 21:59:50 +0100
Subject: [PATCH 2/7] v2 new flex
---
rowers/interactiveplots.py | 180 ++++++++++++++++++++++++++++++++++++-
1 file changed, 176 insertions(+), 4 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 3297a83c..769bc4b3 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4550,10 +4550,182 @@ def interactive_flexchart_stacked(id,r,xparam='time',
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)
+ TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover'
+
+ plot1 = Figure(x_axis_type=x_axis_type,y_axis_type=y1_axis_type,plot_width=920,plot_height=150,
+ tools=TOOLS,toolbar_location='above')
+ plot2 = Figure(x_axis_type=x_axis_type,y_axis_type=y2_axis_type,plot_width=920,plot_height=150,
+ tools=TOOLS,toolbar_location=None)
+ plot3 = Figure(x_axis_type=x_axis_type,y_axis_type=y3_axis_type,plot_width=920,plot_height=150,
+ tools=TOOLS,toolbar_location=None)
+ plot4 = Figure(x_axis_type=x_axis_type,y_axis_type=y4_axis_type,plot_width=920,plot_height=150,
+ tools=TOOLS,toolbar_location=None)
+
+ linked_crosshair = CrosshairTool(dimensions="height")
+ plot1.add_tools(linked_crosshair)
+ plot2.add_tools(linked_crosshair)
+ plot3.add_tools(linked_crosshair)
+ plot4.add_tools(linked_crosshair)
+
+ try:
+ xaxlabel = axlabels[xparam]
+ except KeyError:
+ xaxlabel = xparam
+
+ try:
+ yax1label = axlabels[yparam1]
+ except KeyError:
+ yax1label = yparam1
+
+ plot1.yaxis.axis_label = yax1label
+
+ try:
+ xaxlabel = axlabels[xparam]
+ except KeyError:
+ xaxlabel = xparam
+
+ try:
+ yax2label = axlabels[yparam2]
+ except KeyError:
+ yax2label = yparam2
+
+ plot2.yaxis.axis_label = yax2label
+
+ try:
+ xaxlabel = axlabels[xparam]
+ except KeyError:
+ xaxlabel = xparam
+
+ try:
+ yax3label = axlabels[yparam3]
+ except KeyError:
+ yax3label = yparam3
+
+ plot3.yaxis.axis_label = yax3label
+
+ try:
+ xaxlabel = axlabels[xparam]
+ except KeyError:
+ xaxlabel = xparam
+
+ try:
+ yax4label = axlabels[yparam4]
+ except KeyError:
+ yax4label = yparam4
+
+ try:
+ xaxlabel = axlabels[xparam]
+ except KeyError:
+ xaxlabel = xparam
+
+ plot4.yaxis.axis_label = yax4label
+
+ plot4.xaxis.axis_label = xaxlabel
+
+ if xparam == 'time':
+ xrange1 = Range1d(start=xaxmin,end=xaxmax)
+ plot1.x_range = xrange1
+ plot2.x_range = xrange1
+ plot3.x_range = xrange1
+ plot4.x_range = xrange1
+ plot4.xaxis[0].formatter = DatetimeTickFormatter(
+ hours = ["%H"],
+ minutes = ["%M"],
+ seconds = ["%S"],
+ days = ["0"],
+ months = [""],
+ years = [""]
+ )
+
+ hover1 = plot1.select(dict(type=HoverTool))
+ hover2 = plot2.select(dict(type=HoverTool))
+ hover3 = plot3.select(dict(type=HoverTool))
+ hover4 = plot4.select(dict(type=HoverTool))
+
+ if yparam1 == 'pace':
+ y1tooltip = '@fpace'
+ else:
+ y1tooltip = '@{yparam1}'.format(yparam1=yparam1)
+ if metricsdicts[yparam1]['numtype'] == 'integer' or yparam1 == 'power':
+ y1tooltip+='{int}'
+ else:
+ y1tooltip+='{0.00}'
+
+ if yparam2 == 'pace':
+ y2tooltip = '@fpace'
+ else:
+ y2tooltip = '@{yparam2}'.format(yparam2=yparam2)
+ if metricsdicts[yparam2]['numtype'] == 'integer' or yparam2 == 'power':
+ y2tooltip+='{int}'
+ else:
+ y2tooltip+='{0.00}'
+
+ if yparam3 == 'pace':
+ y3tooltip = '@fpace'
+ else:
+ y3tooltip = '@{yparam3}'.format(yparam3=yparam3)
+ if metricsdicts[yparam3]['numtype'] == 'integer' or yparam3 == 'power':
+ y3tooltip+='{int}'
+ else:
+ y3tooltip+='{0.00}'
+
+ if yparam4 == 'pace':
+ y4tooltip = '@fpace'
+ else:
+ y4tooltip = '@{yparam4}'.format(yparam4=yparam4)
+ if metricsdicts[yparam4]['numtype'] == 'integer' or yparam4 == 'power':
+ y4tooltip+='{int}'
+ else:
+ y4tooltip+='{0.00}'
+
+
+
+
+
+
+
+
+
+
+ hover1.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+ hover2.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+
+ hover3.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+
+ hover4.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+
+ hover1.mode = 'mouse'
+ hover2.mode = 'mouse'
+ hover3.mode = 'mouse'
+ hover4.mode = 'mouse'
y1min = get_yaxminima(r,yparam1,mode)
y2min = get_yaxminima(r,yparam2,mode)
From eaba32af985df135f1584b5f557f47017ae6ec26 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 5 Nov 2020 22:15:47 +0100
Subject: [PATCH 3/7] better pan/zoom
---
rowers/interactiveplots.py | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 769bc4b3..45061f80 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4550,16 +4550,21 @@ def interactive_flexchart_stacked(id,r,xparam='time',
rowdata
)
- TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover'
+ TOOLS = 'box_zoom,wheel_zoom,reset,tap,hover'
+ TOOLS2 = 'box_zoom,hover'
plot1 = Figure(x_axis_type=x_axis_type,y_axis_type=y1_axis_type,plot_width=920,plot_height=150,
tools=TOOLS,toolbar_location='above')
plot2 = Figure(x_axis_type=x_axis_type,y_axis_type=y2_axis_type,plot_width=920,plot_height=150,
- tools=TOOLS,toolbar_location=None)
+ tools=TOOLS2,toolbar_location=None)
plot3 = Figure(x_axis_type=x_axis_type,y_axis_type=y3_axis_type,plot_width=920,plot_height=150,
- tools=TOOLS,toolbar_location=None)
+ tools=TOOLS2,toolbar_location=None)
plot4 = Figure(x_axis_type=x_axis_type,y_axis_type=y4_axis_type,plot_width=920,plot_height=150,
- tools=TOOLS,toolbar_location=None)
+ tools=TOOLS2,toolbar_location=None)
+
+ plot1.xaxis.visible=False
+ plot2.xaxis.visible=False
+ plot3.xaxis.visible=False
linked_crosshair = CrosshairTool(dimensions="height")
plot1.add_tools(linked_crosshair)
@@ -4785,6 +4790,7 @@ def interactive_flexchart_stacked(id,r,xparam='time',
layout.sizing_mode = 'scale_width'
+
script, div = components(layout)
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
From f017df7d0423da53bf48c0b088770e53cad3c8e0 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 5 Nov 2020 22:28:12 +0100
Subject: [PATCH 4/7] menu entry for chart stack
---
rowers/interactiveplots.py | 12 +++++++-----
rowers/templates/menu_workout.html | 5 +++++
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 45061f80..d88c71f1 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4627,12 +4627,14 @@ def interactive_flexchart_stacked(id,r,xparam='time',
plot4.xaxis.axis_label = xaxlabel
+
+ xrange1 = Range1d(start=xaxmin,end=xaxmax)
+ plot1.x_range = xrange1
+ plot2.x_range = xrange1
+ plot3.x_range = xrange1
+ plot4.x_range = xrange1
+
if xparam == 'time':
- xrange1 = Range1d(start=xaxmin,end=xaxmax)
- plot1.x_range = xrange1
- plot2.x_range = xrange1
- plot3.x_range = xrange1
- plot4.x_range = xrange1
plot4.xaxis[0].formatter = DatetimeTickFormatter(
hours = ["%H"],
minutes = ["%M"],
diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html
index a1d3eab9..0de063d9 100644
--- a/rowers/templates/menu_workout.html
+++ b/rowers/templates/menu_workout.html
@@ -78,6 +78,11 @@
Flex Chart
+
+
+ Chart Stack
+
+
{% if workout|water %}
From 72ee5b2dff15abec2c1d588dd5b8d04969004586 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 6 Nov 2020 19:53:15 +0100
Subject: [PATCH 5/7] stack chart improvements
---
rowers/interactiveplots.py | 111 +++++++++++++------------
rowers/templates/flexchartstacked.html | 4 +-
rowers/templates/menu_workout.html | 10 +--
rowers/views/workoutviews.py | 5 +-
4 files changed, 71 insertions(+), 59 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index d88c71f1..2d99154c 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4424,6 +4424,8 @@ def interactive_flexchart_stacked(id,r,xparam='time',
'power','hr','spm','driveenergy',
'time','pace','workoutstate']
+ comment = None
+
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True,
workstrokesonly=False)
@@ -4633,7 +4635,7 @@ def interactive_flexchart_stacked(id,r,xparam='time',
plot2.x_range = xrange1
plot3.x_range = xrange1
plot4.x_range = xrange1
-
+
if xparam == 'time':
plot4.xaxis[0].formatter = DatetimeTickFormatter(
hours = ["%H"],
@@ -4651,88 +4653,95 @@ def interactive_flexchart_stacked(id,r,xparam='time',
if yparam1 == 'pace':
y1tooltip = '@fpace'
- else:
+ elif yparam1 != 'None':
y1tooltip = '@{yparam1}'.format(yparam1=yparam1)
if metricsdicts[yparam1]['numtype'] == 'integer' or yparam1 == 'power':
y1tooltip+='{int}'
else:
y1tooltip+='{0.00}'
+ else:
+ y1tooltip = ''
+ comment = 'The metric in the first chart is only accessible with a Pro plan or higher'
if yparam2 == 'pace':
y2tooltip = '@fpace'
- else:
+ elif yparam2 != 'None':
y2tooltip = '@{yparam2}'.format(yparam2=yparam2)
if metricsdicts[yparam2]['numtype'] == 'integer' or yparam2 == 'power':
y2tooltip+='{int}'
else:
y2tooltip+='{0.00}'
+ else:
+ y2tooltip = ''
+ comment = 'The metric in the second chart is only accessible with a Pro plan or higher'
if yparam3 == 'pace':
y3tooltip = '@fpace'
- else:
+ elif yparam3 != 'None':
y3tooltip = '@{yparam3}'.format(yparam3=yparam3)
if metricsdicts[yparam3]['numtype'] == 'integer' or yparam3 == 'power':
y3tooltip+='{int}'
else:
y3tooltip+='{0.00}'
+ else:
+ y3tooltip = ''
+ comment = 'The metric in the third chart is only accessible with a Pro plan or higher'
if yparam4 == 'pace':
y4tooltip = '@fpace'
- else:
+ elif yparam4 != 'None':
y4tooltip = '@{yparam4}'.format(yparam4=yparam4)
if metricsdicts[yparam4]['numtype'] == 'integer' or yparam4 == 'power':
y4tooltip+='{int}'
else:
y4tooltip+='{0.00}'
+ else:
+ y4tooltip = ''
+ comment = 'The metric in the fourth chart is only accessible with a Pro plan or higher'
+ if yparam1 != 'None':
+ hover1.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+ if yparam2 != 'None':
+ hover2.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+ if yparam3 != 'None':
+ hover3.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
+ if yparam4 != 'None':
+ hover4.tooltips = OrderedDict([
+ ('Time','@ftime'),
+ ('Distance','@distance{int}'),
+ (axlabels[yparam1],y1tooltip),
+ (axlabels[yparam2],y2tooltip),
+ (axlabels[yparam3],y3tooltip),
+ (axlabels[yparam4],y4tooltip),
+ ])
-
-
-
-
-
-
- hover1.tooltips = OrderedDict([
- ('Time','@ftime'),
- ('Distance','@distance{int}'),
- (axlabels[yparam1],y1tooltip),
- (axlabels[yparam2],y2tooltip),
- (axlabels[yparam3],y3tooltip),
- (axlabels[yparam4],y4tooltip),
- ])
- hover2.tooltips = OrderedDict([
- ('Time','@ftime'),
- ('Distance','@distance{int}'),
- (axlabels[yparam1],y1tooltip),
- (axlabels[yparam2],y2tooltip),
- (axlabels[yparam3],y3tooltip),
- (axlabels[yparam4],y4tooltip),
- ])
-
- hover3.tooltips = OrderedDict([
- ('Time','@ftime'),
- ('Distance','@distance{int}'),
- (axlabels[yparam1],y1tooltip),
- (axlabels[yparam2],y2tooltip),
- (axlabels[yparam3],y3tooltip),
- (axlabels[yparam4],y4tooltip),
- ])
-
- hover4.tooltips = OrderedDict([
- ('Time','@ftime'),
- ('Distance','@distance{int}'),
- (axlabels[yparam1],y1tooltip),
- (axlabels[yparam2],y2tooltip),
- (axlabels[yparam3],y3tooltip),
- (axlabels[yparam4],y4tooltip),
- ])
-
- hover1.mode = 'mouse'
- hover2.mode = 'mouse'
- hover3.mode = 'mouse'
- hover4.mode = 'mouse'
+ hover1.mode = 'vline'
+ hover2.mode = 'vline'
+ hover3.mode = 'vline'
+ hover4.mode = 'vline'
y1min = get_yaxminima(r,yparam1,mode)
y2min = get_yaxminima(r,yparam2,mode)
@@ -4797,7 +4806,7 @@ def interactive_flexchart_stacked(id,r,xparam='time',
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
- return script,div,js_resources,css_resources
+ return script,div,js_resources,css_resources,comment
diff --git a/rowers/templates/flexchartstacked.html b/rowers/templates/flexchartstacked.html
index d6468570..f162fc09 100644
--- a/rowers/templates/flexchartstacked.html
+++ b/rowers/templates/flexchartstacked.html
@@ -3,7 +3,7 @@
{% load rowerfilters %}
{% load tz %}
-{% block title %} Flexible Plot {% endblock %}
+{% block title %}Chart Stack{% endblock %}
{% localtime on %}
{% block main %}
@@ -31,7 +31,7 @@
-Flexible Chart
+Chart Stack
-
diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html
index 0de063d9..cf9e7c26 100644
--- a/rowers/templates/menu_workout.html
+++ b/rowers/templates/menu_workout.html
@@ -73,16 +73,16 @@
- -
-
- Flex Chart
-
-
-
Chart Stack
+ -
+
+ Flex Chart
+
+
{% if workout|water %}
-
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index d63c85c6..00cada4c 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -3966,7 +3966,7 @@ def workout_flexchart_stacked_view(request,*args,**kwargs):
yparam4 = cd['yaxis4']
(
- script, div, js_resources, css_resources
+ script, div, js_resources, css_resources, comment
) = interactive_flexchart_stacked(
encoder.decode_hex(id),r,xparam=xparam,
yparam1=yparam1,
@@ -3976,6 +3976,9 @@ def workout_flexchart_stacked_view(request,*args,**kwargs):
mode=workout.workouttype,
)
+ if comment is not None:
+ messages.error(request,comment)
+
initial = {
'xaxis':xparam,
'yaxis1':yparam1,
From 219442f8913abbaa9437b24afc751f078c02b310 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 6 Nov 2020 19:57:55 +0100
Subject: [PATCH 6/7] bug fix
---
rowers/interactiveplots.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 2d99154c..cfbb90cc 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4419,7 +4419,7 @@ def interactive_flexchart_stacked(id,r,xparam='time',
yparam4='spm',
mode='erg'):
- columns = [xparam,yparam1,yparam2,
+ columns = [xparam,yparam1,yparam2,yparam3,yparam4,
'ftime','distance','fpace',
'power','hr','spm','driveenergy',
'time','pace','workoutstate']
@@ -4429,6 +4429,8 @@ def interactive_flexchart_stacked(id,r,xparam='time',
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True,
workstrokesonly=False)
+
+
if r.usersmooth > 1:
for column in columns:
try:
From bc21b787a3c592b01015e66d43d630f23d50a301 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 6 Nov 2020 22:13:43 +0100
Subject: [PATCH 7/7] strava chart
---
rowers/interactiveplots.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index cfbb90cc..b1bb3a9b 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -4448,12 +4448,12 @@ def interactive_flexchart_stacked(id,r,xparam='time',
row = Workout.objects.get(id=id)
if rowdata.empty:
- return "","No valid data",'',''
+ return "","No valid data",'','',comment
try:
tseconds = rowdata.loc[:,'time']
except KeyError:
- return '','No time data - cannot make flex plot','',''
+ return '','No time data - cannot make flex plot','','',comment
try:
rowdata['x1'] = rowdata.loc[:,xparam]