diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index 7bb0c4cb..d2f3c84a 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -1007,7 +1007,10 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
f = rowdatadf['TimeStamp (sec)'].diff().mean()
if f != 0:
- windowsize = 2*(int(10./(f)))+1
+ try:
+ windowsize = 2*(int(10./(f)))+1
+ except ValueError:
+ windowsize = 1
else:
windowsize = 1
if windowsize <= 3:
diff --git a/rowers/forms.py b/rowers/forms.py
index d0608bb1..30c6836b 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -25,6 +25,12 @@ import datetime
from django.forms import formset_factory
from rowers.utils import landingpages
from rowers.metrics import axes
+from rowers.metrics import axlabels
+
+formaxlabels = axlabels.copy()
+formaxlabels.pop('None')
+parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
+
class FlexibleDecimalField(forms.DecimalField):
@@ -710,6 +716,12 @@ class DataFrameColumnsForm(forms.Form):
cols = forms.MultipleChoiceField(choices=colchoices,
label='Table Columns')
+class HistoForm(forms.Form):
+ includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
+ histoparam = forms.ChoiceField(choices=parchoices,initial='power',
+ label='Metric')
+
+
# form to select modality and boat type for trend flex
class TrendFlexModalForm(forms.Form):
modality = forms.ChoiceField(choices=workouttypes,
@@ -722,7 +734,8 @@ class TrendFlexModalForm(forms.Form):
label='Only Ranking Pieces',
required=False)
-
+
+
# This form sets options for the summary stats page
class StatsOptionsForm(forms.Form):
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
@@ -796,12 +809,6 @@ class PlannedSessionMultipleCloneForm(forms.Form):
label='Planned Sessions'
)
-from rowers.metrics import axlabels
-
-formaxlabels = axlabels.copy()
-formaxlabels.pop('None')
-parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
-
class BoxPlotChoiceForm(forms.Form):
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
@@ -1181,24 +1188,39 @@ class FlexOptionsForm(forms.Form):
('line','Line Plot'),
('scatter','Scatter Plot'),
)
- plottype = forms.ChoiceField(choices=plotchoices,initial='scatter',
+ plottype = forms.ChoiceField(choices=plotchoices,initial='line',
label='Chart Type')
-
+class ForceCurveOptionsForm(forms.Form):
+ includereststrokes = forms.BooleanField(initial=False, required = False,
+ label='Include Rest Strokes')
+ plotchoices = (
+ ('line','Force Curve Collection Plot'),
+ ('scatter','Peak Force Scatter Plot'),
+ ('none','Only aggregrate data')
+ )
+ plottype = forms.ChoiceField(choices=plotchoices,initial='line',
+ label='Individual Stroke Chart Type')
+
+
class FlexAxesForm(forms.Form):
- axchoices = (
+ 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 = (
- (ax[0], ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
- )
+ 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 = (
- (ax[0], ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
- )
+ 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/imports.py b/rowers/imports.py
index 6114ad79..fe1a85e6 100644
--- a/rowers/imports.py
+++ b/rowers/imports.py
@@ -43,7 +43,7 @@ from django.contrib.auth.decorators import login_required
# from .models import Profile
from rowingdata import rowingdata, make_cumvalues
import pandas as pd
-from rowers.models import Rower,Workout,checkworkoutuser
+from rowers.models import Rower,Workout,checkworkoutuser,TombStone
import rowers.mytypes as mytypes
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 2112a245..141bc9f0 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -17,6 +17,7 @@ from math import pi
from django.utils import timezone
from bokeh.palettes import Dark2_8 as palette
+from bokeh.models.glyphs import MultiLine
import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation
@@ -64,6 +65,7 @@ activate(settings.TIME_ZONE)
thetimezone = get_current_timezone()
from scipy.stats import linregress,percentileofscore
+from scipy.spatial import ConvexHull,Delaunay
from scipy import optimize
from scipy.signal import savgol_filter
from scipy.interpolate import griddata
@@ -164,7 +166,8 @@ def tailwind(bearing,vwind,winddir):
from rowers.dataprep import nicepaceformat,niceformat
from rowers.dataprep import timedeltaconv
-def interactive_boxchart(datadf,fieldname,extratitle=''):
+def interactive_boxchart(datadf,fieldname,extratitle='',
+ spmmin=0,spmmax=0,workmin=0,workmax=0):
if datadf.empty:
return '','It looks like there are no data matching your filter'
@@ -186,15 +189,6 @@ def interactive_boxchart(datadf,fieldname,extratitle=''):
plot = hv.render(boxwhiskers)
- #plot = BoxPlot(datadf, values=fieldname, label='date',
- # legend=False,
- # title=axlabels[fieldname]+' '+extratitle,
- # outliers=False,
- # tools=TOOLS,
- # toolbar_location="above",
- # toolbar_sticky=False,
- # x_mapper_type='datetime',plot_width=920)
-
yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname])
plot.y_range = yrange1
plot.sizing_mode = 'scale_width'
@@ -221,6 +215,18 @@ def interactive_boxchart(datadf,fieldname,extratitle=''):
plot.plot_width=920
plot.plot_height=600
+ slidertext = 'SPM: {:.0f}-{:.0f}, WpS: {:.0f}-{:.0f}'.format(
+ spmmin,spmmax,workmin,workmax
+ )
+ sliderlabel = Label(x=50,y=20,x_units='screen',y_units='screen',
+ text=slidertext,
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='black',text_font_size='10pt',
+ )
+
+ plot.add_layout(sliderlabel)
+
script, div = components(plot)
return script,div
@@ -331,45 +337,12 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'):
p.toolbar_location = None
p.sizing_mode = 'scale_width'
-# p = hv.Bars(df,values='duration',
-# label = CatAttr(columns=['date'], sort=False),
-# xlabel='Date',
-# ylabel='Time',
-# title='Activity {d1} to {d2}'.format(
-# d1 = startdate.strftime("%Y-%m-%d"),
-# d2 = enddate.strftime("%Y-%m-%d"),
-# ),
-# stack=stack,
-# plot_width=350,
-# plot_height=250,
-# toolbar_location = None,
-# )
-
-
-
-
-
- # for legend in p.legend:
- # new_items = []
- # for legend_item in legend.items:
- # it = legend_item.label['value']
- # tot = df[df[stack]==it].duration.sum()
- # if tot != 0:
- # new_items.append(legend_item)
- # legend.items = new_items
- # p.legend.location = "top_left"
- # p.legend.background_fill_alpha = 0.7
- #p.sizing_mode = 'scale_width'
- #p.sizing_mode = 'stretch_both'
-
- #p.yaxis.axis_label = 'Minutes'
-
script,div = components(p)
return script,div
-def interactive_forcecurve(theworkouts,workstrokesonly=False):
+def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
ids = [int(w.id) for w in theworkouts]
@@ -399,63 +372,296 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
if rowdata.empty:
return "","No Valid Data Available","",""
+
+ # quick linear regression
+ # peakforce = slope*peakforceangle + intercept
try:
- catchav = rowdata['catch'].mean()
+ slope, intercept, r,p,stderr = linregress(rowdata['peakforceangle'],rowdata['peakforce'])
+ except KeyError:
+ slope = 0
+ intercept = 0
+
+ try:
+ covariancematrix = np.cov(rowdata['peakforceangle'],y=rowdata['peakforce'])
+ eig_vals, eig_vecs = np.linalg.eig(covariancematrix)
+
+ a = rowdata['peakforceangle']-rowdata['peakforceangle'].median()
+ F = rowdata['peakforce']-rowdata['peakforce'].median()
+
+ Rinv = eig_vecs
+ R = np.linalg.inv(Rinv)
+
+ x = R[0,0]*a+R[0,1]*F
+ y = R[1,0]*a+R[1,1]*F
+
+
+ x05 = x.quantile(q=0.01)
+ x25 = x.quantile(q=0.15)
+ x75 = x.quantile(q=0.85)
+ x95 = x.quantile(q=0.99)
+
+ y05 = y.quantile(q=0.01)
+ y25 = y.quantile(q=0.15)
+ y75 = y.quantile(q=0.85)
+ y95 = y.quantile(q=0.99)
+
+ a25 = Rinv[0,0]*x25 + rowdata['peakforceangle'].median()
+ F25 = Rinv[1,0]*x25 + rowdata['peakforce'].median()
+
+ a25b = Rinv[0,1]*y25 + rowdata['peakforceangle'].median()
+ F25b = Rinv[1,1]*y25 + rowdata['peakforce'].median()
+
+ a75 = Rinv[0,0]*x75 + rowdata['peakforceangle'].median()
+ F75 = Rinv[1,0]*x75 + rowdata['peakforce'].median()
+
+ a75b = Rinv[0,1]*y75 + rowdata['peakforceangle'].median()
+ F75b = Rinv[1,1]*y75 + rowdata['peakforce'].median()
+
+
+ a05 = Rinv[0,0]*x05 + rowdata['peakforceangle'].median()
+ F05 = Rinv[1,0]*x05 + rowdata['peakforce'].median()
+
+ a05b = Rinv[0,1]*y05 + rowdata['peakforceangle'].median()
+ F05b = Rinv[1,1]*y05 + rowdata['peakforce'].median()
+
+ a95 = Rinv[0,0]*x95 + rowdata['peakforceangle'].median()
+ F95 = Rinv[1,0]*x95 + rowdata['peakforce'].median()
+
+ a95b = Rinv[0,1]*y95 + rowdata['peakforceangle'].median()
+ F95b = Rinv[1,1]*y95 + rowdata['peakforce'].median()
+ except KeyError:
+ a25 = 0
+ F25 = 0
+
+ a25b = 0
+ F25b = 0
+
+ a75 = 0
+ F75 = 0
+
+ a75b = 0
+ F75b = 0
+
+
+ a05 = 0
+ F05 = 0
+
+ a05b = 0
+ F05b = 0
+
+ a95 = 0
+ F95 = 0
+
+ a95b = 0
+ F95b = 0
+
+
+
+ try:
+ catchav = rowdata['catch'].median()
+ catch25 = rowdata['catch'].quantile(q=0.25)
+ catch75 = rowdata['catch'].quantile(q=0.75)
+ catch05 = rowdata['catch'].quantile(q=0.05)
+ catch95 = rowdata['catch'].quantile(q=0.95)
except KeyError:
catchav = 0
+ catch25 = 0
+ catch75 = 0
+ catch05 = 0
+ catch95 = 0
try:
- finishav = rowdata['finish'].mean()
+ finishav = rowdata['finish'].median()
+ finish25 = rowdata['finish'].quantile(q=0.25)
+ finish75 = rowdata['finish'].quantile(q=0.75)
+ finish05 = rowdata['finish'].quantile(q=0.05)
+ finish95 = rowdata['finish'].quantile(q=0.95)
except KeyError:
finishav = 0
+ finish25 = 0
+ finish75 = 0
+ finish05 = 0
+ finish95 = 0
+
try:
- washav = rowdata['wash'].mean()
+ washav = (rowdata['finish']-rowdata['wash']).median()
+ wash25 = (rowdata['finish']-rowdata['wash']).quantile(q=0.25)
+ wash75 = (rowdata['finish']-rowdata['wash']).quantile(q=0.75)
+ wash05 = (rowdata['finish']-rowdata['wash']).quantile(q=0.05)
+ wash95 = (rowdata['finish']-rowdata['wash']).quantile(q=0.95)
except KeyError:
washav = 0
+ wash25 = 0
+ wash75 = 0
+ wash05 = 0
+ wash95 = 0
try:
- slipav = rowdata['slip'].mean()
+ slipav = (rowdata['slip']+rowdata['catch']).median()
+ slip25 = (rowdata['slip']+rowdata['catch']).quantile(q=0.25)
+ slip75 = (rowdata['slip']+rowdata['catch']).quantile(q=0.75)
+ slip05 = (rowdata['slip']+rowdata['catch']).quantile(q=0.05)
+ slip95 = (rowdata['slip']+rowdata['catch']).quantile(q=0.95)
except KeyError:
slipav = 0
-
+ slip25 = 0
+ slip75 = 0
+ slip05 = 0
+ slip95 = 0
+
try:
- peakforceav = rowdata['peakforce'].mean()
+ peakforceav = rowdata['peakforce'].median()
+ peakforce25 = rowdata['peakforce'].quantile(q=0.25)
+ peakforce75 = rowdata['peakforce'].quantile(q=0.75)
+ peakforce05 = rowdata['peakforce'].quantile(q=0.05)
+ peakforce95 = rowdata['peakforce'].quantile(q=0.95)
except KeyError:
peakforceav = 0
+ peakforce25 = 0
+ peakforce75 = 0
+ peakforce05 = 0
+ peakforce95 = 0
+
try:
- averageforceav = rowdata['averageforce'].mean()
+ averageforceav = rowdata['averageforce'].median()
except KeyError:
averageforceav = 0
-
+
try:
- peakforceangleav = rowdata['peakforceangle'].mean()
+ peakforceangleav = rowdata['peakforceangle'].median()
+ peakforceangle05 = rowdata['peakforceangle'].quantile(q=0.05)
+ peakforceangle25 = rowdata['peakforceangle'].quantile(q=0.25)
+ peakforceangle75 = rowdata['peakforceangle'].quantile(q=0.75)
+ peakforceangle95 = rowdata['peakforceangle'].quantile(q=0.95)
except KeyError:
peakforceangleav = 0
+ peakforceangle25 = 0
+ peakforceangle75 = 0
+ peakforceangle05 = 0
+ peakforceangle95 = 0
+
+ #thresholdforce /= 4.45 # N to lbs
+ thresholdforce = 100 if 'x' in boattype else 200
+ points2575 = [
+ (catch25,0), #0
+ (slip25,thresholdforce), #1
+ (a75,F75),#4
+ (a25b,F25b), #9
+ (a25,F25), #2
+ (wash75,thresholdforce), #5
+ (finish75,0), #6
+ (finish25,0), #7
+ (wash25,thresholdforce), #8
+ (a75b,F75b), #3
+ (slip75,thresholdforce), #10
+ (catch75,0), #11
+ ]
+
+ points0595 = [
+ (catch05,0), #0
+ (slip05,thresholdforce), #1
+ (a95,F95),#4
+ (a05b,F05b), #9
+ (a05,F05), #2
+ (wash95,thresholdforce), #5
+ (finish95,0), #6
+ (finish05,0), #7
+ (wash05,thresholdforce), #8
+ (a95b,F95b), #3
+ (slip95,thresholdforce), #10
+ (catch95,0), #11
+ ]
+
+
+
+ angles2575 = []
+ forces2575 = []
+
+ for x,y in points2575:
+ angles2575.append(x)
+ forces2575.append(y)
+
+
+ angles0595 = []
+ forces0595 = []
+
+ for x,y in points0595:
+ angles0595.append(x)
+ forces0595.append(y)
+
+
+
+
+
x = [catchav,
- catchav+slipav,
+ slipav,
peakforceangleav,
- finishav-washav,
+ washav,
finishav]
- thresholdforce = 100 if 'x' in boattype else 200
- #thresholdforce /= 4.45 # N to lbs
y = [0,thresholdforce,
peakforceav,
thresholdforce,0]
+
+
source = ColumnDataSource(
data = dict(
x = x,
y = y,
))
+ sourceslipwash = ColumnDataSource(
+ data = dict(
+ xslip = [slipav,washav],
+ yslip = [thresholdforce,thresholdforce]
+ )
+ )
+
+ sourcetrend = ColumnDataSource(
+ data = dict(
+ x = [peakforceangle25,peakforceangle75],
+ y = [peakforce25,peakforce75]
+ )
+ )
+
+ sourcefit = ColumnDataSource(
+ data = dict(
+ x = np.array([peakforceangle25,peakforceangle75]),
+ y = slope*np.array([peakforceangle25,peakforceangle75])+intercept
+ )
+ )
source2 = ColumnDataSource(
rowdata
)
+ if plottype == 'scatter':
+ sourcepoints = ColumnDataSource(
+ data = dict(
+ peakforceangle = rowdata['peakforceangle'],
+ peakforce = rowdata['peakforce']
+ )
+ )
+ else:
+ sourcepoints = ColumnDataSource(
+ data = dict(
+ peakforceangle = [],
+ peakforce = []
+ ))
+
+
+ sourcerange = ColumnDataSource(
+ data = dict(
+ x2575 = angles2575,
+ y2575 = forces2575,
+ x0595 = angles0595,
+ y0595 = forces0595,
+ )
+ )
+
plot = Figure(tools=TOOLS,
toolbar_sticky=False,toolbar_location="above")
@@ -489,59 +695,142 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
avf = Span(location=averageforceav,dimension='width',line_color='blue',
line_dash=[6,6],line_width=2)
+ plot.patch('x0595','y0595',source=sourcerange,color="red",alpha=0.05)
+ plot.patch('x2575','y2575',source=sourcerange,color="red",alpha=0.2)
+ plot.line('x','y',source=source,color="red")
+ plot.circle('xslip','yslip',source=sourceslipwash,color="red")
+
+ plot.circle('peakforceangle','peakforce',source=sourcepoints,color='black',alpha=0.1)
+
+ if plottype == 'line':
+ multilinedatax = []
+ multilinedatay = []
+ for i in range(len(rowdata)):
+ try:
+ x = [
+ rowdata['catch'].values[i],
+ rowdata['slip'].values[i]+rowdata['catch'].values[i],
+ rowdata['peakforceangle'].values[i],
+ rowdata['finish'].values[i]-rowdata['wash'].values[i],
+ rowdata['finish'].values[i]
+ ]
+
+
+ y = [
+ 0,
+ thresholdforce,
+ rowdata['peakforce'].values[i],
+ thresholdforce,
+ 0]
+ except KeyError:
+ x = [0,0]
+ y = [0,0]
+
+ multilinedatax.append(x)
+ multilinedatay.append(y)
+
+ sourcemultiline = ColumnDataSource(dict(
+ x=multilinedatax,
+ y=multilinedatay,
+ ))
+
+ sourcemultiline2 = ColumnDataSource(dict(
+ x=multilinedatax,
+ y=multilinedatay,
+ ))
+
+ glyph = MultiLine(xs='x',ys='y',line_color='black',line_alpha=0.05)
+ plot.add_glyph(sourcemultiline,glyph)
+ else:
+ sourcemultiline = ColumnDataSource(dict(
+ x=[],y=[]))
+
+ sourcemultiline2 = ColumnDataSource(dict(
+ x=[],y=[]))
+
+
plot.line('x','y',source=source,color="red")
plot.add_layout(avf)
- peakflabel = Label(x=355,y=430,x_units='screen',y_units='screen',
+ peakflabel = Label(x=410,y=460,x_units='screen',y_units='screen',
text="Fpeak: {peakforceav:6.2f}".format(peakforceav=peakforceav),
background_fill_alpha=.7,
background_fill_color='white',
text_color='blue',
)
- avflabel = Label(x=365,y=400,x_units='screen',y_units='screen',
+ avflabel = Label(x=420,y=430,x_units='screen',y_units='screen',
text="Favg: {averageforceav:6.2f}".format(averageforceav=averageforceav),
background_fill_alpha=.7,
background_fill_color='white',
text_color='blue',
)
- catchlabel = Label(x=360,y=370,x_units='screen',y_units='screen',
+ catchlabel = Label(x=415,y=400,x_units='screen',y_units='screen',
text="Catch: {catchav:6.2f}".format(catchav=catchav),
background_fill_alpha=0.7,
background_fill_color='white',
text_color='red',
)
- peakforceanglelabel = Label(x=320,y=340,x_units='screen',y_units='screen',
+ peakforceanglelabel = Label(x=375,y=370,x_units='screen',y_units='screen',
text="Peak angle: {peakforceangleav:6.2f}".format(peakforceangleav=peakforceangleav),
background_fill_alpha=0.7,
background_fill_color='white',
text_color='red',
)
- finishlabel = Label(x=355,y=310,x_units='screen',y_units='screen',
+ finishlabel = Label(x=410,y=340,x_units='screen',y_units='screen',
text="Finish: {finishav:6.2f}".format(finishav=finishav),
background_fill_alpha=0.7,
background_fill_color='white',
text_color='red',
)
- sliplabel = Label(x=370,y=280,x_units='screen',y_units='screen',
- text="Slip: {slipav:6.2f}".format(slipav=slipav),
+ sliplabel = Label(x=425,y=310,x_units='screen',y_units='screen',
+ text="Slip: {slipav:6.2f}".format(slipav=slipav-catchav),
background_fill_alpha=0.7,
background_fill_color='white',
text_color='red',
)
- washlabel = Label(x=360,y=250,x_units='screen',y_units='screen',
- text="Wash: {washav:6.2f}".format(washav=washav),
+ washlabel = Label(x=415,y=280,x_units='screen',y_units='screen',
+ text="Wash: {washav:6.2f}".format(washav=finishav-washav),
background_fill_alpha=0.7,
background_fill_color='white',
text_color='red',
)
+ lengthlabel = Label(x=405,y=250, x_units='screen',y_units='screen',
+ text="Length: {length:6.2f}".format(length=finishav-catchav),
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='green'
+ )
+
+ efflengthlabel = Label(x=340,y=220, x_units='screen',y_units='screen',
+ text="Effective Length: {length:6.2f}".format(length=washav-slipav),
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='green'
+ )
+
+ annolabel = Label(x=50,y=450,x_units='screen',y_units='screen',
+ text='',
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='black',
+ )
+
+ sliderlabel = Label(x=10,y=470,x_units='screen',y_units='screen',
+ text='',
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='black',text_font_size='10pt',
+ )
+
+
plot.add_layout(peakflabel)
plot.add_layout(peakforceanglelabel)
plot.add_layout(avflabel)
@@ -549,6 +838,10 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
plot.add_layout(sliplabel)
plot.add_layout(washlabel)
plot.add_layout(finishlabel)
+ plot.add_layout(annolabel)
+ plot.add_layout(sliderlabel)
+ plot.add_layout(lengthlabel)
+ plot.add_layout(efflengthlabel)
plot.xaxis.axis_label = "Angle"
plot.yaxis.axis_label = "Force (N)"
@@ -564,6 +857,8 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
callback = CustomJS(args = dict(
source=source,
source2=source2,
+ sourceslipwash=sourceslipwash,
+ sourcepoints=sourcepoints,
avf=avf,
avflabel=avflabel,
catchlabel=catchlabel,
@@ -572,12 +867,30 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
washlabel=washlabel,
peakflabel=peakflabel,
peakforceanglelabel=peakforceanglelabel,
+ annolabel=annolabel,
+ sliderlabel=sliderlabel,
+ lengthlabel=lengthlabel,
+ efflengthlabel=efflengthlabel,
+ plottype=plottype,
+ sourcemultiline=sourcemultiline,
+ sourcemultiline2=sourcemultiline2
), code="""
var data = source.data
var data2 = source2.data
+ var dataslipwash = sourceslipwash.data
+ var datapoints = sourcepoints.data
+ var multilines = sourcemultiline.data
+ var multilines2 = sourcemultiline2.data
+ var plottype = plottype
+
+ var multilinesx = multilines2['x']
+ var multilinesy = multilines2['y']
var x = data['x']
var y = data['y']
+
+ var xslip = dataslipwash['xslip']
+
var spm1 = data2['spm']
var distance1 = data2['distance']
var driveenergy1 = data2['driveenergy']
@@ -592,6 +905,10 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var peakforce = data2['peakforce']
var averageforce = data2['averageforce']
+ var peakforcepoints = datapoints['peakforce']
+ var peakforceanglepoints = datapoints['peakforceangle']
+
+ var annotation = annotation.value
var minspm = minspm.value
var maxspm = maxspm.value
var mindist = mindist.value
@@ -599,6 +916,10 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var minwork = minwork.value
var maxwork = maxwork.value
+ sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0)
+ sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0)
+ sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0)
+
var catchav = 0
var finishav = 0
var slipav = 0
@@ -608,11 +929,23 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var peakforceav = 0
var count = 0
+ datapoints['peakforceangle'] = []
+ datapoints['peakforce'] = []
+ multilines['x'] = []
+ multilines['y'] = []
for (i=0; i No non-zero data in selection
- Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces.
-
-
Ranking Pieces
-
-
- Compare Workouts
{% if team %}
@@ -52,14 +40,14 @@
- Plot a power histogram of all your strokes over a date range. + Plot a histogram chart of one metric for all your strokes over a date range.
- - Analyse power vs piece duration to make predictions. For On-The-Water rowing. -
-
- - Analyse power vs piece duration to make predictions, for erg pieces. -
-
+ + Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces. +
+
+ + Analyse power vs piece duration to make predictions. For On-The-Water rowing. +
+
+ + Analyse power vs piece duration to make predictions, for erg pieces. +
+