diff --git a/rowers/dataprep.py b/rowers/dataprep.py index ca5340b6..29cdb2a2 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -8,7 +8,7 @@ from rowers.tasks import handle_sendemail_unrecognized from rowingdata import rower as rrower from rowingdata import main as rmain -from rowingdata import get_file_type +from rowingdata import get_file_type,get_empower_rigging from pandas import DataFrame,Series from pytz import timezone as tz,utc @@ -31,7 +31,7 @@ import os import pandas as pd import numpy as np import itertools - +import math from tasks import handle_sendemail_unrecognized from django.conf import settings @@ -102,6 +102,11 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True, except KeyError: pass + try: + datadf['hr'] = datadf['hr']+10 + except KeyError: + pass + datadf=datadf.clip(lower=0) datadf.replace(to_replace=0,value=np.nan,inplace=True) @@ -116,6 +121,11 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True, except KeyError: pass + try: + datadf['hr'] = datadf['hr']-10 + except KeyError: + pass + # clean data for useful ranges per column if not ignorehr: try: @@ -222,11 +232,11 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True, pass - try: - mask = datadf['catch'] > -30. - datadf.loc[mask,'catch'] = np.nan - except KeyError: - pass + try: + mask = datadf['catch'] > -30. + datadf.loc[mask,'catch'] = np.nan + except KeyError: + pass workoutstateswork = [1,4,5,8,9,6,7] @@ -326,7 +336,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', dosummary=True,title='Workout', notes='',totaldist=0,totaltime=0, summary='', - makeprivate=False): + makeprivate=False, + oarlength=2.89,inboard=0.88): message = None powerperc = 100*np.array([r.pw_ut2, r.pw_ut1, @@ -432,7 +443,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', privacy = 'visible' # check for duplicate start times - ws = Workout.objects.filter(starttime=workoutstarttime, + ws = Workout.objects.filter(startdatetime=workoutstartdatetime, user=r) if (len(ws) != 0): message = "Warning: This workout probably already exists in the database" @@ -446,6 +457,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', csvfilename=f2,notes=notes,summary=summary, maxhr=maxhr,averagehr=averagehr, startdatetime=workoutstartdatetime, + inboard=inboard,oarlength=oarlength, privacy=privacy) @@ -458,11 +470,13 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', # put stroke data in database res = dataprep(row.df,id=w.id,bands=True, - barchart=True,otwpower=True,empower=True) + barchart=True,otwpower=True,empower=True,inboard=inboard) return (w.id,message) def handle_nonpainsled(f2,fileformat,summary=''): + oarlength = 2.89 + inboard = 0.88 # handle RowPro: if (fileformat == 'rp'): row = RowProParser(f2) @@ -506,6 +520,7 @@ def handle_nonpainsled(f2,fileformat,summary=''): if (fileformat == 'speedcoach2'): row = SpeedCoach2Parser(f2) try: + oarlength,inboard = get_empower_rigging(f2) summary = row.allstats() except: pass @@ -534,7 +549,7 @@ def handle_nonpainsled(f2,fileformat,summary=''): except: os.remove(f_to_be_deleted+'.gz') - return (f2,summary) + return (f2,summary,oarlength,inboard) # Create new workout from file and store it in the database # This routine should be used everywhere in views.py and mailprocessing.py @@ -547,6 +562,8 @@ def new_workout_from_file(r,f2, message = None fileformat = get_file_type(f2) summary = '' + oarlength = 2.89 + inboard = 0.88 if len(fileformat)==3 and fileformat[0]=='zip': f_to_be_deleted = f2 with zipfile.ZipFile(f2) as z: @@ -590,7 +607,7 @@ def new_workout_from_file(r,f2, # handle non-Painsled by converting it to painsled compatible CSV if (fileformat != 'csv'): - f2,summary = handle_nonpainsled(f2,fileformat,summary=summary) + f2,summary,oarlength,inboard = handle_nonpainsled(f2,fileformat,summary=summary) dosummary = (fileformat != 'fit') @@ -599,6 +616,7 @@ def new_workout_from_file(r,f2, makeprivate=makeprivate, dosummary=dosummary, summary=summary, + inboard=inboard,oarlength=oarlength, title=title) return (id,message,f2) @@ -905,7 +923,7 @@ def datafusion(id1,id2,columns,offset): # saves it to the stroke_data table in the database # Takes a rowingdata object's DataFrame as input def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True, - empower=True): + empower=True,inboard=0.88): rowdatadf.set_index([range(len(rowdatadf))],inplace=True) t = rowdatadf.ix[:,'TimeStamp (sec)'] t = pd.Series(t-rowdatadf.ix[0,'TimeStamp (sec)']) @@ -1018,8 +1036,15 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True, finish = rowdatadf.ix[:,'finish'] peakforceangle = rowdatadf.ix[:,'peakforceangle'] driveenergy = rowdatadf.ix[:,'driveenergy'] - drivelength = driveenergy/(averageforce*4.44822) + arclength = (inboard-0.05)*(np.radians(finish)-np.radians(catch)) + if arclength.mean()>0: + drivelength = arclength + else: + drivelength = driveenergy/(averageforce*4.44822) + slip = rowdatadf.ix[:,'slip'] + totalangle = finish-catch + effectiveangle = finish-wash-catch-slip if windowsize > 3 and windowsize=minspm && spm1[i]<=maxspm) { if (distance1[i]>=mindist && distance1[i]<=maxdist) { - catchav += c[i] - finishav += finish[i] - slipav += slip[i] - washav += wash[i] - peakforceangleav += peakforceangle[i] - averageforceav += averageforce[i] - peakforceav += peakforce[i] - count += 1 + 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 + } } } } @@ -348,7 +325,16 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False): 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, @@ -361,9 +347,11 @@ def interactive_forcecurve(theworkouts,workstrokesonly=False): callback.args["maxdist"] = slider_dist_max layout = layoutrow([layoutcolumn([slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, ], ), plot]) @@ -386,6 +374,9 @@ def interactive_histoall(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","","" @@ -892,20 +883,21 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, # datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2) ids = [int(w.id) for w in theworkouts] - datadf = dataprep.getsmallrowdata_db([xparam,yparam1,yparam2],ids=ids,doclean=False) + columns = [xparam,yparam1,yparam2,'spm','driveenergy','distance'] + datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=False) yparamname1 = axlabels[yparam1] if yparam2 != 'None': yparamname2 = axlabels[yparam2] - datadf = datadf[datadf[yparam1] > 0] + #datadf = datadf[datadf[yparam1] > 0] - datadf = datadf[datadf[xparam] > 0] + #datadf = datadf[datadf[xparam] > 0] - if yparam2 != 'None': - datadf = datadf[datadf[yparam2] > 0] + #if yparam2 != 'None': + # datadf = datadf[datadf[yparam2] > 0] # check if dataframe not empty if datadf.empty: @@ -973,7 +965,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, line_dash=[6,6],line_width=2) y2means = y1means - xlabel = Label(x=370,y=130,x_units='screen',y_units='screen', + xlabel = Label(x=100,y=130,x_units='screen',y_units='screen', text=xparam+": {x1mean:6.2f}".format(x1mean=x1mean), background_fill_alpha=.7, text_color='green', @@ -985,7 +977,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, plot.add_layout(y1means) - y1label = Label(x=370,y=100,x_units='screen',y_units='screen', + y1label = Label(x=100,y=100,x_units='screen',y_units='screen', text=yparam1+": {y1mean:6.2f}".format(y1mean=y1mean), background_fill_alpha=.7, text_color='blue', @@ -1025,8 +1017,8 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, plot.add_layout(y2means) - y2label = Label(x=370,y=70,x_units='screen',y_units='screen', - text=yparam2+": {y2mean:6.2f}".format(y2mean=y2mean), + 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, text_color='red', ) @@ -1048,6 +1040,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, 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] @@ -1056,6 +1049,8 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, 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 @@ -1075,16 +1070,17 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, for (i=0; i=minspm && spm1[i]<=maxspm) { if (distance1[i]>=mindist && distance1[i]<=maxdist) { - 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] + 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] + } } } } @@ -1099,9 +1095,9 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, 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) + y1label.text = yname1+': '+(ym1).toFixed(2) + y2label.text = yname2+': '+(ym2).toFixed(2) + xlabel.text = xname+': '+(xm).toFixed(2) source2.trigger('change'); """) @@ -1115,6 +1111,15 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, 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, @@ -1127,9 +1132,11 @@ def interactive_cum_flex_chart2(theworkouts,promember=0, callback.args["maxdist"] = slider_dist_max layout = layoutrow([layoutcolumn([slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, ], ), plot]) @@ -1157,11 +1164,13 @@ def interactive_flex_chart2(id=0,promember=0, #rowdata,row = dataprep.getrowdata_db(id=id) columns = [xparam,yparam1,yparam2, 'ftime','distance','fpace', - 'power','hr','spm', - 'time','pace','workoutstate'] + 'power','hr','spm','driveenergy', + 'time','pace','workoutstate','time'] rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True) + rowdata.dropna(axis=1,how='all',inplace=True) + rowdata.dropna(axis=0,how='any',inplace=True) row = Workout.objects.get(id=id) if rowdata.empty: @@ -1182,7 +1191,7 @@ def interactive_flex_chart2(id=0,promember=0, try: rowdata['x1'] = rowdata.ix[:,xparam] except KeyError: - rowdata['x1'] = 0*rowdata.ix[:'time'] + rowdata['x1'] = 0*rowdata.ix[:,1] try: rowdata['y1'] = rowdata.ix[:,yparam1] @@ -1211,7 +1220,10 @@ def interactive_flex_chart2(id=0,promember=0, # average values if xparam != 'time': - x1mean = rowdata['x1'].mean() + try: + x1mean = rowdata['x1'].mean() + except TypeError: + x1mean = 0 else: x1mean = 0 @@ -1277,8 +1289,8 @@ def interactive_flex_chart2(id=0,promember=0, line_dash=[6,6],line_width=2) y2means = y1means - xlabel = Label(x=370,y=130,x_units='screen',y_units='screen', - text=xparam+": {x1mean:6.2f}".format(x1mean=x1mean), + 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, text_color='green', ) @@ -1290,8 +1302,8 @@ def interactive_flex_chart2(id=0,promember=0, plot.add_layout(y1means) - y1label = Label(x=370,y=100,x_units='screen',y_units='screen', - text=yparam1+": {y1mean:6.2f}".format(y1mean=y1mean), + 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, text_color='blue', ) @@ -1365,8 +1377,8 @@ def interactive_flex_chart2(id=0,promember=0, plot.add_layout(y2means) - y2label = Label(x=370,y=70,x_units='screen',y_units='screen', - text=yparam2+": {y2mean:6.2f}".format(y2mean=y2mean), + 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, text_color='red', ) @@ -1400,6 +1412,7 @@ def interactive_flex_chart2(id=0,promember=0, var y1 = data['y1'] var y2 = data['y2'] var spm1 = data['spm'] + var driveenergy1 = data['driveenergy'] var time1 = data['time'] var pace1 = data['pace'] var hr1 = data['hr'] @@ -1414,6 +1427,8 @@ def interactive_flex_chart2(id=0,promember=0, 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 @@ -1438,21 +1453,22 @@ def interactive_flex_chart2(id=0,promember=0, for (i=0; i=minspm && spm1[i]<=maxspm) { if (distance1[i]>=mindist && distance1[i]<=maxdist) { - 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] + 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] + } } } } @@ -1483,6 +1499,15 @@ def interactive_flex_chart2(id=0,promember=0, 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(rowdata['distance'].max()/100.) slider_dist_min = Slider(start=0,end=distmax,value=0,step=1, @@ -1495,9 +1520,11 @@ def interactive_flex_chart2(id=0,promember=0, callback.args["maxdist"] = slider_dist_max layout = layoutrow([layoutcolumn([slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, + slider_spm_max, + slider_dist_min, + slider_dist_max, + slider_work_min, + slider_work_max, ], ), plot]) @@ -1512,7 +1539,9 @@ def interactive_flex_chart2(id=0,promember=0, 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" @@ -1610,7 +1639,9 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', 'workoutid'] datadf = dataprep.getsmallrowdata_db(columns,ids=ids) + datadf.dropna(axis=1,how='all',inplace=True) datadf.dropna(axis=0,how='any',inplace=True) + tseconds = datadf.ix[:,'time'] yparamname = axlabels[yparam] @@ -1672,12 +1703,15 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', group = datadf[datadf['workoutid']==int(id)].copy() group.sort_values(by='time',ascending=True,inplace=True) group['x'] = group[xparam] - group['y'] = group[yparam] + try: + group['y'] = group[yparam] + except KeyError: + group['y'] = 0.0*group['x'] ymean = group['y'].mean() ylabel = Label(x=100,y=70+20*cntr, x_units='screen',y_units='screen', - text=yparam+": {ymean:6.2f}".format(ymean=ymean), + text=axlabels[yparam]+": {ymean:6.2f}".format(ymean=ymean), background_fill_alpha=.7, text_color=color, ) @@ -1772,8 +1806,11 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', 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) @@ -1787,11 +1824,14 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', else: rowdata2.sort_values(by='time',ascending=True,inplace=True) - x1 = rowdata1.ix[:,xparam] - x2 = rowdata2.ix[:,xparam] + try: + x1 = rowdata1.ix[:,xparam] + x2 = rowdata2.ix[:,xparam] - y1 = rowdata1.ix[:,yparam] - y2 = rowdata2.ix[:,yparam] + y1 = rowdata1.ix[:,yparam] + y2 = rowdata2.ix[:,yparam] + except KeyError: + return "","No valid Data Available" x_axis_type = 'linear' y_axis_type = 'linear' @@ -1914,7 +1954,7 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', 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] if xparam == 'time': plot.xaxis[0].formatter = DatetimeTickFormatter( @@ -1943,7 +1983,9 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', 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" diff --git a/rowers/models.py b/rowers/models.py index c52c34ed..2b8b20f9 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -393,6 +393,12 @@ class Workout(models.Model): maxhr = models.IntegerField(blank=True,null=True) uploadedtostrava = models.IntegerField(default=0) uploadedtosporttracks = models.IntegerField(default=0) + + # empower stuff + inboard = models.FloatField(default=0.88) + oarlength = models.FloatField(default=2.89) + + notes = models.CharField(blank=True,null=True,max_length=1000) summary = models.TextField(blank=True) privacy = models.CharField(default='visible',max_length=30, @@ -492,7 +498,9 @@ class StrokeData(models.Model): wash = models.FloatField(default=0,null=True,verbose_name='Wash') peakforceangle = models.FloatField(default=0,null=True,verbose_name='Peak Force Angle') rhythm = models.FloatField(default=1.0,null=True,verbose_name='Rhythm') - + totalangle = models.FloatField(default=0.0,null=True,verbose_name='Total Stroke Length (deg)') + effectiveangle = models.FloatField(default=0.0,null=True,verbose_name='Effective Stroke Length (deg)') + # A wrapper around the png files class GraphImage(models.Model): filename = models.CharField(default='',max_length=150,blank=True,null=True) diff --git a/rowers/templates/comparisonchart2.html b/rowers/templates/comparisonchart2.html index c6f664a0..a07b41bd 100644 --- a/rowers/templates/comparisonchart2.html +++ b/rowers/templates/comparisonchart2.html @@ -6,144 +6,134 @@ {% block content %} - - + + - {{ interactiveplot |safe }} +{{ interactiveplot |safe }} - - + +

 

+
+ - -
- - -
- -
-
- Line Plot -
- -
- +
+ +
+
+ Line Plot +
+ +
+
- {{ the_div|safe }} + {{ the_div|safe }}
diff --git a/rowers/templates/developers.html b/rowers/templates/developers.html index 268fed20..b81bb596 100644 --- a/rowers/templates/developers.html +++ b/rowers/templates/developers.html @@ -47,13 +47,13 @@
  • Advantages
      -
    • It may take up to five minutes for the workout to show up - on the site.
    • +
    • It's a simple process, which can be automated.
  • Disadvantages
      -
    • It's a simple process, which can be automated.
    • +
    • It may take up to five minutes for the workout to show up + on the site.
@@ -86,7 +86,7 @@
  • The API is not stable and not fully tested yet.
  • You need to register your app with us. We can revoke your permissions if you misuse them.
  • -
  • The first time user must grant permissions to your app.
  • +
  • The user user must grant permissions to your app.
  • You need to manage authorization tokens.
  • diff --git a/rowers/templates/flexchart3otw.html b/rowers/templates/flexchart3otw.html index 4697f3a7..52a8eeaa 100644 --- a/rowers/templates/flexchart3otw.html +++ b/rowers/templates/flexchart3otw.html @@ -51,122 +51,70 @@ -