diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 6110cf26..393c5fc1 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -56,7 +56,7 @@ import sys import utils import datautils -from utils import lbstoN,myqueue +from utils import lbstoN,myqueue,is_ranking_piece from timezonefinder import TimezoneFinder @@ -295,6 +295,12 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, except KeyError: pass + try: + mask = datadf['efficiency'] > 200. + datadf.loc[mask, 'efficiency'] = np.nan + except KeyError: + pass + try: mask = datadf['spm'] < 10 datadf.loc[mask, 'spm'] = np.nan @@ -921,6 +927,9 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', w.startdatetime = timezone.now() w.save() + if is_ranking_piece(w): + w.rankingpiece = True + w.save() if privacy == 'visible': ts = Team.objects.filter(rower=r) @@ -1557,7 +1566,11 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True): if extracols and len(ids)==1: w = Workout.objects.get(id=ids[0]) row = rdata(w.csvfilename) - f = row.df['TimeStamp (sec)'].diff().mean() + try: + f = row.df['TimeStamp (sec)'].diff().mean() + except AttributeError: + f = 0 + if f != 0 and not np.isnan(f): windowsize = 2 * (int(10. / (f))) + 1 else: diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index eb5ebae6..3ce3e6fb 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -582,7 +582,53 @@ def deletecpdata_sql(rower_id,table='cpdata',debug=False): conn.close() engine.dispose() +def delete_agegroup_db(age,sex,weightcategory,debug=False): + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + query = sa.text('DELETE from {table} WHERE age={age} and weightcategory = {weightcategory} and sex={sex};'.format( + sex=sex, + age=age, + weightcategory=weightcategory, + table='calcagegrouprecords' + )) + with engine.connect() as conn, conn.begin(): + try: + result = conn.execute(query) + except: + print "Database locked" + conn.close() + engine.dispose() +def update_agegroup_db(age,sex,weightcategory,wcdurations,wcpower, + debug=False): + + delete_agegroup_db(age,sex,weightcategory,debug=debug) + + df = pd.DataFrame( + { + 'duration':wcdurations, + 'power':wcpower, + } + ) + + df['sex'] = sex + df['age'] = age + df['weightcategory'] = weightcategory + + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + table = 'calcagegrouprecords' + with engine.connect() as conn, conn.begin(): + df.to_sql(table, engine, if_exists='append', index=False) + conn.close() + engine.dispose() + def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=pd.Series([]),debug=False): deletecpdata_sql(rower_id,table=table,debug=debug) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e7d4d724..6c465eba 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1,3791 +1,3804 @@ -import colorsys -from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage -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 -from django.utils import timezone - -from bokeh.palettes import Dark2_8 as palette -import itertools -from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc -from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation -from bokeh.charts import Histogram,HeatMap,Area,BoxPlot,Bar -from bokeh.resources import CDN,INLINE -from bokeh.embed import components -from bokeh.layouts import layout,widgetbox -from bokeh.layouts import row as layoutrow -from bokeh.layouts import column as layoutcolumn -from bokeh.models import LinearAxis,LogAxis,Range1d,DatetimeTickFormatter,HoverTool -from bokeh.io import output_file, show, vplot -from bokeh.models import ( - GMapPlot, GMapOptions, ColumnDataSource, Circle, - DataRange1d, PanTool, WheelZoomTool, BoxSelectTool, - SaveTool, ResizeTool, ResetTool, TapTool,CrosshairTool,BoxZoomTool, - Span, Label -) -from bokeh.models.glyphs import ImageURL - -#from bokeh.models.widgets import Slider, Select, TextInput -from bokeh.core.properties import value - -from collections import OrderedDict -from django.conf import settings - -import datetime -import math -import numpy as np -import pandas as pd -from pytz import timezone as tz,utc -from django.utils.timezone import get_current_timezone -from django.utils.timezone import activate -from django.utils import timezone -activate(settings.TIME_ZONE) -thetimezone = get_current_timezone() - -from scipy.stats import linregress,percentileofscore -from scipy import optimize -from scipy.signal import savgol_filter - - -import stravastuff - -from rowers.dataprep import rdata -import rowers.dataprep as dataprep -import rowers.metrics as metrics - -from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima - -from utils import lbstoN -import datautils - -watermarkurl = "/static/img/logo7.png" -watermarksource = ColumnDataSource(dict( - url = [watermarkurl],)) - -watermarkrange = Range1d(start=0,end=1) -watermarkalpha = 0.6 -watermarkx = 0.99 -watermarky = 0.01 -watermarkw = 184 -watermarkh = 35 -watermarkanchor = 'bottom_right' - -def errorbar(fig, x, y, source=ColumnDataSource(), - xerr=False, yerr=False, color='black', - point_kwargs={}, error_kwargs={}): - - - xvalues = source.data[x] - yvalues = source.data[y] - - xerrvalues = source.data['xerror'] - yerrvalues = source.data['yerror'] - try: - colorvalues = source.data['color'] - except KeyError: - colorvalues = ["#%02x%02x%02x" % (255,0,0) for x in xvalues] - - - try: - a = xvalues[0]+1 - if xerr: - x_err_x = [] - x_err_y = [] - err_color = [] - for px, py, err, color in zip(xvalues, yvalues, xerrvalues, colorvalues): - x_err_x.append((px - err, px + err)) - x_err_y.append((py, py)) - (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) - h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) - v = v*0.8 - r, g, b = colorsys.hsv_to_rgb(h, s, v) - color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) - err_color.append(color2) - - fig.multi_line(x_err_x, x_err_y, color=err_color, - name='xerr', - **error_kwargs) - except TypeError: - pass - - try: - a = yvalues[0]+1 - if yerr: - y_err_x = [] - y_err_y = [] - err_color = [] - for px, py, err, color in zip(xvalues, yvalues, yerrvalues, colorvalues): - y_err_x.append((px, px)) - y_err_y.append((py - err, py + err)) - (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) - h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) - v = v*0.8 - r, g, b = colorsys.hsv_to_rgb(h, s, v) - color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) - err_color.append(color2) - - fig.multi_line(y_err_x, y_err_y, color=err_color, - name='yerr',**error_kwargs) - except TypeError: - pass - - fig.circle(x, y, source=source, name='data',color=color, - **point_kwargs) - - -def tailwind(bearing,vwind,winddir): - """ Calculates head-on head/tailwind in direction of rowing - - positive numbers are tail wind - - """ - - b = np.radians(bearing) - w = np.radians(winddir) - - vtail = -vwind*np.cos(w-b) - - return vtail - - -from rowers.dataprep import nicepaceformat,niceformat -from rowers.dataprep import timedeltaconv - -def interactive_boxchart(datadf,fieldname,extratitle=''): - - if datadf.empty: - return '','It looks like there are no data matching your filter' - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,resize,hover' - - plot = BoxPlot(datadf, values=fieldname, label='date', - legend=False, - title=axlabels[fieldname]+' '+extratitle, - outliers=False, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False, - x_mapper_type='datetime') - - yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname]) - plot.y_range = yrange1 - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = axlabels[fieldname] - - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],watermarkx,watermarky, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - - plot.xaxis.formatter = DatetimeTickFormatter( - days=["%d %B %Y"], - months=["%d %B %Y"], - years=["%d %B %Y"], - ) - - if fieldname == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - - plot.xaxis.major_label_orientation = pi/4 - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Value','@y'), - ('Date','@x'), - ]) - - hover.mode = 'mouse' - - script, div = components(plot) - - return script,div - - -def interactive_activitychart(workouts,startdate,enddate,stack='type'): - if len(workouts) == 0: - return "","" - - dates = [] - types = [] - rowers = [] - durations = [] - - for w in workouts: - if w.privacy == 'visible': - dd = w.date.strftime('%m/%d') - du = w.duration.hour*60+w.duration.minute - dates.append(dd) - durations.append(du) - types.append(w.workouttype) - try: - rowers.append(w.user.user.first_name[0]+w.user.user.last_name[0]) - except IndexError: - rowers.append(str(w.user)) - - try: - d = utc.localize(startdate) - except ValueError: - d = startdate - - try: - enddate = utc.localize(enddate) - except ValueError: - pass - - - while d<=enddate: - dates.append(d.strftime('%m/%d')) - durations.append(0) - types.append('rower') - rowers.append('Sander') - d += datetime.timedelta(days=1) - - df = pd.DataFrame({ - 'date':dates, - 'duration':durations, - 'type':types, - 'rower':rowers, - }) - - p = Bar(df,'date',values='duration',title='Activity', - stack=stack, - plot_width=350, - plot_height=250, - toolbar_location = None, - ) - - for legend in p.legend: - new_items = [] - for legend_item in legend.items: - it = legend_item.label['value'] - tot = df[df[stack]==it].duration.sum() - if tot != 0: - new_items.append(legend_item) - legend.items = new_items - - - p.legend.location = "top_left" - p.legend.background_fill_alpha = 0.7 - - p.yaxis.axis_label = 'Minutes' - - script, div = components(p) - - return script,div - -def interactive_forcecurve(theworkouts,workstrokesonly=False): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,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) - - rowdata.dropna(axis=1,how='all',inplace=True) - rowdata.dropna(axis=0,how='any',inplace=True) - - workoutstateswork = [1,4,5,8,9,6,7] - workoutstatesrest = [3] - workoutstatetransition = [0,2,10,11,12,13] - - if workstrokesonly: - try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] - except KeyError: - pass - - if rowdata.empty: - return "","No Valid Data Available","","" - - try: - catchav = rowdata['catch'].mean() - except KeyError: - catchav = 0 - - try: - finishav = rowdata['finish'].mean() - except KeyError: - finishav = 0 - try: - washav = rowdata['wash'].mean() - except KeyError: - washav = 0 - - try: - slipav = rowdata['slip'].mean() - except KeyError: - slipav = 0 - - try: - peakforceav = rowdata['peakforce'].mean() - except KeyError: - peakforceav = 0 - - try: - averageforceav = rowdata['averageforce'].mean() - except KeyError: - averageforceav = 0 - - try: - peakforceangleav = rowdata['peakforceangle'].mean() - except KeyError: - peakforceangleav = 0 - - x = [catchav, - catchav+slipav, - peakforceangleav, - finishav-washav, - finishav] - - thresholdforce = 100 if 'x' in boattype else 200 - #thresholdforce /= 4.45 # N to lbs - y = [0,thresholdforce, - peakforceav, - thresholdforce,0] - - source = ColumnDataSource( - data = dict( - x = x, - y = y, - )) - - - source2 = ColumnDataSource( - rowdata - ) - - plot = Figure(tools=TOOLS, - toolbar_sticky=False) - - # add watermark - 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.line('x','y',source=source,color="red") - - plot.add_layout(avf) - - peakflabel = Label(x=455,y=530,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=465,y=500,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=460,y=470,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=420,y=440,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=455,y=410,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=470,y=380,x_units='screen',y_units='screen', - text="Slip: {slipav:6.2f}".format(slipav=slipav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - washlabel = Label(x=460,y=350,x_units='screen',y_units='screen', - text="Wash: {washav:6.2f}".format(washav=washav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - 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.xaxis.axis_label = "Angle" - plot.yaxis.axis_label = "Force (N)" - plot.title.text = theworkouts[0].name - plot.title.text_font_size=value("1.0em") - - yrange1 = Range1d(start=0,end=900) - plot.y_range = yrange1 - - xrange1 = Range1d(start=yaxmaxima['catch'],end=yaxmaxima['finish']) - plot.x_range = xrange1 - - callback = CustomJS(args = dict( - source=source, - source2=source2, - avf=avf, - avflabel=avflabel, - catchlabel=catchlabel, - finishlabel=finishlabel, - sliplabel=sliplabel, - washlabel=washlabel, - peakflabel=peakflabel, - peakforceanglelabel=peakforceanglelabel, - ), code=""" - var data = source.data - var data2 = source2.data - - var x = data['x'] - var y = data['y'] - 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 minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - - var catchav = 0 - var finishav = 0 - var slipav = 0 - var washav = 0 - var peakforceangleav = 0 - var averageforceav = 0 - var peakforceav = 0 - var count = 0 - - - for (i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - 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] - - 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) - - source.trigger('change'); - """) - - slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, - title="Min SPM",callback=callback) - callback.args["minspm"] = slider_spm_min - - - slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, - title="Max SPM",callback=callback) - callback.args["maxspm"] = slider_spm_max - - slider_work_min = Slider(start=0, end=1500,value=0, step=10, - title="Min Work per Stroke",callback=callback) - callback.args["minwork"] = slider_work_min - - - slider_work_max = Slider(start=0, end=1500,value=1500, step=10, - title="Max Work per Stroke",callback=callback) - callback.args["maxwork"] = slider_work_max - - distmax = 100+100*int(rowdata['distance'].max()/100.) - - slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, - title="Min Distance",callback=callback) - callback.args["mindist"] = slider_dist_min - - slider_dist_max = Slider(start=0,end=distmax,value=distmax, - step=1, - title="Max Distance",callback=callback) - callback.args["maxdist"] = slider_dist_max - - layout = layoutrow([layoutcolumn([slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, - slider_work_min, - slider_work_max, - ], - ), - plot]) - - script, div = components(layout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - - - return [script,div,js_resources,css_resources] - - - -def interactive_histoall(theworkouts): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - - ids = [int(w.id) for w in theworkouts] - - rowdata = dataprep.getsmallrowdata_db(['power'],ids=ids,doclean=True) - - - rowdata.dropna(axis=0,how='any',inplace=True) - - if rowdata.empty: - return "","No Valid Data Available","","" - - histopwr = rowdata['power'].values - if len(histopwr) == 0: - return "","No valid data available","","" - - # throw out nans - histopwr = histopwr[~np.isinf(histopwr)] - histopwr = histopwr[histopwr > 25] - histopwr = histopwr[histopwr < 1000] - - plot = Figure(tools=TOOLS,plot_width=900, - toolbar_sticky=False, - toolbar_location="above" - ) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],watermarkx,watermarky, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - hist,edges = np.histogram(histopwr,bins=150) - - histsum = np.cumsum(hist) - histsum = 100.*histsum/max(histsum) - - hist_norm = 100.*hist/float(hist.sum()) - - source = ColumnDataSource( - data = dict( - left = edges[:-1], - right = edges[1:], - histsum = histsum, - hist_norm = hist_norm, - ) - ) - - -# plot.quad(top='hist_norm',bottom=0,left=edges[:-1],right=edges[1:]) - plot.quad(top='hist_norm',bottom=0,left='left',right='right',source=source) - - plot.xaxis.axis_label = "Power (W)" - plot.yaxis.axis_label = "% of strokes" - plot.y_range = Range1d(0,1.05*max(hist_norm)) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Power(W)','@left{int}'), - ('% of strokes','@hist_norm'), - ('Cumulative %','@histsum{int}'), - ]) - - hover.mode = 'mouse' - - plot.extra_y_ranges["fraction"] = Range1d(start=0,end=105) - plot.line('right','histsum',source=source,color="red", - y_range_name="fraction") - plot.add_layout(LinearAxis(y_range_name="fraction", - axis_label="Cumulative % of strokes"),'right') - - script, div = components(plot) - return [script,div] - - -def leaflet_chart(lat,lon,name=""): - if lat.empty or lon.empty: - return [0,"invalid coordinate data"] - - - # Throw out 0,0 - df = pd.DataFrame({ - 'lat':lat, - 'lon':lon - }) - - df = df.replace(0,np.nan) - df = df.loc[(df!=0).any(axis=1)] - df.fillna(method='bfill',axis=0,inplace=True) - df.fillna(method='ffill',axis=0,inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: - return [0,"invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] - - coordinates = zip(lat,lon) - - scoordinates = "[" - - for x,y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin = latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - - - return script,div - -def leaflet_chart2(lat,lon,name=""): - if lat.empty or lon.empty: - return [0,"invalid coordinate data"] - - - # Throw out 0,0 - df = pd.DataFrame({ - 'lat':lat, - 'lon':lon - }) - - df = df.replace(0,np.nan) - df = df.loc[(df!=0).any(axis=1)] - df.fillna(method='bfill',axis=0,inplace=True) - df.fillna(method='ffill',axis=0,inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: - return [0,"invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] - - coordinates = zip(lat,lon) - - scoordinates = "[" - - for x,y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin = latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - - - return script,div - -def googlemap_chart(lat,lon,name=""): - if lat.empty or lon.empty: - return [0,"invalid coordinate data"] - # plot tools - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' - - map_options = GMapOptions(lat = lat.mean(),lng=lon.mean(), - map_type="roadmap",zoom=13) - - plot = GMapPlot( - x_range=DataRange1d(), y_range=DataRange1d(), - map_options=map_options, - api_key = "AIzaSyAgu1w9QSthaGPMLp8y9JedPoMc9sfEgJ8", - plot_width=400,plot_height=400, - toolbar_sticky=False, - - ) - - - source = ColumnDataSource( - data = dict( - lat=lat, - lon=lon, - ) - ) - - circle = Circle(x="lon",y="lat",size=5,fill_color="blue", - fill_alpha=0.2,line_color=None) - plot.add_glyph(source,circle) - plot.add_tools(PanTool(), WheelZoomTool(), - SaveTool(), ResizeTool(), ResetTool(), - TapTool(),CrosshairTool(), - ) - plot.title.text = name - plot.title.text_font="1.0em" - script, div = components(plot) - - 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 = metrics.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: - duration = distance/velo - fhduration.append(duration) - fhpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = metrics.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - fhduration.append(60.*duration) - fhpower.append(worldclasspower) - except ValueError: - pass - - flduration = [] - flpower = [] - - for distance in distances: - worldclasspower = metrics.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: - duration = distance/velo - flduration.append(duration) - flpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = metrics.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - flduration.append(60.*duration) - flpower.append(worldclasspower) - except ValueError: - pass - - mlduration = [] - mlpower = [] - - for distance in distances: - worldclasspower = metrics.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: - duration = distance/velo - mlduration.append(duration) - mlpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = metrics.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mlduration.append(60.*duration) - mlpower.append(worldclasspower) - except ValueError: - pass - - - mhduration = [] - mhpower = [] - - for distance in distances: - worldclasspower = metrics.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: - duration = distance/velo - mhduration.append(duration) - mhpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = metrics.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mhduration.append(60.*duration) - mhpower.append(worldclasspower) - except ValueError: - pass - - - - fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - errfunc = lambda pars,x,y: fitfunc(pars,x)-y - - p0 = [500,350,10,8000] - - # fitting WC data to three parameter CP model - if len(fhduration)>=4: - p1fh, success = optimize.leastsq(errfunc, p0[:], - args = (fhduration,fhpower)) - else: - p1fh = None - - # fitting WC data to three parameter CP model - if len(flduration)>=4: - p1fl, success = optimize.leastsq(errfunc, p0[:], - args = (flduration,flpower)) - else: - p1fl = None - - # fitting WC data to three parameter CP model - if len(mlduration)>=4: - p1ml, success = optimize.leastsq(errfunc, p0[:], - args = (mlduration,mlpower)) - else: - p1ml = None - - if len(mhduration)>=4: - p1mh, success = optimize.leastsq(errfunc, p0[:], - args = (mhduration,mhpower)) - else: - p1mh = None - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpowerfh = fitfunc(p1fh,fitt) - fitpowerfl = fitfunc(p1fl,fitt) - fitpowerml = fitfunc(p1ml,fitt) - fitpowermh = fitfunc(p1mh,fitt) - - if normalized: - facfh = fitfunc(p1fh,60) - facfl = fitfunc(p1fl,60) - facml = fitfunc(p1ml,60) - facmh = fitfunc(p1mh,60) - fitpowerfh /= facfh - fitpowerfl /= facfl - fitpowermh /= facmh - fitpowerml /= facml - fhpower /= facfh - flpower /= facfl - mlpower /= facml - mhpower /= facmh - - - - source = ColumnDataSource( - data = dict( - duration = fitt, - fitpowerfh = fitpowerfh, - fitpowerfl = fitpowerfl, - fitpowerml = fitpowerml, - fitpowermh = fitpowermh, - flduration = flduration, - flpower = flpower, - fhduration = fhduration, - fhpower = fhpower, - mlduration = mlduration, - mlpower = mlpower, - mhduration = mhduration, - mhpower = mhpower, - ) - ) - - x_axis_type = 'log' - y_axis_type = 'linear' - - plot = Figure(plot_width=900,x_axis_type=x_axis_type) - - plot.line('duration','fitpowerfh',source=source, - legend='Female HW',color='blue') - plot.line('duration','fitpowerfl',source=source, - legend='Female LW',color='red') - - plot.line('duration','fitpowerml',source=source, - legend='Male LW',color='green') - - plot.line('duration','fitpowermh',source=source, - legend='Male HW',color='orange') - - - plot.circle('flduration','flpower',source=source, - fill_color='red',size=15) - - plot.circle('fhduration','fhpower',source=source, - fill_color='blue',size=15) - - plot.circle('mlduration','mlpower',source=source, - fill_color='green',size=15) - - plot.circle('mhduration','mhpower',source=source, - fill_color='orange',size=15) - - plot.title.text = 'age '+str(age) - - plot.xaxis.axis_label = "Duration (seconds)" - if normalized: - plot.yaxis.axis_label = "Power (normalized)" - else: - plot.yaxis.axis_label = "Power (W)" - - script,div = components(plot) - - return script,div - - -def interactive_otwcpchart(powerdf,promember=0): - powerdf = powerdf[~(powerdf == 0).any(axis=1)] - # plot tools - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - - x_axis_type = 'log' - y_axis_type = 'linear' - - deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x)) - powerdf['ftime'] = niceformat(deltas) - - - source = ColumnDataSource( - data = powerdf - ) - - # there is no Paul's law for OTW - - thesecs = powerdf['Delta'] - theavpower = powerdf['CP'] - - p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) - - 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) - - sourcecomplex = ColumnDataSource( - data = dict( - CP = fitpower, - CPmax = ratio*fitpower, - duration = fitt, - ftime = ftime - ) - ) - - # making the plot - plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, - plot_width=900, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - - 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('Delta','CP',source=source,fill_color='red',size=15, - legend='Power Data') - plot.xaxis.axis_label = "Duration (seconds)" - plot.yaxis.axis_label = "Power (W)" - - plot.y_range = Range1d(0,1.5*max(theavpower)) - plot.x_range = Range1d(1,2*max(thesecs)) - plot.legend.orientation = "vertical" - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Duration ','@ftime'), - ('Power (W)','@CP{int}'), - ('Power (W) upper','@CPmax{int}'), - ]) - - hover.mode = 'mouse' - - plot.line('duration','CP',source=sourcecomplex,legend="CP Model", - color='green') - - plot.line('duration','CPmax',source=sourcecomplex,legend="CP Model", - color='red') - - script, div = components(plot) - - return [script,div,p1,ratio,message] - -def interactive_agegroup_plot(df,distance=2000,duration=None, - sex='male',weightcategory='hwt'): - - age = df['age'] - power = df['power'] - name = df['name'] - season = df['season'] - - if duration: - plottitle = sex+' '+weightcategory+' '+duration+' min' - else: - plottitle = sex+' '+weightcategory+' '+distance+'m' - -# poly_coefficients = np.polyfit(age,power,6) - - age2 = np.linspace(11,95) -# poly_vals = np.polyval(poly_coefficients,age2) -# poly_vals = 0.5*(np.abs(poly_vals)+poly_vals) - - fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5]))) - errfunc = lambda pars, x,y: fitfunc(pars,x)-y - - p0 = [700,120,700,10,100,100] - - p1, success = optimize.leastsq(errfunc,p0[:], - args = (age,power)) - - expo_vals = fitfunc(p1, age2) - expo_vals = 0.5*(np.abs(expo_vals)+expo_vals) - - - source = ColumnDataSource( - data = dict( - age = age, - power = power, - age2 = age2, - expo_vals = expo_vals, - season = season, - name=name, - ) - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - - plot = Figure(tools=TOOLS,plot_width=900) - plot.circle('age','power',source=source,fill_color='red',size=15, - legend='World Record') - - plot.line(age2,expo_vals) - plot.xaxis.axis_label = "Age" - plot.yaxis.axis_label = "Concept2 power" - plot.title.text = plottitle - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Name ','@name'), - ('Season ','@season'), - ]) - - hover.mode = 'mouse' - - script,div = components(plot) - - return script,div - -def interactive_cpchart(rower,thedistances,thesecs,theavpower, - theworkouts,promember=0, - wcpower=[],wcdurations=[]): - - message = 0 - # plot tools - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - - x_axis_type = 'log' - y_axis_type = 'linear' - - thesecs = pd.Series(thesecs) - - velo = thedistances/thesecs - p = pd.Series(500./velo) - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source = ColumnDataSource( - data = dict( - dist = thedistances, - duration = thesecs, - spm = 0*theavpower, - tim = niceformat( - thesecs.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - - power = theavpower, - fpace = nicepaceformat(p2), - ) - ) - - # fitting the data to Paul - if len(thedistances)>=2: - paulslope, paulintercept,r,p,stderr = linregress(np.log10(thedistances),p) - else: - paulslope = 5.0/np.log10(2.0) - paulintercept = p[0]-paulslope*np.log10(thedistances[0]) - - - fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.) - - fitp = paulslope*fitx+paulintercept - - fitvelo = 500./fitp - fitpower = 2.8*(fitvelo**3) - fitt = 10**fitx/fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - - sourcepaul = ColumnDataSource( - data = dict( - dist = 10**fitx, - duration = fitt, - power = fitpower, - spm = 0*fitpower, - tim = niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - fpace = nicepaceformat(fitp2), - ) - ) - - - fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - errfunc = lambda pars,x,y: fitfunc(pars,x)-y - - p0 = [500,350,10,8000] - wcpower = pd.Series(wcpower) - wcdurations = pd.Series(wcdurations) - - # fitting WC data to three parameter CP model - if len(wcdurations)>=4: - p1wc, success = optimize.leastsq(errfunc, p0[:], - args = (wcdurations,wcpower)) - else: - p1wc = None - - # fitting the data to three parameter CP model - - - p1 = p0 - if len(thesecs)>=4: - p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower)) - else: - factor = fitfunc(p0,thesecs.mean())/theavpower.mean() - p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] - - - # Get stayer score - if success == 1: - power1min = fitfunc(p1,60.) - power4min = fitfunc(p1,240.) - power6min = fitfunc(p1,360.) - power30min = fitfunc(p1,1800.) - power1h = fitfunc(p1,3600.) - power10sec = fitfunc(p1,10.) - r10sec4min = 100.*power10sec/power4min - r1h4min = 100.*power1h/power4min - r1min6min = 100.*power1min/power6min - r30min6min = 100.*power30min/power6min - - combined = r1h4min-0.2*(r10sec4min-100) - combined2 = r30min6min-1.5*(r1min6min-100) - - dataset = pd.read_csv('static/stats/combined_set.csv') - dataset2 = pd.read_csv('static/stats/combined_set6min.csv') - - stayerscore = int(percentileofscore(dataset['combined'],combined)) - stayerscore2 = int(percentileofscore(dataset2['combined'],combined2)) - else: - stayerscore = None - stayerscore2 = None - - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpower = fitfunc(p1,fitt) - if p1wc is not None: - fitpowerwc = 0.95*fitfunc(p1wc,fitt) - fitpowerexcellent = 0.7*fitfunc(p1wc,fitt) - fitpowergood = 0.6*fitfunc(p1wc,fitt) - fitpowerfair = 0.5*fitfunc(p1wc,fitt) - fitpoweraverage = 0.4*fitfunc(p1wc,fitt) - - else: - fitpowerwc = 0*fitpower - fitpowerexcellent = 0*fitpower - fitpowergood = 0*fitpower - fitpowerfair = 0*fitpower - fitpoweraverage = 0*fitpower - - - - message = "" - if len(fitpower[fitpower<0]) > 0: - message = "CP model fit didn't give correct results" - - fitvelo = (fitpower/2.8)**(1./3.) - fitdist = fitt*fitvelo - fitp = 500./fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - sourcecomplex = ColumnDataSource( - data = dict( - dist = fitdist, - duration = fitt, - tim = niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - spm = 0*fitpower, - power = fitpower, - fitpowerwc = fitpowerwc, - fitpowerexcellent = fitpowerexcellent, - fitpowergood = fitpowergood, - fitpowerfair = fitpowerfair, - fitpoweraverage = fitpoweraverage, - fpace = nicepaceformat(fitp2), - ) - ) - - - - # making the plot - plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, - plot_width=900, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - - 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='Power') - plot.xaxis.axis_label = "Duration (seconds)" - plot.yaxis.axis_label = "Power (W)" - - if stayerscore is not None: - plot.add_layout( - Label(x=100,y=100,x_units='screen',y_units='screen', - text='Stayer Score '+str(stayerscore)+'%', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black') - ) - plot.add_layout( - Label(x=100,y=120,x_units='screen',y_units='screen', - text='Stayer Score (6min) '+str(stayerscore2)+'%', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black') - ) - - cpdata = dataprep.fetchcperg(rower, theworkouts) - - if cpdata.empty: - message = 'Calculations are running in the background. Please refresh this page to see updated results' - return ['','',paulslope,paulintercept,p1,message,p1wc] - - velo = cpdata['distance']/cpdata['delta'] - - p = 500./velo - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source2 = ColumnDataSource( - data = dict( - duration = cpdata['delta'], - power = cpdata['cp'], - tim = niceformat( - cpdata['delta'].fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - dist = cpdata['distance'], - pace = nicepaceformat(p2), - ) - ) - - plot.circle('duration','power',source=source2, - fill_color='blue',size=3, - legend = '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="Paul's Law") - plot.line('duration','power',source=sourcecomplex,legend="CP Model", - color='green') - plot.line('duration','fitpowerwc',source=sourcecomplex, - legend="World Class", - color='Maroon',line_dash='dotted') - - plot.line('duration','fitpowerexcellent',source=sourcecomplex, - legend="90% percentile", - color='Purple',line_dash='dotted') - - plot.line('duration','fitpowergood',source=sourcecomplex, - legend="75% percentile", - color='Olive',line_dash='dotted') - - plot.line('duration','fitpowerfair',source=sourcecomplex, - legend="50% percentile", - color='Gray',line_dash='dotted') - - plot.line('duration','fitpoweraverage',source=sourcecomplex, - legend="25% percentile", - color='SkyBlue',line_dash='dotted') - - - script, div = components(plot) - - - return [script,div,paulslope,paulintercept,p1,message,p1wc] - -def interactive_windchart(id=0,promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = Figure(plot_width=400,plot_height=300) - - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - u = r.user - - - - rr = rrower(hrmax=r.max,hrut2=r.ut2, - hrut1=r.ut1,hrat=r.at, - hrtr=r.tr,hran=r.an,ftp=r.ftp) - - rowdata = rdata(f1,rower=rr) - if rowdata == 0: - return 0 - - dist = rowdata.df.ix[:,'cum_dist'] - - try: - vwind = rowdata.df.ix[:,'vwind'] - winddirection = rowdata.df.ix[:,'winddirection'] - bearing = rowdata.df.ix[:,'bearing'] - except KeyError: - rowdata.add_wind(0,0) - rowdata.add_bearing() - vwind = rowdata.df.ix[:,'vwind'] - winddirection = rowdata.df.ix[:,'winddirection'] - bearing = rowdata.df.ix[:,'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,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - - - # making the plot - plot = Figure(tools=TOOLS,plot_width=400,height=500, -# toolbar_location="below", - toolbar_sticky=False, - ) - plot.line('dist','vwind',source=source,legend="Wind Speed (m/s)") - plot.line('dist','tw',source=source,legend="Tail (+)/Head (-) Wind (m/s)",color='black') - plot.title.text = row.name - # plot.title.text_font_size=value("1.0em") - plot.title.text_font="1.0em" - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "Wind Speed (m/s)" - plot.y_range = Range1d(-7,7) - - - plot.extra_y_ranges = {"winddirection": Range1d(start=0,end=360)} - plot.line('dist','winddirection',source=source, - legend='Wind Direction',color="red", - y_range_name="winddirection") - plot.add_layout(LinearAxis(y_range_name="winddirection",axis_label="Wind Direction (degree)"),'right') - - - script, div = components(plot) - - return [script,div] - -def interactive_streamchart(id=0,promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = Figure(plot_width=400, - ) - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - u = r.user - - - - rr = rrower(hrmax=r.max,hrut2=r.ut2, - hrut1=r.ut1,hrat=r.at, - hrtr=r.tr,hran=r.an,ftp=r.ftp) - - rowdata = rdata(f1,rower=rr) - if rowdata == 0: - return "","No Valid Data Available" - - dist = rowdata.df.ix[:,'cum_dist'] - - try: - vstream = rowdata.df.ix[:,'vstream'] - except KeyError: - rowdata.add_stream(0) - vstream = rowdata.df.ix[:,'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,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - - - # making the plot - plot = Figure(tools=TOOLS,plot_width=400,height=500, -# toolbar_location="below", - toolbar_sticky=False, - ) - plot.line(dist,vstream,legend="River Stream Velocity (m/s)") - plot.title.text = row.name - plot.title.text_font_size=value("1.0em") - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "River Current (m/s)" - plot.y_range = Range1d(-2,2) - - - script, div = components(plot) - - return [script,div] - -def interactive_chart(id=0,promember=0): - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - - columns = ['time','pace','hr','fpace','ftime'] - datadf = dataprep.getsmallrowdata_db(columns,ids=[id]) - - datadf.dropna(axis=0,how='any',inplace=True) - row = Workout.objects.get(id=id) - if datadf.empty: - return "","No Valid Data Available" - else: - datadf.sort_values(by='time',ascending=True,inplace=True) - - #datadf,row = dataprep.getrowdata_db(id=id) - #if datadf.empty: - #return "","No Valid Data Available" - - source = ColumnDataSource( - datadf - ) - - plot = Figure(x_axis_type="datetime",y_axis_type="datetime", - plot_width=400, - plot_height=400, - toolbar_sticky=False, - tools=TOOLS) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],0.01,0.99, - 0.5*watermarkw,0.5*watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - plot.line('time','pace',source=source,legend="Pace") - plot.title.text = row.name - plot.title.text_font_size=value("1.0em") - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - ymax = 90. - ymin = 150. - - if row.workouttype == 'water': - ymax = 90. - ymin = 210. - - plot.y_range = Range1d(1.e3*ymin,1.e3*ymax) - - - hover = plot.select(dict(type=HoverTool)) - - - hover.tooltips = OrderedDict([ - ('Time','@ftime'), - ('Pace','@fpace'), - ('HR','@hr{int}'), - ('SPM','@spm{1.1}'), - ]) - - hover.mode = 'mouse' - - plot.extra_y_ranges["hrax"] = Range1d(start=100,end=200) - plot.line('time','hr',source=source,color="red", - y_range_name="hrax", legend="Heart Rate") - plot.add_layout(LinearAxis(y_range_name="hrax",axis_label="HR"),'right') - - plot.legend.location = "bottom_right" - - script, div = components(plot) - - return [script,div] - -def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', - ploterrorbars=False, - title=None,binsize=1,colorlegend=[]): - - if datadf.empty: - return ['','

No non-zero data in selection

'] - - if xparam == 'workoutid': - xparamname = 'Workout' - else: - xparamname = axlabels[xparam] - - if yparam == 'workoutid': - yparamname = 'Workout' - else: - yparamname = axlabels[yparam] - - if groupby == 'workoutid': - groupname = 'Workout' - elif groupby == 'date': - groupname = 'Date' - else: - groupname = axlabels[groupby] - - - if title==None: - title = '{y} vs {x} grouped by {gr}'.format( - x = xparamname, - y = yparamname, - gr = groupname, - ) - - - if xparam=='distance': - xaxmax = datadf[xparam].max() - xaxmin = datadf[xparam].min() - elif xparam=='time': - tseconds = datadf.ix[:,'time'] - xaxmax = tseconds.max() - xaxmin = 0 - elif xparam == 'workoutid': - xaxmax = datadf[xparam].max()-5 - xaxmin = datadf[xparam].min()+5 - else: - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - - if yparam=='distance': - yaxmax = datadf[yparam].max() - yaxmin = datadf[yparam].min() - elif yparam=='time': - tseconds = datadf.ix[:,'time'] - yaxmax = tseconds.max() - yaxmin = 0 - elif yparam == 'workoutid': - yaxmax = datadf[yparam].max()-5 - yaxmin = datadf[yparam].min()+5 - else: - yaxmax = yaxmaxima[yparam] - yaxmin = yaxminima[yparam] - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - if yparam == 'pace': - y_axis_type = 'datetime' - - source = ColumnDataSource( - datadf, - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' - - if groupby != 'date': - hover = HoverTool(names=['data'], - tooltips = [ - (groupby,'@groupval{1.1}'), - (xparamname,'@x{1.1}'), - (yparamname,'@y') - ]) - else: - hover = HoverTool(names=['data'], - tooltips = [ - (groupby,'@groupval'), - (xparamname,'@x{1.1}'), - (yparamname,'@y') - , - ]) - - hover.mode = 'mouse' - TOOLS = [SaveTool(),PanTool(),BoxZoomTool(),WheelZoomTool(), - ResetTool(),TapTool(),ResizeTool(),hover] - - - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False) #,plot_width=500,plot_height=500) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.title.text = title - plot.title.text_font_size=value("1.0em") - - plot.image_url([watermarkurl],watermarkx,watermarky, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - errorbar(plot,xparam,yparam,source=source, - xerr=ploterrorbars, - yerr=ploterrorbars, - point_kwargs={ - 'line_color':"#969696", - 'size':"groupsize", - 'fill_color':"color", - 'fill_alpha':1.0, - }, - ) - - - - for nr, gvalue, color in colorlegend: - box = BoxAnnotation(bottom=400+20*nr,left=550,top=420+20*nr, - right=570, - bottom_units='screen', - top_units='screen', - left_units='screen', - right_units='screen', - fill_color=color, - fill_alpha=1.0, - line_color=color) - legendlabel = Label(x=571,y=403+20*nr,x_units='screen', - y_units='screen', - text = "{gvalue:3.0f}".format(gvalue=gvalue), - background_fill_alpha=1.0, - text_color='black', - text_font_size=value("0.7em")) - plot.add_layout(box) - plot.add_layout(legendlabel) - - if colorlegend: - legendlabel = Label(x=372,y=300,x_units='screen', - y_units='screen', - text = 'group legend', - text_color='black', - text_font_size=value("0.7em"), - angle=90, - angle_units='deg') - - if xparam == 'workoutid': - plot.xaxis.axis_label = 'Workout' - else: - plot.xaxis.axis_label = axlabels[xparam] - - if yparam == 'workoutid': - plot.xaxis.axis_label = 'Workout' - else: - plot.yaxis.axis_label = axlabels[yparam] - - binlabel = Label(x=100,y=100,x_units='screen', - y_units='screen', - text="Bin size {binsize:3.1f}".format(binsize=binsize), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - plot.add_layout(binlabel) - - yrange1 = Range1d(start=yaxmin,end=yaxmax) - plot.y_range = yrange1 - - xrange1 = Range1d(start=xaxmin,end=xaxmax) - plot.x_range = xrange1 - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - - script,div = components(plot) - - - return [script,div] - -def interactive_cum_flex_chart2(theworkouts,promember=0, - xparam='spm', - yparam1='power', - yparam2='spm', - workstrokesonly=False): - - # datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2) - ids = [int(w.id) for w in theworkouts] - columns = [xparam,yparam1,yparam2,'spm','driveenergy','distance'] - datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=True, - workstrokesonly=workstrokesonly) - - try: - tests = datadf[yparam2] - except KeyError: - yparam2 = 'None' - - try: - tests = datadf[yparam1] - except KeyError: - yparam1 = 'None' - - datadf.dropna(axis=1,how='all',inplace=True) - datadf.dropna(axis=0,how='any',inplace=True) - - # test if we have drive energy - nowork = 1 - try: - test = datadf['driveenergy'].mean() - nowork = 0 - except KeyError: - datadf['driveenergy'] = 500. - - # test if we have power - nopower = 1 - try: - test = datadf['power'].mean() - nopower = 0 - except KeyError: - datadf['power'] = 50. - - - yparamname1 = axlabels[yparam1] - if yparam2 != 'None': - yparamname2 = axlabels[yparam2] - - - # check if dataframe not empty - if datadf.empty: - return ['','

No non-zero data in selection

','',''] - - - try: - datadf['x1'] = datadf.ix[:,xparam] - except KeyError: - datadf['x1'] = datadf['distance'] - try: - datadf['y1'] = datadf.ix[:,yparam1] - except KeyError: - try: - datadf['y1'] = datadf['pace'] - except KeyError: - return ['','

No non-zero data in selection

','',''] - if yparam2 != 'None': - try: - datadf['y2'] = datadf.ix[:,yparam2] - except KeyError: - datadf['y2'] = datadf['y1'] - else: - datadf['y2'] = datadf['y1'] - - - if xparam=='distance': - xaxmax = datadf['x1'].max() - xaxmin = datadf['x1'].min() - else: - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - - # average values - x1mean = datadf['x1'].mean() - - y1mean = datadf['y1'].mean() - y2mean = datadf['y2'].mean() - - - xvals = pd.Series(xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.) - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': - y_axis_type = 'datetime' - y1mean = datadf.ix[:,'pace'].mean() - - datadf['xname'] = axlabels[xparam] - datadf['yname1'] = axlabels[yparam1] - if yparam2 != 'None': - datadf['yname2'] = axlabels[yparam2] - else: - datadf['yname2'] = axlabels[yparam1] - - - source = ColumnDataSource( - datadf - ) - - source2 = ColumnDataSource( - datadf.copy() - ) - - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - 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", - ) - - x1means = Span(location=x1mean,dimension='height',line_color='green', - line_dash=[6,6], line_width=2) - - y1means = Span(location=y1mean,dimension='width',line_color='blue', - line_dash=[6,6],line_width=2) - y2means = y1means - - xlabel = Label(x=100,y=130,x_units='screen',y_units='screen', - text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='green', - ) - - - plot.add_layout(x1means) - plot.add_layout(xlabel) - - plot.add_layout(y1means) - - y1label = Label(x=100,y=100,x_units='screen',y_units='screen', - text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - if yparam1 != 'time' and yparam1 != 'pace': - plot.add_layout(y1label) - - y2label = y1label - plot.circle('x1','y1',source=source2,fill_alpha=0.3,line_color=None, - legend=yparamname1, - ) - - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam1] - - - yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) - plot.y_range = yrange1 - - xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - if yparam1 == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - if yparam2 != 'None': - yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2]) - plot.extra_y_ranges["yax2"] = yrange2 - - plot.circle('x1','y2',color="red",y_range_name="yax2", - legend=yparamname2, - source=source2,fill_alpha=0.3,line_color=None) - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlabels[yparam2]),'right') - - y2means = Span(location=y2mean,dimension='width',line_color='red', - line_dash=[6,6],line_width=2,y_range_name="yax2") - - - plot.add_layout(y2means) - y2label = Label(x=100,y=70,x_units='screen',y_units='screen', - text=axlabels[yparam2]+": {y2mean:6.2f}".format(y2mean=y2mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='red', - ) - if yparam2 != 'pace' and yparam2 != 'time': - plot.add_layout(y2label) - - - callback = CustomJS(args = dict(source=source,source2=source2, - x1means=x1means, - y1means=y1means, - y1label=y1label, - y2label=y2label, - xlabel=xlabel, - y2means=y2means), code=""" - var data = source.data - var data2 = source2.data - var x1 = data['x1'] - var y1 = data['y1'] - var y2 = data['y2'] - var spm1 = data['spm'] - var distance1 = data['distance'] - var driveenergy1 = data['driveenergy'] - var xname = data['xname'][0] - var yname1 = data['yname1'][0] - var yname2 = data['yname2'][0] - - var minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - var xm = 0 - var ym1 = 0 - var ym2 = 0 - - data2['x1'] = [] - data2['y1'] = [] - data2['y2'] = [] - data2['spm'] = [] - data2['distance'] = [] - data2['x1mean'] = [] - data2['y1mean'] = [] - data2['y2mean'] = [] - data2['xvals'] = [] - data2['y1vals'] = [] - data2['y2vals'] = [] - - for (i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - data2['x1'].push(x1[i]) - data2['y1'].push(y1[i]) - data2['y2'].push(y2[i]) - data2['spm'].push(spm1[i]) - data2['distance'].push(distance1[i]) - - xm += x1[i] - ym1 += y1[i] - ym2 += y2[i] - } - } - } - } - - xm /= data2['x1'].length - ym1 /= data2['x1'].length - ym2 /= data2['x1'].length - - data2['x1mean'] = [xm,xm] - data2['y1mean'] = [ym1,ym1] - data2['y2mean'] = [ym2,ym2] - x1means.location = xm - y1means.location = ym1 - y2means.location = ym2 - y1label.text = yname1+': '+(ym1).toFixed(2) - y2label.text = yname2+': '+(ym2).toFixed(2) - xlabel.text = xname+': '+(xm).toFixed(2) - - source2.trigger('change'); - """) - - slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, - title="Min SPM",callback=callback) - callback.args["minspm"] = slider_spm_min - - - slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, - title="Max SPM",callback=callback) - callback.args["maxspm"] = slider_spm_max - - slider_work_min = Slider(start=0.0, end=1500,value=0.0, step=10, - title="Min Work per Stroke",callback=callback) - callback.args["minwork"] = slider_work_min - - - slider_work_max = Slider(start=0.0, end=1500,value=1500.0, step=10, - title="Max Work per Stroke",callback=callback) - callback.args["maxwork"] = slider_work_max - - distmax = 100+100*int(datadf['distance'].max()/100.) - - slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, - title="Min Distance",callback=callback) - callback.args["mindist"] = slider_dist_min - - slider_dist_max = Slider(start=0,end=distmax,value=distmax, - step=1, - title="Max Distance",callback=callback) - callback.args["maxdist"] = slider_dist_max - - layout = layoutrow([layoutcolumn([slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, - slider_work_min, - slider_work_max, - ], - ), - plot]) - - script, div = components(layout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - - - return [script,div,js_resources,css_resources] - - - - -def interactive_flex_chart2(id=0,promember=0, - xparam='time', - yparam1='pace', - yparam2='hr', - plottype='line', - workstrokesonly=False): - - #rowdata,row = dataprep.getrowdata_db(id=id) - columns = [xparam,yparam1,yparam2, - 'ftime','distance','fpace', - 'power','hr','spm','driveenergy', - 'time','pace','workoutstate','time'] - - rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True, - workstrokesonly=workstrokesonly) - - if rowdata.empty: - rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True, - workstrokesonly=False) - workstrokesonly=False - - try: - tests = rowdata[yparam2] - except KeyError: - yparam2 = 'None' - - try: - tests = rowdata[yparam1] - except KeyError: - yparam1 = 'None' - - rowdata.dropna(axis=1,how='all',inplace=True) - - # test if we have drive energy - nowork = 1 - try: - test = rowdata['driveenergy'].mean() - nowork = 0 - except KeyError: - rowdata['driveenergy'] = 500. - - # test if we have power - nopower = 1 - try: - test = rowdata['power'].mean() - nopower = 0 - except KeyError: - rowdata['power'] = 50. - - - row = Workout.objects.get(id=id) - if rowdata.empty: - return "","No valid data" - else: - try: - rowdata.sort_values(by='time',ascending=True,inplace=True) - except KeyError: - pass - - workoutstateswork = [1,4,5,8,9,6,7] - workoutstatesrest = [3] - workoutstatetransition = [0,2,10,11,12,13] - - if workstrokesonly: - try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] - except KeyError: - pass - - try: - tseconds = rowdata.ix[:,'time'] - except KeyError: - return '','No time data - cannot make flex plot','','' - - - try: - rowdata['x1'] = rowdata.ix[:,xparam] - rowmin = rowdata[xparam].min() - except KeyError: - rowdata['x1'] = 0*rowdata.ix[:,'time'] - - try: - rowdata['y1'] = rowdata.ix[:,yparam1] - rowmin = rowdata[yparam1].min() - except KeyError: - rowdata['y1'] = 0*rowdata.ix[:,'time'] - rowdata[yparam1] = rowdata['y1'] - - if yparam2 != 'None': - try: - rowdata['y2'] = rowdata.ix[:,yparam2] - rowmin = rowdata[yparam2].min() - except KeyError: - rowdata['y2'] = 0*rowdata.ix[:,'time'] - rowdata[yparam2] = rowdata['y2'] - else: - rowdata['y2'] = rowdata['y1'] - - if xparam=='time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam=='distance' or xparam=='cumdist': - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - else: - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - - # average values - if xparam != 'time': - try: - x1mean = rowdata['x1'].mean() - except TypeError: - x1mean = 0 - else: - x1mean = 0 - - y1mean = rowdata['y1'].mean() - y2mean = rowdata['y2'].mean() - - if xparam != 'time': - xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. - else: - xvals = np.arange(100) - - # constant power plot - if yparam1 == 'driveenergy': - if xparam == 'spm': - yconstantpower = rowdata['y1'].mean()*rowdata['x1'].mean()/xvals - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': - y_axis_type = 'datetime' - try: - y1mean = rowdata.ix[:,'pace'].mean() - except KeyError: - y1mean = 0 - - - rowdata['xname'] = axlabels[xparam] - try: - rowdata['yname1'] = axlabels[yparam1] - except KeyError: - rowdata['yname1'] = yparam1 - if yparam2 != 'None': - try: - rowdata['yname2'] = axlabels[yparam2] - except KeyError: - rowdata['yname2'] = yparam2 - else: - rowdata['yname2'] = rowdata['yname1'] - - - # prepare data - source = ColumnDataSource( - rowdata - ) - - # second source for filtering - source2 = ColumnDataSource( - rowdata.copy() - ) - - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - - - sizing_mode = 'fixed' # 'scale_width' also looks nice with this example - - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_sticky=False - ) - - - - # add watermark - 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", - ) - - x1means = Span(location=x1mean,dimension='height',line_color='green', - line_dash=[6,6], line_width=2) - - y1means = Span(location=y1mean,dimension='width',line_color='blue', - line_dash=[6,6],line_width=2) - y2means = y1means - - xlabel = Label(x=100,y=130,x_units='screen',y_units='screen', - text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='green', - ) - - annolabel = Label(x=100,y=500,x_units='screen',y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): - plot.add_layout(x1means) - plot.add_layout(xlabel) - - - plot.add_layout(y1means) - plot.add_layout(annolabel) - - try: - yaxlabel = axlabels[yparam1] - except KeyError: - yaxlabel = str(yparam1)+' ' - - - y1label = Label(x=100,y=100,x_units='screen',y_units='screen', - text=yaxlabel+": {y1mean:6.2f}".format(y1mean=y1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - if yparam1 != 'time' and yparam1 != 'pace': - plot.add_layout(y1label) - y2label = y1label - - # average values - if yparam1 == 'driveenergy': - if xparam == 'spm': - plot.line(xvals,yconstantpower,color="green",legend="Constant Power") - - if plottype=='line': - plot.line('x1','y1',source=source2,legend=yaxlabel) - elif plottype=='scatter': - plot.scatter('x1','y1',source=source2,legend=yaxlabel,fill_alpha=0.4, - line_color=None) - - plot.title.text = row.name - plot.title.text_font_size=value("1.0em") - - plot.xaxis.axis_label = axlabels[xparam] - - plot.yaxis.axis_label = yaxlabel - - - - try: - yrange1 = Range1d(start=yaxminima[yparam1], - end=yaxmaxima[yparam1]) - except KeyError: - yrange1 = Range1d(start=rowdata[yparam1].min(), - end=rowdata[yparam1].max()) - - plot.y_range = yrange1 - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): - try: - xrange1 = Range1d(start=yaxminima[xparam], - end=yaxmaxima[xparam]) - except KeyError: - xrange1 = Range1d(start=rowdata[xparam].min(), - end=rowdata[xparam].max()) - - plot.x_range = xrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin,end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - - if yparam1 == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - - if yparam2 != 'None': - try: - yrange2 = Range1d(start=yaxminima[yparam2], - end=yaxmaxima[yparam2]) - except KeyError: - yrange2 = Range1d(start=rowdata[yparam2].min(), - end=rowdata[yparam2].max()) - - plot.extra_y_ranges["yax2"] = yrange2 - #= {"yax2": yrange2} - try: - axlegend = axlabels[yparam2] - except KeyError: - axlegend = str(yparam2)+' ' - - if plottype=='line': - plot.line('x1','y2',color="red",y_range_name="yax2", - legend=axlegend, - source=source2) - - elif plottype=='scatter': - plot.scatter('x1','y2',source=source2,legend=axlegend, - fill_alpha=0.4, - line_color=None,color="red",y_range_name="yax2") - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlegend),'right') - - y2means = Span(location=y2mean,dimension='width',line_color='red', - line_dash=[6,6],line_width=2,y_range_name="yax2") - - - plot.add_layout(y2means) - y2label = Label(x=100,y=70,x_units='screen',y_units='screen', - text=axlegend+": {y2mean:6.2f}".format(y2mean=y2mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='red', - ) - if yparam2 != 'pace' and yparam2 != 'time': - plot.add_layout(y2label) - - hover = plot.select(dict(type=HoverTool)) - - - hover.tooltips = OrderedDict([ - ('Time','@ftime'), - ('Distance','@distance{int}'), - ('Pace','@fpace'), - ('HR','@hr{int}'), - ('SPM','@spm{1.1}'), - ('Power','@power{int}'), - ]) - - hover.mode = 'mouse' - - - callback = CustomJS(args = dict(source=source,source2=source2, - x1means=x1means, - y1means=y1means, - y1label=y1label, - y2label=y2label, - xlabel=xlabel, - annolabel=annolabel, - y2means=y2means, - ), code=""" - var data = source.data - var data2 = source2.data - var x1 = data['x1'] - var y1 = data['y1'] - var y2 = data['y2'] - var spm1 = data['spm'] - var time1 = data['time'] - var pace1 = data['pace'] - var hr1 = data['hr'] - var fpace1 = data['fpace'] - var distance1 = data['distance'] - var power1 = data['power'] - var driveenergy1 = data['driveenergy'] - var xname = data['xname'][0] - var yname1 = data['yname1'][0] - var yname2 = data['yname2'][0] - - 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 - var xm = 0 - var ym1 = 0 - var ym2 = 0 - - data2['x1'] = [] - data2['y1'] = [] - data2['y2'] = [] - data2['spm'] = [] - data2['time'] = [] - data2['pace'] = [] - data2['hr'] = [] - data2['fpace'] = [] - data2['distance'] = [] - data2['power'] = [] - data2['x1mean'] = [] - data2['y1mean'] = [] - data2['y2mean'] = [] - data2['xvals'] = [] - data2['y1vals'] = [] - data2['y2vals'] = [] - - for (i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - data2['x1'].push(x1[i]) - data2['y1'].push(y1[i]) - data2['y2'].push(y2[i]) - data2['spm'].push(spm1[i]) - data2['time'].push(time1[i]) - data2['fpace'].push(fpace1[i]) - data2['pace'].push(pace1[i]) - data2['hr'].push(hr1[i]) - data2['distance'].push(distance1[i]) - data2['power'].push(power1[i]) - - xm += x1[i] - ym1 += y1[i] - ym2 += y2[i] - } - } - } - } - - xm /= data2['x1'].length - ym1 /= data2['x1'].length - ym2 /= data2['x1'].length - - data2['x1mean'] = [xm,xm] - data2['y1mean'] = [ym1,ym1] - data2['y2mean'] = [ym2,ym2] - x1means.location = xm - y1means.location = ym1 - y2means.location = ym2 - y1label.text = yname1+': '+ym1.toFixed(2) - y2label.text = yname2+': '+ym2.toFixed(2) - xlabel.text = xname+': '+xm.toFixed(2) - annolabel.text = annotation - - source2.trigger('change'); - """) - - annotation = TextInput(title="Type your plot notes here", value="", - callback=callback) - callback.args["annotation"] = annotation - - slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, - title="Min SPM",callback=callback) - callback.args["minspm"] = slider_spm_min - - - slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, - title="Max SPM",callback=callback) - callback.args["maxspm"] = slider_spm_max - - slider_work_min = Slider(start=0.0, end=1500,value=0.0, step=10, - title="Min Work per Stroke",callback=callback) - callback.args["minwork"] = slider_work_min - - - slider_work_max = Slider(start=0.0, end=1500,value=1500.0, step=10, - title="Max Work per Stroke",callback=callback) - callback.args["maxwork"] = slider_work_max - - try: - distmax = 100+100*int(rowdata['distance'].max()/100.) - except KeyError: - distmax = 100 - - slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, - title="Min Distance",callback=callback) - callback.args["mindist"] = slider_dist_min - - slider_dist_max = Slider(start=0,end=distmax,value=distmax, - step=1, - title="Max Distance",callback=callback) - callback.args["maxdist"] = slider_dist_max - - layout = layoutrow([layoutcolumn([annotation, - slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, - slider_work_min, - slider_work_max, - ], - ), - plot]) - - script, div = components(layout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - return [script,div,js_resources,css_resources,workstrokesonly] - -def thumbnails_set(r,id,favorites): - charts = [] - - columns = [f.xparam for f in favorites] - columns += [f.yparam1 for f in favorites] - columns += [f.yparam2 for f in favorites] - - columns += ['time'] - try: - rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True) - except: - return [ - {'script':"", - 'div':"", - 'notes':"" - }] - - - rowdata.dropna(axis=1,how='all',inplace=True) - - if rowdata.empty: - return [ - {'script':"", - 'div':"", - 'notes':"" - }] - else: - try: - rowdata.sort_values(by='time',ascending=True,inplace=True) - except KeyError: - pass - - l = len(rowdata) - maxlength = 50 - if l > maxlength: - bins = np.linspace(rowdata['time'].min(),rowdata['time'].max(),maxlength) - groups = rowdata.groupby(np.digitize(rowdata['time'],bins)) - rowdata = groups.mean() - - for f in favorites: - workstrokesonly = not f.reststrokes - script,div = thumbnail_flex_chart( - rowdata, - id=id, - xparam=f.xparam, - yparam1=f.yparam1, - yparam2=f.yparam2, - plottype=f.plottype, - ) - - - charts.append({ - 'script':script, - 'div':div, - 'notes':f.notes}) - - return charts - - -def thumbnail_flex_chart(rowdata,id=0,promember=0, - xparam='time', - yparam1='pace', - yparam2='hr', - plottype='line', - workstrokesonly=False): - - - try: - tests = rowdata[yparam2] - except KeyError: - yparam2 = 'None' - - try: - tests = rowdata[yparam1] - except KeyError: - yparam1 = 'None' - - - - try: - tseconds = rowdata.ix[:,'time'] - except KeyError: - return '','No time data - cannot make flex plot','','' - - - try: - rowdata['x1'] = rowdata.ix[:,xparam] - except KeyError: - rowdata['x1'] = 0*rowdata.ix[:,'time'] - - try: - rowdata['y1'] = rowdata.ix[:,yparam1] - except KeyError: - rowdata['y1'] = 0*rowdata.ix[:,'time'] - - if yparam2 != 'None': - try: - rowdata['y2'] = rowdata.ix[:,yparam2] - except KeyError: - rowdata['y2'] = 0*rowdata.ix[:,'time'] - else: - rowdata['y2'] = rowdata['y1'] - - if xparam=='time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam=='distance' or xparam=='cumdist': - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - else: - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': - y_axis_type = 'datetime' - y1mean = rowdata.ix[:,'pace'].mean() - - - rowdata['xname'] = axlabels[xparam] - rowdata['yname1'] = axlabels[yparam1] - if yparam2 != 'None': - rowdata['yname2'] = axlabels[yparam2] - else: - rowdata['yname2'] = axlabels[yparam1] - - - # prepare data - source = ColumnDataSource( - rowdata - ) - - - sizing_mode = 'fixed' # 'scale_width' also looks nice with this example - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - plot_width=200,plot_height=150, - ) - - - - plot.toolbar.logo = None - plot.toolbar_location = None - #plot.yaxis.visible = False - plot.xaxis.axis_label_text_font_size = "7pt" - plot.yaxis.axis_label_text_font_size = "7pt" - plot.xaxis.major_label_text_font_size = "7pt" - plot.yaxis.major_label_text_font_size = "7pt" - - if plottype=='line': - plot.line('x1','y1',source=source) - elif plottype=='scatter': - plot.scatter('x1','y1',source=source,fill_alpha=0.4, - line_color=None) - - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam1] - - - - yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) - plot.y_range = yrange1 - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): - xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin,end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - - if yparam1 == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - - if yparam2 != 'None': - yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2]) - plot.extra_y_ranges["yax2"] = yrange2 - #= {"yax2": yrange2} - - if plottype=='line': - plot.line('x1','y2',color="red",y_range_name="yax2", - source=source) - - elif plottype=='scatter': - plot.scatter('x1','y2',source=source, - fill_alpha=0.4, - line_color=None,color="red",y_range_name="yax2") - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlabels[yparam2], - major_label_text_font_size="7pt", - axis_label_text_font_size="7pt", - ),'right', - ) - - - script, div = components(plot) - - return [script,div] - - -def interactive_bar_chart(id=0,promember=0): - # check if valid ID exists (workout exists) - rowdata,row = dataprep.getrowdata_db(id=id) - rowdata.dropna(axis=1,how='all',inplace=True) - rowdata.dropna(axis=0,how='any',inplace=True) - - if rowdata.empty: - return "","No Valid Data Available" - - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - source = ColumnDataSource( - rowdata - ) - - plot = Figure(x_axis_type="datetime",y_axis_type="datetime", - toolbar_sticky=False, - plot_width=920, - tools=TOOLS) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],0.01,0.99, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - plot.title.text = row.name - plot.title.text_font_size=value("1.0em") - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours ="", - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - plot.yaxis[0].formatter = DatetimeTickFormatter( - hours = "", - seconds = ["%S"], - minutes = ["%M"], - ) - ymax = 1.0e3*90 - ymin = 1.0e3*180 - - if row.workouttype == 'water': - ymax = 1.0e3*90 - ymin = 1.0e3*210 - - plot.y_range = Range1d(ymin,ymax) - - - hover = plot.select(dict(type=HoverTool)) - - - hover.tooltips = OrderedDict([ - ('Time','@ftime'), - ('Pace','@fpace'), - ('HR','@hr{int}'), - ('SPM','@spm{1.1}'), - ]) - - hover.mode = 'mouse' - - plot.extra_y_ranges["hr"] = Range1d(start=100,end=200) - plot.quad(left='time',top='hr_ut2',bottom='hr_bottom', - right='x_right',source=source,color="gray", - y_range_name="hr", legend=" 0] - - #datadf = datadf[datadf[xparam] > 0] - - # check if dataframe not empty - if datadf.empty: - return ['','

No non-zero data in selection

','','No non-zero data in selection'] - - if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - elif xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - else: - xaxmax = datadf['distance'].max() - xaxmin = datadf['distance'].min() - - if yparam == 'distance': - yaxmin = datadf['distance'].min() - yaxmax = datadf['distance'].max() - elif yparam == 'cumdist': - yaxmin = datadf['cumdist'].min() - yaxmax = datadf['cumdist'].max() - else: - yaxmin = yaxminima[yparam] - yaxmax = yaxmaxima[yparam] - - x_axis_type = 'linear' - y_axis_type = 'linear' - - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - if yparam == 'pace': - y_axis_type = 'datetime' - yaxmax = 90.*1e3 - yaxmin = 150.*1e3 - - if xparam == 'time': - x_axis_type = 'datetime' - - if xparam != 'time': - xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. - else: - xvals = np.arange(100) - - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - plot_width=920, - toolbar_sticky=False) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - 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 - - for id,color in itertools.izip(ids,colors): - group = datadf[datadf['workoutid']==int(id)].copy() - group.sort_values(by='time',ascending=True,inplace=True) - try: - group['x'] = group[xparam] - except KeyError: - group['x'] = group['time'] - errormessage = xparam+' has no values. Plot invalid' - try: - group['y'] = group[yparam] - except KeyError: - group['y'] = 0.0*group['x'] - - ymean = group['y'].mean() - 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: - legend=labeldict[id] - else: - legend=str(id) - - if plottype=='line': - l1 = plot.line('x','y',source=source,color=color,legend=legend) - else: - l1 = plot.scatter('x','y',source=source,color=color,legend=legend, - fill_alpha=0.4,line_color=None) - - plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) - cntr += 1 - - plot.legend.location='bottom_right' - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam] - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): - xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - yrange1 = Range1d(start=yaxmin,end=yaxmax) - plot.y_range = yrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin,end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - script, div = components(plot) - - - - return [script,div,message,errormessage] - - - -def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', - promember=0,plottype='line'): - - - - - columns = [xparam,yparam, - 'ftime','distance','fpace', - 'power','hr','spm', - 'time','pace','workoutstate'] - - # check if valid ID exists (workout exists) - #rowdata1,row1 = dataprep.getrowdata_db(id=id1) - #rowdata2,row2 = dataprep.getrowdata_db(id=id2) - - rowdata1 = dataprep.getsmallrowdata_db(columns,ids=[id1]) - rowdata2 = dataprep.getsmallrowdata_db(columns,ids=[id2]) - for n in ['distance','power','hr','spm','time','pace','workoutstate']: - rowdata1[n].fillna(value=0,inplace=True) - rowdata2[n].fillna(value=0,inplace=True) - - rowdata1.dropna(axis=1,how='all',inplace=True) - rowdata1.dropna(axis=0,how='any',inplace=True) - rowdata2.dropna(axis=1,how='all',inplace=True) - rowdata2.dropna(axis=0,how='any',inplace=True) - - row1 = Workout.objects.get(id=id1) - row2 = Workout.objects.get(id=id2) - - if rowdata1.empty: - return "","No Valid Data Available" - else: - rowdata1.sort_values(by='time',ascending=True,inplace=True) - - if rowdata2.empty: - return "","No Valid Data Available" - else: - rowdata2.sort_values(by='time',ascending=True,inplace=True) - - try: - x1 = rowdata1.ix[:,xparam] - x2 = rowdata2.ix[:,xparam] - - y1 = rowdata1.ix[:,yparam] - y2 = rowdata2.ix[:,yparam] - except KeyError: - return "","No valid Data Available" - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam == 'pace': - y_axis_type = 'datetime' - ymax = 1.0e3*90 - ymin = 1.0e3*180 - - if row1.workouttype == 'water': - ymax = 1.0e3*90 - ymin = 1.0e3*210 - - ftime1 = rowdata1.ix[:,'ftime'] - ftime2 = rowdata2.ix[:,'ftime'] - - hr1 = rowdata1.ix[:,'hr'] - hr2 = rowdata2.ix[:,'hr'] - - - fpace1 = rowdata1.ix[:,'fpace'] - fpace2 = rowdata2.ix[:,'fpace'] - - distance1 = rowdata1.ix[:,'distance'] - distance2 = rowdata2.ix[:,'distance'] - - spm1 = rowdata1.ix[:,'spm'] - spm2 = rowdata2.ix[:,'spm'] - - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - - data1 = pd.DataFrame( - dict( - x1=x1, - y1=y1, - ftime1=ftime1, - fpace1=fpace1, - hr1 = hr1, - spm1 = spm1, - distance1=distance1, - ) - ).dropna() - - data2 = pd.DataFrame( - dict( - x2=x2, - y2=y2, - ftime2=ftime2, - fpace2=fpace2, - hr2 = hr2, - spm2 = spm2, - distance2=distance2, - ) - ).dropna() - - - - source1 = ColumnDataSource( - data1 - ) - - source2 = ColumnDataSource( - data2 - ) - - ymean1 = data1['y1'].mean() - ymean2 = data2['y2'].mean() - - # create interactive plot - plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, - tools=TOOLS, - plot_width=920, - toolbar_sticky=False) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],0.05,watermarky, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='bottom_left', - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - TIPS = OrderedDict([ - ('time','@ftime1'), - ('pace','@fpace1'), - ('hr','@hr1'), - ('spm','@spm1{1.1}'), - ('distance','@distance1{5}'), - ]) - TIPS2 = OrderedDict([ - ('time','@ftime2'), - ('pace','@fpace2'), - ('hr','@hr2'), - ('spm','@spm2{1.1}'), - ('distance','@distance2{5}'), - ]) - - - - hover1 = plot.select(type=HoverTool) - hover1.tooltips = TIPS - hover2 = plot.select(type=HoverTool) - hover2.tooltips = TIPS2 - - - if plottype=='line': - l1 = plot.line('x1','y1',source=source1, - color="blue",legend=row1.name, - ) - l2 = plot.line('x2','y2',source=source2, - color="red",legend=row2.name, - ) - elif plottype=='scatter': - l1 = plot.scatter('x1','y1',source=source1,legend=row1.name, - fill_alpha=0.4, - line_color=None) - l2 = plot.scatter('x2','y2',source=source2,legend=row2.name, - fill_alpha=0.4, - line_color=None,color="red") - - plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) - plot.add_tools(HoverTool(renderers=[l2],tooltips=TIPS2)) - plot.legend.location = "bottom_right" - - plot.title.text = row1.name+' vs '+row2.name - plot.title.text_font_size=value("1.2em") - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam] - - ylabel1 = Label(x=100,y=90,x_units='screen',y_units='screen', - text=axlabels[yparam]+": {ymean1:6.2f}".format( - ymean1=ymean1 - ), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue' - ) - ylabel2 = Label(x=100,y=110,x_units='screen',y_units='screen', - text=axlabels[yparam]+": {ymean2:6.2f}".format( - ymean2=ymean2 - ), - background_fill_alpha=.7, - background_fill_color='white', - text_color='red' - ) - plot.add_layout(ylabel1) - plot.add_layout(ylabel2) - - if xparam == 'time': - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - plot.y_range = Range1d(ymin,ymax) - - - script, div = components(plot) - - return [script,div] - -def interactive_otw_advanced_pace_chart(id=0,promember=0): - # check if valid ID exists (workout exists) - rowdata,row = dataprep.getrowdata_db(id=id) - rowdata.dropna(axis=1,how='all',inplace=True) - rowdata.dropna(axis=0,how='any',inplace=True) - - if rowdata.empty: - return "","No Valid Data Available" - - # Add hover to this comma-separated string and see what changes - if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - source = ColumnDataSource( - rowdata - ) - - - plot = Figure(x_axis_type="datetime",y_axis_type="datetime", - tools=TOOLS, - plot_width=920, - toolbar_sticky=False) - - # add watermark - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl],watermarkx,watermarky, - watermarkw,watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name = "watermark", - y_range_name = "watermark", - ) - - plot.title.text = row.name - plot.title.text_font_size=value("1.2em") - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) - - ymax = 1.0e3*90 - ymin = 1.0e3*210 - - plot.y_range = Range1d(ymin,ymax) - - - hover = plot.select(dict(type=HoverTool)) - - plot.line('time','pace',source=source,legend="Pace",color="black") - plot.line('time','nowindpace',source=source,legend="Corrected Pace",color="red") - - hover.tooltips = OrderedDict([ - ('Time','@ftime'), - ('Pace','@fpace'), - ('Corrected Pace','@fnowindpace'), - ('HR','@hr{int}'), - ('SPM','@spm{1.1}'), - ]) - - hover.mode = 'mouse' - - script, div = components(plot) - - return [script,div] - +import colorsys +from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage +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 +from django.utils import timezone + +from bokeh.palettes import Dark2_8 as palette +import itertools +from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc +from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation +from bokeh.charts import Histogram,HeatMap,Area,BoxPlot,Bar +from bokeh.charts.attributes import CatAttr +from bokeh.resources import CDN,INLINE +from bokeh.embed import components +from bokeh.layouts import layout,widgetbox +from bokeh.layouts import row as layoutrow +from bokeh.layouts import column as layoutcolumn +from bokeh.models import LinearAxis,LogAxis,Range1d,DatetimeTickFormatter,HoverTool +from bokeh.io import output_file, show, vplot +from bokeh.models import ( + GMapPlot, GMapOptions, ColumnDataSource, Circle, + DataRange1d, PanTool, WheelZoomTool, BoxSelectTool, + SaveTool, ResizeTool, ResetTool, TapTool,CrosshairTool,BoxZoomTool, + Span, Label +) +from bokeh.models.glyphs import ImageURL + +#from bokeh.models.widgets import Slider, Select, TextInput +from bokeh.core.properties import value + +from collections import OrderedDict +from django.conf import settings + +import datetime +import math +import numpy as np +import pandas as pd +from pytz import timezone as tz,utc +from django.utils.timezone import get_current_timezone +from django.utils.timezone import activate +from django.utils import timezone +activate(settings.TIME_ZONE) +thetimezone = get_current_timezone() + +from scipy.stats import linregress,percentileofscore +from scipy import optimize +from scipy.signal import savgol_filter + + +import stravastuff + +from rowers.dataprep import rdata +import rowers.dataprep as dataprep +import rowers.metrics as metrics + +from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima + +from utils import lbstoN +import datautils + +watermarkurl = "/static/img/logo7.png" +watermarksource = ColumnDataSource(dict( + url = [watermarkurl],)) + +watermarkrange = Range1d(start=0,end=1) +watermarkalpha = 0.6 +watermarkx = 0.99 +watermarky = 0.01 +watermarkw = 184 +watermarkh = 35 +watermarkanchor = 'bottom_right' + +def errorbar(fig, x, y, source=ColumnDataSource(), + xerr=False, yerr=False, color='black', + point_kwargs={}, error_kwargs={}): + + + xvalues = source.data[x] + yvalues = source.data[y] + + xerrvalues = source.data['xerror'] + yerrvalues = source.data['yerror'] + try: + colorvalues = source.data['color'] + except KeyError: + colorvalues = ["#%02x%02x%02x" % (255,0,0) for x in xvalues] + + + try: + a = xvalues[0]+1 + if xerr: + x_err_x = [] + x_err_y = [] + err_color = [] + for px, py, err, color in zip(xvalues, yvalues, xerrvalues, colorvalues): + x_err_x.append((px - err, px + err)) + x_err_y.append((py, py)) + (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) + h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) + v = v*0.8 + r, g, b = colorsys.hsv_to_rgb(h, s, v) + color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) + err_color.append(color2) + + fig.multi_line(x_err_x, x_err_y, color=err_color, + name='xerr', + **error_kwargs) + except TypeError: + pass + + try: + a = yvalues[0]+1 + if yerr: + y_err_x = [] + y_err_y = [] + err_color = [] + for px, py, err, color in zip(xvalues, yvalues, yerrvalues, colorvalues): + y_err_x.append((px, px)) + y_err_y.append((py - err, py + err)) + (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) + h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) + v = v*0.8 + r, g, b = colorsys.hsv_to_rgb(h, s, v) + color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) + err_color.append(color2) + + fig.multi_line(y_err_x, y_err_y, color=err_color, + name='yerr',**error_kwargs) + except TypeError: + pass + + fig.circle(x, y, source=source, name='data',color=color, + **point_kwargs) + + +def tailwind(bearing,vwind,winddir): + """ Calculates head-on head/tailwind in direction of rowing + + positive numbers are tail wind + + """ + + b = np.radians(bearing) + w = np.radians(winddir) + + vtail = -vwind*np.cos(w-b) + + return vtail + + +from rowers.dataprep import nicepaceformat,niceformat +from rowers.dataprep import timedeltaconv + +def interactive_boxchart(datadf,fieldname,extratitle=''): + + if datadf.empty: + return '','It looks like there are no data matching your filter' + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,resize,hover' + + plot = BoxPlot(datadf, values=fieldname, label='date', + legend=False, + title=axlabels[fieldname]+' '+extratitle, + outliers=False, + tools=TOOLS, + toolbar_location="above", + toolbar_sticky=False, + x_mapper_type='datetime') + + yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname]) + plot.y_range = yrange1 + + plot.xaxis.axis_label = 'Date' + plot.yaxis.axis_label = axlabels[fieldname] + + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],watermarkx,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + + plot.xaxis.formatter = DatetimeTickFormatter( + days=["%d %B %Y"], + months=["%d %B %Y"], + years=["%d %B %Y"], + ) + + if fieldname == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + + plot.xaxis.major_label_orientation = pi/4 + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Value','@y'), + ('Date','@x'), + ]) + + hover.mode = 'mouse' + + script, div = components(plot) + + return script,div + + +def interactive_activitychart(workouts,startdate,enddate,stack='type'): + if len(workouts) == 0: + return "","" + + dates = [] + dates_sorting = [] + types = [] + rowers = [] + durations = [] + + for w in workouts: + if w.privacy == 'visible': + dd = w.date.strftime('%m/%d') + dd2 = w.date.strftime('%Y/%m/%d') + du = w.duration.hour*60+w.duration.minute + dates.append(dd) + dates_sorting.append(dd2) + durations.append(du) + types.append(w.workouttype) + try: + rowers.append(w.user.user.first_name[0]+w.user.user.last_name[0]) + except IndexError: + rowers.append(str(w.user)) + + try: + d = utc.localize(startdate) + except ValueError: + d = startdate + + try: + enddate = utc.localize(enddate) + except ValueError: + pass + + # add dates with no activity + while d<=enddate: + dates.append(d.strftime('%m/%d')) + dates_sorting.append(d.strftime('%Y/%m/%d')) + durations.append(0) + types.append('rower') + rowers.append('Sander') + d += datetime.timedelta(days=1) + + df = pd.DataFrame({ + 'date':dates, + 'date_sorting':dates_sorting, + 'duration':durations, + 'type':types, + 'rower':rowers, + }) + + df.sort_values('date_sorting',inplace=True) + + p = Bar(df,values='duration', + label = CatAttr(columns=['date'], sort=False), + xlabel='Date', + ylabel='Time', + title='Activity', + stack=stack, + plot_width=350, + plot_height=250, + toolbar_location = None, + ) + + for legend in p.legend: + new_items = [] + for legend_item in legend.items: + it = legend_item.label['value'] + tot = df[df[stack]==it].duration.sum() + if tot != 0: + new_items.append(legend_item) + legend.items = new_items + + + p.legend.location = "top_left" + p.legend.background_fill_alpha = 0.7 + + p.yaxis.axis_label = 'Minutes' + + script, div = components(p) + + return script,div + +def interactive_forcecurve(theworkouts,workstrokesonly=False): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,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) + + rowdata.dropna(axis=1,how='all',inplace=True) + rowdata.dropna(axis=0,how='any',inplace=True) + + workoutstateswork = [1,4,5,8,9,6,7] + workoutstatesrest = [3] + workoutstatetransition = [0,2,10,11,12,13] + + if workstrokesonly: + try: + rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] + except KeyError: + pass + + if rowdata.empty: + return "","No Valid Data Available","","" + + try: + catchav = rowdata['catch'].mean() + except KeyError: + catchav = 0 + + try: + finishav = rowdata['finish'].mean() + except KeyError: + finishav = 0 + try: + washav = rowdata['wash'].mean() + except KeyError: + washav = 0 + + try: + slipav = rowdata['slip'].mean() + except KeyError: + slipav = 0 + + try: + peakforceav = rowdata['peakforce'].mean() + except KeyError: + peakforceav = 0 + + try: + averageforceav = rowdata['averageforce'].mean() + except KeyError: + averageforceav = 0 + + try: + peakforceangleav = rowdata['peakforceangle'].mean() + except KeyError: + peakforceangleav = 0 + + x = [catchav, + catchav+slipav, + peakforceangleav, + finishav-washav, + finishav] + + thresholdforce = 100 if 'x' in boattype else 200 + #thresholdforce /= 4.45 # N to lbs + y = [0,thresholdforce, + peakforceav, + thresholdforce,0] + + source = ColumnDataSource( + data = dict( + x = x, + y = y, + )) + + + source2 = ColumnDataSource( + rowdata + ) + + plot = Figure(tools=TOOLS, + toolbar_sticky=False) + + # add watermark + 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.line('x','y',source=source,color="red") + + plot.add_layout(avf) + + peakflabel = Label(x=455,y=530,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=465,y=500,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=460,y=470,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=420,y=440,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=455,y=410,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=470,y=380,x_units='screen',y_units='screen', + text="Slip: {slipav:6.2f}".format(slipav=slipav), + background_fill_alpha=0.7, + background_fill_color='white', + text_color='red', + ) + + washlabel = Label(x=460,y=350,x_units='screen',y_units='screen', + text="Wash: {washav:6.2f}".format(washav=washav), + background_fill_alpha=0.7, + background_fill_color='white', + text_color='red', + ) + + 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.xaxis.axis_label = "Angle" + plot.yaxis.axis_label = "Force (N)" + plot.title.text = theworkouts[0].name + plot.title.text_font_size=value("1.0em") + + yrange1 = Range1d(start=0,end=900) + plot.y_range = yrange1 + + xrange1 = Range1d(start=yaxmaxima['catch'],end=yaxmaxima['finish']) + plot.x_range = xrange1 + + callback = CustomJS(args = dict( + source=source, + source2=source2, + avf=avf, + avflabel=avflabel, + catchlabel=catchlabel, + finishlabel=finishlabel, + sliplabel=sliplabel, + washlabel=washlabel, + peakflabel=peakflabel, + peakforceanglelabel=peakforceanglelabel, + ), code=""" + var data = source.data + var data2 = source2.data + + var x = data['x'] + var y = data['y'] + 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 minspm = minspm.value + var maxspm = maxspm.value + var mindist = mindist.value + var maxdist = maxdist.value + var minwork = minwork.value + var maxwork = maxwork.value + + var catchav = 0 + var finishav = 0 + var slipav = 0 + var washav = 0 + var peakforceangleav = 0 + var averageforceav = 0 + var peakforceav = 0 + var count = 0 + + + for (i=0; i=minspm && spm1[i]<=maxspm) { + if (distance1[i]>=mindist && distance1[i]<=maxdist) { + if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { + 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] + + 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) + + source.trigger('change'); + """) + + slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, + title="Min SPM",callback=callback) + callback.args["minspm"] = slider_spm_min + + + slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, + title="Max SPM",callback=callback) + callback.args["maxspm"] = slider_spm_max + + slider_work_min = Slider(start=0, end=1500,value=0, step=10, + title="Min Work per Stroke",callback=callback) + callback.args["minwork"] = slider_work_min + + + slider_work_max = Slider(start=0, end=1500,value=1500, step=10, + title="Max Work per Stroke",callback=callback) + callback.args["maxwork"] = slider_work_max + + distmax = 100+100*int(rowdata['distance'].max()/100.) + + slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, + title="Min Distance",callback=callback) + callback.args["mindist"] = slider_dist_min + + slider_dist_max = Slider(start=0,end=distmax,value=distmax, + step=1, + title="Max Distance",callback=callback) + callback.args["maxdist"] = slider_dist_max + + layout = layoutrow([layoutcolumn([slider_spm_min, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, + ], + ), + plot]) + + script, div = components(layout) + js_resources = INLINE.render_js() + css_resources = INLINE.render_css() + + + + return [script,div,js_resources,css_resources] + + + +def interactive_histoall(theworkouts): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + + ids = [int(w.id) for w in theworkouts] + + rowdata = dataprep.getsmallrowdata_db(['power'],ids=ids,doclean=True) + + + rowdata.dropna(axis=0,how='any',inplace=True) + + if rowdata.empty: + return "","No Valid Data Available","","" + + histopwr = rowdata['power'].values + if len(histopwr) == 0: + return "","No valid data available","","" + + # throw out nans + histopwr = histopwr[~np.isinf(histopwr)] + histopwr = histopwr[histopwr > 25] + histopwr = histopwr[histopwr < 1000] + + plot = Figure(tools=TOOLS,plot_width=900, + toolbar_sticky=False, + toolbar_location="above" + ) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],watermarkx,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + hist,edges = np.histogram(histopwr,bins=150) + + histsum = np.cumsum(hist) + histsum = 100.*histsum/max(histsum) + + hist_norm = 100.*hist/float(hist.sum()) + + source = ColumnDataSource( + data = dict( + left = edges[:-1], + right = edges[1:], + histsum = histsum, + hist_norm = hist_norm, + ) + ) + + +# plot.quad(top='hist_norm',bottom=0,left=edges[:-1],right=edges[1:]) + plot.quad(top='hist_norm',bottom=0,left='left',right='right',source=source) + + plot.xaxis.axis_label = "Power (W)" + plot.yaxis.axis_label = "% of strokes" + plot.y_range = Range1d(0,1.05*max(hist_norm)) + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Power(W)','@left{int}'), + ('% of strokes','@hist_norm'), + ('Cumulative %','@histsum{int}'), + ]) + + hover.mode = 'mouse' + + plot.extra_y_ranges["fraction"] = Range1d(start=0,end=105) + plot.line('right','histsum',source=source,color="red", + y_range_name="fraction") + plot.add_layout(LinearAxis(y_range_name="fraction", + axis_label="Cumulative % of strokes"),'right') + + script, div = components(plot) + return [script,div] + + +def leaflet_chart(lat,lon,name=""): + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + + # Throw out 0,0 + df = pd.DataFrame({ + 'lat':lat, + 'lon':lon + }) + + df = df.replace(0,np.nan) + df = df.loc[(df!=0).any(axis=1)] + df.fillna(method='bfill',axis=0,inplace=True) + df.fillna(method='ffill',axis=0,inplace=True) + lat = df['lat'] + lon = df['lon'] + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + latmean = lat.mean() + lonmean = lon.mean() + + latbegin = lat[lat.index[0]] + longbegin = lon[lon.index[0]] + latend = lat[lat.index[-1]] + longend = lon[lon.index[-1]] + + coordinates = zip(lat,lon) + + scoordinates = "[" + + for x,y in coordinates: + scoordinates += """[{x},{y}], + """.format( + x=x, + y=y + ) + + scoordinates += "]" + + script = """ + + """.format( + latmean=latmean, + lonmean=lonmean, + latbegin = latbegin, + latend=latend, + longbegin=longbegin, + longend=longend, + scoordinates=scoordinates, + ) + + div = """ +

 

+ """ + + + + return script,div + +def leaflet_chart2(lat,lon,name=""): + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + + # Throw out 0,0 + df = pd.DataFrame({ + 'lat':lat, + 'lon':lon + }) + + df = df.replace(0,np.nan) + df = df.loc[(df!=0).any(axis=1)] + df.fillna(method='bfill',axis=0,inplace=True) + df.fillna(method='ffill',axis=0,inplace=True) + lat = df['lat'] + lon = df['lon'] + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + latmean = lat.mean() + lonmean = lon.mean() + latbegin = lat[lat.index[0]] + longbegin = lon[lon.index[0]] + latend = lat[lat.index[-1]] + longend = lon[lon.index[-1]] + + coordinates = zip(lat,lon) + + scoordinates = "[" + + for x,y in coordinates: + scoordinates += """[{x},{y}], + """.format( + x=x, + y=y + ) + + scoordinates += "]" + + script = """ + + """.format( + latmean=latmean, + lonmean=lonmean, + latbegin = latbegin, + latend=latend, + longbegin=longbegin, + longend=longend, + scoordinates=scoordinates, + ) + + div = """ +

 

+ """ + + + + return script,div + +def googlemap_chart(lat,lon,name=""): + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + # plot tools + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' + + map_options = GMapOptions(lat = lat.mean(),lng=lon.mean(), + map_type="roadmap",zoom=13) + + plot = GMapPlot( + x_range=DataRange1d(), y_range=DataRange1d(), + map_options=map_options, + api_key = "AIzaSyAgu1w9QSthaGPMLp8y9JedPoMc9sfEgJ8", + plot_width=400,plot_height=400, + toolbar_sticky=False, + + ) + + + source = ColumnDataSource( + data = dict( + lat=lat, + lon=lon, + ) + ) + + circle = Circle(x="lon",y="lat",size=5,fill_color="blue", + fill_alpha=0.2,line_color=None) + plot.add_glyph(source,circle) + plot.add_tools(PanTool(), WheelZoomTool(), + SaveTool(), ResizeTool(), ResetTool(), + TapTool(),CrosshairTool(), + ) + plot.title.text = name + plot.title.text_font="1.0em" + script, div = components(plot) + + 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 = metrics.getagegrouprecord( + age, + sex='female', + distance=distance, + weightcategory='hwt' + ) + velo = (worldclasspower/2.8)**(1./3.) + try: + duration = distance/velo + fhduration.append(duration) + fhpower.append(worldclasspower) + except ZeroDivisionError: + pass + for duration in durations: + worldclasspower = metrics.getagegrouprecord( + age, + sex='female', + duration=duration, + weightcategory='hwt' + ) + try: + velo = (worldclasspower/2.8)**(1./3.) + distance = int(60*duration*velo) + fhduration.append(60.*duration) + fhpower.append(worldclasspower) + except ValueError: + pass + + flduration = [] + flpower = [] + + for distance in distances: + worldclasspower = metrics.getagegrouprecord( + age, + sex='female', + distance=distance, + weightcategory='lwt' + ) + velo = (worldclasspower/2.8)**(1./3.) + try: + duration = distance/velo + flduration.append(duration) + flpower.append(worldclasspower) + except ZeroDivisionError: + pass + for duration in durations: + worldclasspower = metrics.getagegrouprecord( + age, + sex='female', + duration=duration, + weightcategory='lwt' + ) + try: + velo = (worldclasspower/2.8)**(1./3.) + distance = int(60*duration*velo) + flduration.append(60.*duration) + flpower.append(worldclasspower) + except ValueError: + pass + + mlduration = [] + mlpower = [] + + for distance in distances: + worldclasspower = metrics.getagegrouprecord( + age, + sex='male', + distance=distance, + weightcategory='lwt' + ) + velo = (worldclasspower/2.8)**(1./3.) + try: + duration = distance/velo + mlduration.append(duration) + mlpower.append(worldclasspower) + except ZeroDivisionError: + pass + for duration in durations: + worldclasspower = metrics.getagegrouprecord( + age, + sex='male', + duration=duration, + weightcategory='lwt' + ) + try: + velo = (worldclasspower/2.8)**(1./3.) + distance = int(60*duration*velo) + mlduration.append(60.*duration) + mlpower.append(worldclasspower) + except ValueError: + pass + + + mhduration = [] + mhpower = [] + + for distance in distances: + worldclasspower = metrics.getagegrouprecord( + age, + sex='male', + distance=distance, + weightcategory='hwt' + ) + velo = (worldclasspower/2.8)**(1./3.) + try: + duration = distance/velo + mhduration.append(duration) + mhpower.append(worldclasspower) + except ZeroDivisionError: + pass + for duration in durations: + worldclasspower = metrics.getagegrouprecord( + age, + sex='male', + duration=duration, + weightcategory='hwt' + ) + try: + velo = (worldclasspower/2.8)**(1./3.) + distance = int(60*duration*velo) + mhduration.append(60.*duration) + mhpower.append(worldclasspower) + except ValueError: + pass + + + + fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + p0 = [500,350,10,8000] + + # fitting WC data to three parameter CP model + if len(fhduration)>=4: + p1fh, success = optimize.leastsq(errfunc, p0[:], + args = (fhduration,fhpower)) + else: + p1fh = None + + # fitting WC data to three parameter CP model + if len(flduration)>=4: + p1fl, success = optimize.leastsq(errfunc, p0[:], + args = (flduration,flpower)) + else: + p1fl = None + + # fitting WC data to three parameter CP model + if len(mlduration)>=4: + p1ml, success = optimize.leastsq(errfunc, p0[:], + args = (mlduration,mlpower)) + else: + p1ml = None + + if len(mhduration)>=4: + p1mh, success = optimize.leastsq(errfunc, p0[:], + args = (mhduration,mhpower)) + else: + p1mh = None + + fitt = pd.Series(10**(4*np.arange(100)/100.)) + + fitpowerfh = fitfunc(p1fh,fitt) + fitpowerfl = fitfunc(p1fl,fitt) + fitpowerml = fitfunc(p1ml,fitt) + fitpowermh = fitfunc(p1mh,fitt) + + if normalized: + facfh = fitfunc(p1fh,60) + facfl = fitfunc(p1fl,60) + facml = fitfunc(p1ml,60) + facmh = fitfunc(p1mh,60) + fitpowerfh /= facfh + fitpowerfl /= facfl + fitpowermh /= facmh + fitpowerml /= facml + fhpower /= facfh + flpower /= facfl + mlpower /= facml + mhpower /= facmh + + + + source = ColumnDataSource( + data = dict( + duration = fitt, + fitpowerfh = fitpowerfh, + fitpowerfl = fitpowerfl, + fitpowerml = fitpowerml, + fitpowermh = fitpowermh, + flduration = flduration, + flpower = flpower, + fhduration = fhduration, + fhpower = fhpower, + mlduration = mlduration, + mlpower = mlpower, + mhduration = mhduration, + mhpower = mhpower, + ) + ) + + x_axis_type = 'log' + y_axis_type = 'linear' + + plot = Figure(plot_width=900,x_axis_type=x_axis_type) + + plot.line('duration','fitpowerfh',source=source, + legend='Female HW',color='blue') + plot.line('duration','fitpowerfl',source=source, + legend='Female LW',color='red') + + plot.line('duration','fitpowerml',source=source, + legend='Male LW',color='green') + + plot.line('duration','fitpowermh',source=source, + legend='Male HW',color='orange') + + + plot.circle('flduration','flpower',source=source, + fill_color='red',size=15) + + plot.circle('fhduration','fhpower',source=source, + fill_color='blue',size=15) + + plot.circle('mlduration','mlpower',source=source, + fill_color='green',size=15) + + plot.circle('mhduration','mhpower',source=source, + fill_color='orange',size=15) + + plot.title.text = 'age '+str(age) + + plot.xaxis.axis_label = "Duration (seconds)" + if normalized: + plot.yaxis.axis_label = "Power (normalized)" + else: + plot.yaxis.axis_label = "Power (W)" + + script,div = components(plot) + + return script,div + + +def interactive_otwcpchart(powerdf,promember=0): + powerdf = powerdf[~(powerdf == 0).any(axis=1)] + # plot tools + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + x_axis_type = 'log' + y_axis_type = 'linear' + + deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x)) + powerdf['ftime'] = niceformat(deltas) + + + source = ColumnDataSource( + data = powerdf + ) + + # there is no Paul's law for OTW + + thesecs = powerdf['Delta'] + theavpower = powerdf['CP'] + + p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) + + 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) + + sourcecomplex = ColumnDataSource( + data = dict( + CP = fitpower, + CPmax = ratio*fitpower, + duration = fitt, + ftime = ftime + ) + ) + + # making the plot + plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + + 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('Delta','CP',source=source,fill_color='red',size=15, + legend='Power Data') + plot.xaxis.axis_label = "Duration (seconds)" + plot.yaxis.axis_label = "Power (W)" + + plot.y_range = Range1d(0,1.5*max(theavpower)) + plot.x_range = Range1d(1,2*max(thesecs)) + plot.legend.orientation = "vertical" + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Duration ','@ftime'), + ('Power (W)','@CP{int}'), + ('Power (W) upper','@CPmax{int}'), + ]) + + hover.mode = 'mouse' + + plot.line('duration','CP',source=sourcecomplex,legend="CP Model", + color='green') + + plot.line('duration','CPmax',source=sourcecomplex,legend="CP Model", + color='red') + + script, div = components(plot) + + return [script,div,p1,ratio,message] + +def interactive_agegroup_plot(df,distance=2000,duration=None, + sex='male',weightcategory='hwt'): + + age = df['age'] + power = df['power'] + name = df['name'] + season = df['season'] + + if duration: + plottitle = sex+' '+weightcategory+' '+duration+' min' + else: + plottitle = sex+' '+weightcategory+' '+distance+'m' + +# poly_coefficients = np.polyfit(age,power,6) + + age2 = np.linspace(11,95) +# poly_vals = np.polyval(poly_coefficients,age2) +# poly_vals = 0.5*(np.abs(poly_vals)+poly_vals) + + fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5]))) + errfunc = lambda pars, x,y: fitfunc(pars,x)-y + + p0 = [700,120,700,10,100,100] + + p1, success = optimize.leastsq(errfunc,p0[:], + args = (age,power)) + + expo_vals = fitfunc(p1, age2) + expo_vals = 0.5*(np.abs(expo_vals)+expo_vals) + + + source = ColumnDataSource( + data = dict( + age = age, + power = power, + age2 = age2, + expo_vals = expo_vals, + season = season, + name=name, + ) + ) + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + + plot = Figure(tools=TOOLS,plot_width=900) + plot.circle('age','power',source=source,fill_color='red',size=15, + legend='World Record') + + plot.line(age2,expo_vals) + plot.xaxis.axis_label = "Age" + plot.yaxis.axis_label = "Concept2 power" + plot.title.text = plottitle + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Name ','@name'), + ('Season ','@season'), + ]) + + hover.mode = 'mouse' + + script,div = components(plot) + + return script,div + +def interactive_cpchart(rower,thedistances,thesecs,theavpower, + theworkouts,promember=0, + wcpower=[],wcdurations=[]): + + message = 0 + # plot tools + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + x_axis_type = 'log' + y_axis_type = 'linear' + + thesecs = pd.Series(thesecs) + + velo = thedistances/thesecs + p = pd.Series(500./velo) + + p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + + source = ColumnDataSource( + data = dict( + dist = thedistances, + duration = thesecs, + spm = 0*theavpower, + tim = niceformat( + thesecs.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + ), + + power = theavpower, + fpace = nicepaceformat(p2), + ) + ) + + # fitting the data to Paul + if len(thedistances)>=2: + paulslope, paulintercept,r,p,stderr = linregress(np.log10(thedistances),p) + else: + paulslope = 5.0/np.log10(2.0) + paulintercept = p[0]-paulslope*np.log10(thedistances[0]) + + + fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.) + + fitp = paulslope*fitx+paulintercept + + fitvelo = 500./fitp + fitpower = 2.8*(fitvelo**3) + fitt = 10**fitx/fitvelo + fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + + + sourcepaul = ColumnDataSource( + data = dict( + dist = 10**fitx, + duration = fitt, + power = fitpower, + spm = 0*fitpower, + tim = niceformat( + fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + ), + fpace = nicepaceformat(fitp2), + ) + ) + + + fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + p0 = [500,350,10,8000] + wcpower = pd.Series(wcpower) + wcdurations = pd.Series(wcdurations) + + # fitting WC data to three parameter CP model + if len(wcdurations)>=4: + p1wc, success = optimize.leastsq(errfunc, p0[:], + args = (wcdurations,wcpower)) + else: + p1wc = None + + # fitting the data to three parameter CP model + + + p1 = p0 + if len(thesecs)>=4: + p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower)) + else: + factor = fitfunc(p0,thesecs.mean())/theavpower.mean() + p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] + + + # Get stayer score + if success == 1: + power1min = fitfunc(p1,60.) + power4min = fitfunc(p1,240.) + power6min = fitfunc(p1,360.) + power30min = fitfunc(p1,1800.) + power1h = fitfunc(p1,3600.) + power10sec = fitfunc(p1,10.) + r10sec4min = 100.*power10sec/power4min + r1h4min = 100.*power1h/power4min + r1min6min = 100.*power1min/power6min + r30min6min = 100.*power30min/power6min + + combined = r1h4min-0.2*(r10sec4min-100) + combined2 = r30min6min-1.5*(r1min6min-100) + + dataset = pd.read_csv('static/stats/combined_set.csv') + dataset2 = pd.read_csv('static/stats/combined_set6min.csv') + + stayerscore = int(percentileofscore(dataset['combined'],combined)) + stayerscore2 = int(percentileofscore(dataset2['combined'],combined2)) + else: + stayerscore = None + stayerscore2 = None + + + fitt = pd.Series(10**(4*np.arange(100)/100.)) + + fitpower = fitfunc(p1,fitt) + if p1wc is not None: + fitpowerwc = 0.95*fitfunc(p1wc,fitt) + fitpowerexcellent = 0.7*fitfunc(p1wc,fitt) + fitpowergood = 0.6*fitfunc(p1wc,fitt) + fitpowerfair = 0.5*fitfunc(p1wc,fitt) + fitpoweraverage = 0.4*fitfunc(p1wc,fitt) + + else: + fitpowerwc = 0*fitpower + fitpowerexcellent = 0*fitpower + fitpowergood = 0*fitpower + fitpowerfair = 0*fitpower + fitpoweraverage = 0*fitpower + + + + message = "" + if len(fitpower[fitpower<0]) > 0: + message = "CP model fit didn't give correct results" + + fitvelo = (fitpower/2.8)**(1./3.) + fitdist = fitt*fitvelo + fitp = 500./fitvelo + fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + + sourcecomplex = ColumnDataSource( + data = dict( + dist = fitdist, + duration = fitt, + tim = niceformat( + fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + ), + spm = 0*fitpower, + power = fitpower, + fitpowerwc = fitpowerwc, + fitpowerexcellent = fitpowerexcellent, + fitpowergood = fitpowergood, + fitpowerfair = fitpowerfair, + fitpoweraverage = fitpoweraverage, + fpace = nicepaceformat(fitp2), + ) + ) + + + + # making the plot + plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + + 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='Power') + plot.xaxis.axis_label = "Duration (seconds)" + plot.yaxis.axis_label = "Power (W)" + + if stayerscore is not None: + plot.add_layout( + Label(x=100,y=100,x_units='screen',y_units='screen', + text='Stayer Score '+str(stayerscore)+'%', + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black') + ) +# plot.add_layout( +# Label(x=100,y=120,x_units='screen',y_units='screen', +# text='Stayer Score (6min) '+str(stayerscore2)+'%', +# background_fill_alpha=0.7, +# background_fill_color='white', +# text_color='black') +# ) + + cpdata = dataprep.fetchcperg(rower, theworkouts) + + if cpdata.empty: + message = 'Calculations are running in the background. Please refresh this page to see updated results' + return ['','',paulslope,paulintercept,p1,message,p1wc] + + velo = cpdata['distance']/cpdata['delta'] + + p = 500./velo + + p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + + source2 = ColumnDataSource( + data = dict( + duration = cpdata['delta'], + power = cpdata['cp'], + tim = niceformat( + cpdata['delta'].fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + ), + dist = cpdata['distance'], + pace = nicepaceformat(p2), + ) + ) + + plot.circle('duration','power',source=source2, + fill_color='blue',size=3, + legend = '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="Paul's Law") + plot.line('duration','power',source=sourcecomplex,legend="CP Model", + color='green') + if p1wc is not None: + plot.line('duration','fitpowerwc',source=sourcecomplex, + legend="World Class", + color='Maroon',line_dash='dotted') + + plot.line('duration','fitpowerexcellent',source=sourcecomplex, + legend="90% percentile", + color='Purple',line_dash='dotted') + + plot.line('duration','fitpowergood',source=sourcecomplex, + legend="75% percentile", + color='Olive',line_dash='dotted') + + plot.line('duration','fitpowerfair',source=sourcecomplex, + legend="50% percentile", + color='Gray',line_dash='dotted') + + plot.line('duration','fitpoweraverage',source=sourcecomplex, + legend="25% percentile", + color='SkyBlue',line_dash='dotted') + + + script, div = components(plot) + + + return [script,div,paulslope,paulintercept,p1,message,p1wc] + +def interactive_windchart(id=0,promember=0): + # check if valid ID exists (workout exists) + row = Workout.objects.get(id=id) + # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") + + f1 = row.csvfilename + + # create interactive plot + plot = Figure(plot_width=400,plot_height=300) + + # get user + # u = User.objects.get(id=row.user.id) + r = row.user + u = r.user + + + + rr = rrower(hrmax=r.max,hrut2=r.ut2, + hrut1=r.ut1,hrat=r.at, + hrtr=r.tr,hran=r.an,ftp=r.ftp) + + rowdata = rdata(f1,rower=rr) + if rowdata == 0: + return 0 + + dist = rowdata.df.ix[:,'cum_dist'] + + try: + vwind = rowdata.df.ix[:,'vwind'] + winddirection = rowdata.df.ix[:,'winddirection'] + bearing = rowdata.df.ix[:,'bearing'] + except KeyError: + rowdata.add_wind(0,0) + rowdata.add_bearing() + vwind = rowdata.df.ix[:,'vwind'] + winddirection = rowdata.df.ix[:,'winddirection'] + bearing = rowdata.df.ix[:,'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,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + + + + # making the plot + plot = Figure(tools=TOOLS,plot_width=400,height=500, +# toolbar_location="below", + toolbar_sticky=False, + ) + plot.line('dist','vwind',source=source,legend="Wind Speed (m/s)") + plot.line('dist','tw',source=source,legend="Tail (+)/Head (-) Wind (m/s)",color='black') + plot.title.text = row.name + # plot.title.text_font_size=value("1.0em") + plot.title.text_font="1.0em" + plot.xaxis.axis_label = "Distance (m)" + plot.yaxis.axis_label = "Wind Speed (m/s)" + plot.y_range = Range1d(-7,7) + + + plot.extra_y_ranges = {"winddirection": Range1d(start=0,end=360)} + plot.line('dist','winddirection',source=source, + legend='Wind Direction',color="red", + y_range_name="winddirection") + plot.add_layout(LinearAxis(y_range_name="winddirection",axis_label="Wind Direction (degree)"),'right') + + + script, div = components(plot) + + return [script,div] + +def interactive_streamchart(id=0,promember=0): + # check if valid ID exists (workout exists) + row = Workout.objects.get(id=id) + # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") + + f1 = row.csvfilename + + # create interactive plot + plot = Figure(plot_width=400, + ) + # get user + # u = User.objects.get(id=row.user.id) + r = row.user + u = r.user + + + + rr = rrower(hrmax=r.max,hrut2=r.ut2, + hrut1=r.ut1,hrat=r.at, + hrtr=r.tr,hran=r.an,ftp=r.ftp) + + rowdata = rdata(f1,rower=rr) + if rowdata == 0: + return "","No Valid Data Available" + + dist = rowdata.df.ix[:,'cum_dist'] + + try: + vstream = rowdata.df.ix[:,'vstream'] + except KeyError: + rowdata.add_stream(0) + vstream = rowdata.df.ix[:,'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,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + + + + # making the plot + plot = Figure(tools=TOOLS,plot_width=400,height=500, +# toolbar_location="below", + toolbar_sticky=False, + ) + plot.line(dist,vstream,legend="River Stream Velocity (m/s)") + plot.title.text = row.name + plot.title.text_font_size=value("1.0em") + plot.xaxis.axis_label = "Distance (m)" + plot.yaxis.axis_label = "River Current (m/s)" + plot.y_range = Range1d(-2,2) + + + script, div = components(plot) + + return [script,div] + +def interactive_chart(id=0,promember=0): + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + columns = ['time','pace','hr','fpace','ftime'] + datadf = dataprep.getsmallrowdata_db(columns,ids=[id]) + + datadf.dropna(axis=0,how='any',inplace=True) + row = Workout.objects.get(id=id) + if datadf.empty: + return "","No Valid Data Available" + else: + datadf.sort_values(by='time',ascending=True,inplace=True) + + #datadf,row = dataprep.getrowdata_db(id=id) + #if datadf.empty: + #return "","No Valid Data Available" + + source = ColumnDataSource( + datadf + ) + + plot = Figure(x_axis_type="datetime",y_axis_type="datetime", + plot_width=400, + plot_height=400, + toolbar_sticky=False, + tools=TOOLS) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],0.01,0.99, + 0.5*watermarkw,0.5*watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor='top_left', + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + plot.line('time','pace',source=source,legend="Pace") + plot.title.text = row.name + plot.title.text_font_size=value("1.0em") + plot.xaxis.axis_label = "Time" + plot.yaxis.axis_label = "Pace (/500m)" + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + ymax = 90. + ymin = 150. + + if row.workouttype == 'water': + ymax = 90. + ymin = 210. + + plot.y_range = Range1d(1.e3*ymin,1.e3*ymax) + + + hover = plot.select(dict(type=HoverTool)) + + + hover.tooltips = OrderedDict([ + ('Time','@ftime'), + ('Pace','@fpace'), + ('HR','@hr{int}'), + ('SPM','@spm{1.1}'), + ]) + + hover.mode = 'mouse' + + plot.extra_y_ranges["hrax"] = Range1d(start=100,end=200) + plot.line('time','hr',source=source,color="red", + y_range_name="hrax", legend="Heart Rate") + plot.add_layout(LinearAxis(y_range_name="hrax",axis_label="HR"),'right') + + plot.legend.location = "bottom_right" + + script, div = components(plot) + + return [script,div] + +def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', + ploterrorbars=False, + title=None,binsize=1,colorlegend=[]): + + if datadf.empty: + return ['','

No non-zero data in selection

'] + + if xparam == 'workoutid': + xparamname = 'Workout' + else: + xparamname = axlabels[xparam] + + if yparam == 'workoutid': + yparamname = 'Workout' + else: + yparamname = axlabels[yparam] + + if groupby == 'workoutid': + groupname = 'Workout' + elif groupby == 'date': + groupname = 'Date' + else: + groupname = axlabels[groupby] + + + if title==None: + title = '{y} vs {x} grouped by {gr}'.format( + x = xparamname, + y = yparamname, + gr = groupname, + ) + + + if xparam=='distance': + xaxmax = datadf[xparam].max() + xaxmin = datadf[xparam].min() + elif xparam=='time': + tseconds = datadf.ix[:,'time'] + xaxmax = tseconds.max() + xaxmin = 0 + elif xparam == 'workoutid': + xaxmax = datadf[xparam].max()-5 + xaxmin = datadf[xparam].min()+5 + else: + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + + if yparam=='distance': + yaxmax = datadf[yparam].max() + yaxmin = datadf[yparam].min() + elif yparam=='time': + tseconds = datadf.ix[:,'time'] + yaxmax = tseconds.max() + yaxmin = 0 + elif yparam == 'workoutid': + yaxmax = datadf[yparam].max()-5 + yaxmin = datadf[yparam].min()+5 + else: + yaxmax = yaxmaxima[yparam] + yaxmin = yaxminima[yparam] + + x_axis_type = 'linear' + y_axis_type = 'linear' + if xparam == 'time': + x_axis_type = 'datetime' + if yparam == 'pace': + y_axis_type = 'datetime' + + source = ColumnDataSource( + datadf, + ) + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' + + if groupby != 'date': + hover = HoverTool(names=['data'], + tooltips = [ + (groupby,'@groupval{1.1}'), + (xparamname,'@x{1.1}'), + (yparamname,'@y') + ]) + else: + hover = HoverTool(names=['data'], + tooltips = [ + (groupby,'@groupval'), + (xparamname,'@x{1.1}'), + (yparamname,'@y') + , + ]) + + hover.mode = 'mouse' + TOOLS = [SaveTool(),PanTool(),BoxZoomTool(),WheelZoomTool(), + ResetTool(),TapTool(),ResizeTool(),hover] + + + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + toolbar_location="above", + toolbar_sticky=False) #,plot_width=500,plot_height=500) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.title.text = title + plot.title.text_font_size=value("1.0em") + + plot.image_url([watermarkurl],watermarkx,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + errorbar(plot,xparam,yparam,source=source, + xerr=ploterrorbars, + yerr=ploterrorbars, + point_kwargs={ + 'line_color':"#969696", + 'size':"groupsize", + 'fill_color':"color", + 'fill_alpha':1.0, + }, + ) + + + + for nr, gvalue, color in colorlegend: + box = BoxAnnotation(bottom=400+20*nr,left=550,top=420+20*nr, + right=570, + bottom_units='screen', + top_units='screen', + left_units='screen', + right_units='screen', + fill_color=color, + fill_alpha=1.0, + line_color=color) + legendlabel = Label(x=571,y=403+20*nr,x_units='screen', + y_units='screen', + text = "{gvalue:3.0f}".format(gvalue=gvalue), + background_fill_alpha=1.0, + text_color='black', + text_font_size=value("0.7em")) + plot.add_layout(box) + plot.add_layout(legendlabel) + + if colorlegend: + legendlabel = Label(x=372,y=300,x_units='screen', + y_units='screen', + text = 'group legend', + text_color='black', + text_font_size=value("0.7em"), + angle=90, + angle_units='deg') + + if xparam == 'workoutid': + plot.xaxis.axis_label = 'Workout' + else: + plot.xaxis.axis_label = axlabels[xparam] + + if yparam == 'workoutid': + plot.xaxis.axis_label = 'Workout' + else: + plot.yaxis.axis_label = axlabels[yparam] + + binlabel = Label(x=100,y=100,x_units='screen', + y_units='screen', + text="Bin size {binsize:3.1f}".format(binsize=binsize), + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black', + ) + + plot.add_layout(binlabel) + + yrange1 = Range1d(start=yaxmin,end=yaxmax) + plot.y_range = yrange1 + + xrange1 = Range1d(start=xaxmin,end=xaxmax) + plot.x_range = xrange1 + + if yparam == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + + script,div = components(plot) + + + return [script,div] + +def interactive_cum_flex_chart2(theworkouts,promember=0, + xparam='spm', + yparam1='power', + yparam2='spm', + workstrokesonly=False): + + # datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2) + ids = [int(w.id) for w in theworkouts] + columns = [xparam,yparam1,yparam2,'spm','driveenergy','distance'] + datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=True, + workstrokesonly=workstrokesonly) + + try: + tests = datadf[yparam2] + except KeyError: + yparam2 = 'None' + + try: + tests = datadf[yparam1] + except KeyError: + yparam1 = 'None' + + datadf.dropna(axis=1,how='all',inplace=True) + datadf.dropna(axis=0,how='any',inplace=True) + + # test if we have drive energy + nowork = 1 + try: + test = datadf['driveenergy'].mean() + nowork = 0 + except KeyError: + datadf['driveenergy'] = 500. + + # test if we have power + nopower = 1 + try: + test = datadf['power'].mean() + nopower = 0 + except KeyError: + datadf['power'] = 50. + + + yparamname1 = axlabels[yparam1] + if yparam2 != 'None': + yparamname2 = axlabels[yparam2] + + + # check if dataframe not empty + if datadf.empty: + return ['','

No non-zero data in selection

','',''] + + + try: + datadf['x1'] = datadf.ix[:,xparam] + except KeyError: + datadf['x1'] = datadf['distance'] + try: + datadf['y1'] = datadf.ix[:,yparam1] + except KeyError: + try: + datadf['y1'] = datadf['pace'] + except KeyError: + return ['','

No non-zero data in selection

','',''] + if yparam2 != 'None': + try: + datadf['y2'] = datadf.ix[:,yparam2] + except KeyError: + datadf['y2'] = datadf['y1'] + else: + datadf['y2'] = datadf['y1'] + + + if xparam=='distance': + xaxmax = datadf['x1'].max() + xaxmin = datadf['x1'].min() + else: + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + + # average values + x1mean = datadf['x1'].mean() + + y1mean = datadf['y1'].mean() + y2mean = datadf['y2'].mean() + + + xvals = pd.Series(xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.) + + x_axis_type = 'linear' + y_axis_type = 'linear' + if xparam == 'time': + x_axis_type = 'datetime' + + if yparam1 == 'pace': + y_axis_type = 'datetime' + y1mean = datadf.ix[:,'pace'].mean() + + datadf['xname'] = axlabels[xparam] + datadf['yname1'] = axlabels[yparam1] + if yparam2 != 'None': + datadf['yname2'] = axlabels[yparam2] + else: + datadf['yname2'] = axlabels[yparam1] + + + source = ColumnDataSource( + datadf + ) + + source2 = ColumnDataSource( + datadf.copy() + ) + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + 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", + ) + + x1means = Span(location=x1mean,dimension='height',line_color='green', + line_dash=[6,6], line_width=2) + + y1means = Span(location=y1mean,dimension='width',line_color='blue', + line_dash=[6,6],line_width=2) + y2means = y1means + + xlabel = Label(x=100,y=130,x_units='screen',y_units='screen', + text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='green', + ) + + + plot.add_layout(x1means) + plot.add_layout(xlabel) + + plot.add_layout(y1means) + + y1label = Label(x=100,y=100,x_units='screen',y_units='screen', + text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='blue', + ) + + if yparam1 != 'time' and yparam1 != 'pace': + plot.add_layout(y1label) + + y2label = y1label + plot.circle('x1','y1',source=source2,fill_alpha=0.3,line_color=None, + legend=yparamname1, + ) + + plot.xaxis.axis_label = axlabels[xparam] + plot.yaxis.axis_label = axlabels[yparam1] + + + yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) + plot.y_range = yrange1 + + xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) + plot.x_range = xrange1 + + if yparam1 == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + if yparam2 != 'None': + yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2]) + plot.extra_y_ranges["yax2"] = yrange2 + + plot.circle('x1','y2',color="red",y_range_name="yax2", + legend=yparamname2, + source=source2,fill_alpha=0.3,line_color=None) + + plot.add_layout(LinearAxis(y_range_name="yax2", + axis_label=axlabels[yparam2]),'right') + + y2means = Span(location=y2mean,dimension='width',line_color='red', + line_dash=[6,6],line_width=2,y_range_name="yax2") + + + plot.add_layout(y2means) + y2label = Label(x=100,y=70,x_units='screen',y_units='screen', + text=axlabels[yparam2]+": {y2mean:6.2f}".format(y2mean=y2mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='red', + ) + if yparam2 != 'pace' and yparam2 != 'time': + plot.add_layout(y2label) + + + callback = CustomJS(args = dict(source=source,source2=source2, + x1means=x1means, + y1means=y1means, + y1label=y1label, + y2label=y2label, + xlabel=xlabel, + y2means=y2means), code=""" + var data = source.data + var data2 = source2.data + var x1 = data['x1'] + var y1 = data['y1'] + var y2 = data['y2'] + var spm1 = data['spm'] + var distance1 = data['distance'] + var driveenergy1 = data['driveenergy'] + var xname = data['xname'][0] + var yname1 = data['yname1'][0] + var yname2 = data['yname2'][0] + + var minspm = minspm.value + var maxspm = maxspm.value + var mindist = mindist.value + var maxdist = maxdist.value + var minwork = minwork.value + var maxwork = maxwork.value + var xm = 0 + var ym1 = 0 + var ym2 = 0 + + data2['x1'] = [] + data2['y1'] = [] + data2['y2'] = [] + data2['spm'] = [] + data2['distance'] = [] + data2['x1mean'] = [] + data2['y1mean'] = [] + data2['y2mean'] = [] + data2['xvals'] = [] + data2['y1vals'] = [] + data2['y2vals'] = [] + + for (i=0; i=minspm && spm1[i]<=maxspm) { + if (distance1[i]>=mindist && distance1[i]<=maxdist) { + if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { + data2['x1'].push(x1[i]) + data2['y1'].push(y1[i]) + data2['y2'].push(y2[i]) + data2['spm'].push(spm1[i]) + data2['distance'].push(distance1[i]) + + xm += x1[i] + ym1 += y1[i] + ym2 += y2[i] + } + } + } + } + + xm /= data2['x1'].length + ym1 /= data2['x1'].length + ym2 /= data2['x1'].length + + data2['x1mean'] = [xm,xm] + data2['y1mean'] = [ym1,ym1] + data2['y2mean'] = [ym2,ym2] + x1means.location = xm + y1means.location = ym1 + y2means.location = ym2 + y1label.text = yname1+': '+(ym1).toFixed(2) + y2label.text = yname2+': '+(ym2).toFixed(2) + xlabel.text = xname+': '+(xm).toFixed(2) + + source2.trigger('change'); + """) + + slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, + title="Min SPM",callback=callback) + callback.args["minspm"] = slider_spm_min + + + slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, + title="Max SPM",callback=callback) + callback.args["maxspm"] = slider_spm_max + + slider_work_min = Slider(start=0.0, end=1500,value=0.0, step=10, + title="Min Work per Stroke",callback=callback) + callback.args["minwork"] = slider_work_min + + + slider_work_max = Slider(start=0.0, end=1500,value=1500.0, step=10, + title="Max Work per Stroke",callback=callback) + callback.args["maxwork"] = slider_work_max + + distmax = 100+100*int(datadf['distance'].max()/100.) + + slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, + title="Min Distance",callback=callback) + callback.args["mindist"] = slider_dist_min + + slider_dist_max = Slider(start=0,end=distmax,value=distmax, + step=1, + title="Max Distance",callback=callback) + callback.args["maxdist"] = slider_dist_max + + layout = layoutrow([layoutcolumn([slider_spm_min, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, + ], + ), + plot]) + + script, div = components(layout) + js_resources = INLINE.render_js() + css_resources = INLINE.render_css() + + + + return [script,div,js_resources,css_resources] + + + + +def interactive_flex_chart2(id=0,promember=0, + xparam='time', + yparam1='pace', + yparam2='hr', + plottype='line', + workstrokesonly=False): + + #rowdata,row = dataprep.getrowdata_db(id=id) + columns = [xparam,yparam1,yparam2, + 'ftime','distance','fpace', + 'power','hr','spm','driveenergy', + 'time','pace','workoutstate','time'] + + rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True, + workstrokesonly=workstrokesonly) + + if rowdata.empty: + rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True, + workstrokesonly=False) + workstrokesonly=False + + try: + tests = rowdata[yparam2] + except KeyError: + yparam2 = 'None' + + try: + tests = rowdata[yparam1] + except KeyError: + yparam1 = 'None' + + rowdata.dropna(axis=1,how='all',inplace=True) + + # test if we have drive energy + nowork = 1 + try: + test = rowdata['driveenergy'].mean() + nowork = 0 + except KeyError: + rowdata['driveenergy'] = 500. + + # test if we have power + nopower = 1 + try: + test = rowdata['power'].mean() + nopower = 0 + except KeyError: + rowdata['power'] = 50. + + + row = Workout.objects.get(id=id) + if rowdata.empty: + return "","No valid data" + else: + try: + rowdata.sort_values(by='time',ascending=True,inplace=True) + except KeyError: + pass + + workoutstateswork = [1,4,5,8,9,6,7] + workoutstatesrest = [3] + workoutstatetransition = [0,2,10,11,12,13] + + if workstrokesonly: + try: + rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] + except KeyError: + pass + + try: + tseconds = rowdata.ix[:,'time'] + except KeyError: + return '','No time data - cannot make flex plot','','' + + + try: + rowdata['x1'] = rowdata.ix[:,xparam] + rowmin = rowdata[xparam].min() + except KeyError: + rowdata['x1'] = 0*rowdata.ix[:,'time'] + + try: + rowdata['y1'] = rowdata.ix[:,yparam1] + rowmin = rowdata[yparam1].min() + except KeyError: + rowdata['y1'] = 0*rowdata.ix[:,'time'] + rowdata[yparam1] = rowdata['y1'] + + if yparam2 != 'None': + try: + rowdata['y2'] = rowdata.ix[:,yparam2] + rowmin = rowdata[yparam2].min() + except KeyError: + rowdata['y2'] = 0*rowdata.ix[:,'time'] + rowdata[yparam2] = rowdata['y2'] + else: + rowdata['y2'] = rowdata['y1'] + + if xparam=='time': + xaxmax = tseconds.max() + xaxmin = tseconds.min() + elif xparam=='distance' or xparam=='cumdist': + xaxmax = rowdata['x1'].max() + xaxmin = rowdata['x1'].min() + else: + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + + # average values + if xparam != 'time': + try: + x1mean = rowdata['x1'].mean() + except TypeError: + x1mean = 0 + else: + x1mean = 0 + + y1mean = rowdata['y1'].mean() + y2mean = rowdata['y2'].mean() + + if xparam != 'time': + xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. + else: + xvals = np.arange(100) + + # constant power plot + if yparam1 == 'driveenergy': + if xparam == 'spm': + yconstantpower = rowdata['y1'].mean()*rowdata['x1'].mean()/xvals + + x_axis_type = 'linear' + y_axis_type = 'linear' + if xparam == 'time': + x_axis_type = 'datetime' + + if yparam1 == 'pace': + y_axis_type = 'datetime' + try: + y1mean = rowdata.ix[:,'pace'].mean() + except KeyError: + y1mean = 0 + + + rowdata['xname'] = axlabels[xparam] + try: + rowdata['yname1'] = axlabels[yparam1] + except KeyError: + rowdata['yname1'] = yparam1 + if yparam2 != 'None': + try: + rowdata['yname2'] = axlabels[yparam2] + except KeyError: + rowdata['yname2'] = yparam2 + else: + rowdata['yname2'] = rowdata['yname1'] + + + # prepare data + source = ColumnDataSource( + rowdata + ) + + # second source for filtering + source2 = ColumnDataSource( + rowdata.copy() + ) + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + + sizing_mode = 'fixed' # 'scale_width' also looks nice with this example + + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + toolbar_sticky=False + ) + + + + # add watermark + 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", + ) + + x1means = Span(location=x1mean,dimension='height',line_color='green', + line_dash=[6,6], line_width=2) + + y1means = Span(location=y1mean,dimension='width',line_color='blue', + line_dash=[6,6],line_width=2) + y2means = y1means + + xlabel = Label(x=100,y=130,x_units='screen',y_units='screen', + text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='green', + ) + + annolabel = Label(x=100,y=500,x_units='screen',y_units='screen', + text='', + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black', + ) + + + if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): + plot.add_layout(x1means) + plot.add_layout(xlabel) + + + plot.add_layout(y1means) + plot.add_layout(annolabel) + + try: + yaxlabel = axlabels[yparam1] + except KeyError: + yaxlabel = str(yparam1)+' ' + + + y1label = Label(x=100,y=100,x_units='screen',y_units='screen', + text=yaxlabel+": {y1mean:6.2f}".format(y1mean=y1mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='blue', + ) + if yparam1 != 'time' and yparam1 != 'pace': + plot.add_layout(y1label) + y2label = y1label + + # average values + if yparam1 == 'driveenergy': + if xparam == 'spm': + plot.line(xvals,yconstantpower,color="green",legend="Constant Power") + + if plottype=='line': + plot.line('x1','y1',source=source2,legend=yaxlabel) + elif plottype=='scatter': + plot.scatter('x1','y1',source=source2,legend=yaxlabel,fill_alpha=0.4, + line_color=None) + + plot.title.text = row.name + plot.title.text_font_size=value("1.0em") + + plot.xaxis.axis_label = axlabels[xparam] + + plot.yaxis.axis_label = yaxlabel + + + + try: + yrange1 = Range1d(start=yaxminima[yparam1], + end=yaxmaxima[yparam1]) + except KeyError: + yrange1 = Range1d(start=rowdata[yparam1].min(), + end=rowdata[yparam1].max()) + + plot.y_range = yrange1 + + if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): + try: + xrange1 = Range1d(start=yaxminima[xparam], + end=yaxmaxima[xparam]) + except KeyError: + xrange1 = Range1d(start=rowdata[xparam].min(), + end=rowdata[xparam].max()) + + plot.x_range = xrange1 + + if xparam == 'time': + xrange1 = Range1d(start=xaxmin,end=xaxmax) + plot.x_range = xrange1 + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + + if yparam1 == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + + if yparam2 != 'None': + try: + yrange2 = Range1d(start=yaxminima[yparam2], + end=yaxmaxima[yparam2]) + except KeyError: + yrange2 = Range1d(start=rowdata[yparam2].min(), + end=rowdata[yparam2].max()) + + plot.extra_y_ranges["yax2"] = yrange2 + #= {"yax2": yrange2} + try: + axlegend = axlabels[yparam2] + except KeyError: + axlegend = str(yparam2)+' ' + + if plottype=='line': + plot.line('x1','y2',color="red",y_range_name="yax2", + legend=axlegend, + source=source2) + + elif plottype=='scatter': + plot.scatter('x1','y2',source=source2,legend=axlegend, + fill_alpha=0.4, + line_color=None,color="red",y_range_name="yax2") + + plot.add_layout(LinearAxis(y_range_name="yax2", + axis_label=axlegend),'right') + + y2means = Span(location=y2mean,dimension='width',line_color='red', + line_dash=[6,6],line_width=2,y_range_name="yax2") + + + plot.add_layout(y2means) + y2label = Label(x=100,y=70,x_units='screen',y_units='screen', + text=axlegend+": {y2mean:6.2f}".format(y2mean=y2mean), + background_fill_alpha=.7, + background_fill_color='white', + text_color='red', + ) + if yparam2 != 'pace' and yparam2 != 'time': + plot.add_layout(y2label) + + hover = plot.select(dict(type=HoverTool)) + + + hover.tooltips = OrderedDict([ + ('Time','@ftime'), + ('Distance','@distance{int}'), + ('Pace','@fpace'), + ('HR','@hr{int}'), + ('SPM','@spm{1.1}'), + ('Power','@power{int}'), + ]) + + hover.mode = 'mouse' + + + callback = CustomJS(args = dict(source=source,source2=source2, + x1means=x1means, + y1means=y1means, + y1label=y1label, + y2label=y2label, + xlabel=xlabel, + annolabel=annolabel, + y2means=y2means, + ), code=""" + var data = source.data + var data2 = source2.data + var x1 = data['x1'] + var y1 = data['y1'] + var y2 = data['y2'] + var spm1 = data['spm'] + var time1 = data['time'] + var pace1 = data['pace'] + var hr1 = data['hr'] + var fpace1 = data['fpace'] + var distance1 = data['distance'] + var power1 = data['power'] + var driveenergy1 = data['driveenergy'] + var xname = data['xname'][0] + var yname1 = data['yname1'][0] + var yname2 = data['yname2'][0] + + 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 + var xm = 0 + var ym1 = 0 + var ym2 = 0 + + data2['x1'] = [] + data2['y1'] = [] + data2['y2'] = [] + data2['spm'] = [] + data2['time'] = [] + data2['pace'] = [] + data2['hr'] = [] + data2['fpace'] = [] + data2['distance'] = [] + data2['power'] = [] + data2['x1mean'] = [] + data2['y1mean'] = [] + data2['y2mean'] = [] + data2['xvals'] = [] + data2['y1vals'] = [] + data2['y2vals'] = [] + + for (i=0; i=minspm && spm1[i]<=maxspm) { + if (distance1[i]>=mindist && distance1[i]<=maxdist) { + if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { + data2['x1'].push(x1[i]) + data2['y1'].push(y1[i]) + data2['y2'].push(y2[i]) + data2['spm'].push(spm1[i]) + data2['time'].push(time1[i]) + data2['fpace'].push(fpace1[i]) + data2['pace'].push(pace1[i]) + data2['hr'].push(hr1[i]) + data2['distance'].push(distance1[i]) + data2['power'].push(power1[i]) + + xm += x1[i] + ym1 += y1[i] + ym2 += y2[i] + } + } + } + } + + xm /= data2['x1'].length + ym1 /= data2['x1'].length + ym2 /= data2['x1'].length + + data2['x1mean'] = [xm,xm] + data2['y1mean'] = [ym1,ym1] + data2['y2mean'] = [ym2,ym2] + x1means.location = xm + y1means.location = ym1 + y2means.location = ym2 + y1label.text = yname1+': '+ym1.toFixed(2) + y2label.text = yname2+': '+ym2.toFixed(2) + xlabel.text = xname+': '+xm.toFixed(2) + annolabel.text = annotation + + source2.trigger('change'); + """) + + annotation = TextInput(title="Type your plot notes here", value="", + callback=callback) + callback.args["annotation"] = annotation + + slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1, + title="Min SPM",callback=callback) + callback.args["minspm"] = slider_spm_min + + + slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1, + title="Max SPM",callback=callback) + callback.args["maxspm"] = slider_spm_max + + slider_work_min = Slider(start=0.0, end=1500,value=0.0, step=10, + title="Min Work per Stroke",callback=callback) + callback.args["minwork"] = slider_work_min + + + slider_work_max = Slider(start=0.0, end=1500,value=1500.0, step=10, + title="Max Work per Stroke",callback=callback) + callback.args["maxwork"] = slider_work_max + + try: + distmax = 100+100*int(rowdata['distance'].max()/100.) + except KeyError: + distmax = 100 + + slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, + title="Min Distance",callback=callback) + callback.args["mindist"] = slider_dist_min + + slider_dist_max = Slider(start=0,end=distmax,value=distmax, + step=1, + title="Max Distance",callback=callback) + callback.args["maxdist"] = slider_dist_max + + layout = layoutrow([layoutcolumn([annotation, + slider_spm_min, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, + ], + ), + plot]) + + script, div = components(layout) + js_resources = INLINE.render_js() + css_resources = INLINE.render_css() + + return [script,div,js_resources,css_resources,workstrokesonly] + +def thumbnails_set(r,id,favorites): + charts = [] + + columns = [f.xparam for f in favorites] + columns += [f.yparam1 for f in favorites] + columns += [f.yparam2 for f in favorites] + + columns += ['time'] + try: + rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True) + except: + return [ + {'script':"", + 'div':"", + 'notes':"" + }] + + + rowdata.dropna(axis=1,how='all',inplace=True) + + if rowdata.empty: + return [ + {'script':"", + 'div':"", + 'notes':"" + }] + else: + try: + rowdata.sort_values(by='time',ascending=True,inplace=True) + except KeyError: + pass + + l = len(rowdata) + maxlength = 50 + if l > maxlength: + bins = np.linspace(rowdata['time'].min(),rowdata['time'].max(),maxlength) + groups = rowdata.groupby(np.digitize(rowdata['time'],bins)) + rowdata = groups.mean() + + for f in favorites: + workstrokesonly = not f.reststrokes + script,div = thumbnail_flex_chart( + rowdata, + id=id, + xparam=f.xparam, + yparam1=f.yparam1, + yparam2=f.yparam2, + plottype=f.plottype, + ) + + + charts.append({ + 'script':script, + 'div':div, + 'notes':f.notes}) + + return charts + + +def thumbnail_flex_chart(rowdata,id=0,promember=0, + xparam='time', + yparam1='pace', + yparam2='hr', + plottype='line', + workstrokesonly=False): + + + try: + tests = rowdata[yparam2] + except KeyError: + yparam2 = 'None' + + try: + tests = rowdata[yparam1] + except KeyError: + yparam1 = 'None' + + + + try: + tseconds = rowdata.ix[:,'time'] + except KeyError: + return '','No time data - cannot make flex plot','','' + + + try: + rowdata['x1'] = rowdata.ix[:,xparam] + except KeyError: + rowdata['x1'] = 0*rowdata.ix[:,'time'] + + try: + rowdata['y1'] = rowdata.ix[:,yparam1] + except KeyError: + rowdata['y1'] = 0*rowdata.ix[:,'time'] + + if yparam2 != 'None': + try: + rowdata['y2'] = rowdata.ix[:,yparam2] + except KeyError: + rowdata['y2'] = 0*rowdata.ix[:,'time'] + else: + rowdata['y2'] = rowdata['y1'] + + if xparam=='time': + xaxmax = tseconds.max() + xaxmin = tseconds.min() + elif xparam=='distance' or xparam=='cumdist': + xaxmax = rowdata['x1'].max() + xaxmin = rowdata['x1'].min() + else: + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + + x_axis_type = 'linear' + y_axis_type = 'linear' + if xparam == 'time': + x_axis_type = 'datetime' + + if yparam1 == 'pace': + y_axis_type = 'datetime' + y1mean = rowdata.ix[:,'pace'].mean() + + + rowdata['xname'] = axlabels[xparam] + rowdata['yname1'] = axlabels[yparam1] + if yparam2 != 'None': + rowdata['yname2'] = axlabels[yparam2] + else: + rowdata['yname2'] = axlabels[yparam1] + + + # prepare data + source = ColumnDataSource( + rowdata + ) + + + sizing_mode = 'fixed' # 'scale_width' also looks nice with this example + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + plot_width=200,plot_height=150, + ) + + + + plot.toolbar.logo = None + plot.toolbar_location = None + #plot.yaxis.visible = False + plot.xaxis.axis_label_text_font_size = "7pt" + plot.yaxis.axis_label_text_font_size = "7pt" + plot.xaxis.major_label_text_font_size = "7pt" + plot.yaxis.major_label_text_font_size = "7pt" + + if plottype=='line': + plot.line('x1','y1',source=source) + elif plottype=='scatter': + plot.scatter('x1','y1',source=source,fill_alpha=0.4, + line_color=None) + + plot.xaxis.axis_label = axlabels[xparam] + plot.yaxis.axis_label = axlabels[yparam1] + + + + yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) + plot.y_range = yrange1 + + if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): + xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) + plot.x_range = xrange1 + + if xparam == 'time': + xrange1 = Range1d(start=xaxmin,end=xaxmax) + plot.x_range = xrange1 + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + + if yparam1 == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + + if yparam2 != 'None': + yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2]) + plot.extra_y_ranges["yax2"] = yrange2 + #= {"yax2": yrange2} + + if plottype=='line': + plot.line('x1','y2',color="red",y_range_name="yax2", + source=source) + + elif plottype=='scatter': + plot.scatter('x1','y2',source=source, + fill_alpha=0.4, + line_color=None,color="red",y_range_name="yax2") + + plot.add_layout(LinearAxis(y_range_name="yax2", + axis_label=axlabels[yparam2], + major_label_text_font_size="7pt", + axis_label_text_font_size="7pt", + ),'right', + ) + + + script, div = components(plot) + + return [script,div] + + +def interactive_bar_chart(id=0,promember=0): + # check if valid ID exists (workout exists) + rowdata,row = dataprep.getrowdata_db(id=id) + rowdata.dropna(axis=1,how='all',inplace=True) + rowdata.dropna(axis=0,how='any',inplace=True) + + if rowdata.empty: + return "","No Valid Data Available" + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + source = ColumnDataSource( + rowdata + ) + + plot = Figure(x_axis_type="datetime",y_axis_type="datetime", + toolbar_sticky=False, + plot_width=920, + tools=TOOLS) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],0.01,0.99, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor='top_left', + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + plot.title.text = row.name + plot.title.text_font_size=value("1.0em") + plot.xaxis.axis_label = "Time" + plot.yaxis.axis_label = "Pace (/500m)" + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours ="", + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + plot.yaxis[0].formatter = DatetimeTickFormatter( + hours = "", + seconds = ["%S"], + minutes = ["%M"], + ) + ymax = 1.0e3*90 + ymin = 1.0e3*180 + + if row.workouttype == 'water': + ymax = 1.0e3*90 + ymin = 1.0e3*210 + + plot.y_range = Range1d(ymin,ymax) + + + hover = plot.select(dict(type=HoverTool)) + + + hover.tooltips = OrderedDict([ + ('Time','@ftime'), + ('Pace','@fpace'), + ('HR','@hr{int}'), + ('SPM','@spm{1.1}'), + ]) + + hover.mode = 'mouse' + + plot.extra_y_ranges["hr"] = Range1d(start=100,end=200) + plot.quad(left='time',top='hr_ut2',bottom='hr_bottom', + right='x_right',source=source,color="gray", + y_range_name="hr", legend=" 0] + + #datadf = datadf[datadf[xparam] > 0] + + # check if dataframe not empty + if datadf.empty: + return ['','

No non-zero data in selection

','','No non-zero data in selection'] + + if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + elif xparam == 'time': + xaxmax = tseconds.max() + xaxmin = tseconds.min() + else: + xaxmax = datadf['distance'].max() + xaxmin = datadf['distance'].min() + + if yparam == 'distance': + yaxmin = datadf['distance'].min() + yaxmax = datadf['distance'].max() + elif yparam == 'cumdist': + yaxmin = datadf['cumdist'].min() + yaxmax = datadf['cumdist'].max() + else: + yaxmin = yaxminima[yparam] + yaxmax = yaxmaxima[yparam] + + x_axis_type = 'linear' + y_axis_type = 'linear' + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + + if yparam == 'pace': + y_axis_type = 'datetime' + yaxmax = 90.*1e3 + yaxmin = 150.*1e3 + + if xparam == 'time': + x_axis_type = 'datetime' + + if xparam != 'time': + xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. + else: + xvals = np.arange(100) + + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + toolbar_location="above", + plot_width=920, + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + 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 + + for id,color in itertools.izip(ids,colors): + group = datadf[datadf['workoutid']==int(id)].copy() + group.sort_values(by='time',ascending=True,inplace=True) + try: + group['x'] = group[xparam] + except KeyError: + group['x'] = group['time'] + errormessage = xparam+' has no values. Plot invalid' + try: + group['y'] = group[yparam] + except KeyError: + group['y'] = 0.0*group['x'] + + ymean = group['y'].mean() + 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: + legend=labeldict[id] + else: + legend=str(id) + + if plottype=='line': + l1 = plot.line('x','y',source=source,color=color,legend=legend) + else: + l1 = plot.scatter('x','y',source=source,color=color,legend=legend, + fill_alpha=0.4,line_color=None) + + plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) + cntr += 1 + + plot.legend.location='bottom_right' + plot.xaxis.axis_label = axlabels[xparam] + plot.yaxis.axis_label = axlabels[yparam] + + if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): + xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) + plot.x_range = xrange1 + + yrange1 = Range1d(start=yaxmin,end=yaxmax) + plot.y_range = yrange1 + + if xparam == 'time': + xrange1 = Range1d(start=xaxmin,end=xaxmax) + plot.x_range = xrange1 + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + + if yparam == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + script, div = components(plot) + + + + return [script,div,message,errormessage] + + + +def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', + promember=0,plottype='line'): + + + + + columns = [xparam,yparam, + 'ftime','distance','fpace', + 'power','hr','spm', + 'time','pace','workoutstate'] + + # check if valid ID exists (workout exists) + #rowdata1,row1 = dataprep.getrowdata_db(id=id1) + #rowdata2,row2 = dataprep.getrowdata_db(id=id2) + + rowdata1 = dataprep.getsmallrowdata_db(columns,ids=[id1]) + rowdata2 = dataprep.getsmallrowdata_db(columns,ids=[id2]) + for n in ['distance','power','hr','spm','time','pace','workoutstate']: + rowdata1[n].fillna(value=0,inplace=True) + rowdata2[n].fillna(value=0,inplace=True) + + rowdata1.dropna(axis=1,how='all',inplace=True) + rowdata1.dropna(axis=0,how='any',inplace=True) + rowdata2.dropna(axis=1,how='all',inplace=True) + rowdata2.dropna(axis=0,how='any',inplace=True) + + row1 = Workout.objects.get(id=id1) + row2 = Workout.objects.get(id=id2) + + if rowdata1.empty: + return "","No Valid Data Available" + else: + rowdata1.sort_values(by='time',ascending=True,inplace=True) + + if rowdata2.empty: + return "","No Valid Data Available" + else: + rowdata2.sort_values(by='time',ascending=True,inplace=True) + + try: + x1 = rowdata1.ix[:,xparam] + x2 = rowdata2.ix[:,xparam] + + y1 = rowdata1.ix[:,yparam] + y2 = rowdata2.ix[:,yparam] + except KeyError: + return "","No valid Data Available" + + x_axis_type = 'linear' + y_axis_type = 'linear' + if xparam == 'time': + x_axis_type = 'datetime' + + if yparam == 'pace': + y_axis_type = 'datetime' + ymax = 1.0e3*90 + ymin = 1.0e3*180 + + if row1.workouttype == 'water': + ymax = 1.0e3*90 + ymin = 1.0e3*210 + + ftime1 = rowdata1.ix[:,'ftime'] + ftime2 = rowdata2.ix[:,'ftime'] + + hr1 = rowdata1.ix[:,'hr'] + hr2 = rowdata2.ix[:,'hr'] + + + fpace1 = rowdata1.ix[:,'fpace'] + fpace2 = rowdata2.ix[:,'fpace'] + + distance1 = rowdata1.ix[:,'distance'] + distance2 = rowdata2.ix[:,'distance'] + + spm1 = rowdata1.ix[:,'spm'] + spm2 = rowdata2.ix[:,'spm'] + + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + data1 = pd.DataFrame( + dict( + x1=x1, + y1=y1, + ftime1=ftime1, + fpace1=fpace1, + hr1 = hr1, + spm1 = spm1, + distance1=distance1, + ) + ).dropna() + + data2 = pd.DataFrame( + dict( + x2=x2, + y2=y2, + ftime2=ftime2, + fpace2=fpace2, + hr2 = hr2, + spm2 = spm2, + distance2=distance2, + ) + ).dropna() + + + + source1 = ColumnDataSource( + data1 + ) + + source2 = ColumnDataSource( + data2 + ) + + ymean1 = data1['y1'].mean() + ymean2 = data2['y2'].mean() + + # create interactive plot + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + plot_width=920, + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],0.05,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor='bottom_left', + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + TIPS = OrderedDict([ + ('time','@ftime1'), + ('pace','@fpace1'), + ('hr','@hr1'), + ('spm','@spm1{1.1}'), + ('distance','@distance1{5}'), + ]) + TIPS2 = OrderedDict([ + ('time','@ftime2'), + ('pace','@fpace2'), + ('hr','@hr2'), + ('spm','@spm2{1.1}'), + ('distance','@distance2{5}'), + ]) + + + + hover1 = plot.select(type=HoverTool) + hover1.tooltips = TIPS + hover2 = plot.select(type=HoverTool) + hover2.tooltips = TIPS2 + + + if plottype=='line': + l1 = plot.line('x1','y1',source=source1, + color="blue",legend=row1.name, + ) + l2 = plot.line('x2','y2',source=source2, + color="red",legend=row2.name, + ) + elif plottype=='scatter': + l1 = plot.scatter('x1','y1',source=source1,legend=row1.name, + fill_alpha=0.4, + line_color=None) + l2 = plot.scatter('x2','y2',source=source2,legend=row2.name, + fill_alpha=0.4, + line_color=None,color="red") + + plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) + plot.add_tools(HoverTool(renderers=[l2],tooltips=TIPS2)) + plot.legend.location = "bottom_right" + + plot.title.text = row1.name+' vs '+row2.name + plot.title.text_font_size=value("1.2em") + plot.xaxis.axis_label = axlabels[xparam] + plot.yaxis.axis_label = axlabels[yparam] + + ylabel1 = Label(x=100,y=90,x_units='screen',y_units='screen', + text=axlabels[yparam]+": {ymean1:6.2f}".format( + ymean1=ymean1 + ), + background_fill_alpha=.7, + background_fill_color='white', + text_color='blue' + ) + ylabel2 = Label(x=100,y=110,x_units='screen',y_units='screen', + text=axlabels[yparam]+": {ymean2:6.2f}".format( + ymean2=ymean2 + ), + background_fill_alpha=.7, + background_fill_color='white', + text_color='red' + ) + plot.add_layout(ylabel1) + plot.add_layout(ylabel2) + + if xparam == 'time': + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + + if yparam == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + plot.y_range = Range1d(ymin,ymax) + + + script, div = components(plot) + + return [script,div] + +def interactive_otw_advanced_pace_chart(id=0,promember=0): + # check if valid ID exists (workout exists) + rowdata,row = dataprep.getrowdata_db(id=id) + rowdata.dropna(axis=1,how='all',inplace=True) + rowdata.dropna(axis=0,how='any',inplace=True) + + if rowdata.empty: + return "","No Valid Data Available" + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + source = ColumnDataSource( + rowdata + ) + + + plot = Figure(x_axis_type="datetime",y_axis_type="datetime", + tools=TOOLS, + plot_width=920, + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],watermarkx,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + plot.title.text = row.name + plot.title.text_font_size=value("1.2em") + plot.xaxis.axis_label = "Time" + plot.yaxis.axis_label = "Pace (/500m)" + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + + ymax = 1.0e3*90 + ymin = 1.0e3*210 + + plot.y_range = Range1d(ymin,ymax) + + + hover = plot.select(dict(type=HoverTool)) + + plot.line('time','pace',source=source,legend="Pace",color="black") + plot.line('time','nowindpace',source=source,legend="Corrected Pace",color="red") + + hover.tooltips = OrderedDict([ + ('Time','@ftime'), + ('Pace','@fpace'), + ('Corrected Pace','@fnowindpace'), + ('HR','@hr{int}'), + ('SPM','@spm{1.1}'), + ]) + + hover.mode = 'mouse' + + script, div = components(plot) + + return [script,div] + diff --git a/rowers/metrics.py b/rowers/metrics.py index 72d2d1bb..bfac6b1f 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -3,6 +3,7 @@ import numpy as np from models import C2WorldClassAgePerformance import pandas as pd from scipy import optimize +from django.utils import timezone rowingmetrics = ( ('time',{ @@ -64,7 +65,7 @@ rowingmetrics = ( 'null':True, 'verbose_name': 'Average Drive Force (N)', 'ax_min': 0, - 'ax_max': 900, + 'ax_max': 1200, 'mode':'both', 'type': 'pro'}), @@ -73,7 +74,7 @@ rowingmetrics = ( 'null':True, 'verbose_name': 'Peak Drive Force (N)', 'ax_min': 0, - 'ax_max': 900, + 'ax_max': 1500, 'mode':'both', 'type': 'pro'}), @@ -321,28 +322,36 @@ def calc_trimp(df,sex,hrmax,hrmin): return trimp def getagegrouprecord(age,sex='male',weightcategory='hwt', - distance=2000,duration=None): - if not duration: - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - distance=distance, - sex=sex, - weightcategory=weightcategory - ).values() - ) - ) + distance=2000,duration=None,indf=pd.DataFrame()): + + if not indf.empty: + if not duration: + df = indf[indf['distance'] == distance] + else: + duration = 60*int(duration) + df = indf[indf['duration'] == duration] else: - duration=60*int(duration) - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - duration=duration, - sex=sex, - weightcategory=weightcategory - ).values() + if not duration: + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + distance=distance, + sex=sex, + weightcategory=weightcategory + ).values() + ) + ) + else: + duration=60*int(duration) + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + duration=duration, + sex=sex, + weightcategory=weightcategory + ).values() + ) ) - ) if not df.empty: ages = df['age'] @@ -354,14 +363,21 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt', p0 = [700,120,700,10,100,100] - p1, success = optimize.leastsq(errfunc,p0[:], + try: + p1, success = optimize.leastsq(errfunc,p0[:], args = (ages,powers)) + except: + p1 = p0 + success = 0 - power = fitfunc(p1, float(age)) + if success: + power = fitfunc(p1, float(age)) - #power = np.polyval(poly_coefficients,age) + #power = np.polyval(poly_coefficients,age) - power = 0.5*(np.abs(power)+power) + power = 0.5*(np.abs(power)+power) + else: + power = 0 else: power = 0 diff --git a/rowers/models.py b/rowers/models.py index 8e024c1d..5e0af42b 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -214,7 +214,34 @@ def update_records(url=c2url): except: print record - + +class CalcAgePerformance(models.Model): + weightcategories = ( + ('hwt','heavy-weight'), + ('lwt','light-weight'), + ) + + sexcategories = ( + ('male','male'), + ('female','female'), + ) + + weightcategory = models.CharField(default="hwt", + max_length=30, + choices=weightcategories) + + sex = models.CharField(default="female", + max_length=30, + choices=sexcategories) + + age = models.IntegerField(default=19,verbose_name="Age") + + duration = models.FloatField(default=1,blank=True) + power = models.IntegerField(default=200) + + class Meta: + db_table = 'calcagegrouprecords' + class C2WorldClassAgePerformance(models.Model): weightcategories = ( ('hwt','heavy-weight'), @@ -875,9 +902,11 @@ class WorkoutForm(ModelForm): # Used for the rowing physics calculations class AdvancedWorkoutForm(ModelForm): + quick_calc = forms.BooleanField(initial=True,required=False) + class Meta: model = Workout - fields = ['boattype','weightvalue'] + fields = ['boattype','weightvalue','quick_calc'] class RowerExportForm(ModelForm): class Meta: diff --git a/rowers/tasks.py b/rowers/tasks.py index 3301800e..6cddaa6e 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -6,6 +6,8 @@ import gzip import shutil import numpy as np +from scipy import optimize + import rowingdata from rowingdata import rowingdata as rdata @@ -28,7 +30,8 @@ from utils import deserialize_list from rowers.dataprepnodjango import ( update_strokedata, new_workout_from_file, - getsmallrowdata_db, updatecpdata_sql + getsmallrowdata_db, updatecpdata_sql, + update_agegroup_db, ) from django.core.mail import send_mail, EmailMessage @@ -46,6 +49,96 @@ import longtask def add(x, y): return x + y +def getagegrouprecord(age,sex='male',weightcategory='hwt', + distance=2000,duration=None,indf=pd.DataFrame()): + + if not duration: + df = indf[indf['distance'] == distance] + else: + duration = 60*int(duration) + df = indf[indf['duration'] == duration] + + if not df.empty: + ages = df['age'] + powers = df['power'] + + #poly_coefficients = np.polyfit(ages,powers,6) + fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5]))) + errfunc = lambda pars, x,y: fitfunc(pars,x)-y + + p0 = [700,120,700,10,100,100] + + p1, success = optimize.leastsq(errfunc,p0[:], + args = (ages,powers)) + + if success: + power = fitfunc(p1, float(age)) + + #power = np.polyval(poly_coefficients,age) + + power = 0.5*(np.abs(power)+power) + else: + power = 0 + else: + power = 0 + + return power + + +@app.task(bind=True) +def handle_getagegrouprecords(self, + df, + distances,durations, + age,sex,weightcategory, + **kwargs): + wcdurations = [] + wcpower = [] + + if 'debug' in kwargs: + debug = kwargs['debug'] + else: + debug = False + + + df = pd.read_json(df) + + for distance in distances: + worldclasspower = getagegrouprecord( + age, + sex=sex, + distance=distance, + weightcategory=weightcategory,indf=df, + ) + velo = (worldclasspower/2.8)**(1./3.) + try: + duration = distance/velo + wcdurations.append(duration) + wcpower.append(worldclasspower) + except ZeroDivisionError: + pass + + + + for duration in durations: + worldclasspower = getagegrouprecord( + age, + sex=sex, + duration=duration, + weightcategory=weightcategory,indf=df + ) + try: + velo = (worldclasspower/2.8)**(1./3.) + distance = int(60*duration*velo) + wcdurations.append(60.*duration) + wcpower.append(worldclasspower) + except ValueError: + pass + + update_agegroup_db(age,sex,weightcategory,wcdurations,wcpower, + debug=debug) + + return 1 + @app.task(bind=True) def long_test_task(self,aantal,debug=False,job=None,session_key=None): @@ -66,7 +159,10 @@ def long_test_task2(self,aantal,**kwargs): kwargs['jobid'] = job_id return longtask.longtask2(aantal,**kwargs) + + + # create workout @app.task def handle_new_workout_from_file(r, f2, @@ -356,10 +452,7 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs): def handle_otwsetpower(self,f1, boattype, weightvalue, first_name, last_name, email, workoutid, **kwargs): -# ps=[ -# 1, 1, 1, 1], -# ratio=1.0, -# debug=False): + job = self.request job_id = job.id @@ -378,7 +471,12 @@ def handle_otwsetpower(self,f1, boattype, weightvalue, debug = kwargs['debug'] else: debug = False - + + if 'quick_calc' in kwargs: + usetable = kwargs['quick_calc'] + else: + usetable = False + kwargs['jobid'] = job_id @@ -416,17 +514,25 @@ def handle_otwsetpower(self,f1, boattype, weightvalue, pass progressurl = SITE_URL + siteurl = SITE_URL if debug: progressurl = SITE_URL_DEV + siteurl = SITE_URL_DEV secret = PROGRESS_CACHE_SECRET progressurl += "/rowers/record-progress/" progressurl += job_id + + # determine cache file name + physics_cache = 'media/'+str(boattype)+'_'+str(int(weightvalue)) + - rowdata.otw_setpower_silent(skiprows=5, mc=weightvalue, rg=rg, - powermeasured=powermeasured, - progressurl=progressurl, - secret=secret + rowdata.otw_setpower(skiprows=5, mc=weightvalue, rg=rg, + powermeasured=powermeasured, + progressurl=progressurl, + secret=secret, + silent=True, + usetable=usetable,storetable=physics_cache, ) # save data @@ -465,7 +571,8 @@ def handle_otwsetpower(self,f1, boattype, weightvalue, message += "Thank you for using rowsandall.com.\n\n" message += "Rowsandall OTW calculations have not been fully implemented yet.\n" message += "We are now running an experimental version for debugging purposes. \n" - message += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/" + message += "Your wind/stream corrected plot is available here: " + message += siteurl+"/rowers/workout/" message += str(workoutid) message += "/interactiveotwplot\n\n" message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n" @@ -482,7 +589,17 @@ def handle_otwsetpower(self,f1, boattype, weightvalue, def handle_updateergcp(rower_id,workoutfilenames,debug=False,**kwargs): therows = [] for f1 in workoutfilenames: - rowdata = rdata(f1) + try: + rowdata = rdata(f1) + except IOError: + try: + rowdata = rdata(f1 + '.csv') + except IOError: + try: + rowdata = rdata(f1 + '.gz') + except IOError: + rowdata = 0 + if rowdata != 0: therows.append(rowdata) diff --git a/rowers/templates/agegroupchart.html b/rowers/templates/agegroupchart.html index 2bebbe61..f3ecf8b3 100644 --- a/rowers/templates/agegroupchart.html +++ b/rowers/templates/agegroupchart.html @@ -39,6 +39,11 @@

Interactive Plot

+

This chart shows the Indoor Rower World Records for your gender and + weight class. The red dots are the official records, and hovering + over them with your mouse shows you the name of the record holder. + The blue line is a fit to the data, which is used by rowsandall.com + to calculate your performance assessment. {{ the_div|safe }} diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index 9d3ec5aa..6f7a3531 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -91,9 +91,9 @@

{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} - OTW Ranking Pieces + OTW Critical Power {% else %} - OTW Ranking Pieces + OTW Critical Power {% endif %}

@@ -129,9 +129,9 @@

{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} - OTE Ranking Pieces + OTE Critical Power {% else %} - OTE Ranking Pieces + OTE Critical Power {% endif %}

diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index e051f0b6..d82399c7 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -4,12 +4,49 @@ {% block title %}Rowsandall Workouts List{% endblock %} -{% block content %} - +{% block scripts %} + + +{% endblock %} + +{% block content %} +

@@ -51,6 +88,7 @@ + @@ -76,155 +114,166 @@ {% else %} {% endif %} + - - {% else %} - No Name -{% endif %} -{% else %} -{% if workout.name != '' %} -{{ workout.name }} -{% else %} -No Name -{% endif %} -{% endif %} - - - - - - {% if not team %} - + + {% else %} + + {% endif %} + {% else %} + {% if workout.name != '' %} + + {% else %} + + {% endif %} + {% endif %} + + + + + + {% if not team %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + +
R Date Time Name
+ {% if workout.rankingpiece %} + + {% else %} + + {% endif %} + {{ workout.date|date:"Y-m-d" }} {{ workout.starttime|date:"H:i" }} {% if workout.user.user == user or user == team.manager %} - {% if workout.rankingpiece %} - [RANKING PIECE] - {% endif %} {% if workout.name != '' %} - {{ workout.name }} {{ workout.workouttype }} {{ workout.distance }}m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ workout.averagehr }} {{ workout.maxhr }} - Export - + + {{ workout.name }} + + + No Name + {{ workout.name }}No Name {{ workout.workouttype }} {{ workout.distance }}m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ workout.averagehr }} {{ workout.maxhr }} + Export + + {{ workout.user.user.first_name }} {{ workout.user.user.last_name }} + Flex + Delete +
{% else %} - - {{ workout.user.user.first_name }} {{ workout.user.user.last_name }} - +

No workouts found

{% endif %} - Flex - - Delete - - - - -{% endfor %} - - -{% else %} -

No workouts found

-{% endif %} -
- - -
- {% if team %} -
-
-

-   -

-
- {% endif %} -
- - - - {{ interactiveplot |safe }} - - - - {{ the_div |safe }} -
-
- {% if announcements %} -

What's New?

- {% for a in announcements %} -
-
- {{ a.created }}: - {{ a.announcement|urlize }} + + +
+ {% if team %} +
+
+

+   +

- {% endfor %} -

 

{% endif %} -
-
-

About

-

This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is - Sander Roosendaal. A Masters rower. +

+ + - Read his blog -

-
- - + {{ interactiveplot |safe }} + + + + {{ the_div |safe }} +
+
+ {% if announcements %} +

What's New?

+ {% for a in announcements %} +
+
+ {{ a.created }}: + {{ a.announcement|urlize }} +
+
+ {% endfor %} +

 

+ {% endif %} +
+
+

About

+

This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is + Sander Roosendaal. A Masters rower. + + Read his blog +

+
+ + +
-
{% if rankingonly and not team %} - - {% elif not team %} - - {% endif %} - -

 

+ + {% elif not team %} + + {% endif %} + +

 

{% if team %}
{% else %} - -{% endif %} -
- -
-
- -
-
+
+ {% endif %} +
+ +
+
+ +
+
@@ -248,5 +297,5 @@ {% endif %} {% endif %} - -{% endblock %} + + {% endblock %} diff --git a/rowers/templates/oterankings.html b/rowers/templates/oterankings.html index 9fdc910d..d2bd4d95 100644 --- a/rowers/templates/oterankings.html +++ b/rowers/templates/oterankings.html @@ -85,9 +85,9 @@ In other words: How long you can hold that power.

-

When you change the date range, the algorithm calculates new - parameters in a background process. You may have to reload the - page to get an updated prediction.

+

Whenever you load or reload the page, a new calculation is started + as a background process. The page will reload automatically when + calculation is ready.

At the bottom of the page, you will find predictions derived from the model.

diff --git a/rowers/templates/otwsetpower.html b/rowers/templates/otwsetpower.html index 51218c6a..7a997bed 100644 --- a/rowers/templates/otwsetpower.html +++ b/rowers/templates/otwsetpower.html @@ -35,6 +35,10 @@ We use FISA minimum boat weight and standard rigging for our calculations.

+

The Quick calculation option potentially speeds up the calculation, + at the cost of a slight reduction in accuracy. It is recommended + to keep this option selected.

+
{% if form.errors %}

diff --git a/rowers/templates/rankings.html b/rowers/templates/rankings.html index ee6fc209..369827b5 100644 --- a/rowers/templates/rankings.html +++ b/rowers/templates/rankings.html @@ -5,6 +5,10 @@ {% block title %}Workouts{% endblock %} {% block scripts %} {% include "monitorjobs.html" %} + + {% endblock %} {% block content %} @@ -70,7 +74,9 @@ https://rowsandall.com/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}

-

The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.

+

The table gives the best efforts achieved on the + official Concept2 ranking pieces in the selected date range. Also the percentile scores on the + chart are based on the Concept2 rankings.

diff --git a/rowers/urls.py b/rowers/urls.py index 59e86aa2..7d345028 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -123,6 +123,8 @@ urlpatterns = [ url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), url(r'^agegroupcp/(?P\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P\d+)/(?P\d+)$',views.agegroupcpview), + url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$', + views.ajax_agegrouprecords), url(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)m$', views.agegrouprecordview), url(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)min$', @@ -212,6 +214,7 @@ urlpatterns = [ url(r'^graph/(?P\d+)/deleteconfirm$',views.graph_delete_confirm_view), url(r'^graph/(?P\d+)/delete$',views.graph_delete_view), url(r'^workout/(?P\d+)/get-thumbnails$',views.get_thumbnails), + url(r'^workout/(?P\d+)/toggle-ranking$',views.workout_toggle_ranking), url(r'^workout/(?P\d+)/get-testscript$',views.get_testscript), url(r'^workout/upload/team/$',views.team_workout_upload_view), url(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'), diff --git a/rowers/utils.py b/rowers/utils.py index 5b87053a..26f74d3d 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -5,6 +5,7 @@ import colorsys from django.conf import settings import uuid +import datetime lbstoN = 4.44822 @@ -127,6 +128,22 @@ palettes = { 'yellow_red':trcolors(255,255,178,189,0,39) } +rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000] +rankingdurations = [] +rankingdurations.append(datetime.time(minute=1)) +rankingdurations.append(datetime.time(minute=4)) +rankingdurations.append(datetime.time(minute=30)) +rankingdurations.append(datetime.time(hour=1,minute=15)) +rankingdurations.append(datetime.time(hour=1)) + + +def is_ranking_piece(workout): + if workout.distance in rankingdistances: + return True + elif workout.duration in rankingdurations: + return True + + return False def range_to_color_hex(groupcols,palette='monochrome_blue'): diff --git a/rowers/views.py b/rowers/views.py index 1b98e8a9..745cc37b 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -56,6 +56,7 @@ from rowers.models import ( RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData, Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest, WorkoutComment,WorkoutCommentForm,RowerExportForm, + CalcAgePerformance ) from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement from rowers.metrics import rowingmetrics,defaultfavoritecharts @@ -108,7 +109,7 @@ from rowers.tasks import ( handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemailnewresponse, handle_updatedps, handle_updatecp,long_test_task,long_test_task2, - handle_zip_file + handle_zip_file,handle_getagegrouprecords ) from scipy.signal import savgol_filter @@ -284,6 +285,7 @@ verbose_job_status = { 'updatecp': 'Critical Power Calculation for Ergometer Workouts', 'updatecpwater': 'Critical Power Calculation for OTW Workouts', 'otwsetpower': 'Rowing Physics OTW Power Calculation', + 'agegrouprecords': 'Calculate age group records', 'make_plot': 'Create static chart', 'long_test_task': 'Long Test Task', 'long_test_task2': 'Long Test Task 2', @@ -711,7 +713,8 @@ def splitstdata(lijst): from utils import ( geo_distance,serialize_list,deserialize_list,uniqify, str2bool,range_to_color_hex,absolute,myqueue,get_call, - calculate_age + calculate_age,rankingdistances,rankingdurations, + is_ranking_piece ) import datautils @@ -2956,9 +2959,15 @@ def histo(request,theuser=0, if 'options' in request.session: options = request.session['options'] - workouttypes = options['workouttypes'] - includereststrokes = options['includereststrokes'] - waterboattype = options['waterboattype'] + try: + workouttypes = options['workouttypes'] + includereststrokes = options['includereststrokes'] + waterboattype = options['waterboattype'] + except KeyError: + workouttypes = ['water','rower','dynamic','slides'] + waterboattype = ['1x','2x','2-','4x','4-','8+'] + includereststrokes = False + workstrokesonly = not includereststrokes @@ -3161,6 +3170,7 @@ def addmanual_view(request): ) + print duration,'aap' id,message = dataprep.create_row_df(r, distance, duration,startdatetime, @@ -3175,7 +3185,7 @@ def addmanual_view(request): if id: w = Workout.objects.get(id=id) - w.rankingpiece = rankingpiece + w.rankingpiece = rankingpiece or is_ranking_piece(w) w.notes = notes w.save() messages.info(request,'New workout created') @@ -3297,29 +3307,22 @@ def rankings_view(request,theuser=0, enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = enddate+datetime.timedelta(days=1) - rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000] - rankingdurations = [] - rankingdurations.append(datetime.time(minute=1)) - rankingdurations.append(datetime.time(minute=4)) - rankingdurations.append(datetime.time(minute=30)) - rankingdurations.append(datetime.time(hour=1,minute=15)) - rankingdurations.append(datetime.time(hour=1)) - thedistances = [] theworkouts = [] thesecs = [] - - rankingdistances.sort() rankingdurations.sort() for rankingdistance in rankingdistances: - workouts = Workout.objects.filter(user=r,distance=rankingdistance, - workouttype__in=['rower','dynamic','slides'], - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('duration') + workouts = Workout.objects.filter( + user=r,distance=rankingdistance, + workouttype__in=['rower','dynamic','slides'], + rankingpiece=True, + startdatetime__gte=startdate, + startdatetime__lte=enddate + ).order_by('duration') if workouts: thedistances.append(rankingdistance) theworkouts.append(workouts[0]) @@ -3333,10 +3336,13 @@ def rankings_view(request,theuser=0, for rankingduration in rankingdurations: - workouts = Workout.objects.filter(user=r,duration=rankingduration, - workouttype='rower', - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('-distance') + workouts = Workout.objects.filter( + user=r,duration=rankingduration, + workouttype='rower', + rankingpiece=True, + startdatetime__gte=startdate, + startdatetime__lte=enddate + ).order_by('-distance') if workouts: thedistances.append(workouts[0].distance) theworkouts.append(workouts[0]) @@ -3511,6 +3517,43 @@ def rankings_view(request,theuser=0, 'teams':get_my_teams(request.user), }) +@login_required() +def ajax_agegrouprecords(request, + age=25, + sex='female', + weightcategory='hwt', + userid=0): + + wcdurations = [] + wcpower = [] + durations = [1,4,30,60] + distances = [100,500,1000,2000,5000,6000,10000,21097,42195] + + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + sex=sex, + weightcategory=weightcategory + ).values() + ) + ) + + jsondf = df.to_json() + + job = myqueue(queue, + handle_getagegrouprecords, + jsondf,distances,durations,age,sex,weightcategory, + ) + + + + return JSONResponse( + { + 'job':job.id + } + ) + + # Show ranking distances including predicted paces @login_required() def rankings_view2(request,theuser=0, @@ -3536,48 +3579,78 @@ def rankings_view2(request,theuser=0, if theuser == 0: theuser = request.user.id + else: + lastupdated = "01-01-1900" + promember=0 if not request.user.is_anonymous(): - r = getrower(request.user) + r = getrower(theuser) wcdurations = [] wcpower = [] + lastupdated = "01-01-1900" + userid = 0 + if 'options' in request.session: + options = request.session['options'] + try: + wcdurations = options['wcdurations'] + wcpower = options['wcpower'] + lastupdated = options['lastupdated'] + except KeyError: + pass + try: + userid = options['userid'] + except KeyError: + userid = 0 + else: + options = {} + + + + lastupdatedtime = arrow.get(lastupdated).timestamp + current_time = arrow.utcnow().timestamp + + deltatime_seconds = current_time - lastupdatedtime + recalc = False + if str(userid) != str(theuser) or deltatime_seconds > 3600: + recalc = True + options['lastupdated'] = arrow.utcnow().isoformat() + else: + recalc = False + + options['userid'] = theuser + if r.birthdate: - age = calculate_age(r.birthdate) - durations = [1,4,30,60] - distances = [100,500,1000,2000,5000,6000,10000,21097,42195] - for distance in distances: - worldclasspower = metrics.getagegrouprecord( - age, - sex=r.sex, - distance=distance, - weightcategory=r.weightcategory - ) - velo = (worldclasspower/2.8)**(1./3.) - try: - duration = distance/velo - wcdurations.append(duration) - wcpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = metrics.getagegrouprecord( - age, - sex=r.sex, - duration=duration, - weightcategory=r.weightcategory - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - wcdurations.append(60.*duration) - wcpower.append(worldclasspower) - except ValueError: - pass + age = calculate_age(r.birthdate) else: worldclasspower = None age = 0 + + agerecords = CalcAgePerformance.objects.filter( + age = age, + sex = r.sex, + weightcategory = r.weightcategory) + + print len(agerecords),'aap' + if len(agerecords) == 0: + recalc = True + wcpower = [] + wcduration = [] + else: + wcdurations = [] + wcpower = [] + for record in agerecords: + wcdurations.append(record.duration) + wcpower.append(record.power) + + options['wcpower'] = wcpower + options['wcdurations'] = wcdurations + if theuser: + options['userid'] = theuser + + request.session['options'] = options + result = request.user.is_authenticated() and ispromember(request.user) if result: @@ -3641,13 +3714,6 @@ def rankings_view2(request,theuser=0, enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = enddate+datetime.timedelta(days=1) - rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000] - rankingdurations = [] - rankingdurations.append(datetime.time(minute=1)) - rankingdurations.append(datetime.time(minute=4)) - rankingdurations.append(datetime.time(minute=30)) - rankingdurations.append(datetime.time(hour=1,minute=15)) - rankingdurations.append(datetime.time(hour=1)) thedistances = [] theworkouts = [] @@ -3660,10 +3726,12 @@ def rankings_view2(request,theuser=0, for rankingdistance in rankingdistances: - workouts = Workout.objects.filter(user=r,distance=rankingdistance, - workouttype__in=['rower','dynamic','slides'], - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('duration') + workouts = Workout.objects.filter( + user=r,distance=rankingdistance, + workouttype__in=['rower','dynamic','slides'], + rankingpiece=True, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by('duration') if workouts: thedistances.append(rankingdistance) theworkouts.append(workouts[0]) @@ -3677,10 +3745,12 @@ def rankings_view2(request,theuser=0, for rankingduration in rankingdurations: - workouts = Workout.objects.filter(user=r,duration=rankingduration, - workouttype='rower', - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('-distance') + workouts = Workout.objects.filter( + user=r,duration=rankingduration, + workouttype='rower', + rankingpiece=True, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by('-distance') if workouts: thedistances.append(workouts[0].distance) theworkouts.append(workouts[0]) @@ -3841,6 +3911,32 @@ def rankings_view2(request,theuser=0, 'power':int(pwr)} cpredictions.append(a) + if recalc: + wcdurations = [] + wcpower = [] + durations = [1,4,30,60] + distances = [100,500,1000,2000,5000,6000,10000,21097,42195] + + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + sex=r.sex, + weightcategory=r.weightcategory + ).values() + ) + ) + + jsondf = df.to_json() + + job = myqueue(queue, + handle_getagegrouprecords, + jsondf,distances,durations,age,r.sex,r.weightcategory) + try: + request.session['async_tasks'] += [(job.id,'agegrouprecords')] + except KeyError: + request.session['async_tasks'] = [(job.id,'agegrouprecords')] + + messages.error(request,message) return render(request, 'rankings.html', @@ -3857,6 +3953,7 @@ def rankings_view2(request,theuser=0, 'theuser':uu, 'age':age, 'sex':r.sex, + 'recalc':recalc, 'weightcategory':r.weightcategory, 'startdate':startdate, 'enddate':enddate, @@ -3983,12 +4080,6 @@ def otwrankings_view(request,theuser=0, enddate = enddate+datetime.timedelta(days=1) - rankingdurations = [] - rankingdurations.append(datetime.time(minute=1)) - rankingdurations.append(datetime.time(minute=4)) - rankingdurations.append(datetime.time(minute=30)) - rankingdurations.append(datetime.time(hour=1)) - rankingdurations.append(datetime.time(hour=1,minute=15)) thedistances = [] theworkouts = [] @@ -4238,14 +4329,6 @@ def oterankings_view(request,theuser=0, enddate = enddate+datetime.timedelta(days=1) - rankingdurations = [] - rankingdurations.append(datetime.time(minute=1)) - rankingdurations.append(datetime.time(minute=4)) - rankingdurations.append(datetime.time(minute=30)) - rankingdurations.append(datetime.time(hour=1)) - rankingdurations.append(datetime.time(hour=1,minute=15)) - - rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000] thedistances = [] theworkouts = [] @@ -5712,15 +5795,25 @@ def boxplot_view_data(request,userid=0, if 'options' in request.session: options = request.session['options'] - - includereststrokes = options['includereststrokes'] - spmmin = options['spmmin'] - spmmax = options['spmmax'] - workmin = options['workmin'] - workmax = options['workmax'] - ids = options['ids'] - userid = options['userid'] - plotfield = options['plotfield'] + + try: + includereststrokes = options['includereststrokes'] + spmmin = options['spmmin'] + spmmax = options['spmmax'] + workmin = options['workmin'] + workmax = options['workmax'] + ids = options['ids'] + userid = options['userid'] + plotfield = options['plotfield'] + except KeyError: + includereststrokes = False + spmmin = 15 + spmmax = 55 + workmin = 0 + workmax = 55 + ids = [] + userid = 0 + plotfield = 'spm' workstrokesonly = not includereststrokes @@ -5792,8 +5885,13 @@ def boxplot_view(request,userid=0, if 'options' in request.session: options = request.session['options'] + + try: + includereststrokes = options['includereststrokes'] + except KeyError: + includereststrokes = False + options['includereststrokes'] = False - includereststrokes = options['includereststrokes'] workstrokesonly = not includereststrokes if userid==0: @@ -5912,7 +6010,6 @@ def workouts_view(request,message='',successmessage='', except ValueError: activity_enddate = enddate - if teamid: try: theteam = Team.objects.get(id=teamid) @@ -5979,7 +6076,7 @@ def workouts_view(request,message='',successmessage='', stack='rower' else: stack='type' - + script,div = interactive_activitychart(g_workouts, activity_startdate, activity_enddate, @@ -6734,12 +6831,14 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""): form = AdvancedWorkoutForm(request.POST) if form.is_valid(): + quick_calc = form.cleaned_data['quick_calc'] boattype = form.cleaned_data['boattype'] weightvalue = form.cleaned_data['weightvalue'] row.boattype = boattype row.weightvalue = weightvalue row.save() + # load row data & create power/wind/bearing columns if not set f1 = row.csvfilename rowdata = rdata(f1) @@ -6776,7 +6875,9 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""): weightvalue, first_name,last_name,emailaddress,id, ps=[r.p0,r.p1,r.p2,r.p3], - ratio=r.cpratio) + ratio=r.cpratio, + quick_calc = quick_calc, + ) try: request.session['async_tasks'] += [(job.id,'otwsetpower')] @@ -7312,7 +7413,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""): # Normalized power & TSS duration = datadf['time'].max()-datadf['time'].min() duration /= 1.0e3 - pwr4 = datadf['power']**(4) + pwr4 = datadf['power']**(4.0) normp = (pwr4.mean())**(0.25) if not np.isnan(normp): ftp = float(r.ftp) @@ -9304,6 +9405,35 @@ def workout_getc2workout_view(request,c2id): url = reverse(workout_c2import_view) return HttpResponseRedirect(url) +@login_required +def workout_toggle_ranking(request,id=0): + is_ajax = False + if request.is_ajax(): + is_ajax = True + + try: + # check if valid ID exists (workout exists) + row = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + if not checkworkoutuser(request.user,row): + message = "You are not allowed to change this workout" + messages.error(request,message) + + # we are still here - we own the workout + row.rankingpiece = not row.rankingpiece + row.save() + + if is_ajax: + return JSONResponse({'result':row.rankingpiece}) + else: + url = reverse(workouts_view) + response = HttpResponseRedirect(url) + + return response + + # This is the main view for processing uploaded files @login_required() def workout_upload_view(request, diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index 0362df15..8b649642 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -56,6 +56,19 @@ body { padding-bottom: 20px; } +.notyellow { + font-size: 1.2em; + height: auto; + padding: 0px; +} + +.yellow { + color: #cccc00; + font-size: 1.2em; + height: auto; + padding: 0px; +} + a { /* color: #fff; */