Private
Public Access
1
0

Merge branch 'release/v9.63'

This commit is contained in:
Sander Roosendaal
2019-04-02 22:17:53 +02:00
12 changed files with 499 additions and 80 deletions
+9 -2
View File
@@ -108,9 +108,16 @@ def get_c2_workouts(rower):
for item in res.json()['data']: for item in res.json()['data']:
alldata[item['id']] = item alldata[item['id']] = item
knownc2ids = uniqify([ knownc2ids = [
w.uploadedtoc2 for w in Workout.objects.filter(user=rower) w.uploadedtoc2 for w in Workout.objects.filter(user=rower)
]) ]
tombstones = [
t.uploadedtoc2 for t in TombStone.objects.filter(user=rower)
]
knownc2ids = uniqify(knownc2ids+tombstones)
newids = [c2id for c2id in c2ids if not c2id in knownc2ids] newids = [c2id for c2id in c2ids if not c2id in knownc2ids]
for c2id in newids: for c2id in newids:
+24 -8
View File
@@ -723,6 +723,7 @@ class TrendFlexModalForm(forms.Form):
required=False) required=False)
# This form sets options for the summary stats page # This form sets options for the summary stats page
class StatsOptionsForm(forms.Form): class StatsOptionsForm(forms.Form):
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False) includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
@@ -1181,24 +1182,39 @@ class FlexOptionsForm(forms.Form):
('line','Line Plot'), ('line','Line Plot'),
('scatter','Scatter Plot'), ('scatter','Scatter Plot'),
) )
plottype = forms.ChoiceField(choices=plotchoices,initial='scatter', plottype = forms.ChoiceField(choices=plotchoices,initial='line',
label='Chart Type') 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='scatter',
label='Individual Stroke Chart Type')
class FlexAxesForm(forms.Form): class FlexAxesForm(forms.Form):
axchoices = ( axchoices = list(
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None'] (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 = ( 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'] (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 = ( yaxchoices2 = list(sorted(yaxchoices2.items(), key = lambda x:x[1]))
(ax[0], ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
)
xaxis = forms.ChoiceField( xaxis = forms.ChoiceField(
choices=axchoices,label='X-Axis',required=True) choices=axchoices,label='X-Axis',required=True)
+391 -33
View File
@@ -17,6 +17,7 @@ from math import pi
from django.utils import timezone from django.utils import timezone
from bokeh.palettes import Dark2_8 as palette from bokeh.palettes import Dark2_8 as palette
from bokeh.models.glyphs import MultiLine
import itertools import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation
@@ -64,6 +65,7 @@ activate(settings.TIME_ZONE)
thetimezone = get_current_timezone() thetimezone = get_current_timezone()
from scipy.stats import linregress,percentileofscore from scipy.stats import linregress,percentileofscore
from scipy.spatial import ConvexHull,Delaunay
from scipy import optimize from scipy import optimize
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
from scipy.interpolate import griddata from scipy.interpolate import griddata
@@ -164,7 +166,8 @@ def tailwind(bearing,vwind,winddir):
from rowers.dataprep import nicepaceformat,niceformat from rowers.dataprep import nicepaceformat,niceformat
from rowers.dataprep import timedeltaconv 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: if datadf.empty:
return '','It looks like there are no data matching your filter' 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 = 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]) yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname])
plot.y_range = yrange1 plot.y_range = yrange1
plot.sizing_mode = 'scale_width' plot.sizing_mode = 'scale_width'
@@ -221,6 +215,18 @@ def interactive_boxchart(datadf,fieldname,extratitle=''):
plot.plot_width=920 plot.plot_width=920
plot.plot_height=600 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) script, div = components(plot)
return script,div return script,div
@@ -369,7 +375,7 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'):
script,div = components(p) script,div = components(p)
return script,div 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' TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
ids = [int(w.id) for w in theworkouts] ids = [int(w.id) for w in theworkouts]
@@ -399,63 +405,268 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
if rowdata.empty: if rowdata.empty:
return "","No Valid Data Available","","" return "","No Valid Data Available","",""
# quick linear regression
# peakforce = slope*peakforceangle + intercept
slope, intercept, r,p,stderr = linregress(rowdata['peakforceangle'],rowdata['peakforce'])
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()
try: try:
catchav = rowdata['catch'].mean() 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: except KeyError:
catchav = 0 catchav = 0
catch25 = 0
catch75 = 0
catch05 = 0
catch95 = 0
try: 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: except KeyError:
finishav = 0 finishav = 0
finish25 = 0
finish75 = 0
finish05 = 0
finish95 = 0
try: 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: except KeyError:
washav = 0 washav = 0
wash25 = 0
wash75 = 0
wash05 = 0
wash95 = 0
try: 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: except KeyError:
slipav = 0 slipav = 0
slip25 = 0
slip75 = 0
slip05 = 0
slip95 = 0
try: 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: except KeyError:
peakforceav = 0 peakforceav = 0
peakforce25 = 0
peakforce75 = 0
peakforce05 = 0
peakforce95 = 0
try: try:
averageforceav = rowdata['averageforce'].mean() averageforceav = rowdata['averageforce'].median()
except KeyError: except KeyError:
averageforceav = 0 averageforceav = 0
try: 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: except KeyError:
peakforceangleav = 0 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
]
hull2575 = ConvexHull(points2575)
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, x = [catchav,
catchav+slipav, slipav,
peakforceangleav, peakforceangleav,
finishav-washav, washav,
finishav] finishav]
thresholdforce = 100 if 'x' in boattype else 200
#thresholdforce /= 4.45 # N to lbs
y = [0,thresholdforce, y = [0,thresholdforce,
peakforceav, peakforceav,
thresholdforce,0] thresholdforce,0]
source = ColumnDataSource( source = ColumnDataSource(
data = dict( data = dict(
x = x, x = x,
y = y, 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( source2 = ColumnDataSource(
rowdata 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, plot = Figure(tools=TOOLS,
toolbar_sticky=False,toolbar_location="above") toolbar_sticky=False,toolbar_location="above")
@@ -489,6 +700,56 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
avf = Span(location=averageforceav,dimension='width',line_color='blue', avf = Span(location=averageforceav,dimension='width',line_color='blue',
line_dash=[6,6],line_width=2) 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)):
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]
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.line('x','y',source=source,color="red")
plot.add_layout(avf) plot.add_layout(avf)
@@ -529,19 +790,34 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
) )
sliplabel = Label(x=370,y=280,x_units='screen',y_units='screen', sliplabel = Label(x=370,y=280,x_units='screen',y_units='screen',
text="Slip: {slipav:6.2f}".format(slipav=slipav), text="Slip: {slipav:6.2f}".format(slipav=slipav-catchav),
background_fill_alpha=0.7, background_fill_alpha=0.7,
background_fill_color='white', background_fill_color='white',
text_color='red', text_color='red',
) )
washlabel = Label(x=360,y=250,x_units='screen',y_units='screen', washlabel = Label(x=360,y=250,x_units='screen',y_units='screen',
text="Wash: {washav:6.2f}".format(washav=washav), text="Wash: {washav:6.2f}".format(washav=finishav-washav),
background_fill_alpha=0.7, background_fill_alpha=0.7,
background_fill_color='white', background_fill_color='white',
text_color='red', text_color='red',
) )
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(peakflabel)
plot.add_layout(peakforceanglelabel) plot.add_layout(peakforceanglelabel)
plot.add_layout(avflabel) plot.add_layout(avflabel)
@@ -549,6 +825,8 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
plot.add_layout(sliplabel) plot.add_layout(sliplabel)
plot.add_layout(washlabel) plot.add_layout(washlabel)
plot.add_layout(finishlabel) plot.add_layout(finishlabel)
plot.add_layout(annolabel)
plot.add_layout(sliderlabel)
plot.xaxis.axis_label = "Angle" plot.xaxis.axis_label = "Angle"
plot.yaxis.axis_label = "Force (N)" plot.yaxis.axis_label = "Force (N)"
@@ -564,6 +842,8 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
callback = CustomJS(args = dict( callback = CustomJS(args = dict(
source=source, source=source,
source2=source2, source2=source2,
sourceslipwash=sourceslipwash,
sourcepoints=sourcepoints,
avf=avf, avf=avf,
avflabel=avflabel, avflabel=avflabel,
catchlabel=catchlabel, catchlabel=catchlabel,
@@ -572,12 +852,28 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
washlabel=washlabel, washlabel=washlabel,
peakflabel=peakflabel, peakflabel=peakflabel,
peakforceanglelabel=peakforceanglelabel, peakforceanglelabel=peakforceanglelabel,
annolabel=annolabel,
sliderlabel=sliderlabel,
plottype=plottype,
sourcemultiline=sourcemultiline,
sourcemultiline2=sourcemultiline2
), code=""" ), code="""
var data = source.data var data = source.data
var data2 = source2.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 x = data['x']
var y = data['y'] var y = data['y']
var xslip = dataslipwash['xslip']
var spm1 = data2['spm'] var spm1 = data2['spm']
var distance1 = data2['distance'] var distance1 = data2['distance']
var driveenergy1 = data2['driveenergy'] var driveenergy1 = data2['driveenergy']
@@ -592,6 +888,10 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var peakforce = data2['peakforce'] var peakforce = data2['peakforce']
var averageforce = data2['averageforce'] var averageforce = data2['averageforce']
var peakforcepoints = datapoints['peakforce']
var peakforceanglepoints = datapoints['peakforceangle']
var annotation = annotation.value
var minspm = minspm.value var minspm = minspm.value
var maxspm = maxspm.value var maxspm = maxspm.value
var mindist = mindist.value var mindist = mindist.value
@@ -599,6 +899,10 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var minwork = minwork.value var minwork = minwork.value
var maxwork = maxwork.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 catchav = 0
var finishav = 0 var finishav = 0
var slipav = 0 var slipav = 0
@@ -608,11 +912,23 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
var peakforceav = 0 var peakforceav = 0
var count = 0 var count = 0
datapoints['peakforceangle'] = []
datapoints['peakforce'] = []
multilines['x'] = []
multilines['y'] = []
for (i=0; i<c.length; i++) { for (i=0; i<c.length; i++) {
if (spm1[i]>=minspm && spm1[i]<=maxspm) { if (spm1[i]>=minspm && spm1[i]<=maxspm) {
if (distance1[i]>=mindist && distance1[i]<=maxdist) { if (distance1[i]>=mindist && distance1[i]<=maxdist) {
if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) {
if (plottype=='scatter') {
datapoints['peakforceangle'].push(peakforceangle[i])
datapoints['peakforce'].push(peakforce[i])
}
if (plottype=='line') {
multilines['x'].push(multilinesx[i])
multilines['y'].push(multilinesy[i])
}
catchav += c[i] catchav += c[i]
finishav += finish[i] finishav += finish[i]
slipav += slip[i] slipav += slip[i]
@@ -637,6 +953,9 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav] data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav]
data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0] data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0]
dataslipwash['xslip'] = [catchav+slipav,finishav-washav]
dataslipwash['yslip'] = [thresholdforce,thresholdforce]
avf.location = averageforceav avf.location = averageforceav
avflabel.text = 'Favg: '+averageforceav.toFixed(2) avflabel.text = 'Favg: '+averageforceav.toFixed(2)
catchlabel.text = 'Catch: '+catchav.toFixed(2) catchlabel.text = 'Catch: '+catchav.toFixed(2)
@@ -645,11 +964,23 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
washlabel.text = 'Wash: '+washav.toFixed(2) washlabel.text = 'Wash: '+washav.toFixed(2)
peakflabel.text = 'Fpeak: '+peakforceav.toFixed(2) peakflabel.text = 'Fpeak: '+peakforceav.toFixed(2)
peakforceanglelabel.text = 'Peak angle: '+peakforceangleav.toFixed(2) peakforceanglelabel.text = 'Peak angle: '+peakforceangleav.toFixed(2)
annolabel.text = annotation
console.log(count);
console.log(multilines['x'].length);
console.log(multilines['y'].length);
// source.trigger('change'); // source.trigger('change');
source.change.emit(); source.change.emit();
sourceslipwash.change.emit()
sourcepoints.change.emit();
sourcemultiline.change.emit();
""") """)
annotation = TextInput(title="Type your plot notes here", value="",
callback=callback)
callback.args["annotation"] = annotation
slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1,
title="Min SPM",callback=callback) title="Min SPM",callback=callback)
callback.args["minspm"] = slider_spm_min callback.args["minspm"] = slider_spm_min
@@ -679,7 +1010,8 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False):
title="Max Distance",callback=callback) title="Max Distance",callback=callback)
callback.args["maxdist"] = slider_dist_max callback.args["maxdist"] = slider_dist_max
layout = layoutrow([layoutcolumn([slider_spm_min, layout = layoutrow([layoutcolumn([annotation,
slider_spm_min,
slider_spm_max, slider_spm_max,
slider_dist_min, slider_dist_min,
slider_dist_max, slider_dist_max,
@@ -2370,7 +2702,8 @@ def interactive_chart(id=0,promember=0,intervaldata = {}):
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
ploterrorbars=False, ploterrorbars=False,
title=None,binsize=1,colorlegend=[]): title=None,binsize=1,colorlegend=[],
spmmin=0,spmmax=0,workmin=0,workmax=0):
if datadf.empty: if datadf.empty:
return ['','<p>No non-zero data in selection</p>'] return ['','<p>No non-zero data in selection</p>']
@@ -2515,8 +2848,8 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
for nr, gvalue, color in colorlegend: for nr, gvalue, color in colorlegend:
box = BoxAnnotation(bottom=125+20*nr,left=100,top=145+20*nr, box = BoxAnnotation(bottom=75+20*nr,left=50,top=95+20*nr,
right=120, right=70,
bottom_units='screen', bottom_units='screen',
top_units='screen', top_units='screen',
left_units='screen', left_units='screen',
@@ -2524,7 +2857,7 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
fill_color=color, fill_color=color,
fill_alpha=1.0, fill_alpha=1.0,
line_color=color) line_color=color)
legendlabel = Label(x=121,y=128+20*nr,x_units='screen', legendlabel = Label(x=71,y=78+20*nr,x_units='screen',
y_units='screen', y_units='screen',
text = "{gvalue:3.0f}".format(gvalue=gvalue), text = "{gvalue:3.0f}".format(gvalue=gvalue),
background_fill_alpha=1.0, background_fill_alpha=1.0,
@@ -2534,7 +2867,7 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
plot.add_layout(legendlabel) plot.add_layout(legendlabel)
if colorlegend: if colorlegend:
legendlabel = Label(x=372,y=300,x_units='screen', legendlabel = Label(x=322,y=250,x_units='screen',
y_units='screen', y_units='screen',
text = 'group legend', text = 'group legend',
text_color='black', text_color='black',
@@ -2552,15 +2885,27 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
else: else:
plot.yaxis.axis_label = axlabels[yparam] plot.yaxis.axis_label = axlabels[yparam]
binlabel = Label(x=100,y=100,x_units='screen', binlabel = Label(x=50,y=50,x_units='screen',
y_units='screen', y_units='screen',
text="Bin size {binsize:3.1f}".format(binsize=binsize), text="Bin size {binsize:3.1f}".format(binsize=binsize),
background_fill_alpha=0.7, background_fill_alpha=0.7,
background_fill_color='white', background_fill_color='white',
text_color='black', text_color='black',text_font_size='10pt',
) )
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(binlabel) plot.add_layout(binlabel)
plot.add_layout(sliderlabel)
yrange1 = Range1d(start=yaxmin,end=yaxmax) yrange1 = Range1d(start=yaxmin,end=yaxmax)
plot.y_range = yrange1 plot.y_range = yrange1
@@ -2752,11 +3097,18 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
text_color='green', text_color='green',
) )
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(x1means) plot.add_layout(x1means)
plot.add_layout(xlabel) plot.add_layout(xlabel)
plot.add_layout(y1means) plot.add_layout(y1means)
plot.add_layout(sliderlabel)
y1label = Label(x=50,y=50,x_units='screen',y_units='screen', y1label = Label(x=50,y=50,x_units='screen',y_units='screen',
text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean), text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean),
@@ -2820,6 +3172,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
y1label=y1label, y1label=y1label,
y2label=y2label, y2label=y2label,
xlabel=xlabel, xlabel=xlabel,
sliderlabel=sliderlabel,
y2means=y2means), code=""" y2means=y2means), code="""
var data = source.data var data = source.data
var data2 = source2.data var data2 = source2.data
@@ -2844,6 +3197,11 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
var maxdist = maxdist.value var maxdist = maxdist.value
var minwork = minwork.value var minwork = minwork.value
var maxwork = maxwork.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 xm = 0 var xm = 0
var ym1 = 0 var ym1 = 0
var ym2 = 0 var ym2 = 0
+1 -1
View File
@@ -285,7 +285,7 @@ axesnew = [
(name,d['verbose_name'],d['ax_min'],d['ax_max'],d['type']) for name,d in rowingmetrics (name,d['verbose_name'],d['ax_min'],d['ax_max'],d['type']) for name,d in rowingmetrics
] ]
axes = tuple(axesnew+[('None','None',0,1,'basic')]) axes = tuple(axesnew+[('None','<None>',0,1,'basic')])
axlabels = {ax[0]:ax[1] for ax in axes} axlabels = {ax[0]:ax[1] for ax in axes}
+21 -1
View File
@@ -2506,7 +2506,6 @@ class PlannedSessionFormSmall(ModelForm):
boattypes = mytypes.boattypes boattypes = mytypes.boattypes
# Workout # Workout
@python_2_unicode_compatible
class Workout(models.Model): class Workout(models.Model):
workouttypes = mytypes.workouttypes workouttypes = mytypes.workouttypes
workoutsources = mytypes.workoutsources workoutsources = mytypes.workoutsources
@@ -2612,6 +2611,27 @@ class Workout(models.Model):
return stri return stri
class TombStone(models.Model):
user = models.ForeignKey(Rower,on_delete=models.CASCADE)
uploadedtoc2 = models.IntegerField(default=0)
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = models.BigIntegerField(default=0)
uploadedtounderarmour = models.BigIntegerField(default=0)
uploadedtotp = models.BigIntegerField(default=0)
uploadedtorunkeeper = models.BigIntegerField(default=0)
@receiver(models.signals.pre_delete,sender=Workout)
def create_tombstone_on_delete(sender, instance, **kwargs):
t = TombStone(
user=instance.user,
uploadedtoc2 = instance.uploadedtoc2,
uploadedtostrava = instance.uploadedtostrava,
uploadedtounderarmour = instance.uploadedtounderarmour,
uploadedtotp = instance.uploadedtotp,
uploadedtorunkeeper = instance.uploadedtorunkeeper,
)
t.save()
# delete files belonging to workout instance # delete files belonging to workout instance
# related GraphImage objects should be deleted automatically # related GraphImage objects should be deleted automatically
@receiver(models.signals.post_delete,sender=Workout) @receiver(models.signals.post_delete,sender=Workout)
+8 -2
View File
@@ -165,9 +165,15 @@ def get_strava_workouts(rower):
w.uploadedtostrava = int(stravaid) w.uploadedtostrava = int(stravaid)
w.save() w.save()
knownstravaids = uniqify([ knownstravaids = [
w.uploadedtostrava for w in Workout.objects.filter(user=rower) w.uploadedtostrava for w in Workout.objects.filter(user=rower)
]) ]
tombstones = [
t.uploadedtostrava for t in TombStone.objects.filter(user=rower)
]
knownstravaids = uniqify(knownstravaids+tombstones)
newids = [stravaid for stravaid in stravaids if not stravaid in knownstravaids] newids = [stravaid for stravaid in stravaids if not stravaid in knownstravaids]
+14 -15
View File
@@ -23,23 +23,22 @@
<ul class="main-content"> <ul class="main-content">
<li class="grid_4"> <li class="grid_4">
{% if user.is_authenticated and mayedit %} <div id="theplot" class="flexplot">
<form enctype="multipart/form-data" action="{{ formloc }}" method="post"> {{ the_div|safe }}
{% csrf_token %} </div>
{% if workstrokesonly %}
<input type="hidden" name="workstrokesonly" value="True">
<input class="button blue small" value="Remove Rest Strokes" type="Submit">
{% else %}
<input class="button blue small" type="hidden" name="workstrokesonly" value="False">
<input class="button blue small" value="Include Rest Strokes" type="Submit">
</form>
{% endif %}
<span class="tooltiptext">If your data source allows, this will show or hide strokes taken during rest intervals.</span>
{% endif %}
</li> </li>
<li class="grid_4"> <li class="grid_4">
{{ the_div|safe }} <form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<p>
<input name="chartform" type="submit"
value="Update Chart">
</p>
</form>
</li> </li>
</ul> </ul>
Binary file not shown.
+5 -2
View File
@@ -2593,7 +2593,9 @@ def multiflex_data(request,userid=0,
extratitle=extratitle, extratitle=extratitle,
ploterrorbars=ploterrorbars, ploterrorbars=ploterrorbars,
binsize=binsize, binsize=binsize,
colorlegend=colorlegend) colorlegend=colorlegend,
spmmin=spmmin,spmmax=spmmax,
workmin=workmin,workmax=workmax)
scripta= script.split('\n')[2:-1] scripta= script.split('\n')[2:-1]
script = ''.join(scripta) script = ''.join(scripta)
@@ -3085,7 +3087,8 @@ def boxplot_view_data(request,userid=0,
script,div = interactive_boxchart(datadf,plotfield, script,div = interactive_boxchart(datadf,plotfield,
extratitle=extratitle) extratitle=extratitle,
spmmin=spmmin,spmmax=spmmax,workmin=workmin,workmax=workmax)
scripta = script.split('\n')[2:-1] scripta = script.split('\n')[2:-1]
script = ''.join(scripta) script = ''.join(scripta)
+1
View File
@@ -48,6 +48,7 @@ from django.http import (
) )
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from rowers.forms import ( from rowers.forms import (
ForceCurveOptionsForm,
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm, LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm,
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
+19 -10
View File
@@ -26,15 +26,24 @@ def workout_forcecurve_view(request,id=0,workstrokesonly=False):
if not promember: if not promember:
return HttpResponseRedirect("/rowers/about/") return HttpResponseRedirect("/rowers/about/")
if request.method == 'POST' and 'workstrokesonly' in request.POST: if request.method == 'POST':
workstrokesonly = request.POST['workstrokesonly'] form = ForceCurveOptionsForm(request.POST)
if workstrokesonly == 'True': if form.is_valid():
workstrokesonly = True includereststrokes = form.cleaned_data['includereststrokes']
plottype = form.cleaned_data['plottype']
workstrokesonly = not includereststrokes
else: else:
workstrokesonly = False workstrokesonly = True
plottype = 'line'
else:
form = ForceCurveOptionsForm()
plottype = 'line'
script,div,js_resources,css_resources = interactive_forcecurve([row], script,div,js_resources,css_resources = interactive_forcecurve(
workstrokesonly=workstrokesonly) [row],
workstrokesonly=workstrokesonly,
plottype=plottype,
)
breadcrumbs = [ breadcrumbs = [
{ {
@@ -59,6 +68,7 @@ def workout_forcecurve_view(request,id=0,workstrokesonly=False):
{ {
'the_script':script, 'the_script':script,
'rower':r, 'rower':r,
'form':form,
'workout':row, 'workout':row,
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'active':'nav-workouts', 'active':'nav-workouts',
@@ -67,7 +77,6 @@ def workout_forcecurve_view(request,id=0,workstrokesonly=False):
'css_res':css_resources, 'css_res':css_resources,
'id':id, 'id':id,
'mayedit':mayedit, 'mayedit':mayedit,
'workstrokesonly': not workstrokesonly,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
}) })
@@ -4834,8 +4843,8 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
s = cd["intervalstring"] s = cd["intervalstring"]
try: try:
rowdata.updateinterval_string(s) rowdata.updateinterval_string(s)
except (ParseException,err): except:
messages.error(request,'Parsing error in column '+str(err.col)) messages.error(request,'Parsing error')
intervalstats = rowdata.allstats() intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values() itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist) nrintervals = len(idist)
+1 -1
View File
@@ -608,7 +608,7 @@
#theplot .bk-plot-layout { #theplot .bk-plot-layout {
position: auto; position: auto;
width: 100%; width: 90%;
height: auto; height: auto;
display: inline; display: inline;
} }