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

+ + + +{% 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