6974 lines
217 KiB
Python
6974 lines
217 KiB
Python
from rowers.metrics import axes, axlabels, yaxminima, yaxmaxima, get_yaxminima, get_yaxmaxima
|
|
from rowers.dataprep import nicepaceformat, niceformat, strfdelta
|
|
from rowers.datautils import p0, rpetotss
|
|
from rowers.metrics import rowingmetrics, metricsdicts
|
|
from scipy.spatial import ConvexHull, Delaunay
|
|
from scipy.stats import linregress, percentileofscore
|
|
from pytz import timezone as tz, utc
|
|
from rowers.models import course_spline, VirtualRaceResult
|
|
from bokeh.palettes import Category20c, Category10
|
|
from bokeh.layouts import layout, widgetbox
|
|
from bokeh.resources import CDN, INLINE
|
|
from math import pi
|
|
from rowers.dataprep import timedeltaconv
|
|
from pandas.core.groupby.groupby import DataError
|
|
import rowers.datautils as datautils
|
|
from rowers.utils import lbstoN
|
|
import rowers.c2stuff as c2stuff
|
|
import rowers.metrics as metrics
|
|
import rowers.dataprep as dataprep
|
|
from rowers.dataprep import rdata
|
|
import rowers.stravastuff as stravastuff
|
|
from scipy.interpolate import griddata
|
|
from scipy.signal import savgol_filter
|
|
from scipy import optimize
|
|
from django.utils.timezone import activate
|
|
from django.utils.timezone import get_current_timezone
|
|
from holoviews import opts
|
|
import holoviews as hv
|
|
import pandas as pd
|
|
import numpy as np
|
|
import math
|
|
import datetime
|
|
from rowers import mytypes
|
|
from rowers.courses import (
|
|
course_coord_center, course_coord_maxmin,
|
|
polygon_coord_center
|
|
)
|
|
from django.conf import settings
|
|
from collections import OrderedDict
|
|
from bokeh.core.properties import value
|
|
from rowers.opaque import encoder
|
|
from bokeh.models import OpenURL, TapTool
|
|
from bokeh.models.glyphs import ImageURL
|
|
from bokeh.transform import cumsum
|
|
from bokeh.models import (
|
|
LinearAxis, LogAxis, Range1d, DatetimeTickFormatter, HoverTool, Axis, PrintfTickFormatter
|
|
)
|
|
from bokeh.layouts import column as layoutcolumn
|
|
from bokeh.layouts import row as layoutrow
|
|
from bokeh.embed import components
|
|
|
|
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.io import output_file, show, vplot
|
|
#from bokeh.models.widgets import Slider, Select, TextInput
|
|
|
|
|
|
activate(settings.TIME_ZONE)
|
|
thetimezone = get_current_timezone()
|
|
|
|
|
|
def workoutname(id):
|
|
try:
|
|
w = Workout.objects.get(id=id)
|
|
except Workout.DoesNotExist:
|
|
return ''
|
|
|
|
return str(w)
|
|
|
|
|
|
def all_goldmedalstandards(workouts, startdate, enddate):
|
|
dates = []
|
|
testpowers = []
|
|
testduration = []
|
|
ids = []
|
|
|
|
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)
|
|
ids.append(w.id)
|
|
|
|
return dates, testpowers, testduration, ids
|
|
|
|
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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
|
|
|
|
|
|
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
|
|
|
|
hrzones = rower.hrzones
|
|
|
|
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}'.format(ut2=hrzones[1]): frac_lut2,
|
|
'{ut2}'.format(ut2=hrzones[1]): frac_ut2,
|
|
'{ut1}'.format(ut1=hrzones[2]): frac_ut1,
|
|
'{at}'.format(at=hrzones[3]): frac_at,
|
|
'{tr}'.format(tr=hrzones[4]): frac_tr,
|
|
'{an}'.format(an=hrzones[5]): 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}'.format(ut2=hrzones[1]),
|
|
'{ut2}'.format(ut2=hrzones[1]),
|
|
'{ut1}'.format(ut1=hrzones[2]),
|
|
'{at}'.format(at=hrzones[3]),
|
|
'{tr}'.format(tr=hrzones[4]),
|
|
'{an}'.format(an=hrzones[5])
|
|
]
|
|
|
|
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: # pragma: no cover
|
|
return mytypes.colors[-1]
|
|
|
|
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return '', 'It looks like there are no data matching your filter'
|
|
|
|
columns = datadf.columns
|
|
|
|
if not fieldname in columns: # pragma: no cover
|
|
return '', 'It looks like there are no data matching your filter'
|
|
|
|
if not 'date' in columns: # pragma: no cover
|
|
return '', 'Not enough data'
|
|
|
|
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: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
yaxmaximum = data['planned'].max()
|
|
|
|
if yaxmaximum == 0: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
rscore = w.hrtss
|
|
|
|
if totaldays < 30:
|
|
dates.append(dd)
|
|
dates_sorting.append(dd2)
|
|
else: # pragma: no cover
|
|
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: # pragma: no cover
|
|
rowers.append(str(w.user))
|
|
|
|
try:
|
|
d = utc.localize(startdate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
d = startdate
|
|
|
|
try:
|
|
enddate = utc.localize(enddate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
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: # pragma: no cover
|
|
dates.append(d.strftime('%Y/%m'))
|
|
dates_sorting.append(d.strftime('%Y/%m'))
|
|
durations.append(0)
|
|
rscores.append(0)
|
|
trimps.append(0)
|
|
links.append('')
|
|
try:
|
|
types.append(types[0])
|
|
except IndexError:
|
|
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 = []
|
|
distances = []
|
|
|
|
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: # pragma: no cover
|
|
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
|
|
distance = w.distance
|
|
if rscore == 0: # pragma: no cover
|
|
rscore = w.hrtss
|
|
|
|
if totaldays <= 30: # pragma: no cover
|
|
dates.append(dd)
|
|
dates_sorting.append(dd2)
|
|
else:
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
durations.append(du)
|
|
trimps.append(trimp)
|
|
rscores.append(rscore)
|
|
distances.append(distance)
|
|
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: # pragma: no cover
|
|
rowers.append(str(w.user))
|
|
|
|
try:
|
|
d = utc.localize(startdate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
d = startdate
|
|
|
|
try:
|
|
enddate = utc.localize(enddate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
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)
|
|
distances.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,
|
|
'distance': distances,
|
|
'link': links,
|
|
}
|
|
|
|
dim_expr = hv.dim('type').categorize(mytypes.color_map)
|
|
|
|
df = pd.DataFrame(thedict)
|
|
|
|
if totaldays > 30 and yaxis == 'duration': # pragma: no cover
|
|
df['duration'] = df['duration']/60
|
|
elif yaxis == 'TRIMP':
|
|
df.drop('duration', inplace=True, axis='columns')
|
|
df.drop('rscore', inplace=True, axis='columns')
|
|
df.drop('distance', inplace=True, axis='columns')
|
|
elif yaxis == 'rScore': # pragma: no cover
|
|
df.drop('duration', inplace=True, axis='columns')
|
|
df.drop('trimp', inplace=True, axis='columns')
|
|
df.drop('distance', inplace=True, axis='columns')
|
|
elif yaxis == 'distance': # pragma: no cover
|
|
df.drop('duration', inplace=True, axis='columns')
|
|
df.drop('trimp', inplace=True, axis='columns')
|
|
df.drop('rscore', inplace=True, axis='columns')
|
|
|
|
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')])
|
|
|
|
types_order = mytypes.workouttypes_ordered
|
|
# bars=table.to.bars(['date',stack],[yaxis])
|
|
bars = hv.Bars(df, kdims=['date', stack]).aggregate(
|
|
function=np.sum).redim.values(types=types_order)
|
|
|
|
# print(mytypes.color_map)
|
|
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.xaxis.axis_label = 'Period'
|
|
if yaxis == 'duration':
|
|
p.yaxis.axis_label = 'Duration (min)'
|
|
if totaldays > 30: # pragma: no cover
|
|
p.yaxis.axis_label = 'Duration (h)'
|
|
elif yaxis == 'TRIMP':
|
|
p.yaxis.axis_label = 'TRIMP'
|
|
elif yaxis == 'distance': # pragma: no cover
|
|
p.yaxis.axis_label = 'Distance (m)'
|
|
else: # pragma: no cover
|
|
p.yaxis.axis_label = 'rScore'
|
|
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
peakforceav = 0
|
|
peakforce25 = 0
|
|
peakforce75 = 0
|
|
peakforce05 = 0
|
|
peakforce95 = 0
|
|
|
|
try:
|
|
averageforceav = rowdata['averageforce'].median()
|
|
except KeyError: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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
|
|
|
|
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 += getattr(w, metricchoice)
|
|
if getattr(w, metricchoice) <= 0:
|
|
if metricchoice == 'rscore' and w.hrtss > 0: # pragma: no cover
|
|
weight += w.hrtss
|
|
else:
|
|
trimp, hrtss = dataprep.workout_trimp(w)
|
|
rscore, normp = dataprep.workout_rscore(w)
|
|
if w.rpe and w.rpe > 0: # pragma: no cover
|
|
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
|
|
dd = dd/3600
|
|
weight += 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'
|
|
|
|
# marker workouts
|
|
workouts = Workout.objects.filter(user=user.rower, date__gte=startdate,
|
|
date__lte=enddate,
|
|
workouttype__in=mytypes.rowtypes,
|
|
duplicate=False).order_by('date')
|
|
|
|
markerworkouts = workouts.filter(rankingpiece=True)
|
|
outids = [w.id for w in markerworkouts]
|
|
dates = [arrow.get(w.date).datetime for w in markerworkouts]
|
|
testpower = [
|
|
w.goldmedalstandard if w.rankingpiece else np.nan for w in markerworkouts]
|
|
|
|
testduration = [
|
|
w.goldmedalseconds if w.rankingpiece else 0 for w in markerworkouts]
|
|
|
|
df = pd.DataFrame({
|
|
'id': outids,
|
|
'date': dates,
|
|
'testpower': testpower,
|
|
'testduration': testduration,
|
|
})
|
|
df.sort_values(['date'], inplace=True)
|
|
|
|
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, allids = all_goldmedalstandards(
|
|
workouts, startdate, enddate)
|
|
|
|
nrdays = (enddate-startdate).days
|
|
|
|
td = []
|
|
markerscore = []
|
|
score = []
|
|
markerduration = []
|
|
duration = []
|
|
workoutid = []
|
|
|
|
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(testpower[i])
|
|
duration.append(testduration[i])
|
|
workoutid.append(id)
|
|
|
|
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])
|
|
workoutid.append(allids[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)
|
|
workoutid.append(0)
|
|
|
|
df = pd.DataFrame({
|
|
'markerscore': markerscore,
|
|
'markerduration': markerduration,
|
|
'score': score,
|
|
'duration': duration,
|
|
'date': td,
|
|
'id': workoutid,
|
|
})
|
|
|
|
df['url'] = df['id'].apply(lambda x: settings.SITE_URL +
|
|
'/rowers/workout/{id}/'.format(id=encoder.encode_hex(x)))
|
|
df['workout'] = df['id'].apply(lambda x: workoutname(x))
|
|
|
|
df.sort_values(['date'], inplace=True)
|
|
|
|
# find index values where score is max
|
|
idx = df.groupby(['date'])['score'].transform(max) == df['score']
|
|
df = df[idx]
|
|
|
|
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')),
|
|
url=df['url'],
|
|
workout=df['workout']
|
|
)
|
|
)
|
|
|
|
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'),
|
|
('Workout', '@workout')
|
|
])
|
|
|
|
taptool = plot.select(type=TapTool)
|
|
taptool.callback = OpenURL(url='@url')
|
|
|
|
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 = []
|
|
|
|
workouts = Workout.objects.filter(user=user.rower, date__gte=startdate-datetime.timedelta(days=90),
|
|
date__lte=enddate,
|
|
workouttype__in=mytypes.rowtypes,
|
|
duplicate=False).order_by('date')
|
|
|
|
markerworkouts = workouts.filter(rankingpiece=True)
|
|
outids = [w.id for w in markerworkouts]
|
|
dates = [arrow.get(w.date).datetime for w in workouts]
|
|
testpower = [
|
|
w.goldmedalstandard if w.rankingpiece else np.nan for w in workouts]
|
|
impulses = [np.nan for w in workouts]
|
|
testduration = [
|
|
w.goldmedalseconds if w.rankingpiece else 0 for w in workouts]
|
|
fitnesses = [np.nan for w in workouts]
|
|
fatigues = [np.nan for w in workouts]
|
|
|
|
fatigues, fitnesses, dates, testpower, testduration, impulses = getfatigues(fatigues,
|
|
fitnesses,
|
|
dates,
|
|
testpower, testduration,
|
|
impulses,
|
|
startdate -
|
|
datetime.timedelta(
|
|
days=90),
|
|
enddate,
|
|
user, metricchoice,
|
|
kfatigue, kfitness)
|
|
|
|
df = pd.DataFrame({
|
|
'date': dates,
|
|
'testpower': testpower,
|
|
'testduration': testduration,
|
|
'fatigue': fatigues,
|
|
'fitness': fitnesses,
|
|
'impulse': impulses,
|
|
})
|
|
|
|
endfitness = fitnesses[-2]
|
|
endfatigue = fatigues[-2]
|
|
endform = endfitness-endfatigue
|
|
|
|
if modelchoice == 'banister': # pragma: no cover
|
|
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
|
|
mask = df['date'] > np.datetime64(startdate.astimezone(
|
|
tz=datetime.timezone.utc).replace(tzinfo=None))
|
|
df = df.loc[mask]
|
|
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
plot.line('date', 'fatigue', source=source, color='red',
|
|
legend_label=fatiguelabel)
|
|
if doform: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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 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: # pragma: no cover
|
|
return "", "No valid data available"
|
|
|
|
# throw out nans
|
|
histopwr = histopwr[~np.isinf(histopwr)]
|
|
if histoparam == 'catch': # pragma: no cover
|
|
histopwr = histopwr[histopwr < yaxminima[histoparam]]
|
|
histopwr = histopwr[histopwr > yaxmaxima[histoparam]]
|
|
else:
|
|
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: # pragma: no cover
|
|
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 get_map_script_course(
|
|
latmean,
|
|
lonmean,
|
|
latbegin,
|
|
latend,
|
|
longbegin,
|
|
longend,
|
|
scoordinates,
|
|
course,
|
|
): # pragma: no cover
|
|
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: [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 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}
|
|
|
|
|
|
|
|
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,
|
|
pcoordinates=pcoordinates,
|
|
plabels=plabels
|
|
)
|
|
|
|
return script
|
|
|
|
|
|
def get_map_script(
|
|
latmean,
|
|
lonmean,
|
|
latbegin,
|
|
latend,
|
|
longbegin,
|
|
longend,
|
|
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,
|
|
)
|
|
|
|
return script
|
|
|
|
|
|
def leaflet_chart(lat, lon, name="", raceresult=0):
|
|
if lat.empty or lon.empty: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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 += "]"
|
|
|
|
if raceresult == 0:
|
|
script = get_map_script(
|
|
latmean,
|
|
lonmean,
|
|
latbegin,
|
|
latend,
|
|
longbegin,
|
|
longend,
|
|
scoordinates,
|
|
)
|
|
else: # pragma: no cover
|
|
record = VirtualRaceResult.objects.get(id=raceresult)
|
|
course = record.course
|
|
script = get_map_script_course(
|
|
latmean,
|
|
lonmean,
|
|
latbegin,
|
|
latend,
|
|
longbegin,
|
|
longend,
|
|
scoordinates,
|
|
course,
|
|
)
|
|
|
|
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): # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
startsecond = 0
|
|
endsecond = 0
|
|
|
|
try:
|
|
label = labeldict[id]
|
|
except KeyError: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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): # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
pass
|
|
|
|
flduration = []
|
|
flpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='female',
|
|
distance=distance,
|
|
weightcategory='lwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try: # pragma: no cover
|
|
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: # pragma: no cover
|
|
pass
|
|
|
|
mlduration = []
|
|
mlpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
distance=distance,
|
|
weightcategory='lwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try: # pragma: no cover
|
|
duration = distance/velo
|
|
mlduration.append(duration)
|
|
mlpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
mlduration.append(duration)
|
|
mlpower.append(np.nan)
|
|
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: # pragma: no cover
|
|
mlduration.append(60.*duration)
|
|
mlpower.append(np.nan)
|
|
|
|
mhduration = []
|
|
mhpower = []
|
|
|
|
for distance in distances:
|
|
worldclasspower = c2stuff.getagegrouprecord(
|
|
age,
|
|
sex='male',
|
|
distance=distance,
|
|
weightcategory='hwt'
|
|
)
|
|
velo = (worldclasspower/2.8)**(1./3.)
|
|
try: # pragma: no cover
|
|
duration = distance/velo
|
|
mhduration.append(duration)
|
|
mhpower.append(worldclasspower)
|
|
except ZeroDivisionError:
|
|
mhduration.append(duration)
|
|
mhpower.append(np.nan)
|
|
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: # pragma: no cover
|
|
mhduration.append(60.*duration)
|
|
mhpower.append(np.nan)
|
|
|
|
def fitfunc(pars, x): return pars[0] / \
|
|
(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
|
|
|
def errfunc(pars, x, y): return 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: # pragma: no cover
|
|
p1fh = None
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(flduration) >= 4:
|
|
p1fl, success = optimize.leastsq(errfunc, p0[:],
|
|
args=(flduration, flpower))
|
|
else: # pragma: no cover
|
|
p1fl = None
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(mlduration) >= 4:
|
|
p1ml, success = optimize.leastsq(errfunc, p0[:],
|
|
args=(mlduration, mlpower))
|
|
else: # pragma: no cover
|
|
p1ml = None
|
|
|
|
if len(mhduration) >= 4:
|
|
p1mh, success = optimize.leastsq(errfunc, p0[:],
|
|
args=(mhduration, mhpower))
|
|
else: # pragma: no cover
|
|
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
|
|
|
|
sourcemh = ColumnDataSource(
|
|
data=dict(
|
|
mhduration=mhduration,
|
|
mhpower=mhpower,
|
|
)
|
|
)
|
|
|
|
sourcefl = ColumnDataSource(
|
|
data=dict(
|
|
flduration=flduration,
|
|
flpower=flpower,
|
|
)
|
|
)
|
|
|
|
sourcefh = ColumnDataSource(
|
|
data=dict(
|
|
fhduration=fhduration,
|
|
fhpower=fhpower,
|
|
)
|
|
)
|
|
|
|
sourceml = ColumnDataSource(
|
|
data=dict(
|
|
mlduration=mlduration,
|
|
mlpower=mlpower,
|
|
)
|
|
)
|
|
|
|
sourcefit = ColumnDataSource(
|
|
data=dict(
|
|
duration=fitt,
|
|
fitpowerfh=fitpowerfh,
|
|
fitpowerfl=fitpowerfl,
|
|
fitpowerml=fitpowerml,
|
|
fitpowermh=fitpowermh,
|
|
)
|
|
)
|
|
|
|
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=sourcefit,
|
|
legend_label='Female HW', color='blue')
|
|
plot.line('duration', 'fitpowerfl', source=sourcefit,
|
|
legend_label='Female LW', color='red')
|
|
|
|
plot.line('duration', 'fitpowerml', source=sourcefit,
|
|
legend_label='Male LW', color='green')
|
|
|
|
plot.line('duration', 'fitpowermh', source=sourcefit,
|
|
legend_label='Male HW', color='orange')
|
|
|
|
plot.circle('flduration', 'flpower', source=sourcefl,
|
|
fill_color='red', size=15)
|
|
|
|
plot.circle('fhduration', 'fhpower', source=sourcefh,
|
|
fill_color='blue', size=15)
|
|
|
|
plot.circle('mlduration', 'mlpower', source=sourceml,
|
|
fill_color='green', size=15)
|
|
|
|
plot.circle('mhduration', 'mhpower', source=sourcemh,
|
|
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',
|
|
wcpower=[], wcdurations=[], cpoverlay=False):
|
|
|
|
powerdf2 = powerdf[~(powerdf == 0).any(axis=1)].copy()
|
|
# plot tools
|
|
if (promember == 1): # pragma: no cover
|
|
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 = powerdf2['Delta'].apply(lambda x: timedeltaconv(x))
|
|
powerdf2['ftime'] = deltas.apply(lambda x: strfdelta(x))
|
|
powerdf2['Deltaminutes'] = powerdf2['Delta']/60.
|
|
|
|
source = ColumnDataSource(
|
|
data=powerdf2
|
|
)
|
|
|
|
# there is no Paul's law for OTW
|
|
|
|
thesecs = powerdf2['Delta']
|
|
theavpower = powerdf2['CP']
|
|
|
|
p1, fitt, fitpower, ratio = datautils.cpfit(powerdf2)
|
|
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': # pragma: no cover
|
|
p1 = [r.ep0, r.ep1, r.ep2, r.ep3]
|
|
ratio = r.ecpratio
|
|
|
|
def fitfunc(pars, x): return 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 = powerdf2['workout']
|
|
urls = powerdf2['url']
|
|
|
|
# add world class
|
|
wcpower = pd.Series(wcpower, dtype='float')
|
|
wcdurations = pd.Series(wcdurations, dtype='float')
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(wcdurations) >= 4: # pragma: no cover
|
|
def fitfunc(pars, x): return pars[0] / \
|
|
(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
|
|
|
def errfunc(pars, x, y): return fitfunc(pars, x)-y
|
|
p1wc, success = optimize.leastsq(errfunc, p0[:],
|
|
args=(wcdurations, wcpower))
|
|
else:
|
|
p1wc = None
|
|
|
|
if p1wc is not None and cpoverlay: # pragma: no cover
|
|
fitpowerwc = 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
|
|
|
|
sourcecomplex = ColumnDataSource(
|
|
data=dict(
|
|
CP=fitpower,
|
|
CPmax=ratio*fitpower,
|
|
duration=fitt/60.,
|
|
ftime=ftime,
|
|
# workout = workouts,
|
|
fitpowerwc=fitpowerwc,
|
|
fitpowerexcellent=fitpowerexcellent,
|
|
fitpowergood=fitpowergood,
|
|
fitpowerfair=fitpowerfair,
|
|
fitpoweraverage=fitpoweraverage,
|
|
# url = urls,
|
|
)
|
|
)
|
|
|
|
sourceannot = ColumnDataSource(
|
|
data=dict(
|
|
workout=workouts,
|
|
url=urls,
|
|
)
|
|
)
|
|
|
|
# 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'),
|
|
('World Class', '@fitpowerwc{int}')
|
|
])
|
|
|
|
hover.mode = 'mouse'
|
|
|
|
taptool = plot.select(type=TapTool)
|
|
taptool.callback = OpenURL(url='@url')
|
|
|
|
plot.line('duration', 'CP', source=sourcecomplex, legend_label="CP Model",
|
|
color='green')
|
|
|
|
plot.line('duration', 'CPmax', source=sourcecomplex, legend_label="CP Model",
|
|
color='red')
|
|
|
|
if p1wc is not None: # pragma: no cover
|
|
plot.line('duration', 'fitpowerwc', source=sourcecomplex,
|
|
legend_label="Gold Medal Standard",
|
|
color='darkgoldenrod', line_dash='dotted')
|
|
|
|
plot.line('duration', 'fitpowerexcellent', source=sourcecomplex,
|
|
legend_label="90% percentile",
|
|
color='goldenrod', line_dash='dotted')
|
|
|
|
plot.line('duration', 'fitpowergood', source=sourcecomplex,
|
|
legend_label="75% percentile",
|
|
color='sandybrown', line_dash='dotted')
|
|
|
|
plot.line('duration', 'fitpowerfair', source=sourcecomplex,
|
|
legend_label="50% percentile",
|
|
color='rosybrown', line_dash='dotted')
|
|
|
|
plot.line('duration', 'fitpoweraverage', source=sourcecomplex,
|
|
legend_label="25% percentile",
|
|
color='tan', line_dash='dotted')
|
|
|
|
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: # pragma: no cover
|
|
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)
|
|
|
|
def fitfunc(pars, x): return 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])))
|
|
|
|
def errfunc(pars, x, y): return 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,
|
|
)
|
|
)
|
|
|
|
sourcefit = ColumnDataSource(
|
|
data=dict(
|
|
age2=age2,
|
|
expo_vals=expo_vals,
|
|
)
|
|
)
|
|
|
|
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', source=sourcefit)
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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),
|
|
)
|
|
)
|
|
|
|
def fitfunc(pars, x): return pars[0] / \
|
|
(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
|
|
|
def errfunc(pars, x, y): return fitfunc(pars, x)-y
|
|
|
|
# p0 = [500,350,10,8000]
|
|
wcpower = pd.Series(wcpower, dtype='float')
|
|
wcdurations = pd.Series(wcdurations, dtype='float')
|
|
|
|
# fitting WC data to three parameter CP model
|
|
if len(wcdurations) >= 4:
|
|
p1wc, success = optimize.leastsq(errfunc, p0[:],
|
|
args=(wcdurations, wcpower))
|
|
else: # pragma: no cover
|
|
p1wc = None
|
|
|
|
# fitting the data to three parameter CP model
|
|
|
|
success = 0
|
|
p1 = p0
|
|
if len(thesecs) >= 4:
|
|
try:
|
|
p1, success = optimize.leastsq(
|
|
errfunc, p0[:], args=(thesecs, theavpower))
|
|
except (RuntimeError, RuntimeWarning): # pragma: no cover
|
|
factor = fitfunc(p0, thesecs.mean())/theavpower.mean()
|
|
p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]]
|
|
success = 0
|
|
else: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
fitpowerwc = 0*fitpower
|
|
fitpowerexcellent = 0*fitpower
|
|
fitpowergood = 0*fitpower
|
|
fitpowerfair = 0*fitpower
|
|
fitpoweraverage = 0*fitpower
|
|
|
|
message = ""
|
|
if len(fitpower[fitpower < 0]) > 0: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return 0
|
|
|
|
try:
|
|
dist = rowdata.df.loc[:, 'cum_dist']
|
|
except KeyError:
|
|
return ['', 'No Data Found']
|
|
|
|
try: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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"
|
|
|
|
try:
|
|
spm = datadf['spm']
|
|
except KeyError: # pragma: no cover
|
|
datadf['spm'] = 0
|
|
|
|
try:
|
|
pace = datadf['pace']
|
|
except KeyError: # pragma: no cover
|
|
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[0, 'time'] = 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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return ['', '<p>No non-zero data in selection</p>']
|
|
|
|
if xparam == 'workoutid': # pragma: no cover
|
|
xparamname = 'Workout'
|
|
else:
|
|
xparamname = axlabels[xparam]
|
|
|
|
if yparam == 'workoutid': # pragma: no cover
|
|
yparamname = 'Workout'
|
|
else:
|
|
yparamname = axlabels[yparam]
|
|
|
|
if groupby == 'workoutid': # pragma: no cover
|
|
groupname = 'Workout'
|
|
elif groupby == 'date': # pragma: no cover
|
|
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': # pragma: no cover
|
|
res = make_cumvalues(datadf[xparam])
|
|
datadf[xparam] = res[0]
|
|
if xparam == 'distance': # pragma: no cover
|
|
xaxmax = datadf[xparam].max()
|
|
xaxmin = datadf[xparam].min()
|
|
elif xparam == 'time': # pragma: no cover
|
|
tseconds = datadf.loc[:, 'time']
|
|
xaxmax = tseconds.max()
|
|
xaxmin = 0
|
|
elif xparam == 'workoutid': # pragma: no cover
|
|
xaxmax = datadf[xparam].max()-5
|
|
xaxmin = datadf[xparam].min()+5
|
|
else:
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
|
|
if yparam == 'distance': # pragma: no cover
|
|
yaxmax = datadf[yparam].max()
|
|
yaxmin = datadf[yparam].min()
|
|
elif yparam == 'time': # pragma: no cover
|
|
tseconds = datadf.loc[:, 'time']
|
|
yaxmax = tseconds.max()
|
|
yaxmin = 0
|
|
elif yparam == 'workoutid': # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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': # pragma: no cover
|
|
plot.xaxis.axis_label = 'Workout'
|
|
else:
|
|
plot.xaxis.axis_label = axlabels[xparam]
|
|
|
|
if yparam == 'workoutid': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
test = datadf['driveenergy'].mean()
|
|
nowork = 0
|
|
except KeyError: # pragma: no cover
|
|
datadf['driveenergy'] = 500.
|
|
|
|
# test if we have power
|
|
nopower = 1
|
|
try: # pragma: no cover
|
|
test = datadf['power'].mean()
|
|
nopower = 0
|
|
except KeyError: # pragma: no cover
|
|
datadf['power'] = 50.
|
|
|
|
yparamname1 = axlabels[yparam1]
|
|
if yparam2 != 'None':
|
|
yparamname2 = axlabels[yparam2]
|
|
|
|
# check if dataframe not empty
|
|
if datadf.empty: # pragma: no cover
|
|
return ['', '<p>No non-zero data in selection</p>', '', '']
|
|
|
|
try:
|
|
datadf['x1'] = datadf.loc[:, xparam]
|
|
except KeyError: # pragma: no cover
|
|
try:
|
|
datadf['x1'] = datadf['distance']
|
|
except KeyError:
|
|
try:
|
|
datadf['x1'] = datadf['time']
|
|
except KeyError: # pragma: no cover
|
|
return ['', '<p>No non-zero data in selection</p>', '', '']
|
|
|
|
try:
|
|
datadf['y1'] = datadf.loc[:, yparam1]
|
|
except KeyError:
|
|
try:
|
|
datadf['y1'] = datadf['pace']
|
|
except KeyError: # pragma: no cover
|
|
return ['', '<p>No non-zero data in selection</p>', '', '']
|
|
if yparam2 != 'None':
|
|
try:
|
|
datadf['y2'] = datadf.loc[:, yparam2]
|
|
except KeyError: # pragma: no cover
|
|
datadf['y2'] = datadf['y1']
|
|
else: # pragma: no cover
|
|
datadf['y2'] = datadf['y1']
|
|
|
|
if xparam == 'distance': # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return '', 'No time data - cannot make flex plot', '', '', comment
|
|
|
|
try:
|
|
rowdata['x1'] = rowdata.loc[:, xparam]
|
|
rowmin = rowdata[xparam].min()
|
|
except KeyError: # pragma: no cover
|
|
rowdata['x1'] = 0*rowdata.loc[:, 'time']
|
|
|
|
try:
|
|
rowdata['y1'] = rowdata.loc[:, yparam1]
|
|
rowmin = rowdata[yparam1].min()
|
|
except KeyError: # pragma: no cover
|
|
rowdata['y1'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam1] = rowdata['y1']
|
|
|
|
try: # pragma: no cover
|
|
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: # pragma: no cover
|
|
rowdata['y3'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam3] = rowdata['y3']
|
|
|
|
try:
|
|
rowdata['y4'] = rowdata.loc[:, yparam4]
|
|
rowmin = rowdata[yparam4].min()
|
|
except KeyError: # pragma: no cover
|
|
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': # pragma: no cover
|
|
xaxmax = rowdata['x1'].max()
|
|
xaxmin = rowdata['x1'].min()
|
|
else: # pragma: no cover
|
|
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': # pragma: no cover
|
|
y1_axis_type = 'datetime'
|
|
|
|
if yparam2 == 'pace': # pragma: no cover
|
|
y2_axis_type = 'datetime'
|
|
|
|
if yparam3 == 'pace': # pragma: no cover
|
|
y3_axis_type = 'datetime'
|
|
|
|
if yparam4 == 'pace': # pragma: no cover
|
|
y4_axis_type = 'datetime'
|
|
|
|
try:
|
|
rowdata['xname'] = axlabels[xparam]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['xname'] = xparam
|
|
|
|
try:
|
|
rowdata['yname1'] = axlabels[yparam1]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['yname1'] = yparam1
|
|
|
|
try:
|
|
rowdata['yname2'] = axlabels[yparam2]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['yname2'] = yparam2
|
|
|
|
try:
|
|
rowdata['yname3'] = axlabels[yparam3]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['yname3'] = yparam3
|
|
|
|
try:
|
|
rowdata['yname4'] = axlabels[yparam4]
|
|
except KeyError: # pragma: no cover
|
|
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)
|
|
|
|
xaxlabel = axlabels.get(xparam, xparam)
|
|
yax1label = axlabels.get(yparam1, yparam1)
|
|
|
|
plot1.yaxis.axis_label = yax1label
|
|
|
|
yax2label = axlabels.get(yparam2, yparam2)
|
|
|
|
plot2.yaxis.axis_label = yax2label
|
|
|
|
yax3label = axlabels.get(yparam3, yparam3)
|
|
|
|
plot3.yaxis.axis_label = yax3label
|
|
|
|
yax4label = axlabels.get(yparam4, yparam4)
|
|
|
|
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': # pragma: no cover
|
|
y1tooltip = '@{yparam1}'.format(yparam1=yparam1)
|
|
if metricsdicts[yparam1]['numtype'] == 'integer' or yparam1 == 'power':
|
|
y1tooltip += '{int}'
|
|
else: # pragma: no cover
|
|
y1tooltip += '{0.00}'
|
|
else: # pragma: no cover
|
|
y1tooltip = ''
|
|
comment = 'The metric in the first chart is only accessible with a Pro plan or higher'
|
|
|
|
if yparam2 == 'pace': # pragma: no cover
|
|
y2tooltip = '@fpace'
|
|
elif yparam2 != 'None':
|
|
y2tooltip = '@{yparam2}'.format(yparam2=yparam2)
|
|
if metricsdicts[yparam2]['numtype'] == 'integer' or yparam2 == 'power':
|
|
y2tooltip += '{int}'
|
|
else: # pragma: no cover
|
|
y2tooltip += '{0.00}'
|
|
else: # pragma: no cover
|
|
y2tooltip = ''
|
|
comment = 'The metric in the second chart is only accessible with a Pro plan or higher'
|
|
|
|
if yparam3 == 'pace': # pragma: no cover
|
|
y3tooltip = '@fpace'
|
|
elif yparam3 != 'None':
|
|
y3tooltip = '@{yparam3}'.format(yparam3=yparam3)
|
|
if metricsdicts[yparam3]['numtype'] == 'integer' or yparam3 == 'power':
|
|
y3tooltip += '{int}'
|
|
else: # pragma: no cover
|
|
y3tooltip += '{0.00}'
|
|
else: # pragma: no cover
|
|
y3tooltip = ''
|
|
comment = 'The metric in the third chart is only accessible with a Pro plan or higher'
|
|
|
|
if yparam4 == 'pace': # pragma: no cover
|
|
y4tooltip = '@fpace'
|
|
elif yparam4 != 'None':
|
|
y4tooltip = '@{yparam4}'.format(yparam4=yparam4)
|
|
if metricsdicts[yparam4]['numtype'] == 'integer' or yparam4 == 'power': # pragma: no cover
|
|
y4tooltip += '{int}'
|
|
else: # pragma: no cover
|
|
y4tooltip += '{0.00}'
|
|
else: # pragma: no cover
|
|
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': # pragma: no cover
|
|
plot2.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds=["%S"],
|
|
minutes=["%M"]
|
|
)
|
|
plot2.y_range = Range1d(y2min, y2max)
|
|
|
|
if yparam3 == 'pace': # pragma: no cover
|
|
plot3.yaxis[0].formatter = DatetimeTickFormatter(
|
|
seconds=["%S"],
|
|
minutes=["%M"]
|
|
)
|
|
plot3.y_range = Range1d(y3min, y3max)
|
|
|
|
if yparam4 == 'pace': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
tests = rowdata[yparam1]
|
|
except KeyError: # pragma: no cover
|
|
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: # pragma: no cover
|
|
try:
|
|
rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)]
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
tseconds = rowdata.loc[:, 'time']
|
|
except KeyError: # pragma: no cover
|
|
return '', 'No time data - cannot make flex plot', '', '', workstrokesonly
|
|
|
|
try:
|
|
rowdata['x1'] = rowdata.loc[:, xparam]
|
|
rowmin = rowdata[xparam].min()
|
|
except KeyError: # pragma: no cover
|
|
rowdata['x1'] = 0*rowdata.loc[:, 'time']
|
|
|
|
try:
|
|
rowdata['y1'] = rowdata.loc[:, yparam1]
|
|
rowmin = rowdata[yparam1].min()
|
|
except KeyError: # pragma: no cover
|
|
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: # pragma: no cover
|
|
rowdata['y2'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam2] = rowdata['y2']
|
|
else: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
x1mean = 0
|
|
else: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
y1mean = 0
|
|
|
|
try:
|
|
rowdata['xname'] = axlabels[xparam]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['xname'] = xparam
|
|
try:
|
|
rowdata['yname1'] = axlabels[yparam1]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['yname1'] = yparam1
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['yname2'] = axlabels[yparam2]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['yname2'] = yparam2
|
|
else: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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'): # pragma: no cover
|
|
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: # pragma: no cover
|
|
yaxlabel = str(yparam1)+' '
|
|
|
|
try:
|
|
xaxlabel = axlabels[xparam]
|
|
except KeyError: # pragma: no cover
|
|
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': # pragma: no cover
|
|
plot.add_layout(y1label)
|
|
y2label = y1label
|
|
|
|
# average values
|
|
if yparam1 == 'driveenergy': # pragma: no cover
|
|
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': # pragma: no cover
|
|
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: # pragma: no cover
|
|
yrange1 = Range1d(start=rowdata[yparam1].min(),
|
|
end=rowdata[yparam1].max())
|
|
|
|
plot.y_range = yrange1
|
|
|
|
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover
|
|
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: # pragma: no cover
|
|
yrange2 = Range1d(start=rowdata[yparam2].min(),
|
|
end=rowdata[yparam2].max())
|
|
|
|
plot.extra_y_ranges["yax2"] = yrange2
|
|
# = {"yax2": yrange2}
|
|
try:
|
|
axlegend = axlabels[yparam2]
|
|
except KeyError: # pragma: no cover
|
|
axlegend = str(yparam2)+' '
|
|
|
|
if plottype == 'line':
|
|
plot.line('x1', 'y2', color="red", y_range_name="yax2",
|
|
legend_label=axlegend,
|
|
source=source2)
|
|
|
|
elif plottype == 'scatter': # pragma: no cover
|
|
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): # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return '', 'No time data - cannot make flex plot'
|
|
|
|
try:
|
|
rowdata['x1'] = rowdata.loc[:, xparam]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['x1'] = 0*rowdata.loc[:, 'time']
|
|
|
|
try:
|
|
rowdata['y1'] = rowdata.loc[:, yparam1]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['y1'] = 0*rowdata.loc[:, 'time']
|
|
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['y2'] = rowdata.loc[:, yparam2]
|
|
except KeyError: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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': # pragma: no cover
|
|
y_axis_type = 'datetime'
|
|
y1mean = rowdata.loc[:, 'pace'].mean()
|
|
|
|
rowdata['xname'] = axlabels[xparam]
|
|
try:
|
|
rowdata['yname1'] = axlabels[yparam1]
|
|
except KeyError: # pragma: no cover
|
|
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: # pragma: no cover
|
|
plot.xaxis.axis_label = 'X'
|
|
try:
|
|
plot.yaxis.axis_label = axlabels[yparam1]
|
|
except KeyError: # pragma: no cover
|
|
plot.yaxis.axis_label = 'Y'
|
|
|
|
try:
|
|
yrange1 = Range1d(start=yaxminima[yparam1], end=yaxmaxima[yparam1])
|
|
except KeyError: # pragma: no cover
|
|
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': # pragma: no cover
|
|
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': # pragma: no cover
|
|
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_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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
return ['', '<p>No non-zero data in selection</p>', '', 'No non-zero data in selection']
|
|
|
|
if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': # pragma: no cover
|
|
xaxmax = yaxmaxima[xparam]
|
|
xaxmin = yaxminima[xparam]
|
|
elif xparam == 'time' and not startenddict:
|
|
xaxmax = tseconds.max()
|
|
xaxmin = tseconds.min()
|
|
elif xparam == 'time' and startenddict: # pragma: no cover
|
|
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': # pragma: no cover
|
|
yaxmin = datadf['distance'].min()
|
|
yaxmax = datadf['distance'].max()
|
|
elif yparam == 'cumdist': # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
legend = str(id)
|
|
else: # pragma: no cover
|
|
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'): # pragma: no cover
|
|
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_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: # pragma: no cover
|
|
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: # pragma: no cover
|
|
script = ''
|
|
div = ''
|
|
|
|
return [script, div]
|
|
|
|
|
|
def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week',
|
|
yaxis='time'):
|
|
|
|
duration = enddate-startdate
|
|
|
|
totaldays = duration.total_seconds()/(24*3600)
|
|
|
|
dates = []
|
|
dates_sorting = []
|
|
minutes = []
|
|
hours = []
|
|
zones = []
|
|
|
|
enddate = enddate + datetime.timedelta(days=1)
|
|
|
|
workouts = Workout.objects.filter(
|
|
user=rower,
|
|
startdatetime__gte=startdate,
|
|
startdatetime__lte=enddate,
|
|
duplicate=False,
|
|
).order_by("-startdatetime")
|
|
|
|
ids = [w.id for w in workouts]
|
|
|
|
columns = ['workoutid', 'hr', 'power', 'time']
|
|
|
|
df = dataprep.getsmallrowdata_db(columns, ids=ids)
|
|
try:
|
|
df['deltat'] = df['time'].diff().clip(lower=0).clip(upper=20*1e3)
|
|
except KeyError: # pragma: no cover
|
|
pass
|
|
|
|
df = dataprep.clean_df_stats(df, workstrokesonly=False,
|
|
ignoreadvanced=True, ignorehr=False)
|
|
|
|
#totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts)
|
|
|
|
hrzones = rower.hrzones
|
|
powerzones = rower.powerzones
|
|
|
|
for w in workouts:
|
|
dd = w.date.strftime('%m/%d')
|
|
dd2 = w.date.strftime('%Y/%m/%d')
|
|
dd3 = w.date.strftime('%Y/%m')
|
|
dd4 = '{year}/{week:02d}'.format(
|
|
week=arrow.get(w.date).isocalendar()[1],
|
|
year=w.date.strftime('%y')
|
|
)
|
|
dd4 = (w.date - datetime.timedelta(days=w.date.weekday())
|
|
).strftime('%y/%m/%d')
|
|
|
|
# print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar())
|
|
|
|
qryw = 'workoutid == {workoutid}'.format(workoutid=w.id)
|
|
|
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
|
if trainingzones == 'power':
|
|
qry = 'power < {ut2}'.format(ut2=rower.pw_ut2)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append('<{ut2}'.format(ut2=hrzones[1]))
|
|
else:
|
|
zones.append('<{ut2}'.format(ut2=powerzones[1]))
|
|
# print(w,dd,timeinzone,'<UT2')
|
|
|
|
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1, ut2=rower.ut2)
|
|
if trainingzones == 'power':
|
|
qry = '{ut2} <= power < {ut1}'.format(
|
|
ut1=rower.pw_ut1, ut2=rower.pw_ut2)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[1])
|
|
else:
|
|
zones.append(powerzones[1])
|
|
# print(w,dd,timeinzone,'UT2')
|
|
|
|
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1, at=rower.at)
|
|
if trainingzones == 'power':
|
|
qry = '{ut1} <= power < {at}'.format(
|
|
ut1=rower.pw_ut1, at=rower.pw_at)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[2])
|
|
else:
|
|
zones.append(powerzones[2])
|
|
# print(w,dd,timeinzone,'UT1')
|
|
|
|
qry = '{at} <= hr < {tr}'.format(at=rower.at, tr=rower.tr)
|
|
if trainingzones == 'power':
|
|
qry = '{at} <= power < {tr}'.format(at=rower.pw_at, tr=rower.pw_tr)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[3])
|
|
else:
|
|
zones.append(powerzones[3])
|
|
# print(w,dd,timeinzone,'AT')
|
|
|
|
qry = '{tr} <= hr < {an}'.format(tr=rower.tr, an=rower.an)
|
|
if trainingzones == 'power':
|
|
qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr, an=rower.pw_an)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[4])
|
|
else:
|
|
zones.append(powerzones[4])
|
|
# print(w,dd,timeinzone,'TR')
|
|
|
|
qry = 'hr >= {an}'.format(an=rower.an)
|
|
if trainingzones == 'power':
|
|
qry = 'power >= {an}'.format(an=rower.pw_an)
|
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
|
if date_agg == 'week':
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(dd3)
|
|
dates_sorting.append(dd3)
|
|
minutes.append(timeinzone)
|
|
hours.append(timeinzone/60.)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[5])
|
|
else:
|
|
zones.append(powerzones[5])
|
|
# print(w,dd,timeinzone,'AN')
|
|
|
|
try:
|
|
d = utc.localize(startdate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
d = startdate
|
|
|
|
try:
|
|
enddate = utc.localize(enddate)
|
|
except (ValueError, AttributeError): # pragma: no cover
|
|
pass
|
|
|
|
while d <= enddate:
|
|
dd = d.strftime('%d')
|
|
if date_agg == 'week':
|
|
dd4 = '{year}/{week:02d}'.format(
|
|
week=arrow.get(d).isocalendar()[1],
|
|
year=d.strftime('%y')
|
|
)
|
|
dd4 = (d - datetime.timedelta(days=d.weekday())).strftime('%y/%m/%d')
|
|
|
|
dates.append(dd4)
|
|
dates_sorting.append(dd4)
|
|
else: # pragma: no cover
|
|
dates.append(d.strftime('%Y/%m'))
|
|
dates_sorting.append(d.strftime('%Y/%m'))
|
|
|
|
minutes.append(0)
|
|
hours.append(0)
|
|
if trainingzones == 'hr':
|
|
zones.append(hrzones[1])
|
|
else:
|
|
zones.append(powerzones[1])
|
|
|
|
d += datetime.timedelta(days=1)
|
|
|
|
# this should be renamed with rower zones
|
|
data = {
|
|
'date': dates,
|
|
'date_sorting': dates_sorting,
|
|
'minutes': minutes,
|
|
'zones': zones,
|
|
'hours': hours,
|
|
}
|
|
|
|
# print(pd.DataFrame(data).head())
|
|
|
|
return data
|
|
|
|
|
|
def interactive_zoneschart(rower, data, startdate, enddate, trainingzones='hr', date_agg='week',
|
|
yaxis='time'):
|
|
if startdate >= enddate: # pragma: no cover
|
|
st = startdate
|
|
startdate = enddate
|
|
enddate = st
|
|
|
|
duration = enddate-startdate
|
|
|
|
totaldays = duration.total_seconds()/(24*3600)
|
|
|
|
colors = ['gray', 'yellow', 'lime', 'blue', 'purple', 'red']
|
|
|
|
hrzones = rower.hrzones
|
|
powerzones = rower.powerzones
|
|
|
|
color_map = {
|
|
'<{ut2}'.format(ut2=hrzones[1]): 'green',
|
|
hrzones[1]: 'lime',
|
|
hrzones[2]: 'yellow',
|
|
hrzones[3]: 'blue',
|
|
hrzones[4]: 'purple',
|
|
hrzones[5]: 'red',
|
|
}
|
|
if trainingzones == 'power':
|
|
color_map = {
|
|
'<{ut2}'.format(ut2=powerzones[1]): 'green',
|
|
powerzones[1]: 'lime',
|
|
powerzones[2]: 'yellow',
|
|
powerzones[3]: 'blue',
|
|
powerzones[4]: 'purple',
|
|
powerzones[5]: 'red',
|
|
}
|
|
|
|
zones_order = [
|
|
'<{ut2}'.format(ut2=hrzones[1]),
|
|
hrzones[1],
|
|
hrzones[2],
|
|
hrzones[3],
|
|
hrzones[4],
|
|
hrzones[5]
|
|
]
|
|
|
|
if trainingzones == 'power':
|
|
zones_order = [
|
|
'<{ut2}'.format(ut2=powerzones[1]),
|
|
powerzones[1],
|
|
powerzones[2],
|
|
powerzones[3],
|
|
powerzones[4],
|
|
powerzones[5]
|
|
]
|
|
|
|
df = pd.DataFrame(data)
|
|
df2 = pd.DataFrame(data)
|
|
|
|
df.drop('minutes', inplace=True, axis='columns')
|
|
|
|
# df.drop('hours',inplace=True,axis='columns')
|
|
|
|
source = ColumnDataSource(df)
|
|
|
|
df.sort_values('date_sorting', inplace=True)
|
|
df.drop('date_sorting', inplace=True, axis='columns')
|
|
df['totaltime'] = 0
|
|
if df.empty: # pragma: no cover
|
|
return '', 'No Data Found'
|
|
|
|
if yaxis == 'percentage':
|
|
dates = list(set(df['date'].values))
|
|
for date in dates:
|
|
qry = 'date == "{d}"'.format(d=date)
|
|
|
|
totaltime = df.query(qry)['hours'].sum()
|
|
|
|
mask = df['date'] == date
|
|
df.loc[mask, 'totaltime'] = totaltime
|
|
|
|
df['percentage'] = 100.*df['hours']/df['totaltime']
|
|
df.drop('hours', inplace=True, axis='columns')
|
|
df.drop('totaltime', inplace=True, axis='columns')
|
|
|
|
hv.extension('bokeh')
|
|
|
|
xrotation = 0
|
|
nrdates = len(list(set(df['date'].values)))
|
|
if nrdates > 10:
|
|
xrotation = 45
|
|
|
|
bars = hv.Bars(df, kdims=['date', 'zones']).aggregate(
|
|
function=np.sum).redim.values(zones=zones_order)
|
|
|
|
#bars = table.to.bars(['date','zones'],['minutes'])
|
|
bars.opts(
|
|
opts.Bars(cmap=color_map, show_legend=True, stacked=True,
|
|
tools=['tap', 'hover'], width=550, padding=(0, (0, .1)),
|
|
legend_position='bottom',
|
|
xrotation=xrotation,
|
|
show_frame=False)
|
|
)
|
|
|
|
p = hv.render(bars)
|
|
|
|
p.title.text = 'Activity {d1} to {d2} for {r}'.format(
|
|
d1=startdate.strftime("%Y-%m-%d"),
|
|
d2=enddate.strftime("%Y-%m-%d"),
|
|
r=str(rower),
|
|
)
|
|
|
|
if date_agg == 'week':
|
|
p.xaxis.axis_label = 'Week'
|
|
else: # pragma: no cover
|
|
p.xaxis.axis_label = 'Month'
|
|
|
|
if yaxis == 'percentage':
|
|
p.yaxis.axis_label = 'Percentage'
|
|
|
|
p.plot_width = 550
|
|
p.plot_height = 350
|
|
p.toolbar_location = 'right'
|
|
p.y_range.start = 0
|
|
p.sizing_mode = 'stretch_both'
|
|
|
|
if yaxis == 'percentage':
|
|
tidy_df = df2.groupby(['date']).sum()
|
|
|
|
source2 = ColumnDataSource(tidy_df)
|
|
y2rangemax = tidy_df.loc[:, 'hours'].max()*1.1
|
|
p.extra_y_ranges["yax2"] = Range1d(start=0, end=y2rangemax)
|
|
p.line('date', 'hours', source=source2,
|
|
y_range_name="yax2", color="black", width=5)
|
|
p.circle('date', 'hours', source=source2, y_range_name="yax2", color="black", size=10,
|
|
legend_label='Hours')
|
|
p.add_layout(LinearAxis(y_range_name="yax2",
|
|
axis_label='Hours'), 'right')
|
|
|
|
script, div = components(p)
|
|
|
|
return script, div
|