5010 lines
154 KiB
Python
5010 lines
154 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, InStrokeAnalysis, ForceCurveAnalysis
|
|
from bokeh.palettes import Category20c, Category10
|
|
from bokeh.layouts import layout
|
|
from bokeh.resources import CDN, INLINE
|
|
from rowers.dataprep import timedeltaconv, rscore_approx
|
|
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.utils as utils
|
|
|
|
from rowers.rower_rules import ispromember
|
|
|
|
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, course_coord_crewnerd_navigation,
|
|
)
|
|
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, CrosshairTool, Span, Label, SaveTool,
|
|
PanTool, BoxZoomTool, WheelZoomTool, ResetTool,)
|
|
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, 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')
|
|
|
|
import requests
|
|
from rowers.serializers import *
|
|
|
|
activate(settings.TIME_ZONE)
|
|
thetimezone = get_current_timezone()
|
|
|
|
def get_chart(end_point, chart_data, debug=False):
|
|
if debug:
|
|
print(chart_data)
|
|
url = settings.ROWSANDALL_CHARTS_URL+end_point
|
|
headers = {'authorization':"Bearer {token}".format(token=settings.ROWSANDALL_CHARTS_TOKEN)}
|
|
try:
|
|
response = requests.post(url, json=chart_data, headers=headers)
|
|
except Exception as err:
|
|
if debug:
|
|
print("Chart Server Error")
|
|
print(err)
|
|
script = ''
|
|
div = 'Chart Server Error'
|
|
return script, div
|
|
|
|
if debug:
|
|
print("Status Code",response.status_code)
|
|
|
|
if response.status_code == 200:
|
|
script = response.json()['script']
|
|
div = response.json()['div']
|
|
else:
|
|
script = ''
|
|
try:
|
|
div = response.reason
|
|
except AttributeError:
|
|
div = 'The chart server errored'
|
|
|
|
|
|
#if not debug:
|
|
# script = jsmin(script)
|
|
|
|
return script, div
|
|
|
|
# Example for 3D
|
|
def filmdeaths():
|
|
data = pd.read_csv("~/Downloads/filmdeathcounts.csv")
|
|
|
|
chart_data = data.to_dict("records")
|
|
chart_data_dict = {"data": chart_data}
|
|
|
|
script, div = get_chart("/filmdeaths", chart_data_dict)
|
|
return script, div
|
|
|
|
# Example for BokehJS
|
|
def sleep():
|
|
data = {
|
|
'work': 8,
|
|
'eat': 2,
|
|
'commute': 2,
|
|
'sport': 0,
|
|
'tv': 1,
|
|
'sleep': 8,
|
|
}
|
|
script, div = get_chart("/sleep", data)
|
|
return script, div
|
|
|
|
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:
|
|
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:
|
|
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']])
|
|
|
|
data_dict = data.to_dict("records")
|
|
chart_data = {
|
|
'data': data_dict,
|
|
'title': "HR "+ title
|
|
}
|
|
|
|
script, div = get_chart("/hrpie", chart_data, debug=True)
|
|
return script, div
|
|
|
|
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(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 fieldname not in columns: # pragma: no cover
|
|
return '', 'It looks like there are no data matching your filter'
|
|
|
|
if 'date' not in columns: # pragma: no cover
|
|
return '', 'Not enough data'
|
|
|
|
try:
|
|
datadf.date = datadf.date.apply(lambda x:x.strftime("%Y-%m-%d"))
|
|
except AttributeError:
|
|
datadf.date = "2000-01-01"
|
|
|
|
datadf['value'] = datadf[fieldname]
|
|
data_dict = datadf.to_dict("records")
|
|
boxplot_data = {
|
|
"metric": metricsdicts[fieldname]["verbose_name"],
|
|
"data": data_dict
|
|
}
|
|
|
|
|
|
script, div = get_chart("/boxplot", boxplot_data)
|
|
return script, div
|
|
|
|
|
|
def interactive_planchart(data, startdate, enddate):
|
|
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.width = 550
|
|
p.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,
|
|
}
|
|
|
|
df = pd.DataFrame(thedict)
|
|
|
|
df.sort_values('date_sorting', inplace=True)
|
|
|
|
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.width = 550
|
|
p.height = 350
|
|
p.toolbar_location = toolbar_location
|
|
p.y_range.start = 0
|
|
#p.sizing_mode = 'stretch_both'
|
|
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: # pragma: no cover
|
|
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,
|
|
}
|
|
|
|
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)
|
|
|
|
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.width = 550
|
|
p.height = 350
|
|
p.toolbar_location = toolbar_location
|
|
#p.sizing_mode = 'stretch_both'
|
|
p.y_range.start = 0
|
|
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',
|
|
spm_min=15, spm_max=45,
|
|
notes='',
|
|
dist_min=0,dist_max=0,
|
|
work_min=0,work_max=1500):
|
|
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)
|
|
|
|
workoutstatesrest = [3]
|
|
|
|
if workstrokesonly:
|
|
try:
|
|
rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)]
|
|
except KeyError: # pragma: no cover
|
|
pass
|
|
|
|
if rowdata.empty:
|
|
return "", "No Valid Data Available", "", ""
|
|
|
|
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()
|
|
except KeyError: # pragma: no cover
|
|
peakforceav = 0
|
|
|
|
try:
|
|
averageforceav = rowdata['averageforce'].median()
|
|
except KeyError: # pragma: no cover
|
|
averageforceav = 0
|
|
|
|
try:
|
|
peakforceangleav = rowdata['peakforceangle'].median()
|
|
except KeyError: # pragma: no cover
|
|
peakforceangleav = 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]
|
|
)
|
|
)
|
|
|
|
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", width=800, height=600)
|
|
#plot.sizing_mode = 'stretch_both'
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
|
|
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)"
|
|
try:
|
|
plot.title.text = theworkouts[0].name
|
|
except ValueError: # pragma: no cover
|
|
plot.title.text = ""
|
|
plot.title.text_font_size = "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);
|
|
|
|
// change DOM elements
|
|
document.getElementById("id_spm_min").value = minspm;
|
|
document.getElementById("id_spm_max").value = maxspm;
|
|
document.getElementById("id_dist_min").value = mindist;
|
|
document.getElementById("id_dist_max").value = maxdist;
|
|
document.getElementById("id_notes").value = annotation;
|
|
document.getElementById("id_work_min").value = minwork;
|
|
document.getElementById("id_work_max").value = maxwork;
|
|
|
|
// 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="", name="annotation")
|
|
annotation.js_on_change('value', callback)
|
|
callback.args["annotation"] = annotation
|
|
|
|
slider_spm_min = Slider(width=140, start=15.0, end=55, value=15, step=.1,
|
|
title="Min SPM", name="min_spm_slider")
|
|
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, step=.1,
|
|
title="Max SPM", name="max_spm_slider")
|
|
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", name="min_work_slider")
|
|
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", name="max_work_slider")
|
|
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", name="min_dist_slider")
|
|
slider_dist_min.js_on_change('value', callback)
|
|
callback.args["mindist"] = slider_dist_min
|
|
|
|
if dist_max == 0:
|
|
dist_max = distmax
|
|
|
|
slider_dist_max = Slider(width=140, start=0, end=distmax, value=distmax,
|
|
step=50,
|
|
title="Max Distance", name="max_dist_slider")
|
|
slider_dist_max.js_on_change('value', callback)
|
|
callback.args["maxdist"] = slider_dist_max
|
|
|
|
thesliders = layoutcolumn([annotation,
|
|
slider_spm_min,
|
|
slider_spm_max,
|
|
slider_dist_min,
|
|
slider_dist_max,
|
|
slider_work_min,
|
|
slider_work_max,
|
|
]
|
|
)
|
|
|
|
mylayout = layoutrow([thesliders, plot])
|
|
|
|
#mylayout.sizing_mode = 'stretch_both'
|
|
|
|
script, div = components(mylayout)
|
|
js_resources = INLINE.render_js()
|
|
css_resources = INLINE.render_css()
|
|
|
|
return [script, div, js_resources, css_resources]
|
|
|
|
def weightfromrecord(row,metricchoice):
|
|
vv = row[metricchoice]
|
|
if vv > 0:
|
|
return vv
|
|
if metricchoice == 'rscore': # pragma: no cover
|
|
return rscore_approx(row)
|
|
|
|
return 0 # pragma: no cover
|
|
|
|
|
|
def getfatigues(
|
|
df,
|
|
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)
|
|
datekey = date.strftime('%Y-%m-%d')
|
|
weight = 0
|
|
|
|
try:
|
|
df2 = df.loc[date.date()]
|
|
|
|
if type(df2) == pd.Series: # pragma: no cover
|
|
weight += weightfromrecord(df2,metricchoice)
|
|
else:
|
|
for index, row in df2.iterrows():
|
|
weight += weightfromrecord(row,metricchoice)
|
|
except KeyError:
|
|
pass
|
|
|
|
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)
|
|
|
|
|
|
# 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 = []
|
|
|
|
for i in range(len(dates)):
|
|
id = ids[i]
|
|
w = Workout.objects.get(id=id)
|
|
# 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]
|
|
|
|
df.fillna(value=0, inplace=True)
|
|
df['dat1'] = df['date'].map(lambda x: x.to_pydatetime(x).strftime("%Y-%m-%d"))
|
|
|
|
df2 = pd.DataFrame({
|
|
'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['dat1'],
|
|
'url':df['url'],
|
|
'workout':df['workout']
|
|
})
|
|
|
|
data_dicts = df2.to_dict("records")
|
|
chart_data = {
|
|
'data': data_dicts
|
|
}
|
|
|
|
script, div = get_chart("/markerworkouts", chart_data)
|
|
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,
|
|
duplicate=False).order_by('date')
|
|
|
|
# make fast dict for dates / workouts
|
|
records = []
|
|
|
|
for w in workouts:
|
|
dd = {
|
|
'date':w.date,
|
|
'trimp':w.trimp,
|
|
'rscore':w.rscore,
|
|
'hrtss':w.hrtss,
|
|
'duration':w.duration,
|
|
'id':w.id,
|
|
'rpe':w.rpe,
|
|
}
|
|
records.append(dd)
|
|
|
|
df = pd.DataFrame.from_records(records)
|
|
if df.empty: # pragma: no cover
|
|
return ['', 'No Data', 0, 0, 0, outids]
|
|
df.set_index('date', inplace=True)
|
|
|
|
markerworkouts = Workout.objects.filter(
|
|
user=user.rower, date__gte=startdate-datetime.timedelta(days=90),
|
|
date__lte=enddate,
|
|
duplicate=False,
|
|
rankingpiece=True, workouttype__in=mytypes.rowtypes).order_by('date')
|
|
|
|
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(
|
|
df,
|
|
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',
|
|
width=900, height=300,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
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",
|
|
)
|
|
|
|
fitlabel = 'Fitness'
|
|
fatiguelabel = 'Fatigue'
|
|
formlabel = 'Freshness'
|
|
rightaxlabel = 'Freshness'
|
|
if dofatigue: # pragma: no cover
|
|
yaxlabel = 'Fitness/Fatigue'
|
|
else: # pragma: no cover
|
|
yaxlabel = 'Fitness'
|
|
|
|
if modelchoice == 'banister': # pragma: no cover
|
|
fitlabel = 'PTE (fitness)'
|
|
fatiguelabel = 'NTE (fatigue)'
|
|
formlabel = 'Performance'
|
|
rightaxlabel = 'Performance'
|
|
if dofatigue:
|
|
yaxlabel = 'PTE/NTE'
|
|
else:
|
|
yaxlabel = 'PTE'
|
|
|
|
|
|
plot.xaxis.axis_label = None
|
|
plot.yaxis.axis_label = yaxlabel
|
|
|
|
y2rangemin = df.loc[:, ['form']].min().min()
|
|
y2rangemax = df.loc[:, ['form']].max().max()
|
|
|
|
if dofatigue: # pragma: no cover
|
|
y1rangemax = df.loc[:, ['fitness', 'fatigue']].max().max()*1.02
|
|
else: # pragma: no cover
|
|
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', lower=0, 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'
|
|
|
|
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([
|
|
('Date', '@fdate'),
|
|
(fitlabel, '@fitness{int}'),
|
|
(fatiguelabel, '@fatigue{int}'),
|
|
(formlabel, '@form{int}'),
|
|
('Impulse', '@impulse{int}')
|
|
])
|
|
|
|
if showtests:
|
|
hover.tooltips = OrderedDict([
|
|
('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',
|
|
width=900, 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)
|
|
|
|
mylayout = layoutcolumn([plot, plot2])
|
|
|
|
try:
|
|
script, div = components(mylayout)
|
|
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,
|
|
extratitle='',
|
|
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]]
|
|
|
|
data_dict = {"data": histopwr.tolist(),
|
|
"metric": metricsdicts[histoparam]["verbose_name"]}
|
|
|
|
script, div = get_chart("/histogram", data_dict, debug=False)
|
|
|
|
return script, div
|
|
|
|
|
|
|
|
def course_map(course):
|
|
|
|
course_dict = GeoCourseSerializer(course).data
|
|
|
|
script, div = get_chart("/map", course_dict)
|
|
|
|
return script, div
|
|
|
|
|
|
def leaflet_chart(lat, lon, name="", raceresult=0):
|
|
try:
|
|
if lat.empty or lon.empty: # pragma: no cover
|
|
return [0, "invalid coordinate data"]
|
|
except AttributeError:
|
|
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)
|
|
|
|
data = {
|
|
'coordinates': [{'latitude': c[0], 'longitude': c[1]} for c in list(coordinates)],
|
|
'latmean': latmean,
|
|
'lonmean': lonmean,
|
|
'latbegin': latbegin,
|
|
'latend': latend,
|
|
'longbegin': longbegin,
|
|
'longend': longend,
|
|
}
|
|
|
|
if raceresult != 0:
|
|
record = VirtualRaceResult.objects.get(id=raceresult)
|
|
course = record.course
|
|
course_dict = GeoCourseSerializer(course).data
|
|
data['course'] = course_dict
|
|
|
|
|
|
coordinates = zip(lat, lon)
|
|
|
|
script, div = get_chart("/workoutmap", data)
|
|
|
|
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
|
|
try:
|
|
df = pd.concat(data, axis=0)
|
|
except ValueError: # pragma: no cover
|
|
df = pd.DataFrame()
|
|
|
|
latmean, lonmean, coordinates = course_coord_center(course)
|
|
|
|
course_dict = GeoCourseSerializer(course).data
|
|
|
|
# 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)
|
|
|
|
try:
|
|
lat = df['lat']
|
|
lon = df['lon']
|
|
except KeyError: # pragma: no cover
|
|
return [0, "invalid coordinate data"]
|
|
if lat.empty or lon.empty: # pragma: no cover
|
|
return [0, "invalid coordinate data"]
|
|
|
|
colors = itertools.cycle(palette)
|
|
try:
|
|
items = itertools.izip(workoutids, colors)
|
|
except AttributeError:
|
|
items = zip(workoutids, colors)
|
|
|
|
trajectories = []
|
|
|
|
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)
|
|
|
|
trajectory_dict = {
|
|
'coordinates' :[{'latitude':c[0], 'longitude': c[1]} for c in list(coordinates)],
|
|
'color': color,
|
|
'label': label,
|
|
}
|
|
|
|
trajectories.append(trajectory_dict)
|
|
|
|
mapdata = {
|
|
'course': course_dict,
|
|
'latmean': latmean,
|
|
'lonmean': lonmean,
|
|
'trajectories': trajectories,
|
|
}
|
|
|
|
script, div = get_chart("/mapcompare", mapdata)
|
|
|
|
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'
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
|
|
|
plot = figure(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'
|
|
|
|
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)
|
|
|
|
# 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
|
|
|
|
|
|
fit_data = pd.DataFrame(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,
|
|
))
|
|
|
|
|
|
if not title:
|
|
title = "Critical Power for "+rowername
|
|
|
|
chart_dict = {
|
|
'data': powerdf2.to_dict("records"),
|
|
'fitdata': fit_data.to_dict("records"),
|
|
'title': title,
|
|
}
|
|
|
|
script, div = get_chart("/cp", chart_dict)
|
|
|
|
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,
|
|
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, 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'
|
|
|
|
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
|
|
power4min = fitfunc(p1, 240.)
|
|
power1h = fitfunc(p1, 3600.)
|
|
power10sec = fitfunc(p1, 10.)
|
|
r10sec4min = 100.*power10sec/power4min
|
|
r1h4min = 100.*power1h/power4min
|
|
|
|
combined = r1h4min-0.2*(r10sec4min-100)
|
|
|
|
dataset = pd.read_csv('static/stats/combined_set.csv')
|
|
|
|
stayerscore = int(percentileofscore(dataset['combined'], combined))
|
|
else:
|
|
stayerscore = 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,
|
|
width=900,
|
|
toolbar_location="above",
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarkrange = Range1d(start=0, end=1)
|
|
watermarkalpha = 0.6
|
|
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(width=400, height=300)
|
|
|
|
# get user
|
|
# u = User.objects.get(id=row.user.id)
|
|
r = row.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, 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')
|
|
try:
|
|
plot.title.text = row.name
|
|
except ValueError: # pragma: no cover
|
|
plot.title.text = ""
|
|
# plot.title.text_font_size="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(width=400,
|
|
)
|
|
# get user
|
|
# u = User.objects.get(id=row.user.id)
|
|
r = row.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, width=400, height=500,
|
|
# toolbar_location="below",
|
|
toolbar_sticky=False,
|
|
)
|
|
plot.line(dist, vstream, legend_label="River Stream Velocity (m/s)")
|
|
try:
|
|
plot.title.text = row.name
|
|
except ValueError: # pragma: no cover
|
|
plot.title.text = ""
|
|
plot.title.text_font_size = "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 forcecurve_multi_interactive_chart(selected): # pragma: no cover
|
|
df_plot = pd.DataFrame()
|
|
ids = [analysis.id for analysis in selected]
|
|
|
|
columns = ['catch', 'slip', 'wash', 'finish', 'averageforce',
|
|
'peakforceangle', 'peakforce', 'spm', 'distance',
|
|
'workoutstate', 'driveenergy']
|
|
|
|
for analysis in selected:
|
|
workstrokesonly = not analysis.include_rest_strokes
|
|
spm_min = analysis.spm_min
|
|
spm_max = analysis.spm_max
|
|
dist_min = analysis.dist_min
|
|
dist_max = analysis.dist_max
|
|
work_min = analysis.work_min
|
|
work_max = analysis.work_max
|
|
rowdata = dataprep.getsmallrowdata_db(columns, ids=[analysis.workout.id],
|
|
workstrokesonly=workstrokesonly)
|
|
|
|
rowdata = rowdata[rowdata['spm']>spm_min]
|
|
rowdata = rowdata[rowdata['spm']<spm_max]
|
|
rowdata = rowdata[rowdata['driveenergy']>work_min]
|
|
rowdata = rowdata[rowdata['driveenergy']<work_max]
|
|
rowdata = rowdata[rowdata['distance']<dist_max]
|
|
rowdata = rowdata[rowdata['distance']>dist_min]
|
|
|
|
catchav = rowdata['catch'].median()
|
|
finishav = rowdata['finish'].median()
|
|
washav = (rowdata['finish']-rowdata['wash']).median()
|
|
slipav = (rowdata['slip']+rowdata['catch']).median()
|
|
peakforceav = rowdata['peakforce'].median()
|
|
peakforceangleav = rowdata['peakforceangle'].median()
|
|
thresholdforce = 100 if 'x' in analysis.workout.boattype else 200
|
|
x = [catchav,
|
|
slipav,
|
|
peakforceangleav,
|
|
washav,
|
|
finishav]
|
|
|
|
y = [0, thresholdforce,
|
|
peakforceav,
|
|
thresholdforce, 0]
|
|
|
|
xname = 'x_'+str(analysis.id)
|
|
yname = 'y_'+str(analysis.id)
|
|
|
|
df_plot[xname] = x
|
|
df_plot[yname] = y
|
|
|
|
source = ColumnDataSource(
|
|
df_plot
|
|
)
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
plot = figure(width=920,tools=TOOLS,
|
|
toolbar_location='above',
|
|
toolbar_sticky=False)
|
|
|
|
#plot.sizing_mode = 'stretch_both'
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
|
|
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",
|
|
)
|
|
|
|
colors = itertools.cycle(palette)
|
|
|
|
try:
|
|
items = itertools.izip(ids, colors)
|
|
except AttributeError:
|
|
items = zip(ids, colors)
|
|
|
|
for id, color in items:
|
|
xname = 'x_'+str(id)
|
|
yname = 'y_'+str(id)
|
|
analysis = ForceCurveAnalysis.objects.get(id=id)
|
|
legendlabel = '{name}'.format(
|
|
name = analysis.name,
|
|
)
|
|
if analysis.notes:
|
|
legendlabel = '{name} - {notes}'.format(
|
|
name = analysis.name,
|
|
notes = analysis.notes
|
|
)
|
|
plot.line(xname,yname,source=source,legend_label=legendlabel,
|
|
line_width=2, color=color)
|
|
|
|
plot.legend.location = "top_left"
|
|
plot.xaxis.axis_label = "Angle"
|
|
plot.yaxis.axis_label = "Force (N)"
|
|
|
|
script, div = components(plot)
|
|
|
|
return (script, div)
|
|
|
|
def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no cover
|
|
df_plot = pd.DataFrame()
|
|
ids = [analysis.id for analysis in selected]
|
|
metrics = list(set([analysis.metric for analysis in selected]))
|
|
maximum_values = {}
|
|
for metric in metrics:
|
|
maximum_values[metric] = 0
|
|
for analysis in selected:
|
|
#start_second, end_second, spm_min, spm_max, name
|
|
activeminutesmin = int(analysis.start_second/60.)
|
|
activeminutesmax = int(analysis.end_second/60.)
|
|
rowdata = rrdata(csvfile=analysis.workout.csvfilename)
|
|
data = rowdata.get_instroke_data(
|
|
analysis.metric,
|
|
spm_min=analysis.spm_min,
|
|
spm_max=analysis.spm_max,
|
|
activeminutesmin=activeminutesmin,
|
|
activeminutesmax=activeminutesmax,
|
|
)
|
|
mean_vals = data.mean()
|
|
if analysis.metric == 'boat accelerator curve':
|
|
mean_vals[0] = (mean_vals[1]+ mean_vals[len(mean_vals)-1])/2.
|
|
if len(metrics) > 1:
|
|
if mean_vals.max() > maximum_values[analysis.metric]:
|
|
maximum_values[analysis.metric] = mean_vals.max()
|
|
xvals = np.arange(len(mean_vals))
|
|
xname = 'x_'+str(analysis.id)
|
|
yname = 'y_'+str(analysis.id)
|
|
df_plot[xname] = pd.Series(xvals)
|
|
df_plot[yname] = pd.Series(mean_vals)
|
|
|
|
if len(metrics) > 1:
|
|
for analysis in selected:
|
|
yname = 'y_'+str(analysis.id)
|
|
df_plot[yname] = df_plot[yname] / maximum_values[analysis.metric]
|
|
|
|
source = ColumnDataSource(
|
|
df_plot
|
|
)
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
plot = figure(width=920,tools=TOOLS,
|
|
toolbar_location='above',
|
|
toolbar_sticky=False)
|
|
|
|
#plot.sizing_mode = 'stretch_both'
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
|
|
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}
|
|
|
|
if len(metrics)>1:
|
|
plot.yaxis.axis_label = 'Scaled'
|
|
else:
|
|
plot.yaxis.axis_label = metrics[0]
|
|
|
|
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",
|
|
)
|
|
|
|
colors = itertools.cycle(palette)
|
|
|
|
try:
|
|
items = itertools.izip(ids, colors)
|
|
except AttributeError:
|
|
items = zip(ids, colors)
|
|
|
|
for id, color in items:
|
|
xname = 'x_'+str(id)
|
|
yname = 'y_'+str(id)
|
|
analysis = InStrokeAnalysis.objects.get(id=id)
|
|
legendlabel = '{name} - {metric} - {workout}'.format(
|
|
name = analysis.name,
|
|
metric = analysis.metric,
|
|
date = analysis.date,
|
|
workout = str(analysis.workout)
|
|
)
|
|
plot.line(xname,yname,source=source,legend_label=legendlabel,
|
|
line_width=2, color=color)
|
|
|
|
script, div = components(plot)
|
|
|
|
return (script, div)
|
|
|
|
def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
|
activeminutesmin, activeminutesmax,
|
|
individual_curves,
|
|
name='',notes=''): # pragma: no cover
|
|
|
|
df_pos = (df+abs(df))/2.
|
|
df_min = -(-df+abs(-df))/2.
|
|
|
|
if df.empty:
|
|
return "", "No data in selection"
|
|
|
|
mean_vals = df.median().replace(0, np.nan)
|
|
q75 = df_pos.quantile(q=0.75).replace(0,np.nan)
|
|
q25 = df_pos.quantile(q=0.25).replace(0,np.nan)
|
|
q75min = df_min.quantile(q=0.75).replace(0,np.nan)
|
|
q25min = df_min.quantile(q=0.25).replace(0,np.nan)
|
|
|
|
|
|
mean_vals = mean_vals.interpolate()
|
|
|
|
xvals = np.arange(len(mean_vals))
|
|
|
|
df_plot = pd.DataFrame({
|
|
'x':xvals,
|
|
'median':mean_vals,
|
|
'high':q75,
|
|
'low':q75min,
|
|
'high 2':q25min,
|
|
'low 2': q25,
|
|
})
|
|
|
|
df_plot['high'].update(df_plot.pop('high 2'))
|
|
df_plot['low'].update(df_plot.pop('low 2'))
|
|
try:
|
|
df_plot.interpolate(axis=1,inplace=True)
|
|
except TypeError:
|
|
pass
|
|
|
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
|
plot = figure(width=920,tools=TOOLS,
|
|
toolbar_location='above',
|
|
toolbar_sticky=False)
|
|
|
|
#plot.sizing_mode = 'stretch_both'
|
|
|
|
plot.title.text = str(workout) + ' - ' + metric
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
|
|
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",
|
|
)
|
|
|
|
|
|
source = ColumnDataSource(
|
|
df_plot
|
|
)
|
|
|
|
|
|
TIPS = OrderedDict([
|
|
('x','@x'),
|
|
('median','@median'),
|
|
('high','@high'),
|
|
('low','@low')
|
|
])
|
|
|
|
hover = plot.select(type=HoverTool)
|
|
hover.tooltips = TIPS
|
|
|
|
s = 'SPM: {spm_min} - {spm_max}'.format(
|
|
spm_min = spm_min,
|
|
spm_max = spm_max,
|
|
)
|
|
|
|
label = Label(x=50, y=450, x_units='screen',y_units='screen',
|
|
text=s,
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
text_color='black',
|
|
)
|
|
|
|
s2 = 'Time: {activeminutesmin} - {activeminutesmax}'.format(
|
|
activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin),
|
|
activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax)
|
|
)
|
|
|
|
label2 = Label(x=50,y=400, x_units='screen', y_units='screen',
|
|
text=s2,
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
text_color='black',
|
|
)
|
|
|
|
plot.add_layout(label)
|
|
plot.add_layout(label2)
|
|
|
|
if name:
|
|
namelabel = Label(x=50, y=480, x_units='screen', y_units='screen',
|
|
text=name,
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
text_color='black',
|
|
)
|
|
plot.add_layout(namelabel)
|
|
|
|
if notes:
|
|
noteslabel = Label(x=50, y=50, x_units='screen', y_units='screen',
|
|
text=notes,
|
|
background_fill_alpha=0.7,
|
|
background_fill_color='white',
|
|
text_color='black',
|
|
)
|
|
plot.add_layout(noteslabel)
|
|
|
|
if individual_curves:
|
|
for index,row in df.iterrows():
|
|
plot.line(xvals,row,color='lightgray',line_width=1)
|
|
else:
|
|
plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5)
|
|
|
|
plot.line('x','median',source=source,legend_label='median',color="black",
|
|
line_width=3)
|
|
|
|
medrange = mean_vals.max()-mean_vals.min()
|
|
yrange = Range1d(start=mean_vals.min()-0.2*medrange,
|
|
end=mean_vals.max()+0.2*medrange,)
|
|
plot.y_range = yrange
|
|
|
|
plot.add_tools(HoverTool(tooltips=TIPS))
|
|
|
|
if metric == 'boat accelerator curve':
|
|
plot.yaxis.axis_label = "Boat acceleration (m/s^2)"
|
|
elif metric == 'instroke boat speed':
|
|
plot.yaxis.axis_label = "Boat Speed (m/s)"
|
|
vavg = mean_vals.median()
|
|
elif metric == 'oar angle velocity curve':
|
|
plot.yaxis.axis_label = "Oar Angular Velocity (degree/s)"
|
|
elif metric == 'seat curve':
|
|
plot.yaxis.axis_label = "Seat Speed (m/s)"
|
|
|
|
plot.xaxis.axis_label = 'Time (%)'
|
|
|
|
try:
|
|
script, div = components(plot)
|
|
except ValueError:
|
|
script = ""
|
|
div = "Something went wrong with the chart"
|
|
|
|
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:
|
|
_ = datadf['spm']
|
|
except KeyError: # pragma: no cover
|
|
datadf['spm'] = 0
|
|
|
|
try:
|
|
_ = datadf['pace']
|
|
except KeyError: # pragma: no cover
|
|
datadf['pace'] = 0
|
|
|
|
data_dict = datadf.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
|
|
intervals = []
|
|
# 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'] = 0
|
|
|
|
intervals = intervaldf.to_dict("records")
|
|
|
|
chart_data = {
|
|
'title': row.name,
|
|
'x': "time",
|
|
'y1': "pace",
|
|
'y2': "spm",
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
'intervals': intervals,
|
|
}
|
|
|
|
script, div = get_chart("/interactive", chart_data)
|
|
|
|
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.append( {'x': t, 'y': s})
|
|
|
|
markerpoint = {
|
|
'x': time[0],
|
|
'y': spm[0],
|
|
'r': 10,
|
|
}
|
|
|
|
chart_data = {
|
|
'data': data2,
|
|
'markerpoint': markerpoint,
|
|
}
|
|
|
|
|
|
script, div = get_chart("/videochart", chart_data)
|
|
|
|
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 is None:
|
|
title = '{y} vs {x} grouped by {gr}'.format(
|
|
x=xparamname,
|
|
y=yparamname,
|
|
gr=groupname,
|
|
)
|
|
|
|
if extratitle is not None:
|
|
title = title+' '+extratitle
|
|
|
|
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]
|
|
|
|
data_dict = datadf.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
chart_data = {
|
|
'title': title,
|
|
'x': xparam,
|
|
'y': yparam,
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
'errorbars':ploterrorbars,
|
|
'groupname': groupname,
|
|
}
|
|
|
|
script, div = get_chart("/trendflex", chart_data)
|
|
|
|
return script, div
|
|
|
|
|
|
|
|
def interactive_cum_flex_chart2(theworkouts, promember=0,
|
|
xparam='spm',
|
|
yparam1='power',
|
|
yparam2='spm',
|
|
workstrokesonly=False,
|
|
extratitle='',
|
|
trendline=False):
|
|
|
|
ids = [int(w.id) for w in theworkouts]
|
|
|
|
columns = [name for name, d in metrics.rowingmetrics]
|
|
columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
|
|
columns = columns + ['spm', 'driveenergy', 'distance']
|
|
columns_basic = columns_basic + ['spm', 'driveenergy', 'distance']
|
|
|
|
datadf = pd.DataFrame()
|
|
if promember:
|
|
datadf = dataprep.getsmallrowdata_db(columns, ids=ids, doclean=True,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
else:
|
|
datadf = dataprep.getsmallrowdata_db(columns_basic, ids=ids, doclean=True,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
|
|
try:
|
|
_ = datadf[yparam2]
|
|
except KeyError: # pragma: no cover
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
_ = 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
|
|
try: # pragma: no cover
|
|
_ = datadf['driveenergy'].mean()
|
|
except KeyError: # pragma: no cover
|
|
datadf['driveenergy'] = 500.
|
|
|
|
# test if we have power
|
|
try: # pragma: no cover
|
|
_ = datadf['power'].mean()
|
|
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']
|
|
|
|
|
|
datadf['xname'] = axlabels[xparam]
|
|
datadf['yname1'] = axlabels[yparam1]
|
|
if yparam2 != 'None':
|
|
datadf['yname2'] = axlabels[yparam2]
|
|
else: # pragma: no cover
|
|
datadf['yname2'] = axlabels[yparam1]
|
|
|
|
def func(x, a, b):
|
|
return a*x+b
|
|
|
|
x1 = datadf['x1']
|
|
y1 = datadf['y1']
|
|
popt, pcov = optimize.curve_fit(func, x1, y1)
|
|
ytrend = func(x1, popt[0], popt[1])
|
|
datadf['ytrend'] = ytrend
|
|
|
|
data_dict = datadf.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
chart_data = {
|
|
'title': extratitle,
|
|
'x': xparam,
|
|
'y1': yparam1,
|
|
'y2': yparam2,
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
'trendline': trendline,
|
|
}
|
|
|
|
script, div = get_chart("/dots", chart_data)
|
|
|
|
return script, div
|
|
|
|
|
|
|
|
def interactive_flexchart_stacked(id, r, xparam='time',
|
|
yparam1='pace',
|
|
yparam2='power',
|
|
yparam3='hr',
|
|
yparam4='spm',
|
|
mode='erg'):
|
|
|
|
columns = [name for name, d in metrics.rowingmetrics]
|
|
columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
|
|
columns = columns + ['spm', 'driveenergy', 'distance']
|
|
columns_basic = columns_basic + ['spm', 'driveenergy', 'distance']
|
|
|
|
rowdata = pd.DataFrame()
|
|
row = Workout.objects.get(id=id)
|
|
|
|
if ispromember(r.user):
|
|
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True,
|
|
workstrokesonly=False, for_chart=True)
|
|
else:
|
|
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True,
|
|
workstrokesonly=False, for_chart=True)
|
|
|
|
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] = utils.ewmovingaverage(
|
|
rowdata[column], 5)
|
|
except KeyError:
|
|
pass
|
|
|
|
if len(rowdata) < 2:
|
|
if ispromember(r.user):
|
|
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id],
|
|
doclean=False,
|
|
workstrokesonly=False,
|
|
for_chart=True)
|
|
else:
|
|
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id],
|
|
doclean=False,
|
|
workstrokesonly=False,
|
|
for_chart=True)
|
|
|
|
|
|
if rowdata.empty:
|
|
return "", "No valid data"
|
|
|
|
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']
|
|
rowdata[yparam1] = rowdata['y1']
|
|
|
|
try: # pragma: no cover
|
|
rowdata['y2'] = rowdata.loc[:, yparam2]
|
|
except KeyError:
|
|
rowdata['y2'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam2] = rowdata['y2']
|
|
|
|
try:
|
|
rowdata['y3'] = rowdata.loc[:, yparam3]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['y3'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam3] = rowdata['y3']
|
|
|
|
try:
|
|
rowdata['y4'] = rowdata.loc[:, yparam4]
|
|
except KeyError: # pragma: no cover
|
|
rowdata['y4'] = 0*rowdata.loc[:, 'time']
|
|
rowdata[yparam4] = rowdata['y4']
|
|
|
|
# replace nans
|
|
rowdata.fillna(value=0, inplace=True)
|
|
|
|
data_dict = rowdata.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
chart_data = {
|
|
'title': row.name,
|
|
'x': xparam,
|
|
'y1': yparam1,
|
|
'y2': yparam2,
|
|
'y3': yparam3,
|
|
'y4': yparam4,
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
}
|
|
|
|
script, div = get_chart("/stacked", chart_data)
|
|
|
|
return script, div
|
|
|
|
|
|
|
|
def interactive_flex_chart2(id, r, promember=0,
|
|
xparam='time',
|
|
yparam1='pace',
|
|
yparam2='hr',
|
|
plottype='line',
|
|
workstrokesonly=False,
|
|
trendline=False,
|
|
mode='rower'):
|
|
|
|
|
|
columns = [name for name, d in metrics.rowingmetrics]
|
|
columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
|
|
columns = columns + ['spm', 'driveenergy', 'distance']
|
|
columns_basic = columns_basic + ['spm', 'driveenergy', 'distance']
|
|
|
|
datadf = pd.DataFrame()
|
|
if promember:
|
|
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
else:
|
|
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
|
|
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] = utils.ewmovingaverage(
|
|
rowdata[column], 5)
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
if len(rowdata) < 2:
|
|
if promember:
|
|
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id],
|
|
doclean=False,
|
|
workstrokesonly=False, for_chart=True)
|
|
else:
|
|
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=False,
|
|
workstrokesonly=False, for_chart=True)
|
|
workstrokesonly = False
|
|
except (KeyError, TypeError): # pragma: no cover
|
|
workstrokesonly = False
|
|
try:
|
|
_ = rowdata[yparam2]
|
|
except (KeyError, TypeError): # pragma: no cover
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
_ = rowdata[yparam1]
|
|
except (TypeError, KeyError): # pragma: no cover
|
|
yparam1 = 'None'
|
|
|
|
# test if we have drive energy
|
|
try:
|
|
_ = rowdata['driveenergy'].mean()
|
|
except (KeyError, TypeError):
|
|
rowdata['driveenergy'] = 500.
|
|
|
|
# test if we have power
|
|
try:
|
|
_ = rowdata['power'].mean()
|
|
except (KeyError, TypeError):
|
|
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
|
|
|
|
workoutstatesrest = [3]
|
|
|
|
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]
|
|
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']
|
|
rowdata[yparam1] = rowdata['y1']
|
|
|
|
if yparam2 != 'None':
|
|
try:
|
|
rowdata['y2'] = rowdata.loc[:, yparam2]
|
|
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()
|
|
|
|
|
|
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']
|
|
|
|
def func(x, a, b):
|
|
return a*x+b
|
|
|
|
x1 = rowdata['x1']
|
|
y1 = rowdata['y1']
|
|
try:
|
|
popt, pcov = optimize.curve_fit(func, x1, y1)
|
|
ytrend = func(x1, popt[0], popt[1])
|
|
rowdata['ytrend'] = ytrend
|
|
except TypeError: # pragma: no cover
|
|
rowdata['ytrend'] = y1
|
|
|
|
data_dict = rowdata.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
chart_data = {
|
|
'title': row.name,
|
|
'x': xparam,
|
|
'y1': yparam1,
|
|
'y2': yparam2,
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
'trendline': trendline,
|
|
'plottype': plottype,
|
|
}
|
|
|
|
script, div = get_chart("/flex", chart_data)
|
|
|
|
return script, div, 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)
|
|
|
|
try:
|
|
rowdata.dropna(axis=1, how='all', inplace=True)
|
|
except TypeError: # pragma: no cover
|
|
return [
|
|
{'script': "",
|
|
'div': "",
|
|
'notes': ""
|
|
}]
|
|
|
|
|
|
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': ""
|
|
}]
|
|
|
|
lengte = len(rowdata)
|
|
maxlength = 50
|
|
if lengte > 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, TypeError): # pragma: no cover
|
|
pass
|
|
|
|
for f in favorites:
|
|
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:
|
|
_ = rowdata[yparam2]
|
|
except KeyError:
|
|
yparam2 = 'None'
|
|
|
|
try:
|
|
_ = 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'
|
|
|
|
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
|
|
)
|
|
|
|
plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type,
|
|
width=200, height=150,
|
|
)
|
|
|
|
|
|
# plot.sizing_mode = 'stretch_both'
|
|
plot.sizing_mode = 'fixed'
|
|
plot.toolbar.logo = None
|
|
plot.toolbar_location = None
|
|
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 = [name for name, d in metrics.rowingmetrics]
|
|
columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
|
|
add_columns = [
|
|
'ftime', 'distance', 'fpace',
|
|
'power', 'hr', 'spm',
|
|
'time', 'pace', 'workoutstate',
|
|
'workoutid'
|
|
]
|
|
columns = columns + add_columns
|
|
columns_basic = columns_basic + add_columns
|
|
|
|
compute = False
|
|
doclean = False
|
|
if workstrokesonly:
|
|
compute = True
|
|
doclean = True
|
|
|
|
|
|
datadf = pd.DataFrame()
|
|
if promember:
|
|
datadf = dataprep.getsmallrowdata_db(columns, ids=ids, doclean=doclean,
|
|
compute=compute,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
else:
|
|
datadf = dataprep.getsmallrowdata_db(columns_basic, ids=ids, doclean=doclean,
|
|
compute=compute,
|
|
workstrokesonly=workstrokesonly, for_chart=True)
|
|
|
|
datadf['workoutid'] = datadf['workoutid'].astype(int)
|
|
datadf.dropna(axis=1, how='all', inplace=True)
|
|
datadf.dropna(axis=0, how='all', 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']
|
|
|
|
# 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 == 'time'):
|
|
datadf[xparam] = datadf[xparam] - datadf[xparam].iloc[0]
|
|
|
|
datadf = datadf.fillna(0)
|
|
|
|
data_dict = datadf.to_dict("records")
|
|
|
|
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
|
|
|
|
workoutsdict = [{'id': id, 'label': labeldict[id]} for id in ids]
|
|
|
|
chart_data = {
|
|
'title': '',
|
|
'x': xparam,
|
|
'y': yparam,
|
|
'data': data_dict,
|
|
'metrics': metrics_list,
|
|
'plottype': plottype,
|
|
'workouts': workoutsdict,
|
|
}
|
|
|
|
script, div = get_chart("/compare", chart_data)
|
|
return script, div, message, errormessage
|
|
|
|
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'
|
|
|
|
plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type,
|
|
tools=TOOLS,
|
|
toolbar_location="above",
|
|
width=920, height=500,
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
watermarkrange = Range1d(start=0, end=1)
|
|
watermarkalpha = 0.6
|
|
watermarkw = 184
|
|
watermarkh = 35
|
|
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_label = 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,
|
|
width=920,
|
|
toolbar_sticky=False)
|
|
|
|
# add watermark
|
|
watermarkurl = "/static/img/logo7.png"
|
|
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",
|
|
)
|
|
|
|
try:
|
|
plot.title.text = row.name
|
|
except ValueError: # pragma: no cover
|
|
plot.title.text = ""
|
|
#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'):
|
|
|
|
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)
|
|
|
|
hrzones = rower.hrzones
|
|
powerzones = rower.powerzones
|
|
|
|
for w in workouts:
|
|
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())
|
|
iswater = w.workouttype in mytypes.otwtypes
|
|
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)
|
|
if iswater:
|
|
qry = '{ut2} <= power < {ut1}'.format(
|
|
ut1=rower.pw_ut1*rower.otwslack/100., ut2=rower.pw_ut2*rower.otwslack/100.)
|
|
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)
|
|
if iswater:
|
|
qry = '{ut1} <= power < {at}'.format(
|
|
ut1=rower.pw_ut1*rower.otwslack/100., at=rower.pw_at*rower.otwslack/100.)
|
|
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)
|
|
if iswater:
|
|
qry = '{at} <= power < {tr}'.format(at=rower.pw_at*rower.otwslack/100.,
|
|
tr=rower.pw_tr*rower.otwslack/100.)
|
|
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)
|
|
if iswater:
|
|
qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr*rower.otwslack/100.,
|
|
an=rower.pw_an*rower.otwslack/100.)
|
|
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)
|
|
if iswater:
|
|
qry = 'power >= {an}'.format(an=rower.pw_an*rower.otwslack/100.)
|
|
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:
|
|
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
|
|
|
|
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.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.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.width = 550
|
|
p.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)
|
|
|
|
# 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
|