7415 lines
217 KiB
Python
7415 lines
217 KiB
Python
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import colorsys
|
|
from rowers.models import (
|
|
Workout, User, Rower, WorkoutForm,RowerForm,
|
|
GraphImage,GeoPolygon,GeoCourse,GeoPoint,
|
|
)
|
|
from rowers.tasks import handle_setcp
|
|
from rowingdata import rower as rrower
|
|
from rowingdata import main as rmain
|
|
from rowingdata import cumcpdata,histodata
|
|
|
|
from rowingdata import rowingdata as rrdata
|
|
from math import pi,log2
|
|
from django.utils import timezone
|
|
from rowingdata import make_cumvalues
|
|
|
|
from bokeh.palettes import Dark2_8 as palette
|
|
from bokeh.palettes import Set1_4 as palette2
|
|
from bokeh.models.glyphs import MultiLine
|
|
import itertools
|
|
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
|
|
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band
|
|
|
|
import arrow
|
|
|
|
from rowers.utils import myqueue, totaltime_sec_to_string
|
|
import django_rq
|
|
queue = django_rq.get_queue('default')
|
|
queuelow = django_rq.get_queue('low')
|
|
queuehigh = django_rq.get_queue('low')
|
|
|
|
from bokeh.resources import CDN,INLINE
|
|
from bokeh.embed import components
|
|
from bokeh.layouts import layout,widgetbox
|
|
from bokeh.palettes import Category20c,Category10
|
|
from bokeh.layouts import row as layoutrow
|
|
from bokeh.layouts import column as layoutcolumn
|
|
from bokeh.models import (
|
|
LinearAxis,LogAxis,Range1d,DatetimeTickFormatter,HoverTool,Axis,PrintfTickFormatter
|
|
)
|
|
#from bokeh.io import output_file, show, vplot
|
|
from bokeh.models import (
|
|
GMapPlot, GMapOptions, ColumnDataSource, Circle,
|
|
DataRange1d, PanTool, WheelZoomTool, BoxSelectTool,
|
|
SaveTool, # ResizeTool,
|
|
ResetTool, TapTool,CrosshairTool,BoxZoomTool,
|
|
Span, Label
|
|
)
|
|
from bokeh.transform import cumsum
|
|
from bokeh.models.glyphs import ImageURL
|
|
from bokeh.models import OpenURL, TapTool
|
|
from rowers.opaque import encoder
|
|
#from bokeh.models.widgets import Slider, Select, TextInput
|
|
from bokeh.core.properties import value
|
|
|
|
from collections import OrderedDict
|
|
from django.conf import settings
|
|
|
|
from rowers.courses import (
|
|
course_coord_center,course_coord_maxmin,
|
|
polygon_coord_center
|
|
)
|
|
|
|
from rowers import mytypes
|
|
from rowers.models import course_spline
|
|
|
|
import datetime
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
import holoviews as hv
|
|
from holoviews import opts
|
|
from pytz import timezone as tz,utc
|
|
from django.utils.timezone import get_current_timezone
|
|
from django.utils.timezone import activate
|
|
from django.utils import timezone
|
|
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
|
|
|
|
|
|
import rowers.stravastuff as stravastuff
|
|
from rowers.metrics import rowingmetrics,metricsdicts
|
|
|
|
from rowers.dataprep import rdata
|
|
import rowers.dataprep as dataprep
|
|
import rowers.metrics as metrics
|
|
import rowers.c2stuff as c2stuff
|
|
|
|
from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima,get_yaxminima,get_yaxmaxima
|
|
|
|
from rowers.utils import lbstoN
|
|
from rowers.datautils import p0,rpetotss
|
|
import rowers.datautils as datautils
|
|
|
|
from pandas.core.groupby.groupby import DataError
|
|
|
|
def newtestpower(x):
|
|
try:
|
|
if abs(x['testpower'] - x['testdup']) < 1:
|
|
return np.nan
|
|
except (AttributeError,TypeError):
|
|
return np.nan
|
|
|
|
|
|
return x['testpower']
|
|
|
|
def newtestpowerid(x):
|
|
try:
|
|
if np.isnan(x['testpower']):
|
|
return np.nan
|
|
except (AttributeError,TypeError):
|
|
return np.nan
|
|
|
|
return x['id']
|
|
|
|
def newtestpowerdate(x):
|
|
try:
|
|
if np.isnan(x['testpower']):
|
|
return np.nan
|
|
except (AttributeError,TypeError):
|
|
return np.nan
|
|
|
|
return x['date']
|
|
|
|
def all_goldmedalstandards(workouts,startdate,enddate):
|
|
dates = []
|
|
testpowers = []
|
|
testduration = []
|
|
|
|
for w in workouts:
|
|
goldmedalstandard, goldmedalseconds = dataprep.workout_goldmedalstandard(w)
|
|
if goldmedalseconds > 60:
|
|
dates.append(arrow.get(w.date).datetime)
|
|
testpowers.append(goldmedalstandard)
|
|
testduration.append(goldmedalseconds)
|
|
|
|
return dates,testpowers,testduration
|
|
|
|
def build_goldmedalstandards(workouts,kfitness):
|
|
dates = []
|
|
testpower = []
|
|
testduration = []
|
|
fatigues = []
|
|
fitnesses = []
|
|
impulses = []
|
|
|
|
data = []
|
|
goldmedalstandards = []
|
|
goldmedaldurations = []
|
|
workoutdt = []
|
|
ids = []
|
|
|
|
outids = []
|
|
|
|
for w in workouts:
|
|
ids.append(w.id)
|
|
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
|
|
if goldmedalseconds > 60:
|
|
goldmedalstandards.append(goldmedalstandard)
|
|
goldmedaldurations.append(goldmedalseconds)
|
|
else:
|
|
goldmedalstandards.append(0)
|
|
goldmedaldurations.append(0)
|
|
workoutdt.append(arrow.get(w.date).datetime)
|
|
|
|
df = pd.DataFrame({
|
|
'workout':ids,
|
|
'workoutdt': workoutdt,
|
|
'goldmedalstandard':goldmedalstandards,
|
|
'goldmedalduration':goldmedaldurations,
|
|
})
|
|
|
|
df.sort_values(['workoutdt'],inplace=True)
|
|
|
|
#for id, row in df.iterrows():
|
|
# d = row['workoutdt']
|
|
# dd = d-datetime.timedelta(days=90)
|
|
# mask = df['workoutdt']>dd
|
|
# mask2 = df['workoutdt']<=d
|
|
# df2 = df.where(mask & mask2)
|
|
# powertest = df2['goldmedalstandard'].max()
|
|
# idx = df2['goldmedalstandard'].argmax()
|
|
# durationtest = df2['goldmedalduration'].values[idx]
|
|
# dates.append(d)
|
|
# testpower.append(powertest)
|
|
# testduration.append(durationtest)
|
|
# fatigues.append(np.nan)
|
|
# fitnesses.append(np.nan)
|
|
|
|
for w in workouts:
|
|
#ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=90),
|
|
# date__lte=w.date)]
|
|
|
|
ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=10),
|
|
date__lte=w.date)]
|
|
|
|
powerdf = df[df['workout'].isin(ids)]
|
|
indexmax = powerdf['goldmedalstandard'].idxmax()
|
|
theid = powerdf.loc[indexmax,'workout']
|
|
powertest = powerdf['goldmedalstandard'].max()
|
|
durationtest = powerdf.loc[indexmax,'goldmedalduration']
|
|
|
|
|
|
dates.append(arrow.get(w.date).datetime)
|
|
if powertest > 0:
|
|
testpower.append(powertest)
|
|
testduration.append(durationtest)
|
|
outids.append(theid)
|
|
else:
|
|
testpower.append(np.nan)
|
|
testduration.append(np.nan)
|
|
outids.append(np.nan)
|
|
|
|
fatigues.append(np.nan)
|
|
fitnesses.append(np.nan)
|
|
impulses.append(np.nan)
|
|
|
|
return dates, testpower, testduration, fatigues, fitnesses,impulses,outids
|
|
|
|
|
|
def get_testpower(workouts,fitnesstestsecs,kfitness):
|
|
dates = []
|
|
testpower = []
|
|
testduration = []
|
|
fatigues = []
|
|
fitnesses = []
|
|
data = []
|
|
for w in workouts:
|
|
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=w.id)
|
|
try:
|
|
df = pd.read_parquet(cpfile)
|
|
df['workout'] = w.id
|
|
df['workoutdate'] = arrow.get(w.date.strftime('%d-%m-%Y')).datetime
|
|
data.append(df)
|
|
except:
|
|
strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id])
|
|
res = myqueue(queuelow,
|
|
handle_setcp,
|
|
strokesdf,
|
|
cpfile,w.id)
|
|
|
|
if len(data)>1:
|
|
df = pd.concat(data,axis=0)
|
|
|
|
fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3])))
|
|
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
|
|
|
|
for w in workouts:
|
|
# Create CP data point for date range
|
|
ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness),
|
|
date__lte=w.date)]
|
|
|
|
try:
|
|
powerdf = df[df['workout'].isin(ids)]
|
|
|
|
powerdf = powerdf[powerdf['cp'] == powerdf.groupby(['delta'])['cp'].transform('max')]
|
|
powerdf = powerdf.sort_values(['delta']).reset_index()
|
|
|
|
|
|
powerdf = powerdf[powerdf['cp']>0]
|
|
powerdf.dropna(axis=0,inplace=True)
|
|
powerdf.sort_values(['delta','cp'],ascending=[1,0],inplace=True)
|
|
powerdf.drop_duplicates(subset='delta',keep='first',inplace=True)
|
|
except KeyError:
|
|
powerdf = pd.DataFrame()
|
|
|
|
# p1,fitt,fitpower,ratio = datautils.cpfit(powerdf)
|
|
if len(powerdf['delta'])>= 4:
|
|
thesecs = powerdf['delta'].values
|
|
theavpower = powerdf['cp'].values
|
|
|
|
if thesecs.min() < fitnesstestsecs and thesecs.max() > fitnesstestsecs:
|
|
ww = griddata(thesecs,theavpower,np.array([fitnesstestsecs]),method='linear',rescale=True)
|
|
powertest = ww[0]
|
|
else:
|
|
powertest = np.nan
|
|
|
|
|
|
|
|
dates.append(arrow.get(w.date).datetime)
|
|
testpower.append(powertest)
|
|
testduration.append(fitnesstestsecs)
|
|
fatigues.append(np.nan)
|
|
fitnesses.append(np.nan)
|
|
|
|
return dates,testpower, testduration,fatigues,fitnesses
|
|
|
|
|
|
|
|
def errorbar(fig, x, y, source=ColumnDataSource(),
|
|
xerr=False, yerr=False, color='black',
|
|
point_kwargs={}, error_kwargs={}):
|
|
|
|
|
|
xvalues = source.data[x]
|
|
yvalues = source.data[y]
|
|
|
|
|
|
xerrvalues = source.data['xerror']
|
|
yerrvalues = source.data['yerror']
|
|
try:
|
|
colorvalues = source.data['color']
|
|
except KeyError:
|
|
colorvalues = ["#%02x%02x%02x" % (255,0,0) for x in xvalues]
|
|
|
|
|
|
try:
|
|
a = xvalues[0]+1
|
|
if xerr:
|
|
x_err_x = []
|
|
x_err_y = []
|
|
err_color = []
|
|
for px, py, err, color in zip(xvalues, yvalues, xerrvalues, colorvalues):
|
|
x_err_x.append((px - err, px + err))
|
|
x_err_y.append((py, py))
|
|
(r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5))
|
|
h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.)
|
|
v = v*0.8
|
|
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
|
color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b))
|
|
err_color.append(color2)
|
|
|
|
fig.multi_line(x_err_x, x_err_y, color=err_color,
|
|
name='xerr',
|
|
**error_kwargs)
|
|
except TypeError:
|
|
pass
|
|
|
|
try:
|
|
a = yvalues[0]+1
|
|
if yerr:
|
|
y_err_x = []
|
|
y_err_y = []
|
|
err_color = []
|
|
for px, py, err, color in zip(xvalues, yvalues, yerrvalues, colorvalues):
|
|
y_err_x.append((px, px))
|
|
y_err_y.append((py - err, py + err))
|
|
(r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5))
|
|
h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.)
|
|
v = v*0.8
|
|
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
|
color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b))
|
|
err_color.append(color2)
|
|
|
|
fig.multi_line(y_err_x, y_err_y, color=err_color,
|
|
name='yerr',**error_kwargs)
|
|
except TypeError:
|
|
pass
|
|
|
|
fig.circle(x, y, source=source, name='data',color=color,
|
|
**point_kwargs)
|
|
|
|
|
|
def tailwind(bearing,vwind,winddir):
|
|
""" Calculates head-on head/tailwind in direction of rowing
|
|
|
|
positive numbers are tail wind
|
|
|
|
"""
|
|
|
|
b = np.radians(bearing)
|
|
w = np.radians(winddir)
|
|
|
|
vtail = -vwind*np.cos(w-b)
|
|
|
|
return vtail
|
|
|
|
|
|
from rowers.dataprep import nicepaceformat,niceformat
|
|
from rowers.dataprep import timedeltaconv
|
|
|
|
from math import pi
|
|
|
|
def interactive_hr_piechart(df,rower,title,totalseconds=0):
|
|
if df.empty:
|
|
return "","Not enough data to make a chart"
|
|
|
|
|
|
|
|
df.sort_values(by='hr',inplace=True)
|
|
df['timehr'] = df['deltat']*df['hr']
|
|
|
|
sumtimehr = df['deltat'].sum()
|
|
|
|
if totalseconds == 0:
|
|
totalseconds = sumtimehr
|
|
|
|
|
|
|
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
|
qrydata = df.query(qry)
|
|
frac_lut2 = totalseconds*qrydata['deltat'].sum()/sumtimehr
|
|
|
|
|
|
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
|
frac_ut2 = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
|
|
|
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
|
frac_ut1 = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
|
|
|
qry = '{at} <= hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
|
frac_at = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
|
|
|
qry = '{tr} <= hr < {an}'.format(tr=rower.tr,an=rower.an)
|
|
frac_tr = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
|
|
|
qry = 'hr >= {an}'.format(an=rower.an)
|
|
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
|
|
|
datadict = {
|
|
'<ut2':frac_lut2,
|
|
'ut2': frac_ut2,
|
|
'ut1': frac_ut1,
|
|
'at': frac_at,
|
|
'tr': frac_tr,
|
|
'an': frac_an,
|
|
}
|
|
|
|
|
|
|
|
colors = ['gray','yellow','lime','blue','purple','red']
|
|
|
|
|
|
|
|
data = pd.Series(datadict).reset_index(name='value').rename(columns={'index':'zone'})
|
|
data['angle'] = data['value']/data['value'].sum() * 2*pi
|
|
data['color'] = colors
|
|
data['zone'] = ['<ut2','ut2','ut1','at','tr','an']
|
|
data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
|
|
|
|
|
|
|
|
size=350
|
|
TOOLS = 'save,hover'
|
|
|
|
|
|
z = figure(title="HR "+title, x_range=(-0.5,1), plot_height=375,
|
|
tools=TOOLS,toolbar_location=None,tooltips="@zone: @totaltime",
|
|
)
|
|
|
|
|
|
|
|
z.wedge(x=0,y=1, radius=0.4,
|
|
start_angle=cumsum('angle',include_zero=True), end_angle=cumsum('angle'),
|
|
line_color='white',fill_color='color',source=data,legend_group='zone')
|
|
|
|
|
|
|
|
z.axis.axis_label = None
|
|
z.axis.visible = False
|
|
z.grid.grid_line_color = None
|
|
z.outline_line_color = None
|
|
z.toolbar_location = 'right'
|
|
|
|
|
|
return components(z)
|
|
|
|
def pretty_timedelta(secs):
|
|
hours, remainder = divmod(secs,3600)
|
|
minutes, seconds = divmod(remainder,60)
|
|
|
|
return '{}:{:02}:{:02}'.format(int(hours),int(minutes),int(seconds))
|
|
|
|
def mapcolors(x):
|
|
try:
|
|
return mytypes.color_map[x]
|
|
except KeyError:
|
|
return mytypes.colors[-1]
|
|
|
|
def maptypes(x):
|
|
try:
|
|
return mytypes.workouttypes_ordered[x]
|
|
except KeyError:
|
|
return 'Other'
|
|
|
|
def interactive_workouttype_piechart(workouts):
|
|
if len(workouts) == 0:
|
|
return "","Not enough workouts to make a chart"
|
|
|
|
datadict = {}
|
|
|
|
|
|
for w in workouts:
|
|
try:
|
|
# label = mytypes.workouttypes_ordered[w.workouttype]
|
|
label = w.workouttype
|
|
except KeyError:
|
|
label = w.workouttype
|
|
try:
|
|
datadict[label] += 60*(60*w.duration.hour+w.duration.minute)+w.duration.second
|
|
except KeyError:
|
|
datadict[label] = 60*(60*w.duration.hour+w.duration.minute)+w.duration.second
|
|
|
|
data = pd.Series(datadict).reset_index(name='value').rename(columns={'index':'type'})
|
|
data['angle'] = data['value']/data['value'].sum() * 2*pi
|
|
|
|
|
|
|
|
|
|
data = pd.DataFrame(data)
|
|
|
|
data['color'] = data['type'].apply(lambda x:mapcolors(x))
|
|
data['totaltime'] = data['value'].apply(lambda x:pretty_timedelta(x))
|
|
try:
|
|
data['type'] = data['type'].apply(lambda x:mytypes.workouttypes_ordered[x])
|
|
except KeyError:
|
|
pass
|
|
|
|
|
|
|
|
|
|
p = figure(plot_height=350, title="Types", toolbar_location=None,
|
|
tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0))
|
|
|
|
p.wedge(x=0, y=1, radius=0.4,
|
|
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
|
|
line_color="white", fill_color='color', source=data,legend_group='type', )
|
|
|
|
p.axis.axis_label=None
|
|
p.axis.visible=False
|
|
p.grid.grid_line_color = None
|
|
p.outline_line_color = None
|
|
p.toolbar_location = 'right'
|
|
|
|
return components(p)
|
|
|
|
|
|
|
|
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'
|
|
|
|
tooltips = [
|
|
('Value', '@'+fieldname),
|
|
]
|
|
|
|
hover = HoverTool(tooltips=tooltips)
|
|
|
|
TOOLS = [hover]
|
|
|
|
|
|
hv.extension('bokeh')
|
|
|
|
try:
|
|
boxwhiskers = hv.BoxWhisker(datadf,'date',fieldname)
|
|
boxwhiskers.opts(tools=TOOLS,outlier_color='white')
|
|
except DataError:
|
|
return "","Invalid Data"
|
|
|
|
|
|
plot = hv.render(boxwhiskers)
|
|
|
|
yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname])
|
|
plot.y_range = yrange1
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.xaxis.axis_label = 'Date'
|
|
plot.yaxis.axis_label = axlabels[fieldname]
|
|
|
|
|
|
plot.xaxis.formatter = DatetimeTickFormatter(
|
|
days=["%d %B %Y"],
|
|
months=["%d %B %Y"],
|
|
years=["%d %B %Y"],
|
|
)
|
|
|
|
if fieldname == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
|
|
plot.xaxis.major_label_orientation = pi/4
|
|
|
|
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
|
|
|
|
|
|
def interactive_planchart(data,startdate,enddate):
|
|
source = ColumnDataSource(data)
|
|
|
|
hv.extension('bokeh')
|
|
|
|
|
|
yaxmaximum = data['executed'].max()
|
|
if data['planned'].max() > yaxmaximum:
|
|
yaxmaximum = data['planned'].max()
|
|
|
|
if yaxmaximum == 0:
|
|
yaxmaximum = 250
|
|
|
|
yrange1 = Range1d(start=0,end=1.1*yaxmaximum)
|
|
|
|
|
|
tidy_df = data.melt(id_vars=['startdate'],value_vars=['executed','planned'])
|
|
bars = hv.Bars(tidy_df,['startdate','variable'],['value'])
|
|
bars.opts(
|
|
opts.Bars(show_legend=True,tools=['tap','hover'],legend_position='bottom',show_frame=True))
|
|
|
|
p = hv.render(bars)
|
|
|
|
p.plot_width=550
|
|
p.plot_height=350
|
|
p.y_range = yrange1
|
|
p.toolbar_location = 'above'
|
|
p.sizing_mode = 'stretch_both'
|
|
|
|
script,div = components(p)
|
|
|
|
return script,div
|
|
|
|
def interactive_activitychart(workouts,startdate,enddate,stack='type',toolbar_location=None,
|
|
yaxis='trimp'):
|
|
|
|
dates = []
|
|
dates_sorting = []
|
|
types = []
|
|
rowers = []
|
|
durations = []
|
|
rscores = []
|
|
trimps = []
|
|
links = []
|
|
|
|
|
|
rowersinitials = {}
|
|
seen = ['seen']
|
|
idseen = []
|
|
|
|
startdate = datetime.datetime(year=startdate.year,month=startdate.month,day=startdate.day)
|
|
enddate = datetime.datetime(year=enddate.year,month=enddate.month,day=enddate.day)
|
|
|
|
duration = enddate-startdate
|
|
|
|
totaldays = duration.total_seconds()/(24*3600)
|
|
|
|
|
|
for w in workouts:
|
|
aantal=1
|
|
initials = w.user.user.first_name[0:aantal]+w.user.user.last_name[0:aantal]
|
|
if w.user.id not in idseen:
|
|
while initials in seen:
|
|
aantal += 1
|
|
initials = w.user.user.first_name[0:aantal]+w.user.user.last_name[0:aantal]
|
|
|
|
seen.append(initials)
|
|
idseen.append(w.user.id)
|
|
rowersinitials[w.user.id] = initials
|
|
|
|
|
|
|
|
for w in workouts:
|
|
dd = w.date.strftime('%m/%d')
|
|
dd2 = w.date.strftime('%Y/%m/%d')
|
|
dd3 = w.date.strftime('%Y/%m')
|
|
du = w.duration.hour*60+w.duration.minute
|
|
rscore = w.rscore
|
|
trimp = w.trimp
|
|
|
|
if rscore == 0:
|
|
rscore = w.hrtss
|
|
|
|
if totaldays<30:
|
|
dates.append(dd)
|
|
dates_sorting.append(dd2)
|
|
else:
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
durations.append(du)
|
|
rscores.append(rscore)
|
|
trimps.append(trimp)
|
|
links.append(
|
|
"{siteurl}/rowers/workout/{code}/".format(
|
|
siteurl = settings.SITE_URL,
|
|
code = encoder.encode_hex(w.id)
|
|
)
|
|
)
|
|
|
|
|
|
types.append(w.workouttype)
|
|
try:
|
|
rowers.append(rowersinitials[w.user.id])
|
|
except IndexError:
|
|
rowers.append(str(w.user))
|
|
|
|
try:
|
|
d = utc.localize(startdate)
|
|
except (ValueError,AttributeError):
|
|
d = startdate
|
|
|
|
try:
|
|
enddate = utc.localize(enddate)
|
|
except (ValueError,AttributeError):
|
|
pass
|
|
|
|
# add dates with no activity
|
|
while d<=enddate:
|
|
dd = d.strftime('%d')
|
|
|
|
|
|
if totaldays<30:
|
|
dates.append(d.strftime('%m/%d'))
|
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
|
else:
|
|
dates.append(d.strftime('%Y/%m'))
|
|
dates_sorting.append(d.strftime('%Y/%m'))
|
|
durations.append(0)
|
|
rscores.append(0)
|
|
trimps.append(0)
|
|
links.append('')
|
|
types.append('rower')
|
|
|
|
try:
|
|
rowers.append(rowers[0])
|
|
except IndexError:
|
|
try:
|
|
rowers.append(str(workouts[0].user))
|
|
except IndexError:
|
|
rowers.append(' ')
|
|
|
|
d += datetime.timedelta(days=1)
|
|
|
|
|
|
thedict = {
|
|
'date':dates,
|
|
'date_sorting':dates_sorting,
|
|
'duration':durations,
|
|
'trimp':trimps,
|
|
'rscore':rscores,
|
|
'type':types,
|
|
'rower':rowers,
|
|
'link':links,
|
|
}
|
|
|
|
dim_expr = hv.dim('type').categorize(mytypes.color_map)
|
|
|
|
df = pd.DataFrame(thedict)
|
|
|
|
source = ColumnDataSource(df)
|
|
|
|
df.sort_values('date_sorting',inplace=True)
|
|
#clrs = hv.Cycle(df['colors'].values)
|
|
|
|
hv.extension('bokeh')
|
|
|
|
if stack == 'type':
|
|
table = hv.Table(df,[('date','Date'),('type','Workout Type')],
|
|
[('duration','Minutes'),('rscore','rScore'),('trimp','TRIMP'),('link','link')])
|
|
|
|
else:
|
|
table = hv.Table(df,[('date','Date'),('rower','Rower')],
|
|
[('duration','Minutes'),('rscore','rScore'),('trimp','TRIMP'),('link','link')])
|
|
|
|
|
|
bars=table.to.bars(['date',stack],[yaxis])
|
|
if stack == 'type':
|
|
bars.opts(
|
|
opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True,
|
|
tools=['tap','hover'], width=550, xrotation=45,padding=(0,(0,.1)),
|
|
legend_position='bottom',show_frame=True))
|
|
else:
|
|
bars.opts(
|
|
opts.Bars(cmap='Category10', show_legend=True, stacked=True,
|
|
tools=['tap','hover'], width=550, xrotation=45,padding=(0,(0,.1)),
|
|
legend_position='bottom',show_frame=True))
|
|
|
|
p = hv.render(bars)
|
|
|
|
p.title.text = 'Activity {d1} to {d2}'.format(
|
|
d1 = startdate.strftime("%Y-%m-%d"),
|
|
d2 = enddate.strftime("%Y-%m-%d"),
|
|
)
|
|
|
|
p.plot_width=550
|
|
p.plot_height=350
|
|
p.toolbar_location = toolbar_location
|
|
p.y_range.start = 0
|
|
p.sizing_mode = 'stretch_both'
|
|
url = "http://rowsandall.com/rowers/workout/@duration/"
|
|
taptool = p.select(type=TapTool)
|
|
|
|
|
|
|
|
callback = CustomJS(args={'links':df.link}, code="""
|
|
var index = cb_data.source.selected['1d'].indices[0];
|
|
console.log(links);
|
|
console.log(index);
|
|
console.log(links[index]);
|
|
window.location.href = links[index]
|
|
""")
|
|
|
|
taptool.js_on_event('tap',callback)
|
|
|
|
|
|
script,div = components(p)
|
|
return script,div
|
|
|
|
def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_location=None,
|
|
yaxis='duration'):
|
|
|
|
|
|
dates = []
|
|
dates_sorting = []
|
|
types = []
|
|
rowers = []
|
|
durations = []
|
|
rscores = []
|
|
trimps = []
|
|
links = []
|
|
|
|
|
|
rowersinitials = {}
|
|
seen = ['seen']
|
|
idseen = []
|
|
|
|
startdate = datetime.datetime(year=startdate.year,month=startdate.month,day=startdate.day)
|
|
enddate = datetime.datetime(year=enddate.year,month=enddate.month,day=enddate.day)
|
|
|
|
duration = enddate-startdate
|
|
|
|
totaldays = duration.total_seconds()/(24*3600)
|
|
|
|
|
|
for w in workouts:
|
|
aantal=1
|
|
initials = w.user.user.first_name[0:aantal]+w.user.user.last_name[0:aantal]
|
|
if w.user.id not in idseen:
|
|
while initials in seen:
|
|
aantal += 1
|
|
initials = w.user.user.first_name[0:aantal]+w.user.user.last_name[0:aantal]
|
|
|
|
seen.append(initials)
|
|
idseen.append(w.user.id)
|
|
rowersinitials[w.user.id] = initials
|
|
|
|
|
|
|
|
for w in workouts:
|
|
dd = w.date.strftime('%m/%d')
|
|
dd2 = w.date.strftime('%Y/%m/%d')
|
|
dd3 = w.date.strftime('%Y/%m')
|
|
du = w.duration.hour*60+w.duration.minute
|
|
trimp = w.trimp
|
|
rscore = w.rscore
|
|
if rscore == 0:
|
|
rscore = w.hrtss
|
|
|
|
if totaldays<30:
|
|
dates.append(dd)
|
|
dates_sorting.append(dd2)
|
|
else:
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
durations.append(du)
|
|
trimps.append(trimp)
|
|
rscores.append(rscore)
|
|
links.append(
|
|
"{siteurl}/rowers/workout/{code}/".format(
|
|
siteurl = settings.SITE_URL,
|
|
code = encoder.encode_hex(w.id)
|
|
)
|
|
)
|
|
|
|
|
|
types.append(w.workouttype)
|
|
try:
|
|
rowers.append(rowersinitials[w.user.id])
|
|
except IndexError:
|
|
rowers.append(str(w.user))
|
|
|
|
try:
|
|
d = utc.localize(startdate)
|
|
except (ValueError,AttributeError):
|
|
d = startdate
|
|
|
|
try:
|
|
enddate = utc.localize(enddate)
|
|
except (ValueError,AttributeError):
|
|
pass
|
|
|
|
# add dates with no activity
|
|
while d<=enddate:
|
|
dd = d.strftime('%d')
|
|
|
|
|
|
if totaldays<30:
|
|
dates.append(d.strftime('%m/%d'))
|
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
|
else:
|
|
dates.append(d.strftime('%Y/%m'))
|
|
dates_sorting.append(d.strftime('%Y/%m'))
|
|
durations.append(0)
|
|
trimps.append(0)
|
|
rscores.append(0)
|
|
links.append('')
|
|
types.append('rower')
|
|
|
|
try:
|
|
rowers.append(rowers[0])
|
|
except IndexError:
|
|
try:
|
|
rowers.append(str(workouts[0].user))
|
|
except IndexError:
|
|
rowers.append(' ')
|
|
|
|
d += datetime.timedelta(days=1)
|
|
|
|
|
|
thedict = {
|
|
'date':dates,
|
|
'date_sorting':dates_sorting,
|
|
'duration':durations,
|
|
'trimp':trimps,
|
|
'rscore':rscores,
|
|
'type':types,
|
|
'rower':rowers,
|
|
'link':links,
|
|
}
|
|
|
|
dim_expr = hv.dim('type').categorize(mytypes.color_map)
|
|
|
|
df = pd.DataFrame(thedict)
|
|
|
|
df['color'] = df['type'].apply(lambda x:mapcolors(x))
|
|
|
|
df.sort_values('date_sorting',inplace=True)
|
|
#clrs = hv.Cycle(df['colors'].values)
|
|
source = ColumnDataSource(df)
|
|
|
|
hv.extension('bokeh')
|
|
|
|
|
|
table = hv.Table(df,[('date','Date'),('type','Workout Type')],
|
|
[('duration','Minutes'),('trimp','TRIMP'),('rscore','rScore'),('link','link')])
|
|
|
|
|
|
bars=table.to.bars(['date',stack],[yaxis])
|
|
|
|
|
|
bars.opts(
|
|
opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True,
|
|
tools=['tap','hover'], width=550, xrotation=45,padding=(0,(0,.1)),
|
|
legend_position='bottom',show_frame=True))
|
|
|
|
|
|
|
|
|
|
p = hv.render(bars)
|
|
|
|
p.title.text = 'Activity {d1} to {d2}'.format(
|
|
d1 = startdate.strftime("%Y-%m-%d"),
|
|
d2 = enddate.strftime("%Y-%m-%d"),
|
|
)
|
|
|
|
p.plot_width=550
|
|
p.plot_height=350
|
|
p.toolbar_location = toolbar_location
|
|
p.sizing_mode = 'stretch_both'
|
|
p.y_range.start = 0
|
|
url = "http://rowsandall.com/rowers/workout/@duration/"
|
|
taptool = p.select(type=TapTool)
|
|
|
|
|
|
|
|
callback = CustomJS(args={'links':df.link}, code="""
|
|
var index = cb_data.source.selected['1d'].indices[0];
|
|
console.log(links);
|
|
console.log(index);
|
|
console.log(links[index]);
|
|
window.location.href = links[index]
|
|
""")
|
|
|
|
taptool.js_on_event('tap',callback)
|
|
|
|
|
|
script,div = components(p)
|
|
return script,div
|
|
|
|
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]
|
|
|
|
boattype = theworkouts[0].boattype
|
|
|
|
columns = ['catch','slip','wash','finish','averageforce',
|
|
'peakforceangle','peakforce','spm','distance',
|
|
'workoutstate','driveenergy']
|
|
|
|
|
|
rowdata = dataprep.getsmallrowdata_db(columns,ids=ids,
|
|
workstrokesonly=workstrokesonly)
|
|
|
|
rowdata.dropna(axis=1,how='all',inplace=True)
|
|
rowdata.dropna(axis=0,how='any',inplace=True)
|
|
|
|
workoutstateswork = [1,4,5,8,9,6,7]
|
|
workoutstatesrest = [3]
|
|
workoutstatetransition = [0,2,10,11,12,13]
|
|
|
|
if workstrokesonly:
|
|
try:
|
|
rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)]
|
|
except KeyError:
|
|
pass
|
|
|
|
if rowdata.empty:
|
|
return "","No Valid Data Available","",""
|
|
|
|
|
|
# quick linear regression
|
|
# peakforce = slope*peakforceangle + intercept
|
|
try:
|
|
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'].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['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']+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'].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'].median()
|
|
except KeyError:
|
|
averageforceav = 0
|
|
|
|
try:
|
|
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,
|
|
slipav,
|
|
peakforceangleav,
|
|
washav,
|
|
finishav]
|
|
|
|
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':
|
|
try:
|
|
sourcepoints = ColumnDataSource(
|
|
data = dict(
|
|
peakforceangle = rowdata['peakforceangle'],
|
|
peakforce = rowdata['peakforce']
|
|
)
|
|
)
|
|
except KeyError:
|
|
sourcepoints = ColumnDataSource(
|
|
data = dict(
|
|
peakforceangle = [],
|
|
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",plot_width=800,plot_height=600)
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
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=760,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=770,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=765,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=725,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=760,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=775,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=765,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=755,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=690,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)
|
|
plot.add_layout(catchlabel)
|
|
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)"
|
|
plot.title.text = theworkouts[0].name
|
|
plot.title.text_font_size=value("1.0em")
|
|
|
|
yrange1 = Range1d(start=0,end=900)
|
|
plot.y_range = yrange1
|
|
|
|
xrange1 = Range1d(start=yaxmaxima['catch'],end=yaxmaxima['finish'])
|
|
plot.x_range = xrange1
|
|
|
|
callback = CustomJS(args = dict(
|
|
source=source,
|
|
source2=source2,
|
|
sourceslipwash=sourceslipwash,
|
|
sourcepoints=sourcepoints,
|
|
avf=avf,
|
|
avflabel=avflabel,
|
|
catchlabel=catchlabel,
|
|
finishlabel=finishlabel,
|
|
sliplabel=sliplabel,
|
|
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']
|
|
|
|
var thresholdforce = y[1]
|
|
|
|
var c = source2.data['catch']
|
|
var finish = data2['finish']
|
|
var slip = data2['slip']
|
|
var wash = data2['wash']
|
|
var peakforceangle = data2['peakforceangle']
|
|
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
|
|
var maxdist = maxdist.value
|
|
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
|
|
var washav = 0
|
|
var peakforceangleav = 0
|
|
var averageforceav = 0
|
|
var peakforceav = 0
|
|
var count = 0
|
|
|
|
datapoints['peakforceangle'] = []
|
|
datapoints['peakforce'] = []
|
|
multilines['x'] = []
|
|
multilines['y'] = []
|
|
|
|
for (var i=0; i<c.length; i++) {
|
|
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
|
|
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
|
|
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]
|
|
finishav += finish[i]
|
|
slipav += slip[i]
|
|
washav += wash[i]
|
|
peakforceangleav += peakforceangle[i]
|
|
averageforceav += averageforce[i]
|
|
peakforceav += peakforce[i]
|
|
count += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catchav /= count
|
|
finishav /= count
|
|
slipav /= count
|
|
washav /= count
|
|
peakforceangleav /= count
|
|
peakforceav /= count
|
|
averageforceav /= count
|
|
|
|
data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav]
|
|
data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0]
|
|
|
|
dataslipwash['xslip'] = [catchav+slipav,finishav-washav]
|
|
dataslipwash['yslip'] = [thresholdforce,thresholdforce]
|
|
|
|
var length = finishav-catchav
|
|
var efflength = length-slipav-washav
|
|
|
|
avf.location = averageforceav
|
|
avflabel.text = 'Favg: '+averageforceav.toFixed(2)
|
|
catchlabel.text = 'Catch: '+catchav.toFixed(2)
|
|
finishlabel.text = 'Finish: '+finishav.toFixed(2)
|
|
sliplabel.text = 'Slip: '+slipav.toFixed(2)
|
|
washlabel.text = 'Wash: '+washav.toFixed(2)
|
|
peakflabel.text = 'Fpeak: '+peakforceav.toFixed(2)
|
|
peakforceanglelabel.text = 'Peak angle: '+peakforceangleav.toFixed(2)
|
|
annolabel.text = annotation
|
|
lengthlabel.text = 'Length: '+length.toFixed(2)
|
|
efflengthlabel.text = 'Effective Length: '+efflength.toFixed(2)
|
|
|
|
console.log(count);
|
|
console.log(multilines['x'].length);
|
|
console.log(multilines['y'].length);
|
|
|
|
// source.trigger('change');
|
|
source.change.emit();
|
|
sourceslipwash.change.emit()
|
|
sourcepoints.change.emit();
|
|
sourcemultiline.change.emit();
|
|
""")
|
|
|
|
annotation = TextInput(width=140, title="Type your plot notes here", value="")
|
|
annotation.js_on_change('value',callback)
|
|
callback.args["annotation"] = annotation
|
|
|
|
slider_spm_min = Slider(width=140, start=15.0, end=55,value=15.0, step=.1,
|
|
title="Min SPM")
|
|
slider_spm_min.js_on_change('value',callback)
|
|
callback.args["minspm"] = slider_spm_min
|
|
|
|
|
|
slider_spm_max = Slider(width=140, start=15.0, end=55,value=55.0, step=.1,
|
|
title="Max SPM")
|
|
slider_spm_max.js_on_change('value',callback)
|
|
callback.args["maxspm"] = slider_spm_max
|
|
|
|
slider_work_min = Slider(width=140, start=0, end=1500,value=0, step=10,
|
|
title="Min Work per Stroke")
|
|
slider_work_min.js_on_change('value',callback)
|
|
callback.args["minwork"] = slider_work_min
|
|
|
|
|
|
slider_work_max = Slider(width=140, start=0, end=1500,value=1500, step=10,
|
|
title="Max Work per Stroke")
|
|
slider_work_max.js_on_change('value',callback)
|
|
callback.args["maxwork"] = slider_work_max
|
|
|
|
distmax = 100+100*int(rowdata['distance'].max()/100.)
|
|
|
|
slider_dist_min = Slider(width=140, start=0,end=distmax,value=0,step=50,
|
|
title="Min Distance")
|
|
slider_dist_min.js_on_change('value',callback)
|
|
callback.args["mindist"] = slider_dist_min
|
|
|
|
slider_dist_max = Slider(width=140, start=0,end=distmax,value=distmax,
|
|
step=50,
|
|
title="Max Distance")
|
|
slider_dist_max.js_on_change('value',callback)
|
|
callback.args["maxdist"] = slider_dist_max
|
|
|
|
annotation.sizing_mode = 'fixed'
|
|
slider_spm_min.sizing_mode = 'fixed'
|
|
slider_spm_max.sizing_mode = 'fixed'
|
|
slider_work_min.sizing_mode = 'fixed'
|
|
slider_work_max.sizing_mode = 'fixed'
|
|
slider_dist_min.sizing_mode = 'fixed'
|
|
slider_dist_max.sizing_mode = 'fixed'
|
|
|
|
thesliders = layoutcolumn([annotation,
|
|
slider_spm_min,
|
|
slider_spm_max,
|
|
slider_dist_min,
|
|
slider_dist_max,
|
|
slider_work_min,
|
|
slider_work_max,
|
|
]
|
|
)
|
|
thesliders.sizing_mode = 'fixed'
|
|
|
|
layout = layoutrow([thesliders,
|
|
plot])
|
|
|
|
layout.sizing_mode = 'stretch_both'
|
|
|
|
script, div = components(layout)
|
|
js_resources = INLINE.render_js()
|
|
css_resources = INLINE.render_css()
|
|
|
|
|
|
|
|
return [script,div,js_resources,css_resources]
|
|
|
|
def getfatigues(
|
|
fatigues,fitnesses,dates,testpower,testduration,
|
|
impulses,
|
|
startdate,enddate,user,metricchoice,kfatigue,kfitness):
|
|
|
|
fatigue = 0
|
|
fitness = 0
|
|
|
|
impulses = []
|
|
for f in fatigues:
|
|
impulses.append(0)
|
|
|
|
lambda_a = 2/(kfatigue+1)
|
|
lambda_c = 2/(kfitness+1)
|
|
nrdays = (enddate-startdate).days
|
|
|
|
factor = 1.0
|
|
if metricchoice == 'rscore':
|
|
factor = 2.0
|
|
|
|
for i in range(nrdays+1):
|
|
date = startdate+datetime.timedelta(days=i)
|
|
ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False)
|
|
weight = 0
|
|
for w in ws:
|
|
if getattr(w,metricchoice) > 0:
|
|
weight += factor*getattr(w,metricchoice)
|
|
if getattr(w,metricchoice) <= 0:
|
|
if metricchoice == 'rscore' and w.hrtss > 0:
|
|
weight+= factor*w.hrtss
|
|
elif metricchoice == 'rscore' and w.hrtss <= 0:
|
|
trimp,hrtss = dataprep.workout_trimp(w)
|
|
rscore,normp = dataprep.workout_rscore(w)
|
|
if w.rpe and w.rpe > 0:
|
|
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
|
|
dd = dd/3600
|
|
weight += factor*rpetotss[w.rpe]*dd
|
|
elif metricchoice == 'trimp' and w.trimp <= 0:
|
|
trimp,hrtss = dataprep.workout_trimp(w)
|
|
rscore,normp = dataprep.workout_rscore(w)
|
|
if w.rpe and w.rpe > 0:
|
|
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
|
|
dd = dd/3600
|
|
weight += 2*rpetotss[w.rpe]*dd
|
|
|
|
impulses.append(weight)
|
|
|
|
fatigue = (1-lambda_a)*fatigue+weight*lambda_a
|
|
fitness = (1-lambda_c)*fitness+weight*lambda_c
|
|
|
|
fatigues.append(fatigue)
|
|
fitnesses.append(fitness)
|
|
dates.append(arrow.get(date).datetime)
|
|
testpower.append(np.nan)
|
|
testduration.append(np.nan)
|
|
|
|
|
|
|
|
return fatigues,fitnesses,dates,testpower,testduration,impulses
|
|
|
|
def goldmedalscorechart(user,startdate=None,enddate=None):
|
|
# to avoid data mess later on
|
|
startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0)
|
|
enddate = enddate+datetime.timedelta(days=1)
|
|
enddate = arrow.get(enddate).datetime.replace(hour=0,minute=0,second=0,microsecond=0)
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
workouts = Workout.objects.filter(user=user.rower,date__gte=startdate,
|
|
date__lte=enddate,
|
|
workouttype__in=mytypes.rowtypes,
|
|
duplicate=False)
|
|
|
|
# marker workouts
|
|
dates,testpower,testduration,fatigues,fitnesses,impulses, outids = build_goldmedalstandards(
|
|
workouts,42
|
|
)
|
|
|
|
df = pd.DataFrame({
|
|
'id':outids,
|
|
'date':dates,
|
|
'testpower':testpower,
|
|
'testduration':testduration,
|
|
})
|
|
df.sort_values(['date'],inplace=True)
|
|
|
|
df['testdup'] = df['testpower'].shift(1)
|
|
df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1)
|
|
#df['date'] = df.apply(lambda x: newtestpowerdate(x), axis=1)
|
|
|
|
|
|
|
|
|
|
mask = df['testpower'].isnull()
|
|
dates = df.mask(mask)['date'].dropna().values
|
|
testpower = df.mask(mask)['testpower'].dropna().values
|
|
ids = df.mask(mask)['id'].dropna().values
|
|
|
|
outids = df.mask(mask)['id'].dropna().unique()
|
|
|
|
# all workouts
|
|
alldates,alltestpower,allduration = all_goldmedalstandards(workouts,startdate,enddate)
|
|
|
|
nrdays = (enddate-startdate).days
|
|
|
|
td = []
|
|
markerscore = []
|
|
score = []
|
|
markerduration = []
|
|
duration = []
|
|
|
|
previous = 0
|
|
|
|
for i in range(len(dates)):
|
|
id = ids[i]
|
|
w = Workout.objects.get(id=id)
|
|
dd = str(dates[i])
|
|
#td.append(arrow.get(dd).datetime)
|
|
td.append(arrow.get(w.date).datetime)
|
|
markerscore.append(testpower[i])
|
|
markerduration.append(testduration[i])
|
|
score.append(np.nan)
|
|
duration.append(np.nan)
|
|
|
|
for i in range(len(alldates)):
|
|
td.append(arrow.get(alldates[i]).datetime)
|
|
markerscore.append(np.nan)
|
|
score.append(alltestpower[i])
|
|
markerduration.append(np.nan)
|
|
duration.append(allduration[i])
|
|
|
|
for i in range(nrdays+1):
|
|
td.append(arrow.get(startdate+datetime.timedelta(days=i)).datetime)
|
|
markerscore.append(np.nan)
|
|
score.append(np.nan)
|
|
markerduration.append(np.nan)
|
|
duration.append(np.nan)
|
|
|
|
df = pd.DataFrame({
|
|
'markerscore':markerscore,
|
|
'markerduration':markerduration,
|
|
'score':score,
|
|
'duration':duration,
|
|
'date':td,
|
|
})
|
|
|
|
|
|
df.sort_values(['date'],inplace=True)
|
|
|
|
df = df.groupby(['date']).max()
|
|
df['date'] = df.index.values
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
markerscore = df['markerscore'],
|
|
score = df['score'],
|
|
markerduration = df['markerduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
|
|
duration = df['duration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
|
|
date = df['date'],
|
|
fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')),
|
|
)
|
|
)
|
|
|
|
plot = Figure(tools=TOOLS,x_axis_type='datetime',
|
|
plot_width=900,plot_height=600,
|
|
toolbar_location='above',
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.xaxis.axis_label = 'Date'
|
|
plot.yaxis.axis_label = 'Gold Medal Score'
|
|
|
|
plot.circle('date','score',source=source,fill_color='blue',
|
|
size=10,
|
|
legend_label='Workouts')
|
|
|
|
plot.circle('date','markerscore',source=source,fill_color='red',
|
|
size=10,
|
|
legend_label='Marker Workouts')
|
|
|
|
plot.legend.location = "bottom_left"
|
|
|
|
plot.x_range = Range1d(
|
|
startdate,enddate+datetime.timedelta(days=5),
|
|
)
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Marker','@markerscore{int}'),
|
|
('Test', '@markerduration'),
|
|
('Score','@score{int}'),
|
|
('Duration', '@duration'),
|
|
('Date','@fdate'),
|
|
])
|
|
|
|
script, div = components(plot)
|
|
|
|
return script, div,outids
|
|
|
|
def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
|
|
metricchoice='trimp',doform=False,dofatigue=False,
|
|
showtests=False):
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
TOOLS2 = 'box_zoom,hover'
|
|
|
|
# to avoid data mess later on
|
|
startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0)
|
|
enddate = enddate+datetime.timedelta(days=1)
|
|
enddate = arrow.get(enddate).datetime.replace(hour=0,minute=0,second=0,microsecond=0)
|
|
|
|
|
|
modelchoice = 'coggan'
|
|
p0 = 0
|
|
k1 = 1
|
|
k2 = 1
|
|
|
|
dates = []
|
|
testpower = []
|
|
fatigues = []
|
|
fitnesses = []
|
|
testduration = []
|
|
impulses = []
|
|
|
|
outids = []
|
|
|
|
if showtests:
|
|
workouts = Workout.objects.filter(user=user.rower,date__gte=startdate,
|
|
date__lte=enddate,
|
|
workouttype__in=mytypes.rowtypes,
|
|
duplicate=False)
|
|
dates,testpower,testduration,fatigues,fitnesses,impulses, outids = build_goldmedalstandards(
|
|
workouts,kfitness
|
|
)
|
|
|
|
df = pd.DataFrame({
|
|
'id': outids,
|
|
'date':dates,
|
|
'testpower':testpower,
|
|
'testduration':testduration,
|
|
'fatigue':fatigues,
|
|
'fitness':fitnesses,
|
|
'impulse':impulses,
|
|
})
|
|
df.sort_values(['date'],inplace=True)
|
|
df['testdup'] = df['testpower'].shift(1)
|
|
df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1)
|
|
df['id'] = df.apply(lambda x: newtestpowerid(x),axis=1)
|
|
|
|
#try:
|
|
# df['testpower'].iloc[-1] = df['testdup'].iloc[-1]
|
|
#except IndexError:
|
|
# pass
|
|
|
|
|
|
dates = [d for d in df['date']]
|
|
testpower = df['testpower'].values.tolist()
|
|
fatigues = df['fatigue'].values.tolist()
|
|
fitnesses = df['fitness'].values.tolist()
|
|
testduration = df['testduration'].values.tolist()
|
|
impulses = df['impulse'].tolist()
|
|
outids = df['id'].unique()
|
|
|
|
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues,
|
|
fitnesses,
|
|
dates,
|
|
testpower,testduration,
|
|
impulses,
|
|
startdate,enddate,
|
|
user,metricchoice,
|
|
kfatigue,kfitness)
|
|
|
|
|
|
|
|
df = pd.DataFrame({
|
|
'date':dates,
|
|
'testpower':testpower,
|
|
'testduration': testduration,
|
|
'fatigue':fatigues,
|
|
'fitness':fitnesses,
|
|
'impulse':impulses,
|
|
})
|
|
|
|
|
|
endfitness = fitnesses[-1]
|
|
endfatigue = fatigues[-1]
|
|
endform = endfitness-endfatigue
|
|
|
|
if modelchoice == 'banister':
|
|
df['fatigue'] = k2*df['fatigue']
|
|
df['fitness'] = p0+k1*df['fitness']
|
|
|
|
|
|
df['form'] = df['fitness']-df['fatigue']
|
|
|
|
|
|
|
|
df.sort_values(['date'],inplace=True)
|
|
df = df.groupby(['date']).max()
|
|
df['date'] = df.index.values
|
|
|
|
|
|
#for row in df.iterrows():
|
|
# print(row)
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
testpower = df['testpower'],
|
|
testduration = df['testduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
|
|
date = df['date'],
|
|
fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')),
|
|
fitness = df['fitness'],
|
|
fatigue = df['fatigue'],
|
|
form = df['form'],
|
|
impulse = df['impulse']
|
|
)
|
|
)
|
|
|
|
|
|
plot = Figure(tools=TOOLS,x_axis_type='datetime',
|
|
plot_width=900,plot_height=300,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
if modelchoice == 'banister':
|
|
fitlabel = 'PTE (fitness)'
|
|
fatiguelabel = 'NTE (fatigue)'
|
|
formlabel = 'Performance'
|
|
rightaxlabel = 'Performance'
|
|
if dofatigue:
|
|
yaxlabel = 'PTE/NTE'
|
|
else:
|
|
yaxlabel = 'PTE'
|
|
else:
|
|
fitlabel = 'Fitness'
|
|
fatiguelabel = 'Fatigue'
|
|
formlabel = 'Freshness'
|
|
rightaxlabel = 'Freshness'
|
|
if dofatigue:
|
|
yaxlabel = 'Fitness/Fatigue'
|
|
else:
|
|
yaxlabel = 'Fitness'
|
|
|
|
|
|
#if showtests:
|
|
# plot.circle('date','testpower',source=source,fill_color='green',size=10,
|
|
# legend_label='Your best workouts')
|
|
|
|
plot.xaxis.axis_label = None
|
|
plot.yaxis.axis_label = yaxlabel
|
|
|
|
|
|
y2rangemin = df.loc[:,['form']].min().min()
|
|
y2rangemax = df.loc[:,['form']].max().max()
|
|
#if dofatigue and showtests:
|
|
# y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min()
|
|
# y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max()*1.02
|
|
#elif showtests:
|
|
# y1rangemin = df.loc[:,['testpower','fitness']].min().min()
|
|
# y1rangemax = df.loc[:,['testpower','fitness']].max().max()*1.02
|
|
if dofatigue:
|
|
y1rangemin = df.loc[:,['fitness','fatigue']].min().min()
|
|
y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02
|
|
else:
|
|
y1rangemin = df.loc[:,['fitness']].min().min()
|
|
y1rangemax = df.loc[:,['fitness']].max().max()*1.02
|
|
|
|
if doform:
|
|
plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax)
|
|
plot.add_layout(LinearAxis(y_range_name="yax2",axis_label=rightaxlabel),"right")
|
|
|
|
plot.line('date','fitness',source=source,color='blue',
|
|
legend_label=fitlabel)
|
|
band = Band(base='date', upper='fitness', source=source, level='underlay',
|
|
fill_alpha=0.2, fill_color='blue')
|
|
plot.add_layout(band)
|
|
|
|
|
|
if dofatigue:
|
|
plot.line('date','fatigue',source=source,color='red',
|
|
legend_label=fatiguelabel)
|
|
if doform:
|
|
plot.line('date','form',source=source,color='green',
|
|
legend_label=formlabel,y_range_name="yax2")
|
|
|
|
plot.legend.location = "top_left"
|
|
|
|
plot.sizing_mode = 'scale_both'
|
|
|
|
#plot.y_range = Range1d(0,1.5*max(df['testpower']))
|
|
startdate = datetime.datetime.combine(startdate,datetime.datetime.min.time())
|
|
enddate = datetime.datetime.combine(enddate,datetime.datetime.min.time())
|
|
|
|
xrange = Range1d(
|
|
startdate,enddate,
|
|
)
|
|
plot.x_range = xrange
|
|
plot.y_range = Range1d(
|
|
start=0,end=y1rangemax,
|
|
)
|
|
plot.title.text = 'Performance Manager '+user.first_name
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
linked_crosshair = CrosshairTool(dimensions='height')
|
|
|
|
|
|
hover.tooltips = OrderedDict([
|
|
#(legend_label,'@testpower'),
|
|
('Date','@fdate'),
|
|
(fitlabel,'@fitness{int}'),
|
|
(fatiguelabel,'@fatigue{int}'),
|
|
(formlabel,'@form{int}'),
|
|
('Impulse','@impulse{int}')
|
|
])
|
|
|
|
if showtests:
|
|
hover.tooltips = OrderedDict([
|
|
#(legend_label,'@testpower'),
|
|
('Date','@fdate'),
|
|
(fitlabel,'@fitness{int}'),
|
|
(fatiguelabel,'@fatigue{int}'),
|
|
(formlabel,'@form{int}'),
|
|
('Impulse','@impulse{int}'),
|
|
('Gold Medal Score','@testpower{int}'),
|
|
('Test', '@testduration'),
|
|
])
|
|
|
|
plot2 = Figure(tools=TOOLS2,x_axis_type='datetime',
|
|
plot_width=900,plot_height=150,
|
|
toolbar_location=None,
|
|
toolbar_sticky=False)
|
|
|
|
|
|
|
|
plot2.x_range = xrange
|
|
plot2.y_range = Range1d(0,df['impulse'].max())
|
|
|
|
plot2.vbar(x = df['date'], top = df['impulse'],color='gray')
|
|
plot2.vbar(x = df['date'], top = 0*df['testpower']+df['impulse'], color='red')
|
|
|
|
plot2.sizing_mode = 'scale_both'
|
|
plot2.yaxis.axis_label = 'Impulse'
|
|
plot2.xaxis.axis_label = 'Date'
|
|
|
|
plot.add_tools(linked_crosshair)
|
|
plot2.add_tools(linked_crosshair)
|
|
|
|
layout = layoutcolumn([plot,plot2])
|
|
layout.sizing_mode = 'stretch_both'
|
|
|
|
try:
|
|
script,div = components(layout)
|
|
except Exception as e:
|
|
df.dropna(inplace=True,axis=0,how='any')
|
|
return (
|
|
'',
|
|
'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format(
|
|
nrworkouts = workouts.count(),
|
|
nrdata = len(df),
|
|
e = e,
|
|
),0,0,0,[]
|
|
)
|
|
|
|
return [script,div,endfitness,endfatigue,endform,outids]
|
|
|
|
|
|
def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
|
|
enddate=None,kfitness=42,kfatigue=7,fitnesstest=20,
|
|
metricchoice='rscore',
|
|
k1=1,k2=1,p0=100,
|
|
modelchoice='tsb',
|
|
usegoldmedalstandard=False):
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
|
|
workouts = workouts.order_by('date')
|
|
|
|
fitnesstestsecs = fitnesstest*60
|
|
df = pd.DataFrame()
|
|
|
|
if not usegoldmedalstandard:
|
|
dates,testpower,testduration, fatigues,fitnesses = get_testpower(
|
|
workouts,fitnesstestsecs,kfitness
|
|
)
|
|
else:
|
|
dates,testpower, testduration,fatigues,fitnesses,impulses = build_goldmedalstandards(
|
|
workouts,kfitness
|
|
)
|
|
# create CP data
|
|
|
|
df = pd.DataFrame({
|
|
'date':dates,
|
|
'testpower':testpower,
|
|
'testduration':testduration,
|
|
'fatigue':fatigues,
|
|
'fitness':fitnesses,
|
|
})
|
|
df.sort_values(['date'],inplace=True)
|
|
|
|
|
|
df['testdup'] = df['testpower'].shift(1)
|
|
df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1)
|
|
|
|
try:
|
|
df['testpower'].iloc[-1] = df['testdup'].iloc[-1]
|
|
except IndexError:
|
|
pass
|
|
|
|
|
|
dates = [d for d in df['date']]
|
|
testpower = df['testpower'].values.tolist()
|
|
fatigues = df['fatigue'].values.tolist()
|
|
fitnesses = df['fitness'].values.tolist()
|
|
testduration = df['testduration'].values.tolist()
|
|
|
|
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(
|
|
fatigues,fitnesses,dates,testpower,testduration,
|
|
startdate,enddate,user,metricchoice,kfatigue,kfitness
|
|
)
|
|
|
|
|
|
df = pd.DataFrame({
|
|
'date':dates,
|
|
'testpower':testpower,
|
|
'testduration':testduration,
|
|
'fatigue':fatigues,
|
|
'fitness':fitnesses,
|
|
})
|
|
|
|
|
|
|
|
if modelchoice == 'banister':
|
|
df['fatigue'] = k2*df['fatigue']
|
|
df['fitness'] = p0+k1*df['fitness']
|
|
|
|
|
|
df['form'] = df['fitness']-df['fatigue']
|
|
|
|
|
|
|
|
df.sort_values(['date'],inplace=True)
|
|
df = df.groupby(['date']).max()
|
|
df['date'] = df.index.values
|
|
|
|
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
testpower = df['testpower'],
|
|
testduration = df['testduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
|
|
date = df['date'],
|
|
fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')),
|
|
fitness = df['fitness'],
|
|
fatigue = df['fatigue'],
|
|
form = df['form'],
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
plot = Figure(tools=TOOLS,x_axis_type='datetime',
|
|
plot_width=900,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
if modelchoice == 'banister':
|
|
fitlabel = 'PTE (fitness)'
|
|
fatiguelabel = 'NTE (fatigue)'
|
|
formlabel = 'Performance'
|
|
rightaxlabel = 'Banister PTE/NTE/Performance'
|
|
else:
|
|
fitlabel = 'CTL'
|
|
fatiguelabel = 'ATL'
|
|
formlabel = 'TSB'
|
|
rightaxlabel = 'Coggan CTL/ATL/TSB'
|
|
|
|
if usegoldmedalstandard:
|
|
legend_label = 'Test Score'
|
|
yaxlabel = 'Test Score'
|
|
else:
|
|
legend_label = '{fitnesstest} min power'.format(fitnesstest=fitnesstest)
|
|
yaxlabel = 'Test Power (Watt)'
|
|
|
|
plot.circle('date','testpower',source=source,fill_color='green',size=10,
|
|
legend_label=legend_label.format(fitnesstest=fitnesstest))
|
|
|
|
plot.xaxis.axis_label = 'Date'
|
|
plot.yaxis.axis_label = yaxlabel
|
|
|
|
|
|
y2rangemin = df.loc[:,['fitness','fatigue','form']].min().min()
|
|
y2rangemax = df.loc[:,['fitness','fatigue','form']].max().max()
|
|
plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax)
|
|
plot.add_layout(LinearAxis(y_range_name="yax2",axis_label=rightaxlabel),"right")
|
|
|
|
plot.line('date','fitness',source=source,color='blue',
|
|
legend_label=fitlabel,y_range_name="yax2")
|
|
band = Band(base='date', upper='fitness', source=source, level='underlay',
|
|
fill_alpha=0.2, fill_color='blue',y_range_name="yax2")
|
|
plot.add_layout(band)
|
|
plot.line('date','fatigue',source=source,color='red',
|
|
legend_label=fatiguelabel,y_range_name="yax2")
|
|
plot.line('date','form',source=source,color='green',
|
|
legend_label=formlabel,y_range_name="yax2")
|
|
|
|
|
|
|
|
|
|
plot.legend.location = "top_left"
|
|
|
|
plot.xaxis.formatter = DatetimeTickFormatter(
|
|
days=["%d %B %Y"],
|
|
months=["%d %B %Y"],
|
|
years=["%d %B %Y"],
|
|
)
|
|
|
|
plot.xaxis.major_label_orientation = pi/4
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
#plot.y_range = Range1d(0,1.5*max(df['testpower']))
|
|
startdate = datetime.datetime.combine(startdate,datetime.datetime.min.time())
|
|
enddate = datetime.datetime.combine(enddate,datetime.datetime.min.time())
|
|
|
|
plot.x_range = Range1d(
|
|
startdate,enddate+datetime.timedelta(days=5),
|
|
)
|
|
plot.title.text = 'Power levels ('+workoutmode+') from workouts '+user.first_name
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
(legend_label,'@testpower{int}'),
|
|
('Test', '@testduration'),
|
|
('Date','@fdate'),
|
|
(fitlabel,'@fitness'),
|
|
(fatiguelabel,'@fatigue'),
|
|
(formlabel,'@form')
|
|
])
|
|
|
|
try:
|
|
script,div = components(plot)
|
|
except Exception as e:
|
|
df.dropna(inplace=True,axis=0,how='any')
|
|
return (
|
|
'',
|
|
'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format(
|
|
nrworkouts = workouts.count(),
|
|
nrdata = len(df),
|
|
e = e,
|
|
)
|
|
)
|
|
|
|
return [script,div]
|
|
|
|
def fitnessmetric_chart(fitnessmetrics,user,workoutmode='rower',startdate=None,
|
|
enddate=None):
|
|
|
|
power4min = [int(m.PowerFourMin) for m in fitnessmetrics]
|
|
power2k = [int(m.PowerTwoK) for m in fitnessmetrics]
|
|
power1hr = [int(m.PowerOneHour) for m in fitnessmetrics]
|
|
dates = [m.date for m in fitnessmetrics]
|
|
|
|
|
|
mode = [m.workoutmode for m in fitnessmetrics]
|
|
|
|
if len(power4min) == 0:
|
|
return ['','']
|
|
|
|
df = pd.DataFrame(
|
|
{'power4min':power4min,
|
|
'power2k':power2k,
|
|
'power1hr':power1hr,
|
|
'date':dates,
|
|
'dates':dates,
|
|
'mode':mode
|
|
})
|
|
|
|
|
|
delta = df['power4min'].astype('int').diff()
|
|
|
|
mask = delta == 0
|
|
|
|
df.loc[mask,'power4min'] = np.nan
|
|
df.dropna(inplace=True,axis=0,how='any')
|
|
|
|
|
|
df = df[df['power2k']>0]
|
|
try:
|
|
df = df[df['mode']==workoutmode]
|
|
except TypeError:
|
|
df = pd.DataFrame()
|
|
|
|
if df.empty:
|
|
return ["","no data"]
|
|
|
|
groups = df.groupby(by='date').max()
|
|
|
|
power4min = groups['power4min']
|
|
date = groups['dates']
|
|
power2k = groups['power2k']
|
|
power1hr = groups['power1hr']
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
power4min = power4min,
|
|
power2k = power2k,
|
|
date = date,
|
|
power1hr = power1hr,
|
|
fdate=groups['dates'].map(lambda x: x.strftime('%Y-%m-%d'))
|
|
)
|
|
)
|
|
|
|
# fit
|
|
|
|
resampled = groups.set_index('dates')
|
|
resampled.index = pd.to_datetime(resampled.index)
|
|
resampled = resampled.resample('D').interpolate(
|
|
method='linear',order=2)
|
|
|
|
power4min = resampled['power4min']
|
|
date = resampled.index.values
|
|
power2k = resampled['power2k']
|
|
power1hr = resampled['power1hr']
|
|
|
|
source2 = ColumnDataSource(
|
|
data = dict(
|
|
power4min = power4min,
|
|
power2k = power2k,
|
|
date = date,
|
|
power1hr = power1hr,
|
|
fdate=resampled.index.map(lambda x: x.strftime('%d-%m-%Y')) )
|
|
)
|
|
|
|
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
plot = Figure(tools=TOOLS,toolbar_location="above",
|
|
toolbar_sticky=False,width=900,
|
|
x_axis_type='datetime')
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.circle('date','power2k',source=source,fill_color='red',size=10,
|
|
legend_label='2k power')
|
|
|
|
|
|
plot.circle('date','power1hr',source=source,fill_color='blue',size=10,
|
|
legend_label='1 hr power')
|
|
|
|
plot.circle('date','power4min',source=source,fill_color='green',size=10,
|
|
legend_label='4 min power')
|
|
|
|
plot.line('date','power4min',source=source2,color='green')
|
|
plot.line('date','power2k',source=source2,color='red')
|
|
plot.line('date','power1hr',source=source2,color='blue')
|
|
|
|
plot.xaxis.axis_label = 'Date'
|
|
plot.yaxis.axis_label = 'Power (W)'
|
|
|
|
plot.xaxis.formatter = DatetimeTickFormatter(
|
|
days=["%d %B %Y"],
|
|
months=["%d %B %Y"],
|
|
years=["%d %B %Y"],
|
|
)
|
|
|
|
plot.xaxis.major_label_orientation = pi/4
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.y_range = Range1d(0,1.5*max(power4min))
|
|
startdate = datetime.datetime.combine(startdate,datetime.datetime.min.time())
|
|
enddate = datetime.datetime.combine(enddate,datetime.datetime.min.time())
|
|
if not startdate:
|
|
startdate = datetime.datetime.combine(min(date), datetime.datetime.min.time())
|
|
|
|
if not enddate:
|
|
enddate = datetime.datetime.combine(max(date), datetime.datetime.min.time())
|
|
|
|
plot.x_range = Range1d(
|
|
startdate,enddate,
|
|
)
|
|
plot.title.text = 'Power levels ('+workoutmode+') from workouts '+user.first_name
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Power 4 minutes','@power4min'),
|
|
('Power 2000 m','@power2k'),
|
|
('Power 1 hour','@power1hr'),
|
|
('Date','@fdate'),
|
|
])
|
|
|
|
script,div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_histoall(theworkouts,histoparam,includereststrokes,
|
|
spmmin=0,spmmax=55,
|
|
workmin=0,workmax=1500):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
ids = [int(w.id) for w in theworkouts]
|
|
|
|
workstrokesonly = not includereststrokes
|
|
rowdata = dataprep.getsmallrowdata_db([histoparam],ids=ids,doclean=True,workstrokesonly=workstrokesonly)
|
|
|
|
rowdata.dropna(axis=0,how='any',inplace=True)
|
|
|
|
rowdata = dataprep.filter_df(rowdata,'spm',spmmin,largerthan=True)
|
|
rowdata = dataprep.filter_df(rowdata,'spm',spmmax,largerthan=False)
|
|
|
|
rowdata = dataprep.filter_df(rowdata,'driveenergy',workmin,largerthan=True)
|
|
rowdata = dataprep.filter_df(rowdata,'driveenergy',workmax,largerthan=False)
|
|
|
|
if rowdata.empty:
|
|
return "","No Valid Data Available"
|
|
|
|
try:
|
|
histopwr = rowdata[histoparam].values
|
|
except KeyError:
|
|
return "","No data"
|
|
if len(histopwr) == 0:
|
|
return "","No valid data available"
|
|
|
|
# throw out nans
|
|
histopwr = histopwr[~np.isinf(histopwr)]
|
|
histopwr = histopwr[histopwr > yaxminima[histoparam]]
|
|
histopwr = histopwr[histopwr < yaxmaxima[histoparam]]
|
|
|
|
plot = Figure(tools=TOOLS,plot_width=900,
|
|
toolbar_sticky=False,
|
|
toolbar_location="above"
|
|
)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
hist,edges = np.histogram(histopwr,bins=150)
|
|
|
|
histsum = np.cumsum(hist)
|
|
histsum = 100.*histsum/max(histsum)
|
|
|
|
hist_norm = 100.*hist/float(hist.sum())
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
left = edges[:-1],
|
|
right = edges[1:],
|
|
histsum = histsum,
|
|
hist_norm = hist_norm,
|
|
)
|
|
)
|
|
|
|
|
|
# plot.quad(top='hist_norm',bottom=0,left=edges[:-1],right=edges[1:])
|
|
plot.quad(top='hist_norm',bottom=0,left='left',right='right',source=source)
|
|
|
|
plot.xaxis.axis_label = axlabels[histoparam]
|
|
plot.yaxis.axis_label = "% of strokes"
|
|
plot.y_range = Range1d(0,1.05*max(hist_norm))
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
(axlabels[histoparam],'@left{int}'),
|
|
('% of strokes','@hist_norm'),
|
|
('Cumulative %','@histsum{int}'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
plot.extra_y_ranges["fraction"] = Range1d(start=0,end=105)
|
|
plot.line('right','histsum',source=source,color="red",
|
|
y_range_name="fraction")
|
|
plot.add_layout(LinearAxis(y_range_name="fraction",
|
|
axis_label="Cumulative % of strokes"),'right')
|
|
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
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',
|
|
)
|
|
|
|
plot.add_layout(annolabel)
|
|
|
|
callback = CustomJS(args = dict(
|
|
annolabel=annolabel,
|
|
), code="""
|
|
var annotation = annotation.value
|
|
annolabel.text = annotation
|
|
""")
|
|
|
|
annotation = TextInput(width=140, title="Type your plot notes here", value="")
|
|
annotation.js_on_change('value',callback)
|
|
callback.args["annotation"] = annotation
|
|
|
|
layout = layoutcolumn([annotation,plot])
|
|
|
|
try:
|
|
script, div = components(layout)
|
|
except ValueError:
|
|
script = ''
|
|
div = ''
|
|
|
|
return [script,div]
|
|
|
|
def course_map(course):
|
|
latmean,lonmean,coordinates = course_coord_center(course)
|
|
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
|
|
|
|
coordinates = course_spline(coordinates)
|
|
|
|
scoordinates = "["
|
|
|
|
for index,row in coordinates.iterrows():
|
|
scoordinates += """[{x},{y}],
|
|
""".format(
|
|
x=row['latitude'],
|
|
y=row['longitude']
|
|
)
|
|
|
|
scoordinates +="]"
|
|
|
|
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
|
|
|
|
plabels = ''
|
|
|
|
for p in polygons:
|
|
coords = polygon_coord_center(p)
|
|
|
|
plabels += """
|
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
|
marker.bindPopup("<b>{name}</b>");
|
|
|
|
""".format(
|
|
latbegin = coords[0],
|
|
longbegin = coords[1],
|
|
name = p.name
|
|
)
|
|
|
|
pcoordinates = """[
|
|
"""
|
|
|
|
for p in polygons:
|
|
pcoordinates += """[
|
|
["""
|
|
|
|
points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly")
|
|
|
|
for pt in points:
|
|
pcoordinates += "[{x},{y}],".format(
|
|
x = pt.latitude,
|
|
y = pt.longitude
|
|
)
|
|
|
|
# remove last comma
|
|
pcoordinates = pcoordinates[:-1]
|
|
pcoordinates += """]
|
|
],
|
|
"""
|
|
|
|
pcoordinates += """
|
|
]"""
|
|
|
|
|
|
|
|
script = """
|
|
<script>
|
|
|
|
|
|
var streets = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/streets-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
satellite = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/satellite-v9',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
outdoors = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/outdoors-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
);
|
|
|
|
|
|
|
|
var mymap = L.map('map_canvas', {{
|
|
center: [{latmean}, {lonmean}],
|
|
zoom: 13,
|
|
layers: [outdoors]
|
|
}}).setView([{latmean},{lonmean}], 13);
|
|
|
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
|
navKey: 'Navionics_webapi_03205',
|
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
|
isTransparent: true,
|
|
zIndex: 1
|
|
}});
|
|
|
|
|
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
|
|
|
|
|
//create two TileLayer
|
|
var nautical=new L.TileLayer(osmUrl,{{
|
|
maxZoom:18}});
|
|
|
|
|
|
L.control.layers({{
|
|
"Streets": streets,
|
|
"Satellite": satellite,
|
|
"Outdoors": outdoors,
|
|
"Nautical": nautical,
|
|
}},{{
|
|
"Navionics":navionics,
|
|
}}).addTo(mymap);
|
|
|
|
var latlongs = {scoordinates}
|
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
|
mymap.fitBounds(polyline.getBounds())
|
|
|
|
var platlongs = {pcoordinates}
|
|
var polygons = L.polygon(platlongs, {{color:'blue'}}).addTo(mymap)
|
|
|
|
{plabels}
|
|
|
|
</script>
|
|
""".format(
|
|
latmean=latmean,
|
|
lonmean=lonmean,
|
|
scoordinates=scoordinates,
|
|
pcoordinates=pcoordinates,
|
|
plabels = plabels
|
|
)
|
|
|
|
div = """
|
|
<div id="map_canvas" style="width: 100%; height: 400px; margin:0; padding:0;grid-gap:0;"></div>
|
|
"""
|
|
|
|
return script,div
|
|
|
|
def leaflet_chart(lat,lon,name=""):
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
|
|
# Throw out 0,0
|
|
df = pd.DataFrame({
|
|
'lat':lat,
|
|
'lon':lon
|
|
})
|
|
|
|
df = df.replace(0,np.nan)
|
|
df = df.loc[(df!=0).any(axis=1)]
|
|
df.fillna(method='bfill',axis=0,inplace=True)
|
|
df.fillna(method='ffill',axis=0,inplace=True)
|
|
lat = df['lat']
|
|
lon = df['lon']
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
latmean = lat.mean()
|
|
lonmean = lon.mean()
|
|
|
|
latbegin = lat[lat.index[0]]
|
|
longbegin = lon[lon.index[0]]
|
|
latend = lat[lat.index[-1]]
|
|
longend = lon[lon.index[-1]]
|
|
|
|
coordinates = zip(lat,lon)
|
|
|
|
scoordinates = "["
|
|
|
|
for x,y in coordinates:
|
|
scoordinates += """[{x},{y}],
|
|
""".format(
|
|
x=x,
|
|
y=y
|
|
)
|
|
|
|
scoordinates += "]"
|
|
|
|
script = """
|
|
<script>
|
|
|
|
|
|
var streets = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/streets-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
satellite = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/satellite-v9',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
outdoors = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/outdoors-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
);
|
|
|
|
|
|
|
|
var mymap = L.map('map_canvas', {{
|
|
center: [{latmean}, {lonmean}],
|
|
zoom: 13,
|
|
layers: [streets, satellite]
|
|
}}).setView([{latmean},{lonmean}], 13);
|
|
|
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
|
navKey: 'Navionics_webapi_03205',
|
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
|
isTransparent: true,
|
|
zIndex: 1
|
|
}});
|
|
|
|
|
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
|
|
|
|
|
//create two TileLayer
|
|
var nautical=new L.TileLayer(osmUrl,{{
|
|
maxZoom:18}});
|
|
|
|
|
|
L.control.layers({{
|
|
"Streets": streets,
|
|
"Satellite": satellite,
|
|
"Outdoors": outdoors,
|
|
"Nautical": nautical,
|
|
}},{{
|
|
"Navionics":navionics,
|
|
}}).addTo(mymap);
|
|
|
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
|
marker.bindPopup("<b>Start</b>");
|
|
var emarker = new L.marker([{latend}, {longend}]).addTo(mymap);
|
|
emarker.bindPopup("<b>End</b>");
|
|
|
|
var latlongs = {scoordinates}
|
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
|
mymap.fitBounds(polyline.getBounds())
|
|
|
|
</script>
|
|
""".format(
|
|
latmean=latmean,
|
|
lonmean=lonmean,
|
|
latbegin = latbegin,
|
|
latend=latend,
|
|
longbegin=longbegin,
|
|
longend=longend,
|
|
scoordinates=scoordinates,
|
|
)
|
|
|
|
div = """
|
|
<div id="map_canvas" style="width: 100%; height: 400px;"><p> </p></div>
|
|
"""
|
|
|
|
|
|
|
|
return script,div
|
|
|
|
def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}):
|
|
data = []
|
|
for id in workoutids:
|
|
if id != 0 and id is not None:
|
|
try:
|
|
w = Workout.objects.get(id=id)
|
|
rowdata = rdata(w.csvfilename)
|
|
time = rowdata.df['TimeStamp (sec)']
|
|
df = pd.DataFrame({
|
|
'workoutid':id,
|
|
'lat':rowdata.df[' latitude'],
|
|
'lon':rowdata.df[' longitude'],
|
|
'time':time-time[0],
|
|
})
|
|
data.append(df)
|
|
except (Workout.DoesNotExist,KeyError):
|
|
pass
|
|
|
|
|
|
|
|
df = pd.concat(data,axis=0)
|
|
|
|
latmean,lonmean,coordinates = course_coord_center(course)
|
|
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
|
|
|
|
coordinates = course_spline(coordinates)
|
|
|
|
|
|
|
|
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
|
|
|
|
plabels = ''
|
|
|
|
for p in polygons:
|
|
coords = polygon_coord_center(p)
|
|
|
|
plabels += """
|
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
|
marker.bindPopup("<b>{name}</b>");
|
|
|
|
""".format(
|
|
latbegin = coords[0],
|
|
longbegin = coords[1],
|
|
name = p.name
|
|
)
|
|
|
|
pcoordinates = """[
|
|
"""
|
|
|
|
for p in polygons:
|
|
pcoordinates += """[
|
|
["""
|
|
|
|
points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly")
|
|
|
|
for pt in points:
|
|
pcoordinates += "[{x},{y}],".format(
|
|
x = pt.latitude,
|
|
y = pt.longitude
|
|
)
|
|
|
|
# remove last comma
|
|
pcoordinates = pcoordinates[:-1]
|
|
pcoordinates += """]
|
|
],
|
|
"""
|
|
|
|
pcoordinates += """
|
|
]"""
|
|
|
|
|
|
# Throw out 0,0
|
|
df = df.replace(0,np.nan)
|
|
df = df.loc[(df!=0).any(axis=1)]
|
|
df.fillna(method='bfill',axis=0,inplace=True)
|
|
df.fillna(method='ffill',axis=0,inplace=True)
|
|
|
|
|
|
lat = df['lat']
|
|
lon = df['lon']
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
latbegin = lat.values[0]
|
|
longbegin = lon.values[0]
|
|
latend = lat.values[-1]
|
|
longend = lon.values[-1]
|
|
|
|
colors = itertools.cycle(palette)
|
|
try:
|
|
items = itertools.izip(workoutids,colors)
|
|
except AttributeError:
|
|
items = zip(workoutids,colors)
|
|
|
|
|
|
|
|
script = """
|
|
<script>
|
|
|
|
var streets = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/streets-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
|
|
satellite = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/satellite-v9',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
outdoors = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/outdoors-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
);
|
|
|
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
|
navKey: 'Navionics_webapi_03205',
|
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
|
isTransparent: true,
|
|
zIndex: 1
|
|
}});
|
|
|
|
var mymap = L.map('map_canvas', {{
|
|
center: [{latmean}, {lonmean}],
|
|
zoom: 13,
|
|
layers: [outdoors]
|
|
}}).setView([{latmean},{lonmean}], 13);
|
|
|
|
|
|
|
|
|
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
|
|
|
|
|
//create two TileLayer
|
|
var nautical=new L.TileLayer(osmUrl,{{
|
|
maxZoom:18}});
|
|
|
|
|
|
L.control.layers({{
|
|
"Streets": streets,
|
|
"Satellite": satellite,
|
|
"Outdoors": outdoors,
|
|
"Nautical": nautical,
|
|
}},{{
|
|
"Navionics":navionics,
|
|
}}).addTo(mymap);
|
|
|
|
|
|
var platlongs = {pcoordinates}
|
|
var polygons = L.polygon(platlongs, {{color:'blue'}}).addTo(mymap)
|
|
|
|
{plabels}
|
|
|
|
""".format(
|
|
latmean=latmean,
|
|
lonmean=lonmean,
|
|
latbegin = latbegin,
|
|
latend=latend,
|
|
longbegin=longbegin,
|
|
longend=longend,
|
|
pcoordinates=pcoordinates,
|
|
plabels=plabels,
|
|
)
|
|
|
|
for id,color in items:
|
|
group = df[df['workoutid']==int(id)].copy()
|
|
try:
|
|
startsecond,endsecond = startenddict[id]
|
|
except KeyError:
|
|
startsecond = 0
|
|
endsecond = 0
|
|
|
|
try:
|
|
label = labeldict[id]
|
|
except KeyError:
|
|
label = str(id)
|
|
|
|
group.sort_values(by='time',ascending=True,inplace=True)
|
|
group.dropna(axis=0,how='any',inplace=True)
|
|
if endsecond > 0:
|
|
group['time'] = group['time'] - startsecond
|
|
mask = group['time'] < 0
|
|
group.mask(mask,inplace=True)
|
|
mask = group['time'] > (endsecond-startsecond)
|
|
group.mask(mask,inplace=True)
|
|
|
|
lat = group['lat'].dropna()
|
|
lon = group['lon'].dropna()
|
|
|
|
coordinates = zip(lat,lon)
|
|
|
|
scoordinates = "["
|
|
for x,y in coordinates:
|
|
scoordinates += """[{x},{y}],
|
|
""".format(x=x,y=y)
|
|
scoordinates += "]"
|
|
|
|
if not group.empty:
|
|
script += """
|
|
var latlongs = {scoordinates}
|
|
var polyline = L.polyline(latlongs, {{color:'{color}'}}).addTo(mymap)
|
|
polyline.bindPopup("<b>{label}</b>",{{ autoPan: false, autoClose: false }}).openPopup()
|
|
polyline.on('mouseover',function(ev) {{
|
|
ev.target.openPopup();
|
|
}});
|
|
polyline.on('dblclick', function (e) {{
|
|
mymap.removeLayer(this);
|
|
}});
|
|
mymap.fitBounds(polyline.getBounds())
|
|
""".format(
|
|
scoordinates=scoordinates,
|
|
color=color,
|
|
label=label,
|
|
id=id,
|
|
)
|
|
|
|
script += """
|
|
|
|
</script>
|
|
"""
|
|
|
|
|
|
|
|
|
|
div = """
|
|
<div id="map_canvas" style="width: 100%; height: 100%; min-height: 100vh"><p> </p></div>
|
|
"""
|
|
|
|
|
|
|
|
return script,div
|
|
|
|
def leaflet_chart2(lat,lon,name=""):
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
|
|
# Throw out 0,0
|
|
df = pd.DataFrame({
|
|
'lat':lat,
|
|
'lon':lon
|
|
})
|
|
|
|
df = df.replace(0,np.nan)
|
|
df = df.loc[(df!=0).any(axis=1)]
|
|
df.fillna(method='bfill',axis=0,inplace=True)
|
|
df.fillna(method='ffill',axis=0,inplace=True)
|
|
lat = df['lat']
|
|
lon = df['lon']
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
latmean = lat.mean()
|
|
lonmean = lon.mean()
|
|
latbegin = lat[lat.index[0]]
|
|
longbegin = lon[lon.index[0]]
|
|
latend = lat[lat.index[-1]]
|
|
longend = lon[lon.index[-1]]
|
|
|
|
coordinates = zip(lat,lon)
|
|
|
|
scoordinates = "["
|
|
|
|
for x,y in coordinates:
|
|
scoordinates += """[{x},{y}],
|
|
""".format(
|
|
x=x,
|
|
y=y
|
|
)
|
|
|
|
scoordinates += "]"
|
|
|
|
script = """
|
|
<script>
|
|
|
|
var streets = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/streets-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
|
|
satellite = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/satellite-v9',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
outdoors = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/outdoors-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
);
|
|
|
|
|
|
|
|
var mymap = L.map('map_canvas', {{
|
|
center: [{latmean}, {lonmean}],
|
|
zoom: 13,
|
|
layers: [streets, satellite]
|
|
}}).setView([{latmean},{lonmean}], 13);
|
|
|
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
|
navKey: 'Navionics_webapi_03205',
|
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
|
isTransparent: true,
|
|
zIndex: 1
|
|
}});
|
|
|
|
|
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
|
|
|
|
|
//create two TileLayer
|
|
var nautical=new L.TileLayer(osmUrl,{{
|
|
maxZoom:18}});
|
|
|
|
|
|
L.control.layers({{
|
|
"Streets": streets,
|
|
"Satellite": satellite,
|
|
"Outdoors": outdoors,
|
|
"Nautical": nautical,
|
|
}},{{
|
|
"Navionics":navionics,
|
|
}}).addTo(mymap);
|
|
|
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
|
marker.bindPopup("<b>Start</b>");
|
|
var emarker = new L.marker([{latend}, {longend}]).addTo(mymap);
|
|
emarker.bindPopup("<b>End</b>");
|
|
|
|
var latlongs = {scoordinates}
|
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
|
mymap.fitBounds(polyline.getBounds())
|
|
|
|
</script>
|
|
""".format(
|
|
latmean=latmean,
|
|
lonmean=lonmean,
|
|
latbegin = latbegin,
|
|
latend=latend,
|
|
longbegin=longbegin,
|
|
longend=longend,
|
|
scoordinates=scoordinates,
|
|
)
|
|
|
|
div = """
|
|
<div id="map_canvas" style="width: 100%; height: 100%; min-height: 100vh"><p> </p></div>
|
|
"""
|
|
|
|
|
|
|
|
return script,div
|
|
|
|
def leaflet_chart_video(lat,lon,name=""):
|
|
if not len(lat) or not len(lon):
|
|
return [0,"invalid coordinate data"]
|
|
|
|
|
|
# Throw out 0,0
|
|
df = pd.DataFrame({
|
|
'lat':lat,
|
|
'lon':lon
|
|
})
|
|
|
|
df = df.replace(0,np.nan)
|
|
df = df.loc[(df!=0).any(axis=1)]
|
|
df.fillna(method='bfill',axis=0,inplace=True)
|
|
df.fillna(method='ffill',axis=0,inplace=True)
|
|
lat = df['lat']
|
|
lon = df['lon']
|
|
if lat.empty or lon.empty:
|
|
return [0,"invalid coordinate data"]
|
|
|
|
latmean = lat.mean()
|
|
lonmean = lon.mean()
|
|
latbegin = lat[lat.index[0]]
|
|
longbegin = lon[lon.index[0]]
|
|
latend = lat[lat.index[-1]]
|
|
longend = lon[lon.index[-1]]
|
|
|
|
coordinates = zip(lat,lon)
|
|
|
|
scoordinates = "["
|
|
|
|
for x,y in coordinates:
|
|
scoordinates += """[{x},{y}],
|
|
""".format(
|
|
x=x,
|
|
y=y
|
|
)
|
|
|
|
scoordinates += "]"
|
|
|
|
script = """
|
|
|
|
|
|
var streets = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/streets-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
satellite = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/satellite-v9',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
),
|
|
|
|
outdoors = L.tileLayer(
|
|
'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{
|
|
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
|
|
tileSize: 512,
|
|
maxZoom: 18,
|
|
zoomOffset: -1,
|
|
id: 'mapbox/outdoors-v11',
|
|
accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA'
|
|
}}
|
|
);
|
|
|
|
|
|
|
|
var mymap = L.map('map_canvas', {{
|
|
center: [{latmean}, {lonmean}],
|
|
zoom: 13,
|
|
layers: [streets, satellite]
|
|
}}).setView([{latmean},{lonmean}], 13);
|
|
|
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
|
navKey: 'Navionics_webapi_03205',
|
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
|
isTransparent: true,
|
|
zIndex: 1
|
|
}});
|
|
|
|
|
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
|
|
|
|
|
//create two TileLayer
|
|
var nautical=new L.TileLayer(osmUrl,{{
|
|
maxZoom:18}});
|
|
|
|
|
|
L.control.layers({{
|
|
"Streets": streets,
|
|
"Satellite": satellite,
|
|
"Outdoors": outdoors,
|
|
"Nautical": nautical,
|
|
}},{{
|
|
"Navionics":navionics,
|
|
}},
|
|
{{
|
|
position:'topleft'
|
|
}}).addTo(mymap);
|
|
|
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
|
marker.bindPopup("<b>Start</b>");
|
|
|
|
var latlongs = {scoordinates}
|
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
|
mymap.fitBounds(polyline.getBounds())
|
|
|
|
""".format(
|
|
latmean=latmean,
|
|
lonmean=lonmean,
|
|
latbegin = latbegin,
|
|
latend=latend,
|
|
longbegin=longbegin,
|
|
longend=longend,
|
|
scoordinates=scoordinates,
|
|
)
|
|
|
|
div = """
|
|
<div id="map_canvas" style="width: 640px; height: 390px;"><p> </p></div>
|
|
"""
|
|
|
|
|
|
|
|
return script,div
|
|
|
|
|
|
def interactive_agegroupcpchart(age,normalized=False):
|
|
durations = [1,4,30,60]
|
|
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
|
|
|
|
fhduration = []
|
|
fhpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='female',
|
|
distance=distance,
|
|
weightcategory='hwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try:
|
|
duration = distance/velo
|
|
fhduration.append(duration)
|
|
fhpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
pass
|
|
for duration in durations:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='female',
|
|
duration=duration,
|
|
weightcategory='hwt'
|
|
)
|
|
try:
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
distance = int(60*duration*velo)
|
|
fhduration.append(60.*duration)
|
|
fhpower.append(worldclasspower)
|
|
except ValueError:
|
|
pass
|
|
|
|
flduration = []
|
|
flpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='female',
|
|
distance=distance,
|
|
weightcategory='lwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try:
|
|
duration = distance/velo
|
|
flduration.append(duration)
|
|
flpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
pass
|
|
for duration in durations:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='female',
|
|
duration=duration,
|
|
weightcategory='lwt'
|
|
)
|
|
try:
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
distance = int(60*duration*velo)
|
|
flduration.append(60.*duration)
|
|
flpower.append(worldclasspower)
|
|
except ValueError:
|
|
pass
|
|
|
|
mlduration = []
|
|
mlpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
distance=distance,
|
|
weightcategory='lwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try:
|
|
duration = distance/velo
|
|
mlduration.append(duration)
|
|
mlpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
pass
|
|
for duration in durations:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
duration=duration,
|
|
weightcategory='lwt'
|
|
)
|
|
try:
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
distance = int(60*duration*velo)
|
|
mlduration.append(60.*duration)
|
|
mlpower.append(worldclasspower)
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
mhduration = []
|
|
mhpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
distance=distance,
|
|
weightcategory='hwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try:
|
|
duration = distance/velo
|
|
mhduration.append(duration)
|
|
mhpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
pass
|
|
for duration in durations:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
duration=duration,
|
|
weightcategory='hwt'
|
|
)
|
|
try:
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
distance = int(60*duration*velo)
|
|
mhduration.append(60.*duration)
|
|
mhpower.append(worldclasspower)
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
|
|
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
|
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
|
|
|
|
# p0 = [500,350,10,8000]
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(fhduration)>=4:
|
|
p1fh, success = optimize.leastsq(errfunc, p0[:],
|
|
args = (fhduration,fhpower))
|
|
else:
|
|
p1fh = None
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(flduration)>=4:
|
|
p1fl, success = optimize.leastsq(errfunc, p0[:],
|
|
args = (flduration,flpower))
|
|
else:
|
|
p1fl = None
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(mlduration)>=4:
|
|
p1ml, success = optimize.leastsq(errfunc, p0[:],
|
|
args = (mlduration,mlpower))
|
|
else:
|
|
p1ml = None
|
|
|
|
if len(mhduration)>=4:
|
|
p1mh, success = optimize.leastsq(errfunc, p0[:],
|
|
args = (mhduration,mhpower))
|
|
else:
|
|
p1mh = None
|
|
|
|
fitt = pd.Series(10**(4*np.arange(100)/100.))
|
|
|
|
fitpowerfh = fitfunc(p1fh,fitt)
|
|
fitpowerfl = fitfunc(p1fl,fitt)
|
|
fitpowerml = fitfunc(p1ml,fitt)
|
|
fitpowermh = fitfunc(p1mh,fitt)
|
|
|
|
if normalized:
|
|
facfh = fitfunc(p1fh,60)
|
|
facfl = fitfunc(p1fl,60)
|
|
facml = fitfunc(p1ml,60)
|
|
facmh = fitfunc(p1mh,60)
|
|
fitpowerfh /= facfh
|
|
fitpowerfl /= facfl
|
|
fitpowermh /= facmh
|
|
fitpowerml /= facml
|
|
fhpower /= facfh
|
|
flpower /= facfl
|
|
mlpower /= facml
|
|
mhpower /= facmh
|
|
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
duration = fitt,
|
|
fitpowerfh = fitpowerfh,
|
|
fitpowerfl = fitpowerfl,
|
|
fitpowerml = fitpowerml,
|
|
fitpowermh = fitpowermh,
|
|
flduration = flduration,
|
|
flpower = flpower,
|
|
fhduration = fhduration,
|
|
fhpower = fhpower,
|
|
mlduration = mlduration,
|
|
mlpower = mlpower,
|
|
mhduration = mhduration,
|
|
mhpower = mhpower,
|
|
)
|
|
)
|
|
|
|
x_axis_type = 'log'
|
|
y_axis_type = 'linear'
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
plot = Figure(plot_width=900,x_axis_type=x_axis_type,
|
|
tools=TOOLS)
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.line('duration','fitpowerfh',source=source,
|
|
legend_label='Female HW',color='blue')
|
|
plot.line('duration','fitpowerfl',source=source,
|
|
legend_label='Female LW',color='red')
|
|
|
|
plot.line('duration','fitpowerml',source=source,
|
|
legend_label='Male LW',color='green')
|
|
|
|
plot.line('duration','fitpowermh',source=source,
|
|
legend_label='Male HW',color='orange')
|
|
|
|
|
|
plot.circle('flduration','flpower',source=source,
|
|
fill_color='red',size=15)
|
|
|
|
plot.circle('fhduration','fhpower',source=source,
|
|
fill_color='blue',size=15)
|
|
|
|
plot.circle('mlduration','mlpower',source=source,
|
|
fill_color='green',size=15)
|
|
|
|
plot.circle('mhduration','mhpower',source=source,
|
|
fill_color='orange',size=15)
|
|
|
|
plot.title.text = 'age '+str(age)
|
|
|
|
plot.xaxis.axis_label = "Duration (seconds)"
|
|
if normalized:
|
|
plot.yaxis.axis_label = "Power (normalized)"
|
|
else:
|
|
plot.yaxis.axis_label = "Power (W)"
|
|
|
|
script,div = components(plot)
|
|
|
|
return script,div
|
|
|
|
|
|
def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
|
|
title='',type='water'):
|
|
powerdf = powerdf[~(powerdf == 0).any(axis=1)]
|
|
# plot tools
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
x_axis_type = 'log'
|
|
y_axis_type = 'linear'
|
|
|
|
deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x))
|
|
powerdf['ftime'] = niceformat(deltas)
|
|
powerdf['Deltaminutes'] = powerdf['Delta']/60.
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = powerdf
|
|
)
|
|
|
|
|
|
|
|
# there is no Paul's law for OTW
|
|
|
|
thesecs = powerdf['Delta']
|
|
theavpower = powerdf['CP']
|
|
|
|
|
|
p1,fitt,fitpower,ratio = datautils.cpfit(powerdf)
|
|
if cpfit == 'automatic' and r is not None:
|
|
if type == 'water':
|
|
p1 = [r.p0,r.p1,r.p2,r.p3]
|
|
ratio = r.cpratio
|
|
elif type == 'erg' :
|
|
p1 = [r.ep0,r.ep1,r.ep2,r.ep3]
|
|
ratio = r.ecpratio
|
|
|
|
fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3])))
|
|
fitpower = fitfunc(p1,fitt)
|
|
|
|
message = ""
|
|
#if len(fitpower[fitpower<0]) > 0:
|
|
# message = "CP model fit didn't give correct results"
|
|
|
|
|
|
deltas = fitt.apply(lambda x: timedeltaconv(x))
|
|
ftime = niceformat(deltas)
|
|
workouts = powerdf['workout']
|
|
|
|
sourcecomplex = ColumnDataSource(
|
|
data = dict(
|
|
CP = fitpower,
|
|
CPmax = ratio*fitpower,
|
|
duration = fitt/60.,
|
|
ftime = ftime,
|
|
workout = workouts,
|
|
)
|
|
)
|
|
|
|
# making the plot
|
|
plot = Figure(tools=TOOLS,x_axis_type=x_axis_type,
|
|
plot_width=900,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'scale_both'
|
|
|
|
|
|
|
|
plot.image_url([watermarkurl],1.8*max(thesecs),watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.circle('Deltaminutes','CP',source=source,fill_color='red',size=15,
|
|
legend_label='Power Data')
|
|
plot.xaxis.axis_label = "Duration (minutes)"
|
|
plot.yaxis.axis_label = "Power (W)"
|
|
|
|
plot.y_range = Range1d(0,1.5*max(theavpower))
|
|
plot.x_range = Range1d(0.5*min(thesecs)/60.,2*max(thesecs)/60.)
|
|
plot.legend.orientation = "vertical"
|
|
if not title:
|
|
title = "Critical Power for "+rowername
|
|
plot.title.text = title
|
|
|
|
xaxis = plot.select(dict(type=Axis, layout="below"))[0]
|
|
xaxis.formatter = PrintfTickFormatter()
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Duration ','@ftime'),
|
|
('Power (W)','@CP{int}'),
|
|
('Power (W) upper','@CPmax{int}'),
|
|
('Workout','@workout'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
plot.line('duration','CP',source=sourcecomplex,legend_label="CP Model",
|
|
color='green')
|
|
|
|
plot.line('duration','CPmax',source=sourcecomplex,legend_label="CP Model",
|
|
color='red')
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div,p1,ratio,message]
|
|
|
|
def interactive_agegroup_plot(df,distance=2000,duration=None,
|
|
sex='male',weightcategory='hwt'):
|
|
|
|
if df.empty:
|
|
return '',''
|
|
|
|
age = df['age']
|
|
power = df['power']
|
|
name = df['name']
|
|
season = df['season']
|
|
|
|
if duration:
|
|
duration2 = int(duration/60.)
|
|
plottitle = sex+' '+weightcategory+' %s min' % duration2
|
|
else:
|
|
plottitle = sex+' '+weightcategory+' %s m' % distance
|
|
|
|
# poly_coefficients = np.polyfit(age,power,6)
|
|
|
|
age2 = np.linspace(11,95)
|
|
# poly_vals = np.polyval(poly_coefficients,age2)
|
|
# poly_vals = 0.5*(np.abs(poly_vals)+poly_vals)
|
|
|
|
fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5])))
|
|
errfunc = lambda pars, x,y: fitfunc(pars,x)-y
|
|
|
|
p0age = [700,120,700,10,100,100]
|
|
|
|
p1, success = optimize.leastsq(errfunc,p0age[:],
|
|
args = (age,power))
|
|
|
|
expo_vals = fitfunc(p1, age2)
|
|
expo_vals = 0.5*(np.abs(expo_vals)+expo_vals)
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
age = age,
|
|
power = power,
|
|
age2 = age2,
|
|
expo_vals = expo_vals,
|
|
season = season,
|
|
name=name,
|
|
)
|
|
)
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
plot = Figure(tools=TOOLS,plot_width=900)
|
|
plot.sizing_mode='stretch_both'
|
|
plot.circle('age','power',source=source,fill_color='red',size=15,
|
|
legend_label='World Record')
|
|
|
|
plot.line(age2,expo_vals)
|
|
plot.xaxis.axis_label = "Age"
|
|
plot.yaxis.axis_label = "Concept2 power"
|
|
plot.title.text = plottitle
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Name ','@name'),
|
|
('Season ','@season'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
script,div = components(plot)
|
|
|
|
return script,div
|
|
|
|
def interactive_cpchart(rower,thedistances,thesecs,theavpower,
|
|
theworkouts,promember=0,
|
|
wcpower=[],wcdurations=[]):
|
|
|
|
message = 0
|
|
# plot tools
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
x_axis_type = 'log'
|
|
y_axis_type = 'linear'
|
|
|
|
thesecs = pd.Series(thesecs)
|
|
|
|
velo = thedistances/thesecs
|
|
p = pd.Series(500./velo)
|
|
|
|
p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
dist = thedistances,
|
|
duration = thesecs,
|
|
spm = 0*theavpower,
|
|
tim = niceformat(
|
|
thesecs.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
),
|
|
|
|
power = theavpower,
|
|
fpace = nicepaceformat(p2),
|
|
)
|
|
)
|
|
|
|
# fitting the data to Paul
|
|
if len(thedistances)>=2:
|
|
paulslope, paulintercept,r,p,stderr = linregress(np.log10(thedistances),p)
|
|
else:
|
|
paulslope = 5.0/np.log10(2.0)
|
|
paulintercept = p[0]-paulslope*np.log10(thedistances[0])
|
|
|
|
|
|
fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.)
|
|
|
|
fitp = paulslope*fitx+paulintercept
|
|
|
|
fitvelo = 500./fitp
|
|
fitpower = 2.8*(fitvelo**3)
|
|
fitt = 10**fitx/fitvelo
|
|
fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
|
|
|
|
sourcepaul = ColumnDataSource(
|
|
data = dict(
|
|
dist = 10**fitx,
|
|
duration = fitt,
|
|
power = fitpower,
|
|
spm = 0*fitpower,
|
|
tim = niceformat(
|
|
fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
),
|
|
fpace = nicepaceformat(fitp2),
|
|
)
|
|
)
|
|
|
|
|
|
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
|
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
|
|
|
|
# p0 = [500,350,10,8000]
|
|
wcpower = pd.Series(wcpower)
|
|
wcdurations = pd.Series(wcdurations)
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(wcdurations)>=4:
|
|
p1wc, success = optimize.leastsq(errfunc, p0[:],
|
|
args = (wcdurations,wcpower))
|
|
else:
|
|
p1wc = None
|
|
|
|
# fitting the data to three parameter CP model
|
|
|
|
success = 0
|
|
p1 = p0
|
|
if len(thesecs)>=4:
|
|
p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower))
|
|
else:
|
|
factor = fitfunc(p0,thesecs.mean())/theavpower.mean()
|
|
p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]]
|
|
success = 0
|
|
|
|
|
|
# Get stayer score
|
|
if success == 1:
|
|
power1min = fitfunc(p1,60.)
|
|
power4min = fitfunc(p1,240.)
|
|
power6min = fitfunc(p1,360.)
|
|
power30min = fitfunc(p1,1800.)
|
|
power1h = fitfunc(p1,3600.)
|
|
power10sec = fitfunc(p1,10.)
|
|
r10sec4min = 100.*power10sec/power4min
|
|
r1h4min = 100.*power1h/power4min
|
|
r1min6min = 100.*power1min/power6min
|
|
r30min6min = 100.*power30min/power6min
|
|
|
|
combined = r1h4min-0.2*(r10sec4min-100)
|
|
combined2 = r30min6min-1.5*(r1min6min-100)
|
|
|
|
dataset = pd.read_csv('static/stats/combined_set.csv')
|
|
dataset2 = pd.read_csv('static/stats/combined_set6min.csv')
|
|
|
|
stayerscore = int(percentileofscore(dataset['combined'],combined))
|
|
stayerscore2 = int(percentileofscore(dataset2['combined'],combined2))
|
|
else:
|
|
stayerscore = None
|
|
stayerscore2 = None
|
|
|
|
|
|
fitt = pd.Series(10**(4*np.arange(100)/100.))
|
|
|
|
fitpower = fitfunc(p1,fitt)
|
|
if p1wc is not None:
|
|
fitpowerwc = 0.95*fitfunc(p1wc,fitt)
|
|
fitpowerexcellent = 0.7*fitfunc(p1wc,fitt)
|
|
fitpowergood = 0.6*fitfunc(p1wc,fitt)
|
|
fitpowerfair = 0.5*fitfunc(p1wc,fitt)
|
|
fitpoweraverage = 0.4*fitfunc(p1wc,fitt)
|
|
|
|
else:
|
|
fitpowerwc = 0*fitpower
|
|
fitpowerexcellent = 0*fitpower
|
|
fitpowergood = 0*fitpower
|
|
fitpowerfair = 0*fitpower
|
|
fitpoweraverage = 0*fitpower
|
|
|
|
|
|
|
|
message = ""
|
|
if len(fitpower[fitpower<0]) > 0:
|
|
message = "CP model fit didn't give correct results"
|
|
|
|
fitvelo = (fitpower/2.8)**(1./3.)
|
|
fitdist = fitt*fitvelo
|
|
fitp = 500./fitvelo
|
|
fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
|
|
sourcecomplex = ColumnDataSource(
|
|
data = dict(
|
|
dist = fitdist,
|
|
duration = fitt,
|
|
tim = niceformat(
|
|
fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
),
|
|
spm = 0*fitpower,
|
|
power = fitpower,
|
|
fitpowerwc = fitpowerwc,
|
|
fitpowerexcellent = fitpowerexcellent,
|
|
fitpowergood = fitpowergood,
|
|
fitpowerfair = fitpowerfair,
|
|
fitpoweraverage = fitpoweraverage,
|
|
fpace = nicepaceformat(fitp2),
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# making the plot
|
|
plot = Figure(tools=TOOLS,x_axis_type=x_axis_type,
|
|
plot_width=900,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'scale_both'
|
|
|
|
|
|
plot.image_url([watermarkurl],1.8*max(thesecs),watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.circle('duration','power',source=source,fill_color='red',size=15,
|
|
legend_label='Power')
|
|
plot.xaxis.axis_label = "Duration (seconds)"
|
|
plot.yaxis.axis_label = "Power (W)"
|
|
|
|
if stayerscore is not None:
|
|
plot.add_layout(
|
|
Label(x=100,y=100,x_units='screen',y_units='screen',
|
|
text='Stayer Score '+str(stayerscore)+'%',
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
text_color='black')
|
|
)
|
|
# plot.add_layout(
|
|
# Label(x=100,y=120,x_units='screen',y_units='screen',
|
|
# text='Stayer Score (6min) '+str(stayerscore2)+'%',
|
|
# background_fill_alpha=0.7,
|
|
# background_fill_color='white',
|
|
# text_color='black')
|
|
# )
|
|
|
|
cpdata = dataprep.fetchcperg(rower, theworkouts)
|
|
|
|
|
|
if cpdata.empty:
|
|
message = 'Calculations are running in the background. Please refresh this page to see updated results'
|
|
return ['','',paulslope,paulintercept,p1,message,p1wc]
|
|
|
|
velo = cpdata['distance']/cpdata['delta']
|
|
|
|
p = 500./velo
|
|
|
|
p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
|
|
source2 = ColumnDataSource(
|
|
data = dict(
|
|
duration = cpdata['delta'],
|
|
power = cpdata['cp'],
|
|
tim = niceformat(
|
|
cpdata['delta'].fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
|
),
|
|
dist = cpdata['distance'],
|
|
pace = nicepaceformat(p2),
|
|
)
|
|
)
|
|
|
|
plot.circle('duration','power',source=source2,
|
|
fill_color='blue',size=3,
|
|
legend_label = 'Power from segments')
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Duration ','@tim'),
|
|
('Power (W)','@power{int}'),
|
|
('Distance (m)','@dist{int}'),
|
|
('Pace (/500m)','@fpace'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
plot.y_range = Range1d(0,1.5*max(theavpower))
|
|
plot.x_range = Range1d(1,2*max(thesecs))
|
|
plot.legend.orientation = "vertical"
|
|
|
|
|
|
plot.line('duration','power',source=sourcepaul,legend_label="Paul's Law")
|
|
plot.line('duration','power',source=sourcecomplex,legend_label="CP Model",
|
|
color='green')
|
|
if p1wc is not None:
|
|
plot.line('duration','fitpowerwc',source=sourcecomplex,
|
|
legend_label="World Class",
|
|
color='Maroon',line_dash='dotted')
|
|
|
|
plot.line('duration','fitpowerexcellent',source=sourcecomplex,
|
|
legend_label="90% percentile",
|
|
color='Purple',line_dash='dotted')
|
|
|
|
plot.line('duration','fitpowergood',source=sourcecomplex,
|
|
legend_label="75% percentile",
|
|
color='Olive',line_dash='dotted')
|
|
|
|
plot.line('duration','fitpowerfair',source=sourcecomplex,
|
|
legend_label="50% percentile",
|
|
color='Gray',line_dash='dotted')
|
|
|
|
plot.line('duration','fitpoweraverage',source=sourcecomplex,
|
|
legend_label="25% percentile",
|
|
color='SkyBlue',line_dash='dotted')
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
|
|
return [script,div,paulslope,paulintercept,p1,message,p1wc]
|
|
|
|
def interactive_windchart(id=0,promember=0):
|
|
# check if valid ID exists (workout exists)
|
|
row = Workout.objects.get(id=id)
|
|
# g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
|
|
|
|
f1 = row.csvfilename
|
|
|
|
# create interactive plot
|
|
plot = Figure(plot_width=400,plot_height=300)
|
|
|
|
# get user
|
|
# u = User.objects.get(id=row.user.id)
|
|
r = row.user
|
|
u = r.user
|
|
|
|
|
|
|
|
rr = rrower(hrmax=r.max,hrut2=r.ut2,
|
|
hrut1=r.ut1,hrat=r.at,
|
|
hrtr=r.tr,hran=r.an,ftp=r.ftp)
|
|
|
|
rowdata = rdata(f1,rower=rr)
|
|
if rowdata == 0:
|
|
return 0
|
|
|
|
try:
|
|
dist = rowdata.df.loc[:,'cum_dist']
|
|
except KeyError:
|
|
return ['','No Data Found']
|
|
|
|
try:
|
|
vwind = rowdata.df.loc[:,'vwind']
|
|
winddirection = rowdata.df.loc[:,'winddirection']
|
|
bearing = rowdata.df.loc[:,'bearing']
|
|
except KeyError:
|
|
rowdata.add_wind(0,0)
|
|
rowdata.add_bearing()
|
|
vwind = rowdata.df.loc[:,'vwind']
|
|
winddirection = rowdata.df.loc[:,'winddirection']
|
|
bearing = rowdata.df.loc[:,'winddirection']
|
|
rowdata.write_csv(f1,gzip=True)
|
|
dataprep.update_strokedata(id,rowdata.df)
|
|
|
|
winddirection = winddirection % 360
|
|
winddirection = (winddirection + 360) % 360
|
|
|
|
tw = tailwind(bearing,vwind,1.0*winddirection)
|
|
|
|
|
|
source = ColumnDataSource(
|
|
data = dict(
|
|
dist=dist,
|
|
vwind=vwind,
|
|
tw=tw,
|
|
winddirection=winddirection,
|
|
)
|
|
)
|
|
|
|
# plot tools
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
|
|
|
|
|
|
# making the plot
|
|
plot = Figure(tools=TOOLS,plot_width=400,height=500,
|
|
# toolbar_location="below",
|
|
toolbar_sticky=False,
|
|
)
|
|
plot.line('dist','vwind',source=source,legend_label="Wind Speed (m/s)")
|
|
plot.line('dist','tw',source=source,legend_label="Tail (+)/Head (-) Wind (m/s)",color='black')
|
|
plot.title.text = row.name
|
|
# plot.title.text_font_size=value("1.0em")
|
|
plot.title.text_font="1.0em"
|
|
plot.xaxis.axis_label = "Distance (m)"
|
|
plot.yaxis.axis_label = "Wind Speed (m/s)"
|
|
plot.y_range = Range1d(-7,7)
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
|
|
plot.extra_y_ranges = {"winddirection": Range1d(start=0,end=360)}
|
|
plot.line('dist','winddirection',source=source,
|
|
legend_label='Wind Direction',color="red",
|
|
y_range_name="winddirection")
|
|
plot.add_layout(LinearAxis(y_range_name="winddirection",axis_label="Wind Direction (degree)"),'right')
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_streamchart(id=0,promember=0):
|
|
# check if valid ID exists (workout exists)
|
|
row = Workout.objects.get(id=id)
|
|
# g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
|
|
|
|
f1 = row.csvfilename
|
|
|
|
# create interactive plot
|
|
plot = Figure(plot_width=400,
|
|
)
|
|
# get user
|
|
# u = User.objects.get(id=row.user.id)
|
|
r = row.user
|
|
u = r.user
|
|
|
|
|
|
|
|
rr = rrower(hrmax=r.max,hrut2=r.ut2,
|
|
hrut1=r.ut1,hrat=r.at,
|
|
hrtr=r.tr,hran=r.an,ftp=r.ftp)
|
|
|
|
rowdata = rdata(f1,rower=rr)
|
|
if rowdata == 0:
|
|
return "","No Valid Data Available"
|
|
|
|
try:
|
|
dist = rowdata.df.loc[:,'cum_dist']
|
|
except KeyError:
|
|
return ['','No Data found']
|
|
|
|
try:
|
|
vstream = rowdata.df.loc[:,'vstream']
|
|
except KeyError:
|
|
rowdata.add_stream(0)
|
|
vstream = rowdata.df.loc[:,'vstream']
|
|
rowdata.write_csv(f1,gzip=True)
|
|
dataprep.update_strokedata(id,rowdata.df)
|
|
|
|
# plot tools
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
|
|
|
|
|
|
# making the plot
|
|
plot = Figure(tools=TOOLS,plot_width=400,height=500,
|
|
# toolbar_location="below",
|
|
toolbar_sticky=False,
|
|
)
|
|
plot.line(dist,vstream,legend_label="River Stream Velocity (m/s)")
|
|
plot.title.text = row.name
|
|
plot.title.text_font_size=value("1.0em")
|
|
plot.xaxis.axis_label = "Distance (m)"
|
|
plot.yaxis.axis_label = "River Current (m/s)"
|
|
plot.y_range = Range1d(-2,2)
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_chart(id=0,promember=0,intervaldata = {}):
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
columns = ['time','pace','hr','fpace','ftime','spm']
|
|
datadf = dataprep.getsmallrowdata_db(columns,ids=[id])
|
|
|
|
datadf.dropna(axis=0,how='any',inplace=True)
|
|
|
|
row = Workout.objects.get(id=id)
|
|
if datadf.empty:
|
|
return "","No Valid Data Available"
|
|
#else:
|
|
# try:
|
|
# datadf.sort_values(by='time',ascending=True,inplace=True)
|
|
# except KeyError:
|
|
# return "","No valid data available"
|
|
|
|
try:
|
|
spm = datadf['spm']
|
|
except KeyError:
|
|
datadf['spm'] = 0
|
|
|
|
try:
|
|
pace = datadf['pace']
|
|
except KeyError:
|
|
datadf['pace'] = 0
|
|
|
|
source = ColumnDataSource(
|
|
datadf
|
|
)
|
|
|
|
plot = Figure(x_axis_type="datetime",y_axis_type="datetime",
|
|
plot_width=400,
|
|
plot_height=400,
|
|
toolbar_sticky=False,
|
|
tools=TOOLS)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.image_url([watermarkurl],0.01,0.99,
|
|
0.5*watermarkw,0.5*watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor='top_left',
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.line('time','pace',source=source,legend_label="Pace",name="pace")
|
|
plot.title.text = row.name
|
|
plot.title.text_font_size=value("1.0em")
|
|
plot.sizing_mode = 'stretch_both'
|
|
plot.xaxis.axis_label = "Time"
|
|
plot.yaxis.axis_label = "Pace (/500m)"
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
ymax = 90.
|
|
ymin = 150.
|
|
|
|
if row.workouttype == 'water':
|
|
ymax = 90.
|
|
ymin = 210.
|
|
|
|
plot.y_range = Range1d(1.e3*ymin,1.e3*ymax)
|
|
|
|
|
|
plot.extra_y_ranges["spmax"] = Range1d(start=10,end=45)
|
|
plot.line('time','spm',source=source,color="red",
|
|
y_range_name="spmax", legend_label="Stroke Rate",name="spm")
|
|
plot.add_layout(LinearAxis(y_range_name="spmax",axis_label="SPM"),'right')
|
|
|
|
plot.legend.location = "bottom_right"
|
|
|
|
# add shaded bar chart areas
|
|
if intervaldata != {}:
|
|
intervaldf = pd.DataFrame(intervaldata)
|
|
intervaldf['itime'] = intervaldf['itime']*1.e3
|
|
intervaldf['time'] = intervaldf['itime'].cumsum()
|
|
intervaldf['time'] = intervaldf['time'].shift(1)
|
|
intervaldf.loc[:,'time'].iloc[0] = 0
|
|
intervaldf['time_r'] = intervaldf['time'] +intervaldf['itime']
|
|
intervaldf['value'] = 100
|
|
mask = intervaldf['itype'] == 3
|
|
intervaldf.loc[mask,'value'] = 0
|
|
intervaldf['bottom'] = 10
|
|
|
|
intervalsource = ColumnDataSource(
|
|
intervaldf
|
|
)
|
|
|
|
plot.quad(left='time',top='value',bottom='bottom',
|
|
right='time_r',source=intervalsource,color='mediumvioletred',
|
|
y_range_name='spmax',fill_alpha=0.2,line_alpha=0.2)
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Time','@ftime'),
|
|
('Pace','@fpace'),
|
|
('HR','@hr{int}'),
|
|
('SPM','@spm{1.1}'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
hover.names = ["spm","pace"]
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_chart_video(videodata):
|
|
try:
|
|
spm = videodata['spm']
|
|
except KeyError:
|
|
return "","No SPM data"
|
|
|
|
time = range(len(spm))
|
|
|
|
data = zip(time,spm)
|
|
|
|
data2 = "["
|
|
|
|
for t,s in data:
|
|
data2 += "{x: %s, y: %s}, " % (t,s)
|
|
|
|
data2 = data2[:-2] + "]"
|
|
|
|
markerpoint = {
|
|
'x': time[0],
|
|
'y': spm[0],
|
|
'r': 10,
|
|
}
|
|
|
|
div = """
|
|
<canvas id="myChart">
|
|
</canvas>
|
|
"""
|
|
|
|
script = """
|
|
var ctx = document.getElementById("myChart").getContext('2d');
|
|
var data = %s
|
|
|
|
var myChart = new Chart(ctx, {
|
|
type: 'scatter',
|
|
label: 'SPM',
|
|
animationSteps: 10,
|
|
options: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
animation: {
|
|
duration: 100,
|
|
},
|
|
scales: {
|
|
yAxes: [{
|
|
scaleLabel: {
|
|
display: true,
|
|
labelString: 'Stroke Rate'
|
|
}
|
|
}],
|
|
xAxes: [{
|
|
scaleLabel: {
|
|
type: 'linear',
|
|
display: true,
|
|
labelString: 'Time (seconds)'
|
|
}
|
|
}],
|
|
}
|
|
},
|
|
data:
|
|
{
|
|
datasets: [
|
|
{
|
|
type: 'bubble',
|
|
label: 'now',
|
|
data: [ %s ],
|
|
backgroundColor: '#36a2eb',
|
|
},
|
|
{
|
|
label: 'spm',
|
|
data: data,
|
|
backgroundColor: "#ff0000",
|
|
borderColor: "#ff0000",
|
|
fill: false,
|
|
borderDash: [0, 0],
|
|
pointRadius: 1,
|
|
pointHoverRadius: 1,
|
|
showLine: true,
|
|
tension: 0,
|
|
},
|
|
|
|
]
|
|
},
|
|
|
|
});
|
|
|
|
var marker = {
|
|
datapoint: %s ,
|
|
setLatLng: function (LatLng) {
|
|
var lat = LatLng.lat;
|
|
var lng = LatLng.lng;
|
|
this.datapoint = {
|
|
'x': lat,
|
|
'y': lng,
|
|
'r': 10,
|
|
}
|
|
myChart.data.datasets[0].data[0] = this.datapoint;
|
|
myChart.update();
|
|
}
|
|
}
|
|
marker.setLatLng({
|
|
'lat': data[0]['x'],
|
|
'lng': data[0]['y']
|
|
})
|
|
|
|
""" % (data2, markerpoint, markerpoint)
|
|
|
|
return [script,div]
|
|
|
|
|
|
|
|
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
|
ploterrorbars=False,
|
|
title=None,binsize=1,colorlegend=[],
|
|
spmmin=0,spmmax=0,workmin=0,workmax=0):
|
|
|
|
if datadf.empty:
|
|
return ['','<p>No non-zero data in selection</p>']
|
|
|
|
if xparam == 'workoutid':
|
|
xparamname = 'Workout'
|
|
else:
|
|
xparamname = axlabels[xparam]
|
|
|
|
if yparam == 'workoutid':
|
|
yparamname = 'Workout'
|
|
else:
|
|
yparamname = axlabels[yparam]
|
|
|
|
if groupby == 'workoutid':
|
|
groupname = 'Workout'
|
|
elif groupby == 'date':
|
|
groupname = 'Date'
|
|
else:
|
|
groupname = axlabels[groupby]
|
|
|
|
|
|
if title==None:
|
|
title = '{y} vs {x} grouped by {gr}'.format(
|
|
x = xparamname,
|
|
y = yparamname,
|
|
gr = groupname,
|
|
)
|
|
|
|
if xparam == 'cumdist':
|
|
res = make_cumvalues(datadf[xparam])
|
|
datadf[xparam] = res[0]
|
|
if xparam=='distance':
|
|
xaxmax = datadf[xparam].max()
|
|
xaxmin = datadf[xparam].min()
|
|
elif xparam=='time':
|
|
tseconds = datadf.loc[:,'time']
|
|
xaxmax = tseconds.max()
|
|
xaxmin = 0
|
|
elif xparam == 'workoutid':
|
|
xaxmax = datadf[xparam].max()-5
|
|
xaxmin = datadf[xparam].min()+5
|
|
else:
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
|
|
if yparam=='distance':
|
|
yaxmax = datadf[yparam].max()
|
|
yaxmin = datadf[yparam].min()
|
|
elif yparam=='time':
|
|
tseconds = datadf.loc[:,'time']
|
|
yaxmax = tseconds.max()
|
|
yaxmin = 0
|
|
elif yparam == 'workoutid':
|
|
yaxmax = datadf[yparam].max()-5
|
|
yaxmin = datadf[yparam].min()+5
|
|
else:
|
|
yaxmax = yaxmaxima[yparam]
|
|
yaxmin = yaxminima[yparam]
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
if yparam == 'pace':
|
|
y_axis_type = 'datetime'
|
|
|
|
datadf.index.names = ['index']
|
|
|
|
source = ColumnDataSource(
|
|
datadf,
|
|
)
|
|
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap'
|
|
|
|
if groupby != 'date':
|
|
hover = HoverTool(names=['data'],
|
|
tooltips = [
|
|
(groupby,'@groupval{1.1}'),
|
|
(xparamname,'@x{1.1}'),
|
|
(yparamname,'@y')
|
|
])
|
|
else:
|
|
hover = HoverTool(names=['data'],
|
|
tooltips = [
|
|
(groupby,'@groupval'),
|
|
(xparamname,'@x{1.1}'),
|
|
(yparamname,'@y')
|
|
,
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
TOOLS = [SaveTool(),PanTool(),BoxZoomTool(),WheelZoomTool(),
|
|
ResetTool(),TapTool(),hover]
|
|
|
|
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
tools=TOOLS,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False,plot_width=920)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
|
|
plot.title.text = title
|
|
plot.title.text_font_size=value("1.0em")
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
errorbar(plot,xparam,yparam,source=source,
|
|
xerr=ploterrorbars,
|
|
yerr=ploterrorbars,
|
|
point_kwargs={
|
|
'line_color':"#969696",
|
|
'size':"groupsize",
|
|
'fill_color':"color",
|
|
'fill_alpha':1.0,
|
|
},
|
|
)
|
|
|
|
|
|
|
|
for nr, gvalue, color in colorlegend:
|
|
box = BoxAnnotation(bottom=75+20*nr,left=50,top=95+20*nr,
|
|
right=70,
|
|
bottom_units='screen',
|
|
top_units='screen',
|
|
left_units='screen',
|
|
right_units='screen',
|
|
fill_color=color,
|
|
fill_alpha=1.0,
|
|
line_color=color)
|
|
legendlabel = Label(x=71,y=78+20*nr,x_units='screen',
|
|
y_units='screen',
|
|
text = "{gvalue:3.0f}".format(gvalue=gvalue),
|
|
background_fill_alpha=1.0,
|
|
text_color='black',
|
|
text_font_size="0.7em")
|
|
plot.add_layout(box)
|
|
plot.add_layout(legendlabel)
|
|
|
|
if colorlegend:
|
|
legendlabel = Label(x=322,y=250,x_units='screen',
|
|
y_units='screen',
|
|
text = 'group legend',
|
|
text_color='black',
|
|
text_font_size="0.7em",
|
|
angle=90,
|
|
angle_units='deg')
|
|
|
|
if xparam == 'workoutid':
|
|
plot.xaxis.axis_label = 'Workout'
|
|
else:
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
|
|
if yparam == 'workoutid':
|
|
plot.xaxis.axis_label = 'Workout'
|
|
else:
|
|
plot.yaxis.axis_label = axlabels[yparam]
|
|
|
|
binlabel = Label(x=50,y=50,x_units='screen',
|
|
y_units='screen',
|
|
text="Bin size {binsize:3.1f}".format(binsize=binsize),
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
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(sliderlabel)
|
|
|
|
yrange1 = Range1d(start=yaxmin,end=yaxmax)
|
|
plot.y_range = yrange1
|
|
|
|
xrange1 = Range1d(start=xaxmin,end=xaxmax)
|
|
plot.x_range = xrange1
|
|
|
|
if yparam == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
|
|
script,div = components(plot)
|
|
|
|
|
|
return [script,div]
|
|
|
|
def interactive_cum_flex_chart2(theworkouts,promember=0,
|
|
xparam='spm',
|
|
yparam1='power',
|
|
yparam2='spm',
|
|
workstrokesonly=False):
|
|
|
|
# datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2)
|
|
ids = [int(w.id) for w in theworkouts]
|
|
columns = [xparam,yparam1,yparam2,'spm','driveenergy','distance']
|
|
datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=True,
|
|
workstrokesonly=workstrokesonly)
|
|
|
|
try:
|
|
tests = datadf[yparam2]
|
|
except KeyError:
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
tests = datadf[yparam1]
|
|
except KeyError:
|
|
yparam1 = 'None'
|
|
|
|
datadf.dropna(axis=1,how='all',inplace=True)
|
|
datadf.dropna(axis=0,how='any',inplace=True)
|
|
|
|
# test if we have drive energy
|
|
nowork = 1
|
|
try:
|
|
test = datadf['driveenergy'].mean()
|
|
nowork = 0
|
|
except KeyError:
|
|
datadf['driveenergy'] = 500.
|
|
|
|
# test if we have power
|
|
nopower = 1
|
|
try:
|
|
test = datadf['power'].mean()
|
|
nopower = 0
|
|
except KeyError:
|
|
datadf['power'] = 50.
|
|
|
|
|
|
yparamname1 = axlabels[yparam1]
|
|
if yparam2 != 'None':
|
|
yparamname2 = axlabels[yparam2]
|
|
|
|
|
|
# check if dataframe not empty
|
|
if datadf.empty:
|
|
return ['','<p>No non-zero data in selection</p>','','']
|
|
|
|
|
|
try:
|
|
datadf['x1'] = datadf.loc[:,xparam]
|
|
except KeyError:
|
|
try:
|
|
datadf['x1'] = datadf['distance']
|
|
except KeyError:
|
|
try:
|
|
datadf['x1'] = datadf['time']
|
|
except KeyError:
|
|
return ['','<p>No non-zero data in selection</p>','','']
|
|
|
|
try:
|
|
datadf['y1'] = datadf.loc[:,yparam1]
|
|
except KeyError:
|
|
try:
|
|
datadf['y1'] = datadf['pace']
|
|
except KeyError:
|
|
return ['','<p>No non-zero data in selection</p>','','']
|
|
if yparam2 != 'None':
|
|
try:
|
|
datadf['y2'] = datadf.loc[:,yparam2]
|
|
except KeyError:
|
|
datadf['y2'] = datadf['y1']
|
|
else:
|
|
datadf['y2'] = datadf['y1']
|
|
|
|
|
|
if xparam=='distance':
|
|
xaxmax = datadf['x1'].max()
|
|
xaxmin = datadf['x1'].min()
|
|
else:
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
|
|
# average values
|
|
x1mean = datadf['x1'].mean()
|
|
|
|
y1mean = datadf['y1'].mean()
|
|
y2mean = datadf['y2'].mean()
|
|
|
|
|
|
xvals = pd.Series(xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.)
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
|
|
if yparam1 == 'pace':
|
|
y_axis_type = 'datetime'
|
|
y1mean = datadf.loc[:,'pace'].mean()
|
|
|
|
datadf['xname'] = axlabels[xparam]
|
|
datadf['yname1'] = axlabels[yparam1]
|
|
if yparam2 != 'None':
|
|
datadf['yname2'] = axlabels[yparam2]
|
|
else:
|
|
datadf['yname2'] = axlabels[yparam1]
|
|
|
|
|
|
source = ColumnDataSource(
|
|
datadf
|
|
)
|
|
|
|
source2 = ColumnDataSource(
|
|
datadf.copy()
|
|
)
|
|
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
tools=TOOLS,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
x1means = Span(location=x1mean,dimension='height',line_color='green',
|
|
line_dash=[6,6], line_width=2)
|
|
|
|
y1means = Span(location=y1mean,dimension='width',line_color='blue',
|
|
line_dash=[6,6],line_width=2)
|
|
y2means = y1means
|
|
|
|
xlabel = Label(x=50,y=80,x_units='screen',y_units='screen',
|
|
text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
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(xlabel)
|
|
plot.add_layout(y1means)
|
|
plot.add_layout(sliderlabel)
|
|
|
|
y1label = Label(x=50,y=50,x_units='screen',y_units='screen',
|
|
text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='blue',
|
|
)
|
|
|
|
if yparam1 != 'time' and yparam1 != 'pace':
|
|
plot.add_layout(y1label)
|
|
|
|
y2label = y1label
|
|
plot.circle('x1','y1',source=source2,fill_alpha=0.3,line_color=None,
|
|
legend_label=yparamname1,
|
|
)
|
|
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
plot.yaxis.axis_label = axlabels[yparam1]
|
|
|
|
|
|
yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1])
|
|
plot.y_range = yrange1
|
|
|
|
xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam])
|
|
plot.x_range = xrange1
|
|
|
|
if yparam1 == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
if yparam2 != 'None':
|
|
yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2])
|
|
plot.extra_y_ranges["yax2"] = yrange2
|
|
|
|
plot.circle('x1','y2',color="red",y_range_name="yax2",
|
|
legend_label=yparamname2,
|
|
source=source2,fill_alpha=0.3,line_color=None)
|
|
|
|
plot.add_layout(LinearAxis(y_range_name="yax2",
|
|
axis_label=axlabels[yparam2]),'right')
|
|
|
|
y2means = Span(location=y2mean,dimension='width',line_color='red',
|
|
line_dash=[6,6],line_width=2,y_range_name="yax2")
|
|
|
|
|
|
plot.add_layout(y2means)
|
|
y2label = Label(x=50,y=20,x_units='screen',y_units='screen',
|
|
text=axlabels[yparam2]+": {y2mean:6.2f}".format(y2mean=y2mean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='red',
|
|
)
|
|
if yparam2 != 'pace' and yparam2 != 'time':
|
|
plot.add_layout(y2label)
|
|
|
|
callback = CustomJS(args = dict(source=source,source2=source2,
|
|
x1means=x1means,
|
|
y1means=y1means,
|
|
y1label=y1label,
|
|
y2label=y2label,
|
|
xlabel=xlabel,
|
|
sliderlabel=sliderlabel,
|
|
y2means=y2means), code="""
|
|
var data = source.data
|
|
var data2 = source2.data
|
|
var x1 = data['x1']
|
|
var y1 = data['y1']
|
|
var y2 = data['y2']
|
|
var spm1 = data['spm']
|
|
|
|
var index1 = data['index']
|
|
|
|
var distance1 = data['distance']
|
|
var power1 = data['power']
|
|
var driveenergy1 = data['driveenergy']
|
|
var xname = data['xname']
|
|
var yname1 = data['yname1']
|
|
var yname2 = data['yname2']
|
|
var workoutid1 = data['workoutid']
|
|
|
|
var minspm = minspm.value
|
|
var maxspm = maxspm.value
|
|
var mindist = mindist.value
|
|
var maxdist = maxdist.value
|
|
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 xm = 0
|
|
var ym1 = 0
|
|
var ym2 = 0
|
|
|
|
data2['x1'] = []
|
|
data2['y1'] = []
|
|
data2['y2'] = []
|
|
data2['distance'] = []
|
|
data2['power'] = []
|
|
data2['x1mean'] = []
|
|
data2['y1mean'] = []
|
|
data2['y2mean'] = []
|
|
data2['driveenergy'] = []
|
|
data2['workoutid'] = []
|
|
data2['xname'] = []
|
|
data2['yname1'] = []
|
|
data2['yname2'] = []
|
|
data2['spm'] = []
|
|
|
|
for (var i=0; i<x1.length; i++) {
|
|
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
|
|
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
|
|
if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) {
|
|
data2['x1'].push(x1[i])
|
|
data2['y1'].push(y1[i])
|
|
data2['y2'].push(y2[i])
|
|
data2['spm'].push(spm1[i])
|
|
data2['driveenergy'].push(driveenergy1[i])
|
|
data2['distance'].push(distance1[i])
|
|
data2['power'].push(power1[i])
|
|
data2['workoutid'].push(0)
|
|
data2['xname'].push(0)
|
|
data2['yname1'].push(0)
|
|
data2['yname2'].push(0)
|
|
|
|
xm += x1[i]
|
|
ym1 += y1[i]
|
|
ym2 += y2[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
xm /= data2['x1'].length
|
|
ym1 /= data2['x1'].length
|
|
ym2 /= data2['x1'].length
|
|
|
|
for (var i=0; i<data2['x1'].length; i++) {
|
|
data2['x1mean'].push(xm)
|
|
data2['y1mean'].push(ym1)
|
|
data2['y2mean'].push(ym2)
|
|
}
|
|
|
|
x1means.location = xm
|
|
y1means.location = ym1
|
|
y2means.location = ym2
|
|
y1label.text = yname1[0]+': '+(ym1).toFixed(2)
|
|
y2label.text = yname2[0]+': '+(ym2).toFixed(2)
|
|
xlabel.text = xname[0]+': '+(xm).toFixed(2)
|
|
|
|
source2.change.emit();
|
|
""")
|
|
|
|
slider_spm_min = Slider(width=140, start=15.0, end=55,value=15.0, step=.1,
|
|
title="Min SPM")
|
|
slider_spm_min.js_on_change('value',callback)
|
|
callback.args["minspm"] = slider_spm_min
|
|
|
|
|
|
slider_spm_max = Slider(width=140, start=15.0, end=55,value=55.0, step=.1,
|
|
title="Max SPM")
|
|
slider_spm_max.js_on_change('value',callback)
|
|
callback.args["maxspm"] = slider_spm_max
|
|
|
|
slider_work_min = Slider(width=140, start=0.0, end=1500,value=0.0, step=10,
|
|
title="Min Work per Stroke")
|
|
slider_work_min.js_on_change('value',callback)
|
|
callback.args["minwork"] = slider_work_min
|
|
|
|
|
|
slider_work_max = Slider(width=140, start=0.0, end=1500,value=1500.0, step=10,
|
|
title="Max Work per Stroke")
|
|
slider_work_max.js_on_change('value',callback)
|
|
callback.args["maxwork"] = slider_work_max
|
|
|
|
try:
|
|
distmax = 100+100*int(datadf['distance'].max()/100.)
|
|
except KeyError:
|
|
distmax = 1000.
|
|
|
|
slider_dist_min = Slider(width=140, start=0,end=distmax,value=0,step=50,
|
|
title="Min Distance")
|
|
slider_dist_min.js_on_change('value',callback)
|
|
callback.args["mindist"] = slider_dist_min
|
|
|
|
slider_dist_max = Slider(width=140, start=0,end=distmax,value=distmax,
|
|
step=50,
|
|
title="Max Distance")
|
|
slider_dist_max.js_on_change('value',callback)
|
|
callback.args["maxdist"] = slider_dist_max
|
|
|
|
slider_spm_min.sizing_mode = 'fixed'
|
|
slider_spm_max.sizing_mode = 'fixed'
|
|
slider_work_min.sizing_mode = 'fixed'
|
|
slider_work_max.sizing_mode = 'fixed'
|
|
slider_dist_min.sizing_mode = 'fixed'
|
|
slider_dist_max.sizing_mode = 'fixed'
|
|
|
|
thesliders = layoutcolumn([slider_spm_min,
|
|
slider_spm_max,
|
|
slider_dist_min,
|
|
slider_dist_max,
|
|
slider_work_min,
|
|
slider_work_max,
|
|
],
|
|
)
|
|
|
|
thesliders.sizing_mode = 'fixed'
|
|
|
|
layout = layoutrow([thesliders,
|
|
plot])
|
|
|
|
layout.sizing_mode = 'stretch_both'
|
|
|
|
script, div = components(layout)
|
|
js_resources = INLINE.render_js()
|
|
css_resources = INLINE.render_css()
|
|
|
|
|
|
|
|
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,yparam3,yparam4,
|
|
'ftime','distance','fpace',
|
|
'power','hr','spm','driveenergy',
|
|
'time','pace','workoutstate']
|
|
|
|
comment = None
|
|
|
|
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",'','',comment
|
|
|
|
try:
|
|
tseconds = rowdata.loc[:,'time']
|
|
except KeyError:
|
|
return '','No time data - cannot make flex plot','','',comment
|
|
|
|
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
|
|
)
|
|
|
|
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=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=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=TOOLS2,toolbar_location=None)
|
|
|
|
plot1.xaxis.visible=False
|
|
plot2.xaxis.visible=False
|
|
plot3.xaxis.visible=False
|
|
|
|
plot1.sizing_mode = 'stretch_both'
|
|
plot2.sizing_mode = 'stretch_both'
|
|
plot3.sizing_mode = 'stretch_both'
|
|
plot4.sizing_mode = 'stretch_both'
|
|
|
|
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
|
|
|
|
|
|
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':
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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.mode = 'vline'
|
|
hover2.mode = 'vline'
|
|
hover3.mode = 'vline'
|
|
hover4.mode = 'vline'
|
|
|
|
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=palette2[1])
|
|
plot2.line('x1','y2',source=source,color=palette2[3])
|
|
plot3.line('x1','y3',source=source,color=palette2[0])
|
|
plot4.line('x1','y4',source=source,color=palette2[2])
|
|
|
|
layout = layoutcolumn([
|
|
plot1,
|
|
plot2,
|
|
plot3,
|
|
plot4,
|
|
])
|
|
|
|
layout.sizing_mode = 'stretch_both'
|
|
|
|
|
|
script, div = components(layout)
|
|
js_resources = INLINE.render_js()
|
|
css_resources = INLINE.render_css()
|
|
|
|
return script,div,js_resources,css_resources,comment
|
|
|
|
|
|
|
|
def interactive_flex_chart2(id,r,promember=0,
|
|
xparam='time',
|
|
yparam1='pace',
|
|
yparam2='hr',
|
|
plottype='line',
|
|
workstrokesonly=False,
|
|
mode='rower'):
|
|
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
|
|
#rowdata,row = dataprep.getrowdata_db(id=id)
|
|
columns = [xparam,yparam1,yparam2,
|
|
'ftime','distance','fpace',
|
|
'power','hr','spm','driveenergy',
|
|
'time','pace','workoutstate']
|
|
|
|
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True,
|
|
workstrokesonly=workstrokesonly)
|
|
|
|
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=True,
|
|
workstrokesonly=False)
|
|
workstrokesonly=False
|
|
if len(rowdata)<2:
|
|
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],
|
|
doclean=False,
|
|
workstrokesonly=False)
|
|
workstrokesonly=False
|
|
try:
|
|
tests = rowdata[yparam2]
|
|
except KeyError:
|
|
yparam2 = 'None'
|
|
|
|
|
|
|
|
try:
|
|
tests = rowdata[yparam1]
|
|
except KeyError:
|
|
yparam1 = 'None'
|
|
|
|
|
|
# test if we have drive energy
|
|
nowork = 1
|
|
try:
|
|
test = rowdata['driveenergy'].mean()
|
|
nowork = 0
|
|
except KeyError:
|
|
rowdata['driveenergy'] = 500.
|
|
|
|
# test if we have power
|
|
nopower = 1
|
|
try:
|
|
test = rowdata['power'].mean()
|
|
nopower = 0
|
|
except KeyError:
|
|
rowdata['power'] = 50.
|
|
|
|
# replace nans
|
|
rowdata.fillna(value=0,inplace=True)
|
|
|
|
row = Workout.objects.get(id=id)
|
|
if rowdata.empty:
|
|
return "","No valid data",'','',workstrokesonly
|
|
|
|
|
|
workoutstateswork = [1,4,5,8,9,6,7]
|
|
workoutstatesrest = [3]
|
|
workoutstatetransition = [0,2,10,11,12,13]
|
|
|
|
if workstrokesonly:
|
|
try:
|
|
rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)]
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
tseconds = rowdata.loc[:,'time']
|
|
except KeyError:
|
|
return '','No time data - cannot make flex plot','','',workstrokesonly
|
|
|
|
|
|
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']
|
|
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['y2'] = rowdata.loc[:,yparam2]
|
|
rowmin = rowdata[yparam2].min()
|
|
except KeyError:
|
|
rowdata['y2'] = 0*rowdata.loc[:,'time']
|
|
rowdata[yparam2] = rowdata['y2']
|
|
else:
|
|
rowdata['y2'] = rowdata['y1']
|
|
|
|
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()
|
|
|
|
# average values
|
|
if xparam != 'time':
|
|
try:
|
|
x1mean = rowdata['x1'].mean()
|
|
except TypeError:
|
|
x1mean = 0
|
|
else:
|
|
x1mean = 0
|
|
|
|
y1mean = rowdata['y1'].mean()
|
|
y2mean = rowdata['y2'].mean()
|
|
|
|
if xparam != 'time':
|
|
xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.
|
|
else:
|
|
xvals = np.arange(100)
|
|
|
|
# constant power plot
|
|
if yparam1 == 'driveenergy':
|
|
if xparam == 'spm':
|
|
yconstantpower = rowdata['y1'].mean()*rowdata['x1'].mean()/xvals
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
|
|
if yparam1 == 'pace':
|
|
y_axis_type = 'datetime'
|
|
try:
|
|
y1mean = rowdata.loc[:,'pace'].mean()
|
|
except KeyError:
|
|
y1mean = 0
|
|
|
|
try:
|
|
rowdata['xname'] = axlabels[xparam]
|
|
except KeyError:
|
|
rowdata['xname'] = xparam
|
|
try:
|
|
rowdata['yname1'] = axlabels[yparam1]
|
|
except KeyError:
|
|
rowdata['yname1'] = yparam1
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['yname2'] = axlabels[yparam2]
|
|
except KeyError:
|
|
rowdata['yname2'] = yparam2
|
|
else:
|
|
rowdata['yname2'] = rowdata['yname1']
|
|
|
|
|
|
# prepare data
|
|
source = ColumnDataSource(
|
|
rowdata
|
|
)
|
|
|
|
|
|
|
|
# second source for filtering
|
|
source2 = ColumnDataSource(
|
|
rowdata.copy()
|
|
)
|
|
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
tools=TOOLS,toolbar_location='above',
|
|
toolbar_sticky=False,plot_width=800,plot_height=600,
|
|
)
|
|
plot.sizing_mode = 'stretch_both'
|
|
#plot.width_policy = 'max'
|
|
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
x1means = Span(location=x1mean,dimension='height',line_color='green',
|
|
line_dash=[6,6], line_width=2)
|
|
|
|
y1means = Span(location=y1mean,dimension='width',line_color='blue',
|
|
line_dash=[6,6],line_width=2)
|
|
y2means = y1means
|
|
|
|
try:
|
|
xlabeltext = axlabels[xparam]+": {x1mean:6.2f}".format(
|
|
x1mean=x1mean
|
|
)
|
|
except KeyError:
|
|
xlabeltext = xparam+": {x1mean:6.2f}".format(x1mean=x1mean)
|
|
|
|
xlabel = Label(x=50,y=80,x_units='screen',y_units='screen',
|
|
text=xlabeltext,
|
|
background_fill_alpha=.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',
|
|
)
|
|
|
|
|
|
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
|
|
plot.add_layout(x1means)
|
|
plot.add_layout(xlabel)
|
|
|
|
|
|
plot.add_layout(y1means)
|
|
plot.add_layout(annolabel)
|
|
plot.add_layout(sliderlabel)
|
|
|
|
try:
|
|
yaxlabel = axlabels[yparam1]
|
|
except KeyError:
|
|
yaxlabel = str(yparam1)+' '
|
|
|
|
try:
|
|
xaxlabel = axlabels[xparam]
|
|
except KeyError:
|
|
xaxlabel = xparam
|
|
|
|
y1label = Label(x=50,y=50,x_units='screen',y_units='screen',
|
|
text=yaxlabel+": {y1mean:6.2f}".format(y1mean=y1mean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='blue',
|
|
)
|
|
if yparam1 != 'time' and yparam1 != 'pace':
|
|
plot.add_layout(y1label)
|
|
y2label = y1label
|
|
|
|
# average values
|
|
if yparam1 == 'driveenergy':
|
|
if xparam == 'spm':
|
|
plot.line(xvals,yconstantpower,color="green",legend_label="Constant Power")
|
|
|
|
if plottype=='line':
|
|
plot.line('x1','y1',source=source2,legend_label=yaxlabel)
|
|
elif plottype=='scatter':
|
|
plot.scatter('x1','y1',source=source2,legend_label=yaxlabel,fill_alpha=0.4,
|
|
line_color=None)
|
|
|
|
plot.title.text = row.name
|
|
plot.title.text_font_size=value("1.0em")
|
|
|
|
plot.sizing_mode = 'stretch_both'
|
|
plot.xaxis.axis_label = xaxlabel
|
|
|
|
plot.yaxis.axis_label = yaxlabel
|
|
|
|
|
|
|
|
try:
|
|
yrange1 = Range1d(start=get_yaxminima(r,yparam1,mode),
|
|
end=get_yaxmaxima(r,yparam1,mode))
|
|
except KeyError:
|
|
yrange1 = Range1d(start=rowdata[yparam1].min(),
|
|
end=rowdata[yparam1].max())
|
|
|
|
plot.y_range = yrange1
|
|
|
|
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
|
|
try:
|
|
xrange1 = Range1d(start=get_yaxminima(r,xparam,mode),
|
|
end=get_yaxmaxima(r,xparam,mode))
|
|
except KeyError:
|
|
xrange1 = Range1d(start=rowdata[xparam].min(),
|
|
end=rowdata[xparam].max())
|
|
|
|
plot.x_range = xrange1
|
|
|
|
if xparam == 'time':
|
|
xrange1 = Range1d(start=xaxmin,end=xaxmax)
|
|
plot.x_range = xrange1
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
|
|
|
|
if yparam1 == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
|
|
if yparam2 != 'None':
|
|
try:
|
|
yrange2 = Range1d(start=get_yaxminima(r,yparam2,mode),
|
|
end=get_yaxmaxima(r,yparam2,mode))
|
|
except KeyError:
|
|
yrange2 = Range1d(start=rowdata[yparam2].min(),
|
|
end=rowdata[yparam2].max())
|
|
|
|
plot.extra_y_ranges["yax2"] = yrange2
|
|
#= {"yax2": yrange2}
|
|
try:
|
|
axlegend = axlabels[yparam2]
|
|
except KeyError:
|
|
axlegend = str(yparam2)+' '
|
|
|
|
if plottype=='line':
|
|
plot.line('x1','y2',color="red",y_range_name="yax2",
|
|
legend_label=axlegend,
|
|
source=source2)
|
|
|
|
elif plottype=='scatter':
|
|
plot.scatter('x1','y2',source=source2,legend_label=axlegend,
|
|
fill_alpha=0.4,
|
|
line_color=None,color="red",y_range_name="yax2")
|
|
|
|
plot.add_layout(LinearAxis(y_range_name="yax2",
|
|
axis_label=axlegend),'right')
|
|
|
|
y2means = Span(location=y2mean,dimension='width',line_color='red',
|
|
line_dash=[6,6],line_width=2,y_range_name="yax2")
|
|
|
|
|
|
plot.add_layout(y2means)
|
|
y2label = Label(x=50,y=20,x_units='screen',y_units='screen',
|
|
text=axlegend+": {y2mean:6.2f}".format(y2mean=y2mean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='red',
|
|
)
|
|
if yparam2 != 'pace' and yparam2 != 'time':
|
|
plot.add_layout(y2label)
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Time','@ftime'),
|
|
('Distance','@distance{int}'),
|
|
('Pace','@fpace'),
|
|
('HR','@hr{int}'),
|
|
('SPM','@spm{1.1}'),
|
|
('Power','@power{int}'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
|
|
callback = CustomJS(args = dict(source=source,source2=source2,
|
|
x1means=x1means,
|
|
y1means=y1means,
|
|
y1label=y1label,
|
|
y2label=y2label,
|
|
xlabel=xlabel,
|
|
annolabel=annolabel,
|
|
sliderlabel=sliderlabel,
|
|
y2means=y2means,
|
|
), code="""
|
|
var data = source.data
|
|
var data2 = source2.data
|
|
var x1 = data['x1']
|
|
var y1 = data['y1']
|
|
var y2 = data['y2']
|
|
var spm1 = data['spm']
|
|
var time1 = data['time']
|
|
var ftime1 = data['ftime']
|
|
var pace1 = data['pace']
|
|
var hr1 = data['hr']
|
|
var fpace1 = data['fpace']
|
|
var distance1 = data['distance']
|
|
var power1 = data['power']
|
|
var driveenergy1 = data['driveenergy']
|
|
var xname = data['xname']
|
|
var yname1 = data['yname1']
|
|
var yname2 = data['yname2']
|
|
var workoutid1 = data['workoutid']
|
|
var workoutstate1 = data['workoutstate']
|
|
|
|
var annotation = annotation.value
|
|
var minspm = minspm.value
|
|
var maxspm = maxspm.value
|
|
var mindist = mindist.value
|
|
var maxdist = maxdist.value
|
|
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 xm = 0
|
|
var ym1 = 0
|
|
var ym2 = 0
|
|
|
|
data2['x1'] = []
|
|
data2['y1'] = []
|
|
data2['y2'] = []
|
|
data2['spm'] = []
|
|
data2['time'] = []
|
|
data2['ftime'] = []
|
|
data2['pace'] = []
|
|
data2['hr'] = []
|
|
data2['fpace'] = []
|
|
data2['distance'] = []
|
|
data2['power'] = []
|
|
data2['x1mean'] = []
|
|
data2['y1mean'] = []
|
|
data2['y2mean'] = []
|
|
data2['driveenergy'] = []
|
|
data2['workoutid'] = []
|
|
data2['workoutstate'] = []
|
|
data2['xname'] = []
|
|
data2['yname1'] = []
|
|
data2['yname2'] = []
|
|
|
|
|
|
for (var i=0; i<x1.length; i++) {
|
|
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
|
|
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
|
|
if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) {
|
|
data2['x1'].push(x1[i])
|
|
data2['y1'].push(y1[i])
|
|
data2['y2'].push(y2[i])
|
|
data2['spm'].push(spm1[i])
|
|
data2['time'].push(time1[i])
|
|
data2['ftime'].push(ftime1[i])
|
|
data2['fpace'].push(fpace1[i])
|
|
data2['driveenergy'].push(driveenergy1[i])
|
|
data2['pace'].push(pace1[i])
|
|
data2['hr'].push(hr1[i])
|
|
data2['distance'].push(distance1[i])
|
|
data2['power'].push(power1[i])
|
|
data2['workoutid'].push(0)
|
|
data2['workoutstate'].push(0)
|
|
data2['xname'].push(0)
|
|
data2['yname1'].push(0)
|
|
data2['yname2'].push(0)
|
|
|
|
|
|
xm += x1[i]
|
|
ym1 += y1[i]
|
|
ym2 += y2[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xm /= data2['x1'].length
|
|
ym1 /= data2['x1'].length
|
|
ym2 /= data2['x1'].length
|
|
|
|
for (var i=0; i<data2['x1'].length; i++) {
|
|
data2['x1mean'].push(xm)
|
|
data2['y1mean'].push(ym1)
|
|
data2['y2mean'].push(ym2)
|
|
}
|
|
|
|
|
|
|
|
x1means.location = xm
|
|
y1means.location = ym1
|
|
y2means.location = ym2
|
|
y1label.text = yname1[0]+': '+ym1.toFixed(2)
|
|
y2label.text = yname2[0]+': '+ym2.toFixed(2)
|
|
xlabel.text = xname[0]+': '+xm.toFixed(2)
|
|
annolabel.text = annotation
|
|
|
|
source2.change.emit();
|
|
""")
|
|
|
|
annotation = TextInput(width=140, title="Type your plot notes here", value="")
|
|
annotation.js_on_change('value',callback)
|
|
callback.args["annotation"] = annotation
|
|
|
|
slider_spm_min = Slider(width=140, start=15.0, end=55,value=15.0, step=.1,
|
|
title="Min SPM")
|
|
slider_spm_min.js_on_change('value',callback)
|
|
callback.args["minspm"] = slider_spm_min
|
|
|
|
|
|
slider_spm_max = Slider(width=140, start=15.0, end=55,value=55.0, step=.1,
|
|
title="Max SPM")
|
|
slider_spm_max.js_on_change('value',callback)
|
|
callback.args["maxspm"] = slider_spm_max
|
|
|
|
slider_work_min = Slider(width=140, start=0.0, end=1500,value=0.0, step=10,
|
|
title="Min Work per Stroke")
|
|
slider_work_min.js_on_change('value',callback)
|
|
callback.args["minwork"] = slider_work_min
|
|
|
|
|
|
slider_work_max = Slider(width=140, start=0.0, end=1500,value=1500.0, step=10,
|
|
title="Max Work per Stroke")
|
|
slider_work_max.js_on_change('value',callback)
|
|
callback.args["maxwork"] = slider_work_max
|
|
|
|
try:
|
|
distmax = 100+100*int(rowdata['distance'].max()/100.)
|
|
except (KeyError,ValueError):
|
|
distmax = 100
|
|
|
|
slider_dist_min = Slider(width=140, start=0,end=distmax,value=0,step=50,
|
|
title="Min Distance")
|
|
slider_dist_min.js_on_change('value',callback)
|
|
callback.args["mindist"] = slider_dist_min
|
|
|
|
slider_dist_max = Slider(width=140, start=0,end=distmax,value=distmax,
|
|
step=50,
|
|
title="Max Distance")
|
|
slider_dist_max.js_on_change('value',callback)
|
|
callback.args["maxdist"] = slider_dist_max
|
|
|
|
annotation.sizing_mode = 'fixed'
|
|
slider_spm_min.sizing_mode = 'fixed'
|
|
slider_spm_max.sizing_mode = 'fixed'
|
|
slider_work_min.sizing_mode = 'fixed'
|
|
slider_work_max.sizing_mode = 'fixed'
|
|
slider_dist_min.sizing_mode = 'fixed'
|
|
slider_dist_max.sizing_mode = 'fixed'
|
|
|
|
thesliders = layoutcolumn([
|
|
annotation,
|
|
slider_spm_min,
|
|
slider_spm_max,
|
|
slider_dist_min,
|
|
slider_dist_max,
|
|
slider_work_min,
|
|
slider_work_max,
|
|
])
|
|
|
|
thesliders.sizing_mode = 'fixed'
|
|
|
|
layout = layoutrow([thesliders,
|
|
plot])
|
|
|
|
# layout.sizing_mode = 'stretch_both'
|
|
layout.sizing_mode = 'stretch_both'
|
|
|
|
script, div = components(layout)
|
|
js_resources = INLINE.render_js()
|
|
css_resources = INLINE.render_css()
|
|
|
|
return [script,div,js_resources,css_resources,workstrokesonly]
|
|
|
|
def thumbnails_set(r,id,favorites):
|
|
charts = []
|
|
|
|
columns = [f.xparam for f in favorites]
|
|
columns += [f.yparam1 for f in favorites]
|
|
columns += [f.yparam2 for f in favorites]
|
|
|
|
columns += ['time']
|
|
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True)
|
|
|
|
rowdata.dropna(axis=1,how='all',inplace=True)
|
|
|
|
if rowdata.empty:
|
|
try:
|
|
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=False,
|
|
workstrokesonly=False)
|
|
except:
|
|
return [
|
|
{'script':"",
|
|
'div':"",
|
|
'notes':""
|
|
}]
|
|
|
|
if rowdata.empty:
|
|
return [
|
|
{'script':"",
|
|
'div':"",
|
|
'notes':""
|
|
}]
|
|
|
|
# else:
|
|
# try:
|
|
# rowdata.sort_values(by='time',ascending=True,inplace=True)
|
|
# except KeyError:
|
|
# pass
|
|
|
|
l = len(rowdata)
|
|
maxlength = 50
|
|
if l > maxlength:
|
|
try:
|
|
bins = np.linspace(rowdata['time'].min(),rowdata['time'].max(),maxlength)
|
|
groups = rowdata.groupby(np.digitize(rowdata['time'],bins))
|
|
rowdata = groups.mean()
|
|
except KeyError:
|
|
pass
|
|
|
|
for f in favorites:
|
|
workstrokesonly = not f.reststrokes
|
|
script,div = thumbnail_flex_chart(
|
|
rowdata,
|
|
id=id,
|
|
xparam=f.xparam,
|
|
yparam1=f.yparam1,
|
|
yparam2=f.yparam2,
|
|
plottype=f.plottype,
|
|
)
|
|
|
|
|
|
charts.append({
|
|
'script':script,
|
|
'div':div,
|
|
'notes':f.notes})
|
|
|
|
return charts
|
|
|
|
|
|
def thumbnail_flex_chart(rowdata,id=0,promember=0,
|
|
xparam='time',
|
|
yparam1='pace',
|
|
yparam2='hr',
|
|
plottype='line',
|
|
workstrokesonly=False):
|
|
|
|
|
|
try:
|
|
tests = rowdata[yparam2]
|
|
except KeyError:
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
tests = rowdata[yparam1]
|
|
except KeyError:
|
|
yparam1 = 'None'
|
|
|
|
|
|
|
|
try:
|
|
tseconds = rowdata.loc[:,'time']
|
|
except KeyError:
|
|
return '','No time data - cannot make flex plot'
|
|
|
|
|
|
try:
|
|
rowdata['x1'] = rowdata.loc[:,xparam]
|
|
except KeyError:
|
|
rowdata['x1'] = 0*rowdata.loc[:,'time']
|
|
|
|
try:
|
|
rowdata['y1'] = rowdata.loc[:,yparam1]
|
|
except KeyError:
|
|
rowdata['y1'] = 0*rowdata.loc[:,'time']
|
|
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['y2'] = rowdata.loc[:,yparam2]
|
|
except KeyError:
|
|
rowdata['y2'] = 0*rowdata.loc[:,'time']
|
|
else:
|
|
rowdata['y2'] = rowdata['y1']
|
|
|
|
if xparam=='time':
|
|
xaxmax = tseconds.max()
|
|
xaxmin = tseconds.min()
|
|
elif xparam=='distance' or xparam=='cumdist':
|
|
xaxmax = rowdata['x1'].max()
|
|
xaxmin = rowdata['x1'].min()
|
|
else:
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
|
|
if yparam1 == 'pace':
|
|
y_axis_type = 'datetime'
|
|
y1mean = rowdata.loc[:,'pace'].mean()
|
|
|
|
|
|
rowdata['xname'] = axlabels[xparam]
|
|
try:
|
|
rowdata['yname1'] = axlabels[yparam1]
|
|
except KeyError:
|
|
rowdata['yname1'] = axlabels[xparam]
|
|
if yparam2 != 'None':
|
|
rowdata['yname2'] = axlabels[yparam2]
|
|
else:
|
|
rowdata['yname2'] = axlabels[yparam1]
|
|
|
|
|
|
# prepare data
|
|
source = ColumnDataSource(
|
|
rowdata
|
|
)
|
|
|
|
|
|
sizing_mode = 'fixed' # 'stretch_both' also looks nice with this example
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
plot_width=200,plot_height=150,
|
|
)
|
|
|
|
|
|
|
|
# plot.sizing_mode = 'stretch_both'
|
|
plot.sizing_mode = 'fixed'
|
|
plot.toolbar.logo = None
|
|
plot.toolbar_location = None
|
|
#plot.yaxis.visible = False
|
|
plot.xaxis.axis_label_text_font_size = "7pt"
|
|
plot.yaxis.axis_label_text_font_size = "7pt"
|
|
plot.xaxis.major_label_text_font_size = "7pt"
|
|
plot.yaxis.major_label_text_font_size = "7pt"
|
|
|
|
if plottype=='line':
|
|
plot.line('x1','y1',source=source)
|
|
elif plottype=='scatter':
|
|
plot.scatter('x1','y1',source=source,fill_alpha=0.4,
|
|
line_color=None)
|
|
|
|
try:
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
except KeyError:
|
|
plot.xaxis.axis_label = 'X'
|
|
try:
|
|
plot.yaxis.axis_label = axlabels[yparam1]
|
|
except KeyError:
|
|
plot.yaxis.axis_label = 'Y'
|
|
|
|
|
|
try:
|
|
yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1])
|
|
except KeyError:
|
|
yrange1 = Range1d(start=yparam1.min(), end=yparam1.max())
|
|
|
|
plot.y_range = yrange1
|
|
|
|
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
|
|
xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam])
|
|
plot.x_range = xrange1
|
|
|
|
if xparam == 'time':
|
|
xrange1 = Range1d(start=xaxmin,end=xaxmax)
|
|
plot.x_range = xrange1
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
|
|
|
|
if yparam1 == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
|
|
if yparam2 != 'None':
|
|
yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2])
|
|
plot.extra_y_ranges["yax2"] = yrange2
|
|
#= {"yax2": yrange2}
|
|
|
|
if plottype=='line':
|
|
plot.line('x1','y2',color="red",y_range_name="yax2",
|
|
source=source)
|
|
|
|
elif plottype=='scatter':
|
|
plot.scatter('x1','y2',source=source,
|
|
fill_alpha=0.4,
|
|
line_color=None,color="red",y_range_name="yax2")
|
|
|
|
plot.add_layout(LinearAxis(y_range_name="yax2",
|
|
axis_label=axlabels[yparam2],
|
|
major_label_text_font_size="7pt",
|
|
axis_label_text_font_size="7pt",
|
|
),'right',
|
|
)
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
|
|
def interactive_bar_chart(id=0,promember=0):
|
|
# check if valid ID exists (workout exists)
|
|
rowdata,row = dataprep.getrowdata_db(id=id)
|
|
rowdata.dropna(axis=1,how='all',inplace=True)
|
|
rowdata.dropna(axis=0,how='any',inplace=True)
|
|
|
|
if rowdata.empty:
|
|
return "","No Valid Data Available"
|
|
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
source = ColumnDataSource(
|
|
rowdata
|
|
)
|
|
|
|
plot = Figure(x_axis_type="datetime",y_axis_type="datetime",
|
|
toolbar_sticky=False,
|
|
plot_width=920,
|
|
tools=TOOLS)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.image_url([watermarkurl],0.01,0.99,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor='top_left',
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.title.text = row.name
|
|
plot.title.text_font_size=value("1.0em")
|
|
plot.xaxis.axis_label = "Time"
|
|
plot.yaxis.axis_label = "Pace (/500m)"
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours ="",
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = "",
|
|
seconds = ["%S"],
|
|
minutes = ["%M"],
|
|
)
|
|
ymax = 1.0e3*90
|
|
ymin = 1.0e3*180
|
|
|
|
if row.workouttype == 'water':
|
|
ymax = 1.0e3*90
|
|
ymin = 1.0e3*210
|
|
|
|
plot.y_range = Range1d(ymin,ymax)
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Time','@ftime'),
|
|
('Pace','@fpace'),
|
|
('HR','@hr{int}'),
|
|
('SPM','@spm{1.1}'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
plot.extra_y_ranges["hr"] = Range1d(start=100,end=200)
|
|
plot.quad(left='time',top='hr_ut2',bottom='hr_bottom',
|
|
right='x_right',source=source,color="gray",
|
|
y_range_name="hr", legend_label="<UT2")
|
|
plot.quad(left='time',top='hr_ut1',bottom='hr_bottom',
|
|
right='x_right',source=source,color="tan",
|
|
y_range_name="hr", legend_label="UT2")
|
|
plot.quad(left='time',top='hr_at',bottom='hr_bottom',
|
|
right='x_right',source=source,color="green",
|
|
y_range_name="hr", legend_label="UT1")
|
|
plot.quad(left='time',top='hr_tr',bottom='hr_bottom',
|
|
right='x_right',source=source,color="blue",
|
|
y_range_name="hr", legend_label="AT")
|
|
plot.quad(left='time',top='hr_an',bottom='hr_bottom',
|
|
right='x_right',source=source,color="violet",
|
|
y_range_name="hr", legend_label="TR")
|
|
plot.quad(left='time',top='hr_max',bottom='hr_bottom',
|
|
right='x_right',source=source,color="red",
|
|
y_range_name="hr", legend_label="AN")
|
|
|
|
|
|
plot.add_layout(LinearAxis(y_range_name="hr",axis_label="HR"),'right')
|
|
|
|
plot.line('time','pace',source=source,legend_label="Pace",color="black")
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line',
|
|
promember=0,workstrokesonly=True,
|
|
labeldict=None,startenddict={}):
|
|
|
|
message = ''
|
|
errormessage = ''
|
|
|
|
columns = [xparam,yparam,
|
|
'ftime','distance','fpace',
|
|
'power','hr','spm',
|
|
'time','pace','workoutstate',
|
|
'workoutid']
|
|
|
|
compute = False
|
|
doclean = False
|
|
if workstrokesonly:
|
|
compute = True
|
|
doclean = True
|
|
datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=doclean,compute=compute,
|
|
workstrokesonly=workstrokesonly)
|
|
datadf.dropna(axis=1,how='all',inplace=True)
|
|
datadf.dropna(axis=0,how='any',inplace=True)
|
|
|
|
|
|
nrworkouts = len(ids)
|
|
|
|
try:
|
|
tseconds = datadf.loc[:,'time']
|
|
except KeyError:
|
|
try:
|
|
tseconds = datadf.loc[:,xparam]
|
|
except:
|
|
return ['','<p>A chart data error occurred</p>','','A chart data error occurred']
|
|
|
|
yparamname = axlabels[yparam]
|
|
|
|
#datadf = datadf[datadf[yparam] > 0]
|
|
|
|
#datadf = datadf[datadf[xparam] > 0]
|
|
|
|
# check if dataframe not empty
|
|
if datadf.empty:
|
|
return ['','<p>No non-zero data in selection</p>','','No non-zero data in selection']
|
|
|
|
if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist':
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
elif xparam == 'time' and not startenddict:
|
|
xaxmax = tseconds.max()
|
|
xaxmin = tseconds.min()
|
|
elif xparam == 'time' and startenddict:
|
|
deltas = [pair[1]-pair[0] for key,pair in startenddict.items()]
|
|
xaxmin = 0
|
|
xaxmax = pd.Series(deltas).max()*1000.
|
|
if xaxmax == 0:
|
|
xaxmax = tseconds.max()
|
|
else:
|
|
xaxmax = datadf['distance'].max()
|
|
xaxmin = datadf['distance'].min()
|
|
|
|
if yparam == 'distance':
|
|
yaxmin = datadf['distance'].min()
|
|
yaxmax = datadf['distance'].max()
|
|
elif yparam == 'cumdist':
|
|
yaxmin = datadf['cumdist'].min()
|
|
yaxmax = datadf['cumdist'].max()
|
|
else:
|
|
yaxmin = yaxminima[yparam]
|
|
yaxmax = yaxmaxima[yparam]
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
|
|
if yparam == 'pace':
|
|
y_axis_type = 'datetime'
|
|
yaxmax = 90.*1e3
|
|
yaxmin = 150.*1e3
|
|
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
|
|
if xparam != 'time':
|
|
xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.
|
|
else:
|
|
xvals = np.arange(100)
|
|
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
tools=TOOLS,
|
|
toolbar_location="above",
|
|
plot_width=920,plot_height=500,
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
|
|
plot.image_url([watermarkurl],0.05,0.9,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor='top_left',
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
colors = itertools.cycle(palette)
|
|
|
|
cntr = 0
|
|
l1 = []
|
|
|
|
try:
|
|
items = itertools.izip(ids,colors)
|
|
except AttributeError:
|
|
items = zip(ids,colors)
|
|
|
|
for id,color in items:
|
|
group = datadf[datadf['workoutid']==int(id)].copy()
|
|
try:
|
|
startsecond,endsecond = startenddict[id]
|
|
except KeyError:
|
|
startsecond = 0
|
|
endsecond = 0
|
|
|
|
group.sort_values(by='time',ascending=True,inplace=True)
|
|
|
|
if endsecond > 0:
|
|
group['time'] = group['time'] - 1.e3*startsecond
|
|
mask = group['time'] < 0
|
|
group.mask(mask,inplace=True)
|
|
mask = group['time'] > 1.e3*(endsecond-startsecond)
|
|
group.mask(mask,inplace=True)
|
|
|
|
if xparam == 'cumdist':
|
|
group['cumdist'] = group['cumdist'] - group['cumdist'].min()
|
|
res = make_cumvalues(group[xparam])
|
|
group[xparam] = res[0]
|
|
elif xparam == 'distance':
|
|
group['distance'] = group['distance'] - group['distance'].min()
|
|
|
|
|
|
try:
|
|
group['x'] = group[xparam]
|
|
except KeyError:
|
|
group['x'] = group['time']
|
|
errormessage = xparam+' has no values. Plot invalid'
|
|
try:
|
|
group['y'] = group[yparam]
|
|
except KeyError:
|
|
group['y'] = 0.0*group['x']
|
|
|
|
|
|
ymean = group['y'].mean()
|
|
f = group['time'].diff().mean()
|
|
if f != 0 and not np.isnan(f):
|
|
windowsize = 2* (int(20000./(f))) + 1
|
|
else:
|
|
windowsize = 1
|
|
|
|
if windowsize > 3 and windowsize < len(group['y']):
|
|
try:
|
|
group['y'] = savgol_filter(group['y'],windowsize,3)
|
|
except ValueError:
|
|
pass
|
|
|
|
ylabel = Label(x=100,y=60+nrworkouts*20-20*cntr,
|
|
x_units='screen',y_units='screen',
|
|
text=axlabels[yparam]+": {ymean:6.2f}".format(ymean=ymean),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color=color,
|
|
)
|
|
if yparam != 'time' and yparam != 'pace':
|
|
plot.add_layout(ylabel)
|
|
|
|
source = ColumnDataSource(
|
|
group
|
|
)
|
|
|
|
TIPS = OrderedDict([
|
|
('time','@ftime'),
|
|
('pace','@fpace'),
|
|
('hr','@hr'),
|
|
('spm','@spm{1.1}'),
|
|
('distance','@distance{5}'),
|
|
])
|
|
|
|
hover = plot.select(type=HoverTool)
|
|
hover.tooltips = TIPS
|
|
|
|
if labeldict:
|
|
try:
|
|
legend_label=labeldict[id]
|
|
except KeyError:
|
|
legend = str(id)
|
|
else:
|
|
legend_label=str(id)
|
|
|
|
if plottype=='line':
|
|
l1.append(plot.line('x','y',source=source,color=color,legend_label=legend_label,line_width=2))
|
|
else:
|
|
l1.append(plot.scatter('x','y',source=source,color=color,legend_label=legend_label,
|
|
fill_alpha=0.4,line_color=None))
|
|
|
|
plot.add_tools(HoverTool(renderers=[l1[cntr]],tooltips=TIPS))
|
|
cntr += 1
|
|
|
|
plot.legend.location='top_right'
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
plot.yaxis.axis_label = axlabels[yparam]
|
|
|
|
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
|
|
xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam])
|
|
plot.x_range = xrange1
|
|
|
|
yrange1 = Range1d(start=yaxmin,end=yaxmax)
|
|
plot.y_range = yrange1
|
|
|
|
if xparam == 'time':
|
|
xrange1 = Range1d(start=xaxmin,end=xaxmax)
|
|
plot.x_range = xrange1
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
|
|
|
|
if yparam == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div,message,errormessage]
|
|
|
|
|
|
|
|
def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm',
|
|
promember=0,plottype='line'):
|
|
|
|
|
|
|
|
|
|
columns = [xparam,yparam,
|
|
'ftime','distance','fpace',
|
|
'power','hr','spm',
|
|
'time','pace','workoutstate']
|
|
|
|
# check if valid ID exists (workout exists)
|
|
#rowdata1,row1 = dataprep.getrowdata_db(id=id1)
|
|
#rowdata2,row2 = dataprep.getrowdata_db(id=id2)
|
|
|
|
rowdata1 = dataprep.getsmallrowdata_db(columns,ids=[id1])
|
|
rowdata2 = dataprep.getsmallrowdata_db(columns,ids=[id2])
|
|
for n in ['distance','power','hr','spm','time','pace','workoutstate']:
|
|
try:
|
|
rowdata1[n].fillna(value=0,inplace=True)
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
rowdata2[n].fillna(value=0,inplace=True)
|
|
except KeyError:
|
|
pass
|
|
|
|
rowdata1.dropna(axis=1,how='all',inplace=True)
|
|
rowdata1.dropna(axis=0,how='any',inplace=True)
|
|
rowdata2.dropna(axis=1,how='all',inplace=True)
|
|
rowdata2.dropna(axis=0,how='any',inplace=True)
|
|
|
|
row1 = Workout.objects.get(id=id1)
|
|
row2 = Workout.objects.get(id=id2)
|
|
|
|
if rowdata1.empty:
|
|
return "","No Valid Data Available"
|
|
# else:
|
|
# rowdata1.sort_values(by='time',ascending=True,inplace=True)
|
|
|
|
if rowdata2.empty:
|
|
return "","No Valid Data Available"
|
|
# else:
|
|
# rowdata2.sort_values(by='time',ascending=True,inplace=True)
|
|
|
|
try:
|
|
x1 = rowdata1.loc[:,xparam]
|
|
x2 = rowdata2.loc[:,xparam]
|
|
|
|
y1 = rowdata1.loc[:,yparam]
|
|
y2 = rowdata2.loc[:,yparam]
|
|
except KeyError:
|
|
return "","No valid Data Available"
|
|
|
|
x_axis_type = 'linear'
|
|
y_axis_type = 'linear'
|
|
if xparam == 'time':
|
|
x_axis_type = 'datetime'
|
|
|
|
if yparam == 'pace':
|
|
y_axis_type = 'datetime'
|
|
ymax = 1.0e3*90
|
|
ymin = 1.0e3*180
|
|
|
|
if row1.workouttype == 'water':
|
|
ymax = 1.0e3*90
|
|
ymin = 1.0e3*210
|
|
|
|
ftime1 = rowdata1.loc[:,'ftime']
|
|
ftime2 = rowdata2.loc[:,'ftime']
|
|
|
|
hr1 = rowdata1.loc[:,'hr']
|
|
hr2 = rowdata2.loc[:,'hr']
|
|
|
|
|
|
fpace1 = rowdata1.loc[:,'fpace']
|
|
fpace2 = rowdata2.loc[:,'fpace']
|
|
|
|
distance1 = rowdata1.loc[:,'distance']
|
|
distance2 = rowdata2.loc[:,'distance']
|
|
|
|
spm1 = rowdata1.loc[:,'spm']
|
|
spm2 = rowdata2.loc[:,'spm']
|
|
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
|
|
data1 = pd.DataFrame(
|
|
dict(
|
|
x1=x1,
|
|
y1=y1,
|
|
ftime1=ftime1,
|
|
fpace1=fpace1,
|
|
hr1 = hr1,
|
|
spm1 = spm1,
|
|
distance1=distance1,
|
|
)
|
|
).dropna()
|
|
|
|
data2 = pd.DataFrame(
|
|
dict(
|
|
x2=x2,
|
|
y2=y2,
|
|
ftime2=ftime2,
|
|
fpace2=fpace2,
|
|
hr2 = hr2,
|
|
spm2 = spm2,
|
|
distance2=distance2,
|
|
)
|
|
).dropna()
|
|
|
|
|
|
|
|
source1 = ColumnDataSource(
|
|
data1
|
|
)
|
|
|
|
source2 = ColumnDataSource(
|
|
data2
|
|
)
|
|
|
|
ymean1 = data1['y1'].mean()
|
|
ymean2 = data2['y2'].mean()
|
|
|
|
# create interactive plot
|
|
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
|
|
tools=TOOLS,
|
|
plot_width=920,
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.image_url([watermarkurl],0.05,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor='bottom_left',
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
TIPS = OrderedDict([
|
|
('time','@ftime1'),
|
|
('pace','@fpace1'),
|
|
('hr','@hr1'),
|
|
('spm','@spm1{1.1}'),
|
|
('distance','@distance1{5}'),
|
|
])
|
|
TIPS2 = OrderedDict([
|
|
('time','@ftime2'),
|
|
('pace','@fpace2'),
|
|
('hr','@hr2'),
|
|
('spm','@spm2{1.1}'),
|
|
('distance','@distance2{5}'),
|
|
])
|
|
|
|
|
|
|
|
hover1 = plot.select(type=HoverTool)
|
|
hover1.tooltips = TIPS
|
|
hover2 = plot.select(type=HoverTool)
|
|
hover2.tooltips = TIPS2
|
|
|
|
|
|
if plottype=='line':
|
|
l1 = plot.line('x1','y1',source=source1,
|
|
color="blue",legend_label=row1.name,
|
|
)
|
|
l2 = plot.line('x2','y2',source=source2,
|
|
color="red",legend_label=row2.name,
|
|
)
|
|
elif plottype=='scatter':
|
|
l1 = plot.scatter('x1','y1',source=source1,legend_label=row1.name,
|
|
fill_alpha=0.4,
|
|
line_color=None)
|
|
l2 = plot.scatter('x2','y2',source=source2,legend_label=row2.name,
|
|
fill_alpha=0.4,
|
|
line_color=None,color="red")
|
|
|
|
plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS))
|
|
plot.add_tools(HoverTool(renderers=[l2],tooltips=TIPS2))
|
|
plot.legend.location = "bottom_right"
|
|
|
|
plot.title.text = row1.name+' vs '+row2.name
|
|
plot.title.text_font_size=value("1.2em")
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
plot.yaxis.axis_label = axlabels[yparam]
|
|
|
|
ylabel1 = Label(x=100,y=90,x_units='screen',y_units='screen',
|
|
text=axlabels[yparam]+": {ymean1:6.2f}".format(
|
|
ymean1=ymean1
|
|
),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='blue'
|
|
)
|
|
ylabel2 = Label(x=100,y=110,x_units='screen',y_units='screen',
|
|
text=axlabels[yparam]+": {ymean2:6.2f}".format(
|
|
ymean2=ymean2
|
|
),
|
|
background_fill_alpha=.7,
|
|
background_fill_color='white',
|
|
text_color='red'
|
|
)
|
|
plot.add_layout(ylabel1)
|
|
plot.add_layout(ylabel2)
|
|
|
|
if xparam == 'time':
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
|
|
|
|
if yparam == 'pace':
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
plot.y_range = Range1d(ymin,ymax)
|
|
|
|
|
|
script, div = components(plot)
|
|
|
|
return [script,div]
|
|
|
|
def interactive_otw_advanced_pace_chart(id=0,promember=0):
|
|
# check if valid ID exists (workout exists)
|
|
rowdata,row = dataprep.getrowdata_db(id=id)
|
|
rowdata.dropna(axis=1,how='all',inplace=True)
|
|
rowdata.dropna(axis=0,how='any',inplace=True)
|
|
|
|
if rowdata.empty:
|
|
return "","No Valid Data Available"
|
|
|
|
# Add hover to this comma-separated string and see what changes
|
|
if (promember==1):
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
else:
|
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
source = ColumnDataSource(
|
|
rowdata
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
plot = Figure(x_axis_type="datetime",y_axis_type="datetime",
|
|
tools=TOOLS,
|
|
plot_width=920,
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarksource = ColumnDataSource(dict(
|
|
url = [watermarkurl],))
|
|
|
|
watermarkrange = Range1d(start=0,end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkx = 0.99
|
|
watermarky = 0.01
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
watermarkanchor = 'bottom_right'
|
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
|
plot.extra_x_ranges = {"watermark": watermarkrange}
|
|
plot.sizing_mode = 'scale_both'
|
|
|
|
|
|
plot.image_url([watermarkurl],watermarkx,watermarky,
|
|
watermarkw,watermarkh,
|
|
global_alpha=watermarkalpha,
|
|
w_units='screen',
|
|
h_units='screen',
|
|
anchor=watermarkanchor,
|
|
dilate=True,
|
|
x_range_name = "watermark",
|
|
y_range_name = "watermark",
|
|
)
|
|
|
|
plot.title.text = row.name
|
|
plot.title.text_font_size=value("1.2em")
|
|
plot.xaxis.axis_label = "Time"
|
|
plot.yaxis.axis_label = "Pace (/500m)"
|
|
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
|
hours = ["%H"],
|
|
minutes = ["%M"],
|
|
seconds = ["%S"],
|
|
days = ["0"],
|
|
months = [""],
|
|
years = [""]
|
|
)
|
|
plot.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds = ["%S"],
|
|
minutes = ["%M"]
|
|
)
|
|
|
|
ymax = 1.0e3*90
|
|
ymin = 1.0e3*210
|
|
|
|
plot.y_range = Range1d(ymin,ymax)
|
|
|
|
|
|
hover = plot.select(dict(type=HoverTool))
|
|
|
|
plot.line('time','pace',source=source,legend_label="Pace",color="black")
|
|
plot.line('time','nowindpace',source=source,legend_label="Corrected Pace",color="red")
|
|
|
|
hover.tooltips = OrderedDict([
|
|
('Time','@ftime'),
|
|
('Pace','@fpace'),
|
|
('Corrected Pace','@fnowindpace'),
|
|
('HR','@hr{int}'),
|
|
('SPM','@spm{1.1}'),
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
try:
|
|
script, div = components(plot)
|
|
except:
|
|
script = ''
|
|
div = ''
|
|
|
|
return [script,div]
|