Merge branch 'develop' of https://bitbucket.org/sanderroosendaal/rowsandall into develop
This commit is contained in:
+132
-3
@@ -10,6 +10,7 @@ from __future__ import unicode_literals, absolute_import
|
|||||||
from rowers.models import Workout, Team
|
from rowers.models import Workout, Team
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
import collections
|
||||||
|
|
||||||
from rowingdata import rowingdata as rrdata
|
from rowingdata import rowingdata as rrdata
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ from rowingdata import (
|
|||||||
from rowingdata.csvparsers import HumonParser
|
from rowingdata.csvparsers import HumonParser
|
||||||
|
|
||||||
|
|
||||||
from rowers.metrics import axes,calc_trimp,rowingmetrics,dtypes
|
from rowers.metrics import axes,calc_trimp,rowingmetrics,dtypes,metricsgroups
|
||||||
from rowers.models import strokedatafields
|
from rowers.models import strokedatafields
|
||||||
|
|
||||||
#allowedcolumns = [item[0] for item in rowingmetrics]
|
#allowedcolumns = [item[0] for item in rowingmetrics]
|
||||||
@@ -122,6 +123,100 @@ from scipy.signal import savgol_filter
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def get_video_data(w,groups=['basic'],mode='water'):
|
||||||
|
modes = [mode,'both','basic']
|
||||||
|
columns = ['time','velo','spm']
|
||||||
|
columns += [name for name,d in rowingmetrics if d['group'] in groups and d['mode'] in modes]
|
||||||
|
columns = list(set(columns))
|
||||||
|
df = getsmallrowdata_db(columns,ids=[w.id],
|
||||||
|
workstrokesonly=False,doclean=False,compute=False)
|
||||||
|
df['time'] = (df['time']-df['time'].min())/1000.
|
||||||
|
df.sort_values(by='time',inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
df.set_index(pd.to_timedelta(df['time'],unit='s'),inplace=True)
|
||||||
|
df2 = df.resample('1s').first().fillna(method='ffill')
|
||||||
|
if 'pace' in columns:
|
||||||
|
df2['pace'] = df2['pace']/1000.
|
||||||
|
p = df2['pace']
|
||||||
|
p = p.apply(lambda x:timedeltaconv(x))
|
||||||
|
p = nicepaceformat(p)
|
||||||
|
df2['pace'] = p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#mask = df2['time'] < delay
|
||||||
|
#df2 = df2.mask(mask).dropna()
|
||||||
|
df2['time'] = (df2['time']-df2['time'].min())
|
||||||
|
|
||||||
|
df2 = df2.round(decimals=2)
|
||||||
|
|
||||||
|
boatspeed = (100*df2['velo']).astype(int)/100.
|
||||||
|
|
||||||
|
try:
|
||||||
|
coordinates = get_latlon_time(w.id)
|
||||||
|
except KeyError:
|
||||||
|
nulseries = df['time']*0
|
||||||
|
coordinates = pd.DataFrame({
|
||||||
|
'time': df['time'],
|
||||||
|
'latitude': nulseries,
|
||||||
|
'longitude': nulseries,
|
||||||
|
})
|
||||||
|
|
||||||
|
coordinates.set_index(pd.to_timedelta(coordinates['time'],unit='s'),inplace=True)
|
||||||
|
coordinates = coordinates.resample('1s').mean().interpolate()
|
||||||
|
#mask = coordinates['time'] < delay
|
||||||
|
#coordinates = coordinates.mask(mask).dropna()
|
||||||
|
coordinates['time'] = coordinates['time']-coordinates['time'].min()
|
||||||
|
latitude = coordinates['latitude']
|
||||||
|
longitude = coordinates['longitude']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# bundle data
|
||||||
|
data = {
|
||||||
|
'boatspeed':boatspeed.values.tolist(),
|
||||||
|
'latitude':latitude.values.tolist(),
|
||||||
|
'longitude':longitude.values.tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# metrics = {
|
||||||
|
# 'boatspeed': {
|
||||||
|
# 'unit': 'm/s',
|
||||||
|
# 'metric': 'boatspeed',
|
||||||
|
# 'name': 'Boat Speed'
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
|
metrics = {}
|
||||||
|
|
||||||
|
for c in columns:
|
||||||
|
if c != 'time':
|
||||||
|
try:
|
||||||
|
if dict(rowingmetrics)[c]['numtype'] == 'integer':
|
||||||
|
data[c] = df2[c].astype(int).tolist()
|
||||||
|
else:
|
||||||
|
data[c] = df2[c].values.tolist()
|
||||||
|
metrics[c] = {
|
||||||
|
'name': dict(rowingmetrics)[c]['verbose_name'],
|
||||||
|
'metric': c,
|
||||||
|
'unit': ''
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
metrics['boatspeed'] = metrics.pop('velo')
|
||||||
|
# metrics['workperstroke'] = metrics.pop('driveenergy')
|
||||||
|
metrics = collections.OrderedDict(sorted(metrics.items()))
|
||||||
|
|
||||||
|
maxtime = coordinates['time'].max()
|
||||||
|
|
||||||
|
|
||||||
|
return data, metrics, maxtime
|
||||||
|
|
||||||
|
|
||||||
def polarization_index(df,rower):
|
def polarization_index(df,rower):
|
||||||
df['dt'] = df['time'].diff()/6.e4
|
df['dt'] = df['time'].diff()/6.e4
|
||||||
# remove rest (spm<15)
|
# remove rest (spm<15)
|
||||||
@@ -172,6 +267,36 @@ def get_latlon(id):
|
|||||||
|
|
||||||
return [pd.Series([]), pd.Series([])]
|
return [pd.Series([]), pd.Series([])]
|
||||||
|
|
||||||
|
def get_latlon_time(id):
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=id)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
rowdata = rdata(w.csvfilename)
|
||||||
|
|
||||||
|
if rowdata.df.empty:
|
||||||
|
return [pd.Series([]), pd.Series([])]
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
latitude = rowdata.df.loc[:, ' latitude']
|
||||||
|
longitude = rowdata.df.loc[:, ' longitude']
|
||||||
|
except KeyError:
|
||||||
|
latitude = 0 * rowdata.df.loc[:, 'TimeStamp (sec)']
|
||||||
|
longitude = 0 * rowdata.df.loc[:, 'TimeStamp (sec)']
|
||||||
|
except AttributeError:
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'time': rowdata.df[' ElapsedTime (sec)'],
|
||||||
|
'latitude': rowdata.df[' latitude'],
|
||||||
|
'longitude': rowdata.df[' longitude']
|
||||||
|
})
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
def workout_summary_to_df(
|
def workout_summary_to_df(
|
||||||
rower,
|
rower,
|
||||||
startdate=datetime.datetime(1970,1,1),
|
startdate=datetime.datetime(1970,1,1),
|
||||||
@@ -1693,8 +1818,12 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not data.empty and data['efficiency'].mean() == 0 and data['power'].mean() != 0 and checkefficiency == True:
|
if checkefficiency==True and not data.empty:
|
||||||
data = add_efficiency(id=id)
|
try:
|
||||||
|
if data['efficiency'].mean() == 0 and data['power'].mean() != 0:
|
||||||
|
data = add_efficiency(id=id)
|
||||||
|
except KeyError:
|
||||||
|
data = add_efficiency(id=id)
|
||||||
|
|
||||||
if doclean:
|
if doclean:
|
||||||
data = clean_df_stats(data, ignorehr=True)
|
data = clean_df_stats(data, ignorehr=True)
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ from rowingdata import empower_bug_correction,get_empower_rigging
|
|||||||
from time import strftime
|
from time import strftime
|
||||||
from pandas import DataFrame,Series
|
from pandas import DataFrame,Series
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import itertools
|
import itertools
|
||||||
|
|||||||
+46
-5
@@ -24,7 +24,7 @@ import rowers.mytypes as mytypes
|
|||||||
import datetime
|
import datetime
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
from rowers.utils import landingpages
|
from rowers.utils import landingpages
|
||||||
from rowers.metrics import axes
|
from rowers.metrics import axes, metricsgroups,rowingmetrics
|
||||||
from rowers.metrics import axlabels
|
from rowers.metrics import axlabels
|
||||||
|
|
||||||
formaxlabels = axlabels.copy()
|
formaxlabels = axlabels.copy()
|
||||||
@@ -51,6 +51,40 @@ class FlexibleDecimalField(forms.DecimalField):
|
|||||||
value = value.replace('.', '').replace(',', '.')
|
value = value.replace('.', '').replace(',', '.')
|
||||||
return super(FlexibleDecimalField, self).to_python(value)
|
return super(FlexibleDecimalField, self).to_python(value)
|
||||||
|
|
||||||
|
# Video Analysis creation form
|
||||||
|
class VideoAnalysisCreateForm(forms.Form):
|
||||||
|
name = forms.CharField(max_length=255,label='Analysis Name',required=False)
|
||||||
|
url = forms.CharField(max_length=255,required=True,label='YouTube Video URL')
|
||||||
|
delay = forms.IntegerField(initial=0,label='Delay (seconds)')
|
||||||
|
|
||||||
|
def get_metricschoices(mode='water'):
|
||||||
|
modes = [mode,'both','basic']
|
||||||
|
metricsdescriptions = {}
|
||||||
|
for m in metricsgroups:
|
||||||
|
metricsdescriptions[m] = m+' ('
|
||||||
|
for name,d in rowingmetrics:
|
||||||
|
if d['group']==m and d['mode'] in modes:
|
||||||
|
metricsdescriptions[m]+=d['verbose_name']+', '
|
||||||
|
metricsdescriptions[m]=metricsdescriptions[m][0:-2]+')'
|
||||||
|
|
||||||
|
metricsgroupschoices = ((m,metricsdescriptions[m]) for m in metricsgroups)
|
||||||
|
|
||||||
|
return metricsgroupschoices
|
||||||
|
|
||||||
|
class VideoAnalysisMetricsForm(forms.Form):
|
||||||
|
groups = forms.MultipleChoiceField(label='Metrics Groups',
|
||||||
|
choices=get_metricschoices(mode='water'),
|
||||||
|
widget=forms.CheckboxSelectMultiple,)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
mode = 'water'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
mode = kwargs.pop('mode','water')
|
||||||
|
super(VideoAnalysisMetricsForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['groups'].choices = get_metricschoices(mode=mode)
|
||||||
|
|
||||||
|
|
||||||
# BillingForm form
|
# BillingForm form
|
||||||
class BillingForm(forms.Form):
|
class BillingForm(forms.Form):
|
||||||
amount = FlexibleDecimalField(required=True,decimal_places=2,
|
amount = FlexibleDecimalField(required=True,decimal_places=2,
|
||||||
@@ -816,6 +850,17 @@ class PlanSelectForm(forms.Form):
|
|||||||
class CourseSelectForm(forms.Form):
|
class CourseSelectForm(forms.Form):
|
||||||
course = forms.ModelChoiceField(queryset=GeoCourse.objects.all())
|
course = forms.ModelChoiceField(queryset=GeoCourse.objects.all())
|
||||||
|
|
||||||
|
class WorkoutSingleSelectForm(forms.Form):
|
||||||
|
workout = forms.ModelChoiceField(
|
||||||
|
queryset=Workout.objects.filter(),
|
||||||
|
widget=forms.RadioSelect
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
workouts = kwargs.pop('workouts',Workout.objects.filter().order_by('-date'))
|
||||||
|
super(WorkoutSingleSelectForm,self).__init__(*args,**kwargs)
|
||||||
|
self.fields['workout'].queryset = workouts
|
||||||
|
|
||||||
class WorkoutMultipleCompareForm(forms.Form):
|
class WorkoutMultipleCompareForm(forms.Form):
|
||||||
workouts = forms.ModelMultipleChoiceField(
|
workouts = forms.ModelMultipleChoiceField(
|
||||||
queryset=Workout.objects.filter(),
|
queryset=Workout.objects.filter(),
|
||||||
@@ -1380,7 +1425,3 @@ class FlexAxesForm(forms.Form):
|
|||||||
self.fields['xaxis'].choices = axchoicesbasicx
|
self.fields['xaxis'].choices = axchoicesbasicx
|
||||||
self.fields['yaxis1'].choices = axchoicesbasicy
|
self.fields['yaxis1'].choices = axchoicesbasicy
|
||||||
self.fields['yaxis2'].choices = axchoicesbasicy
|
self.fields['yaxis2'].choices = axchoicesbasicy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1809,6 +1809,127 @@ def leaflet_chart2(lat,lon,name=""):
|
|||||||
|
|
||||||
return script,div
|
return script,div
|
||||||
|
|
||||||
|
def leaflet_chart_video(lat,lon,name=""):
|
||||||
|
if not len(lat) or not len(lon):
|
||||||
|
return [0,"invalid coordinate data"]
|
||||||
|
|
||||||
|
|
||||||
|
# Throw out 0,0
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'lat':lat,
|
||||||
|
'lon':lon
|
||||||
|
})
|
||||||
|
|
||||||
|
df = df.replace(0,np.nan)
|
||||||
|
df = df.loc[(df!=0).any(axis=1)]
|
||||||
|
df.fillna(method='bfill',axis=0,inplace=True)
|
||||||
|
df.fillna(method='ffill',axis=0,inplace=True)
|
||||||
|
lat = df['lat']
|
||||||
|
lon = df['lon']
|
||||||
|
if lat.empty or lon.empty:
|
||||||
|
return [0,"invalid coordinate data"]
|
||||||
|
|
||||||
|
latmean = lat.mean()
|
||||||
|
lonmean = lon.mean()
|
||||||
|
latbegin = lat[lat.index[0]]
|
||||||
|
longbegin = lon[lon.index[0]]
|
||||||
|
latend = lat[lat.index[-1]]
|
||||||
|
longend = lon[lon.index[-1]]
|
||||||
|
|
||||||
|
coordinates = zip(lat,lon)
|
||||||
|
|
||||||
|
scoordinates = "["
|
||||||
|
|
||||||
|
for x,y in coordinates:
|
||||||
|
scoordinates += """[{x},{y}],
|
||||||
|
""".format(
|
||||||
|
x=x,
|
||||||
|
y=y
|
||||||
|
)
|
||||||
|
|
||||||
|
scoordinates += "]"
|
||||||
|
|
||||||
|
script = """
|
||||||
|
|
||||||
|
|
||||||
|
var streets = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.streets'
|
||||||
|
}}),
|
||||||
|
|
||||||
|
satellite = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.satellite'
|
||||||
|
}}),
|
||||||
|
|
||||||
|
outdoors = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.outdoors'
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var mymap = L.map('map_canvas', {{
|
||||||
|
center: [{latmean}, {lonmean}],
|
||||||
|
zoom: 13,
|
||||||
|
layers: [streets, satellite]
|
||||||
|
}}).setView([{latmean},{lonmean}], 13);
|
||||||
|
|
||||||
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
||||||
|
navKey: 'Navionics_webapi_03205',
|
||||||
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
||||||
|
isTransparent: true,
|
||||||
|
zIndex: 1
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
||||||
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
||||||
|
|
||||||
|
|
||||||
|
//create two TileLayer
|
||||||
|
var nautical=new L.TileLayer(osmUrl,{{
|
||||||
|
maxZoom:18}});
|
||||||
|
|
||||||
|
|
||||||
|
L.control.layers({{
|
||||||
|
"Streets": streets,
|
||||||
|
"Satellite": satellite,
|
||||||
|
"Outdoors": outdoors,
|
||||||
|
"Nautical": nautical,
|
||||||
|
}},{{
|
||||||
|
"Navionics":navionics,
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
position:'topleft'
|
||||||
|
}}).addTo(mymap);
|
||||||
|
|
||||||
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Start</b>");
|
||||||
|
|
||||||
|
var latlongs = {scoordinates}
|
||||||
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
||||||
|
mymap.fitBounds(polyline.getBounds())
|
||||||
|
|
||||||
|
""".format(
|
||||||
|
latmean=latmean,
|
||||||
|
lonmean=lonmean,
|
||||||
|
latbegin = latbegin,
|
||||||
|
latend=latend,
|
||||||
|
longbegin=longbegin,
|
||||||
|
longend=longend,
|
||||||
|
scoordinates=scoordinates,
|
||||||
|
)
|
||||||
|
|
||||||
|
div = """
|
||||||
|
<div id="map_canvas" style="width: 640px; height: 390px;"><p> </p></div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return script,div
|
||||||
|
|
||||||
|
|
||||||
def interactive_agegroupcpchart(age,normalized=False):
|
def interactive_agegroupcpchart(age,normalized=False):
|
||||||
durations = [1,4,30,60]
|
durations = [1,4,30,60]
|
||||||
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
|
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
|
||||||
@@ -2828,6 +2949,114 @@ def interactive_chart(id=0,promember=0,intervaldata = {}):
|
|||||||
|
|
||||||
return [script,div]
|
return [script,div]
|
||||||
|
|
||||||
|
def interactive_chart_video(videodata):
|
||||||
|
|
||||||
|
spm = videodata['spm']
|
||||||
|
time = range(len(spm))
|
||||||
|
|
||||||
|
data = zip(time,spm)
|
||||||
|
|
||||||
|
data2 = "["
|
||||||
|
|
||||||
|
for t,s in data:
|
||||||
|
data2 += "{x: %s, y: %s}, " % (t,s)
|
||||||
|
|
||||||
|
data2 = data2[:-2] + "]"
|
||||||
|
|
||||||
|
markerpoint = {
|
||||||
|
'x': time[0],
|
||||||
|
'y': spm[0],
|
||||||
|
'r': 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
div = """
|
||||||
|
<canvas id="myChart">
|
||||||
|
</canvas>
|
||||||
|
"""
|
||||||
|
|
||||||
|
script = """
|
||||||
|
var ctx = document.getElementById("myChart").getContext('2d');
|
||||||
|
var data = %s
|
||||||
|
|
||||||
|
var myChart = new Chart(ctx, {
|
||||||
|
type: 'scatter',
|
||||||
|
label: 'SPM',
|
||||||
|
animationSteps: 10,
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 100,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
scaleLabel: {
|
||||||
|
display: true,
|
||||||
|
labelString: 'Stroke Rate'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
scaleLabel: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
labelString: 'Time (seconds)'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data:
|
||||||
|
{
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'bubble',
|
||||||
|
label: 'now',
|
||||||
|
data: [ %s ],
|
||||||
|
backgroundColor: '#36a2eb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'spm',
|
||||||
|
data: data,
|
||||||
|
backgroundColor: "#ff0000",
|
||||||
|
borderColor: "#ff0000",
|
||||||
|
fill: false,
|
||||||
|
borderDash: [0, 0],
|
||||||
|
pointRadius: 1,
|
||||||
|
pointHoverRadius: 1,
|
||||||
|
showLine: true,
|
||||||
|
tension: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var marker = {
|
||||||
|
datapoint: %s ,
|
||||||
|
setLatLng: function (LatLng) {
|
||||||
|
var lat = LatLng.lat;
|
||||||
|
var lng = LatLng.lng;
|
||||||
|
this.datapoint = {
|
||||||
|
'x': lat,
|
||||||
|
'y': lng,
|
||||||
|
'r': 10,
|
||||||
|
}
|
||||||
|
myChart.data.datasets[0].data[0] = this.datapoint;
|
||||||
|
myChart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
marker.setLatLng({
|
||||||
|
'lat': data[0]['x'],
|
||||||
|
'lng': data[0]['y']
|
||||||
|
})
|
||||||
|
|
||||||
|
""" % (data2, markerpoint, markerpoint)
|
||||||
|
|
||||||
|
return [script,div]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
||||||
ploterrorbars=False,
|
ploterrorbars=False,
|
||||||
title=None,binsize=1,colorlegend=[],
|
title=None,binsize=1,colorlegend=[],
|
||||||
|
|||||||
+54
-30
@@ -34,7 +34,6 @@ nometrics = [
|
|||||||
'workoutid',
|
'workoutid',
|
||||||
'totalangle',
|
'totalangle',
|
||||||
'hr_bottom',
|
'hr_bottom',
|
||||||
'x_right',
|
|
||||||
'Position',
|
'Position',
|
||||||
'Extensions',
|
'Extensions',
|
||||||
'GPS Speed',
|
'GPS Speed',
|
||||||
@@ -56,7 +55,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('hr',{
|
('hr',{
|
||||||
'numtype':'integer',
|
'numtype':'integer',
|
||||||
@@ -65,7 +65,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 100,
|
'ax_min': 100,
|
||||||
'ax_max': 200,
|
'ax_max': 200,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'athlete'}),
|
||||||
|
|
||||||
('pace',{
|
('pace',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -74,7 +75,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 1.0e3*210,
|
'ax_min': 1.0e3*210,
|
||||||
'ax_max': 1.0e3*75,
|
'ax_max': 1.0e3*75,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group': 'basic'}),
|
||||||
|
|
||||||
('velo',{
|
('velo',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -84,7 +86,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 8,
|
'ax_max': 8,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type':'pro'}),
|
'type':'pro',
|
||||||
|
'group': 'basic'}),
|
||||||
|
|
||||||
# ('powerhr',{
|
# ('powerhr',{
|
||||||
# 'numtype':'float',
|
# 'numtype':'float',
|
||||||
@@ -93,7 +96,8 @@ rowingmetrics = (
|
|||||||
# 'ax_min':0,
|
# 'ax_min':0,
|
||||||
# 'ax_max':300,
|
# 'ax_max':300,
|
||||||
# 'mode':'both',
|
# 'mode':'both',
|
||||||
# 'type':'pro'}),
|
# 'type':'pro',
|
||||||
|
# 'group':'athlete'}),
|
||||||
|
|
||||||
('spm',{
|
('spm',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -102,7 +106,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 15,
|
'ax_min': 15,
|
||||||
'ax_max': 45,
|
'ax_max': 45,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('driveenergy',{
|
('driveenergy',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -111,7 +116,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1000,
|
'ax_max': 1000,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('power',{
|
('power',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -120,7 +126,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 600,
|
'ax_max': 600,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('averageforce',{
|
('averageforce',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -129,7 +136,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1200,
|
'ax_max': 1200,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('peakforce',{
|
('peakforce',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -138,7 +146,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1500,
|
'ax_max': 1500,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('drivelength',{
|
('drivelength',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -146,8 +155,9 @@ rowingmetrics = (
|
|||||||
'verbose_name': 'Drive Length (m)',
|
'verbose_name': 'Drive Length (m)',
|
||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 2.5,
|
'ax_max': 2.5,
|
||||||
'mode':'both',
|
'mode':'rower',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('forceratio',{
|
('forceratio',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -156,16 +166,18 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1,
|
'ax_max': 1,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'forcepower'}),
|
||||||
|
|
||||||
('distance',{
|
('distance',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
'verbose_name': 'Distance (m)',
|
'verbose_name': 'Interval Distance (m)',
|
||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('cumdist',{
|
('cumdist',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -174,7 +186,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('drivespeed',{
|
('drivespeed',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -184,7 +197,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 4,
|
'ax_max': 4,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('catch',{
|
('catch',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -194,7 +208,8 @@ rowingmetrics = (
|
|||||||
'ax_max': -75,
|
'ax_max': -75,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('slip',{
|
('slip',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -204,7 +219,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 20,
|
'ax_max': 20,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('finish',{
|
('finish',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -214,7 +230,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 55,
|
'ax_max': 55,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('wash',{
|
('wash',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -224,7 +241,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 30,
|
'ax_max': 30,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('peakforceangle',{
|
('peakforceangle',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -234,7 +252,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 50,
|
'ax_max': 50,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
|
|
||||||
('totalangle',{
|
('totalangle',{
|
||||||
@@ -245,7 +264,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 140,
|
'ax_max': 140,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
|
|
||||||
('effectiveangle',{
|
('effectiveangle',{
|
||||||
@@ -256,7 +276,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 140,
|
'ax_max': 140,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('rhythm',{
|
('rhythm',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -266,7 +287,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 55,
|
'ax_max': 55,
|
||||||
'default': 1.0,
|
'default': 1.0,
|
||||||
'mode':'erg',
|
'mode':'erg',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('efficiency',{
|
('efficiency',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -276,7 +298,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 110,
|
'ax_max': 110,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('distanceperstroke',{
|
('distanceperstroke',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -286,10 +309,13 @@ rowingmetrics = (
|
|||||||
'ax_max': 15,
|
'ax_max': 15,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
metricsgroups = list(set([d['group'] for n,d in rowingmetrics]))
|
||||||
|
|
||||||
dtypes = {}
|
dtypes = {}
|
||||||
|
|
||||||
for name,d in rowingmetrics:
|
for name,d in rowingmetrics:
|
||||||
@@ -395,5 +421,3 @@ def calc_trimp(df,sex,hrmax,hrmin,hrftp):
|
|||||||
hrtss = 100*trimp/trimp1hr
|
hrtss = 100*trimp/trimp1hr
|
||||||
|
|
||||||
return trimp,hrtss
|
return trimp,hrtss
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3720,3 +3720,18 @@ class BlogPost(models.Model):
|
|||||||
title = models.TextField(max_length=300)
|
title = models.TextField(max_length=300)
|
||||||
link = models.TextField(max_length=300)
|
link = models.TextField(max_length=300)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
|
|
||||||
|
defaultgroups = ['basic']
|
||||||
|
|
||||||
|
class VideoAnalysis(models.Model):
|
||||||
|
name = models.CharField(default='', max_length=150,blank=True,null=True)
|
||||||
|
video_id = models.CharField(default='',max_length=150)
|
||||||
|
delay = models.IntegerField(default=0)
|
||||||
|
workout = models.ForeignKey(Workout, on_delete=models.CASCADE)
|
||||||
|
metricsgroups = TemplateListField(default=defaultgroups)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('video_id','workout')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|||||||
@@ -0,0 +1,360 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load leaflet_tags %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}Workout Video{% endblock %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
{% leaflet_js %}
|
||||||
|
{% leaflet_css %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('form').submit(function(e) {
|
||||||
|
$(':disabled').each(function(e) {
|
||||||
|
$(this).removeAttr('disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.slidecontainer {
|
||||||
|
width: 100%; /* Width of the outside container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
/* The slider itself */
|
||||||
|
.slider {
|
||||||
|
-webkit-appearance: none; /* Override default CSS styles */
|
||||||
|
appearance: none;
|
||||||
|
width: 100%; /* Full-width */
|
||||||
|
height: 25px; /* Specified height */
|
||||||
|
background: #d3d3d3; /* Grey background */
|
||||||
|
outline: none; /* Remove outline */
|
||||||
|
opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
|
||||||
|
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mouse-over effects */
|
||||||
|
.slider:hover {
|
||||||
|
opacity: 1; /* Fully shown on mouse-over */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
|
||||||
|
.slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none; /* Override default look */
|
||||||
|
appearance: none;
|
||||||
|
width: 25px; /* Set a specific slider handle width */
|
||||||
|
height: 25px; /* Slider handle height */
|
||||||
|
background: #4CAF50; /* Green background */
|
||||||
|
cursor: pointer; /* Cursor on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-moz-range-thumb {
|
||||||
|
width: 25px; /* Set a specific slider handle width */
|
||||||
|
height: 25px; /* Slider handle height */
|
||||||
|
background: #4CAF50; /* Green background */
|
||||||
|
cursor: pointer; /* Cursor on hover */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% language 'en' %}
|
||||||
|
<h1>Video Analysis for {{ workout.name }}</h1>
|
||||||
|
<ul class="main-content">
|
||||||
|
{% if user.is_authenticated and user == workout.user.user and not locked %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>Paste link to you tube video below</p>
|
||||||
|
<p>Use the slider to locate start point for video on workout map</p>
|
||||||
|
<p>Playing the video will advance the data in synchonization. Use the regular YouTube
|
||||||
|
controls
|
||||||
|
to move around in the video and play it.</p>
|
||||||
|
<p>You can make manual adjustments to the delay to fine tune the alignment.
|
||||||
|
Once you are finished, check "Lock Video and Data" to lock the video and the data.</p>
|
||||||
|
<p>Once you are happy with the alignment, you can save the analysis, and share with other people.</p>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>Playing the video will advance the data in synchronization. Use the regular
|
||||||
|
YouTube controls to move around in the video and play it.</p>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="theplot" class="flexplot mapdiv">
|
||||||
|
{{ mapdiv | safe}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="slidecontainer">
|
||||||
|
<input type="range" min="0" max="{{ maxtime }}" value="{{ analysis.delay }}"
|
||||||
|
id="myRange">
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="player"></div>
|
||||||
|
<script>
|
||||||
|
// 1. Code for the map
|
||||||
|
{{ mapscript | safe }}
|
||||||
|
|
||||||
|
|
||||||
|
// 2. This code loads the IFrame Player API code asynchronously.
|
||||||
|
var tag = document.createElement('script');
|
||||||
|
|
||||||
|
tag.src = "https://www.youtube.com/iframe_api";
|
||||||
|
var firstScriptTag = document.getElementsByTagName('script')[0];
|
||||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||||
|
|
||||||
|
// 3. This function creates an <iframe> (and YouTube player)
|
||||||
|
// after the API code downloads.
|
||||||
|
var player;
|
||||||
|
var playing;
|
||||||
|
var videotime = 0;
|
||||||
|
var data = JSON.parse('{{ data|safe }}');
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
var {{ id }}_values = data["{{ id }}"];
|
||||||
|
// console.log("{{ id }}_values",{{ id }}_values);
|
||||||
|
{% endfor %}
|
||||||
|
// var boatspeed = data["boatspeed"];
|
||||||
|
var latitude = data["latitude"];
|
||||||
|
var longitude = data["longitude"];
|
||||||
|
// var spm = data["spm"];
|
||||||
|
// var ctch = data["catch"];
|
||||||
|
|
||||||
|
function onYouTubeIframeAPIReady() {
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
height: '315',
|
||||||
|
width: '560',
|
||||||
|
modestbranding: '1',
|
||||||
|
videoId: '{{ video_id }}',
|
||||||
|
events: {
|
||||||
|
'onReady': onPlayerReady,
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. The API will call this function when the video player is ready.
|
||||||
|
function onPlayerReady(event) {
|
||||||
|
// event.target.playVideo();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTime() {
|
||||||
|
var oldTime = videotime;
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
if(player && player.getCurrentTime) {
|
||||||
|
videotime = player.getCurrentTime();
|
||||||
|
var delay = document.getElementById("id_delay").value;
|
||||||
|
sliderpos = Math.round(videotime) + Math.round(delay);
|
||||||
|
slider.value = sliderpos;
|
||||||
|
|
||||||
|
var datatime = parseFloat(videotime)+parseFloat(delay);
|
||||||
|
// velo = boatspeed[Math.round(datatime)];
|
||||||
|
lat = latitude[Math.round(datatime)];
|
||||||
|
lon = longitude[Math.round(datatime)];
|
||||||
|
// strokerate = spm[Math.round(datatime)];
|
||||||
|
// catchangle = ctch[Math.round(datatime)];
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
{{ id }}_now = {{ id }}_values[Math.round(datatime)];
|
||||||
|
// console.log(datatime,{{ id }}_now, "{{ metric.name }}")
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
document.getElementById("time").innerHTML = Math.round(videotime);
|
||||||
|
document.getElementById("datatime").innerHTML = Math.round(datatime);
|
||||||
|
// document.getElementById("speed").innerHTML = velo;
|
||||||
|
// document.getElementById("spm").innerHTML = strokerate;
|
||||||
|
// document.getElementById("catch").innerHTML = catchangle;
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
document.getElementById("{{ id }}").innerHTML = {{ id }}_now;
|
||||||
|
document.getElementById("{{ id }}").className = 'bold';
|
||||||
|
{% endfor %}
|
||||||
|
{% for group in metricsgroups %}
|
||||||
|
try {
|
||||||
|
set_{{ group }}();
|
||||||
|
} catch (e) {}
|
||||||
|
{% endfor %}
|
||||||
|
// gauge.set(catch_now);
|
||||||
|
try {
|
||||||
|
var newLatLng = new L.LatLng(lat, lon);
|
||||||
|
// console.log(newLatLng);
|
||||||
|
marker.setLatLng(newLatLng);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
if(videotime !== oldTime) {
|
||||||
|
onProgress(videotime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeupdater = setInterval(updateTime, 1000);
|
||||||
|
|
||||||
|
// when the time changes, this will be called.
|
||||||
|
function onProgress(currentTime) {
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
videotime = player.getCurrentTime();
|
||||||
|
var delay = document.getElementById("id_delay").value;
|
||||||
|
sliderpos = Math.round(videotime) + Math.round(delay);
|
||||||
|
slider.value = sliderpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopVideo() {
|
||||||
|
player.stopVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// call this function when player state changes
|
||||||
|
function onPlayerStateChange(event) {
|
||||||
|
if (event.data == YT.PlayerState.PLAYING) {
|
||||||
|
playing = false;
|
||||||
|
} else {
|
||||||
|
playing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</li>
|
||||||
|
{% if user.is_authenticated and user == workout.user.user %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<input type="checkbox" name="lock" id="lock" value="Lock">Lock Data and Video
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<input type="hidden" name="lock" id="lock" value="Lock">
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
Data Time
|
||||||
|
<span id="datatime">
|
||||||
|
</span> seconds
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Video Time
|
||||||
|
<span id="time">
|
||||||
|
</span> seconds
|
||||||
|
</li>
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
<li>
|
||||||
|
{{ metric.name }}
|
||||||
|
<span id="{{ id }}">
|
||||||
|
</span> {{ metric.unit }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for group in metricsgroups %}
|
||||||
|
<li class="grid">
|
||||||
|
<canvas id="{{ group }}"></canvas>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<p> </p>
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
<ul class="main-content">
|
||||||
|
{% if form and user.is_authenticated and user == workout.user.user %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if not analysis.id %}
|
||||||
|
<input type="submit" name="reload_button" value="Reload">
|
||||||
|
{% endif %}
|
||||||
|
<input type="submit" name="save_button" value="Save">
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<table>
|
||||||
|
{{ metricsform.as_table }}
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" id="id_delay" value="{{ analysis.delay }}">
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
{% if analysis and user.is_authenticated and user == analysis.workout.user.user %}
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/video/{{ analysis.id }}/delete/">Delete Analysis</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
$(document).ready( function() {
|
||||||
|
// lock
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
var output = document.getElementById("id_delay");
|
||||||
|
{% if locked %}
|
||||||
|
lock.checked = true;
|
||||||
|
output.disabled = true;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
// slider
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
{% if user.is_authenticated and user == workout.user.user %}
|
||||||
|
document.getElementById("myRange").style.display = "block";
|
||||||
|
{% else %}
|
||||||
|
document.getElementById("myRange").style.display = "none";
|
||||||
|
{% endif %}
|
||||||
|
var output = document.getElementById("id_delay");
|
||||||
|
try {
|
||||||
|
output.value = Math.round(slider.value)-Math.round(player.getCurrentTime()); // Display the default slider value
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
output.value = Math.round(slider.value);
|
||||||
|
}
|
||||||
|
// Update the current slider value (each time you drag the slider handle)
|
||||||
|
slider.oninput = function() {
|
||||||
|
clearInterval(timeupdater)
|
||||||
|
if (lock.checked) {
|
||||||
|
if (this.value-output.value > 0) {
|
||||||
|
player.seekTo(this.value-output.value);
|
||||||
|
} else {
|
||||||
|
if (playing) {
|
||||||
|
player.seekTo(0);
|
||||||
|
player.playVideo();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
player.seekTo(0);
|
||||||
|
player.pauseVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('changing');
|
||||||
|
output.value = this.value-Math.round(player.getCurrentTime());
|
||||||
|
}
|
||||||
|
timeupdater = setInterval(updateTime, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.oninput = function() {
|
||||||
|
slider.value = this.value+Math.round(player.getCurrentTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock delay form field if checkbox checked
|
||||||
|
lock.oninput = function() {
|
||||||
|
if (this.checked) {
|
||||||
|
output.disabled = true;
|
||||||
|
} else {
|
||||||
|
output.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="https://bernii.github.io/gauge.js/dist/gauge.js"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/videogauges.js' %}"></script>
|
||||||
|
{% endlanguage %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workout.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -12,14 +12,15 @@
|
|||||||
<h1>Recent Graphs</h1>
|
<h1>Recent Graphs</h1>
|
||||||
|
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
{% if graphs %}
|
<li class="grid_2">
|
||||||
<li class="grid_2">
|
|
||||||
<form id="searchform" action="."
|
<form id="searchform" action="."
|
||||||
method="get" accept-charset="utf-8">
|
method="get" accept-charset="utf-8">
|
||||||
{{ searchform }}
|
{{ searchform }}
|
||||||
<input type="submit" value="GO"></input>
|
<input type="submit" value="GO"></input>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
|
{% if graphs %}
|
||||||
|
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<p>
|
<p>
|
||||||
<span>
|
<span>
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
<a href="?page=1&q={{ request.GET.q }}">
|
<a href="?page=1&q={{ request.GET.q }}">
|
||||||
<i class="fas fa-arrow-alt-to-left"></i>
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="?page={{ workouts.previous_page_number }}&q={{ request.GET.q }}">
|
<a href="?page={{ graphs.previous_page_number }}&q={{ request.GET.q }}">
|
||||||
<i class="fas fa-arrow-alt-left"></i>
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Rowsandall Recent Video Analyses{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% include "monitorjobs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Recent videos</h1>
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<form id="searchform" action="."
|
||||||
|
method="get" accept-charset="utf-8">
|
||||||
|
{{ searchform }}
|
||||||
|
<input type="submit" value="GO"></input>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% if analyses %}
|
||||||
|
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
{% if analyses.has_previous %}
|
||||||
|
{% if request.GET.q %}
|
||||||
|
<a href="?page=1&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.previous_page_number }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="?page=1">
|
||||||
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.previous_page_number }}">
|
||||||
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Page {{ analyses.number }} of {{ analyses.paginator.num_pages }}.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if analyses.has_next %}
|
||||||
|
{% if request.GET.q %}
|
||||||
|
<a href="?page={{ analyses.next_page_number }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-right"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.paginator.num_pages }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-right">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="?page={{ analyses.next_page_number }}">
|
||||||
|
<i class="fas fa-arrow-alt-right"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.paginator.num_pages }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-right"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% for video in analyses %}
|
||||||
|
<li>
|
||||||
|
<div>{{ video.name }}</div>
|
||||||
|
<a href="/rowers/video/{{ video.id|encode }}/">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ video.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>
|
||||||
|
No videos found
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -2,6 +2,24 @@
|
|||||||
{% load rowerfilters %}
|
{% load rowerfilters %}
|
||||||
<h1><a href="/rowers/analysis/">Analysis</a></h1>
|
<h1><a href="/rowers/analysis/">Analysis</a></h1>
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
|
<li class="has-children" id="videos">
|
||||||
|
<input type="checkbox" name="group-videos" id="group-videos" checked>
|
||||||
|
<label for="group-videos">
|
||||||
|
<i class="fas fa-film fa-fw"></i> Video Analysis
|
||||||
|
</label>
|
||||||
|
<ul>
|
||||||
|
<li id="videos-analyses">
|
||||||
|
<a href="/rowers/videos/">
|
||||||
|
<i class="fas fa-film fa-fw"></i> Video Analyses
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li id="video-add">
|
||||||
|
<a href="/rowers/add-video/">
|
||||||
|
<i class="fas fa-video-plus fa-fw"></i> New Video Analysis
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li class="has-children" id="fitness">
|
<li class="has-children" id="fitness">
|
||||||
<input type="checkbox" name="group-fitness" id="group-fitness" checked>
|
<input type="checkbox" name="group-fitness" id="group-fitness" checked>
|
||||||
<label for="group-fitness">
|
<label for="group-fitness">
|
||||||
@@ -61,79 +79,79 @@
|
|||||||
<a href="/rowers/user-analysis-select/stats/">
|
<a href="/rowers/user-analysis-select/stats/">
|
||||||
<i class="fal fa-table fa-fw"></i> Statistics
|
<i class="fal fa-table fa-fw"></i> Statistics
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="stats-histo">
|
<li id="stats-histo">
|
||||||
<a href="/rowers/user-analysis-select/histo/">
|
<a href="/rowers/user-analysis-select/histo/">
|
||||||
<i class="fas fa-chart-bar fa-fw"></i> Histogram
|
<i class="fas fa-chart-bar fa-fw"></i> Histogram
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/alerts/">
|
<a href="/rowers/alerts/">
|
||||||
<i class="fas fa-bell fa-fw"></i> Alerts
|
<i class="fas fa-bell fa-fw"></i> Alerts
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/user-analysis-select/flexall/">
|
<a href="/rowers/user-analysis-select/flexall/">
|
||||||
<i class="fas fa-chart-line fa-fw"></i> Cumulative Flex Chart
|
<i class="fas fa-chart-line fa-fw"></i> Cumulative Flex Chart
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/laboratory/">
|
<a href="/rowers/laboratory/">
|
||||||
<i class="fas fa-flask fa-fw"></i> Laboratory</a>
|
<i class="fas fa-flask fa-fw"></i> Laboratory</a>
|
||||||
</li>
|
</li>
|
||||||
</ul><!-- cd-accordion-menu -->
|
</ul><!-- cd-accordion-menu -->
|
||||||
|
|
||||||
{% if user.is_authenticated and user|is_manager and rower %}
|
{% if user.is_authenticated and user|is_manager and rower %}
|
||||||
<p> </p>
|
<p> </p>
|
||||||
|
|
||||||
{% if user|coach_rowers %}
|
{% if user|coach_rowers %}
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
<li class="has-children" id="athletes">
|
<li class="has-children" id="athletes">
|
||||||
<input type="checkbox" name="athlete-selector" id="athlete-selector">
|
<input type="checkbox" name="athlete-selector" id="athlete-selector">
|
||||||
<label for="athlete-selector"><i class="fas fa-users fa-fw"></i> Athletes</label>
|
<label for="athlete-selector"><i class="fas fa-users fa-fw"></i> Athletes</label>
|
||||||
<ul>
|
<ul>
|
||||||
{% for member in user|coach_rowers %}
|
{% for member in user|coach_rowers %}
|
||||||
<a href={{ request.path|userurl:member.user }}>
|
<a href={{ request.path|userurl:member.user }}>
|
||||||
<i class="fas fa-user fa-fw"></i>
|
<i class="fas fa-user fa-fw"></i>
|
||||||
{% if member.user == rower.user %}
|
{% if member.user == rower.user %}
|
||||||
•
|
•
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ member.user.first_name }} {{ member.user.last_name }}
|
{{ member.user.first_name }} {{ member.user.last_name }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user|user_teams %}
|
{% if user|user_teams %}
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
<li class="has-children" id="teams">
|
<li class="has-children" id="teams">
|
||||||
<input type="checkbox" name="team-selector" id="team-selector">
|
<input type="checkbox" name="team-selector" id="team-selector">
|
||||||
<label for="team-selector"><i class="fas fa-bullhorn fa-fw"></i> Groups</label>
|
<label for="team-selector"><i class="fas fa-bullhorn fa-fw"></i> Groups</label>
|
||||||
<ul>
|
<ul>
|
||||||
{% for tteam in teams %}
|
{% for tteam in teams %}
|
||||||
<li>
|
<li>
|
||||||
<a href={{ request.path|teamurl:tteam }}>
|
<a href={{ request.path|teamurl:tteam }}>
|
||||||
<i class="fas fa-users fa-fw"></i>
|
<i class="fas fa-users fa-fw"></i>
|
||||||
{% if tteam == team %}
|
{% if tteam == team %}
|
||||||
•
|
•
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ tteam.name }}
|
{{ tteam.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include 'menuscript.html' %}
|
{% include 'menuscript.html' %}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@
|
|||||||
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li id="video-analysis">
|
||||||
|
<a href="/rowers/workout/{{ workout.id|encode }}/video/">
|
||||||
|
<i class="fas fa-video-plus fa-fw"></i> Video Analysis
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% if user.is_authenticated and workout|may_edit:request %}
|
{% if user.is_authenticated and workout|may_edit:request %}
|
||||||
<li id="chart-image">
|
<li id="chart-image">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/image/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/image/">
|
||||||
|
|||||||
@@ -8,6 +8,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% load rowerfilters %}
|
{% load rowerfilters %}
|
||||||
|
{% load leaflet_tags %}
|
||||||
<div class="grid_2 alpha">
|
<div class="grid_2 alpha">
|
||||||
<p>
|
<p>
|
||||||
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/map">Map View</a>
|
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/map">Map View</a>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Delete Graph Image {% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Are you sure you want to delete this video analysis?</p>
|
||||||
|
<p>
|
||||||
|
<input class="button red" type="submit" value="Confirm">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<a href="/rowers/video/{{ object.id|encode }}">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ object.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workouts.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workouts{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<script>
|
||||||
|
function toggle(source) {
|
||||||
|
checkboxes = document.querySelectorAll("input[name='workouts']");
|
||||||
|
for(var i=0, n=checkboxes.length;i<n;i++) {
|
||||||
|
checkboxes[i].checked = source.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
// Get the form fields and hidden div
|
||||||
|
var modality = $("#id_modality");
|
||||||
|
var hidden = $("#id_waterboattype");
|
||||||
|
|
||||||
|
|
||||||
|
// Hide the fields.
|
||||||
|
// Use JS to do this in case the user doesn't have JS
|
||||||
|
// enabled.
|
||||||
|
|
||||||
|
hidden.hide();
|
||||||
|
|
||||||
|
if (modality.val() == 'water') {
|
||||||
|
hidden.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Setup an event listener for when the state of the
|
||||||
|
// checkbox changes.
|
||||||
|
modality.change(function() {
|
||||||
|
// Check to see if the checkbox is checked.
|
||||||
|
// If it is, show the fields and populate the input.
|
||||||
|
// If not, hide the fields.
|
||||||
|
var Value = modality.val();
|
||||||
|
var otwtypes = ['water','coastal','c-boat','churchboat']
|
||||||
|
if (otwtypes.includes(Value)) {
|
||||||
|
// Show the hidden fields.
|
||||||
|
hidden.show();
|
||||||
|
} else {
|
||||||
|
// Make sure that the hidden fields are indeed
|
||||||
|
// hidden.
|
||||||
|
hidden.hide();
|
||||||
|
|
||||||
|
// You may also want to clear the value of the
|
||||||
|
// hidden fields here. Just in case somebody
|
||||||
|
// shows the fields, enters data to them and then
|
||||||
|
// unticks the checkbox.
|
||||||
|
//
|
||||||
|
// This would do the job:
|
||||||
|
//
|
||||||
|
// $("#hidden_field").val("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>Select the workout you want to add a video to</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2 maxheight">
|
||||||
|
<form id="searchform" action=""
|
||||||
|
method="get" accept-charset="utf-8">
|
||||||
|
{{ searchform }}
|
||||||
|
<input type="submit" value="GO"></input>
|
||||||
|
</form>
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
|
||||||
|
{% if workouts %}
|
||||||
|
|
||||||
|
<table width="100%" class="listtable">
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> No workouts found </p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
<table>
|
||||||
|
{{ dateform.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input name='optionsform' type="submit" value="Submit">
|
||||||
|
<input name='workoutform' type='submit' value="Create Video">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if request.method == 'POST' %}
|
||||||
|
<script type='text/javascript'
|
||||||
|
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -171,6 +171,14 @@ $('#id_workouttype').change();
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% for video in videos %}
|
||||||
|
<li>
|
||||||
|
<div>{{ video.name }}</div>
|
||||||
|
<a href="/rowers/video/{{ video.id|encode }}/">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ video.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -141,6 +141,16 @@ def mocked_read_df_sql(id):
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
def mocked_get_video_data(*args, **kwargs):
|
||||||
|
with open('rowers/tests/testdata/videodata.json','r') as infile:
|
||||||
|
data = json.load(infile)
|
||||||
|
|
||||||
|
maxtime = 4135.
|
||||||
|
with open('rowers/tests/testdata/videometrics.json','r') as infile:
|
||||||
|
metrics = json.load(infile)
|
||||||
|
|
||||||
|
return data,metrics,maxtime
|
||||||
|
|
||||||
def mocked_getrowdata_db(*args, **kwargs):
|
def mocked_getrowdata_db(*args, **kwargs):
|
||||||
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
|
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
|
||||||
|
|
||||||
|
|||||||
@@ -256,12 +256,14 @@ class URLTests(TestCase):
|
|||||||
@patch('rowers.dataprep.getsmallrowdata_db')
|
@patch('rowers.dataprep.getsmallrowdata_db')
|
||||||
@patch('requests.get',side_effect=mocked_requests)
|
@patch('requests.get',side_effect=mocked_requests)
|
||||||
@patch('requests.post',side_effect=mocked_requests)
|
@patch('requests.post',side_effect=mocked_requests)
|
||||||
|
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
|
||||||
def test_url_generator(self,url,expected,
|
def test_url_generator(self,url,expected,
|
||||||
mocked_sqlalchemy,
|
mocked_sqlalchemy,
|
||||||
mocked_read_df_sql,
|
mocked_read_df_sql,
|
||||||
mocked_getsmallrowdata_db,
|
mocked_getsmallrowdata_db,
|
||||||
mock_get,
|
mock_get,
|
||||||
mock_post):
|
mock_post,
|
||||||
|
mocked_get_video_data):
|
||||||
|
|
||||||
if url not in tested:
|
if url not in tested:
|
||||||
login = self.c.login(username='john',password='koeinsloot')
|
login = self.c.login(username='john',password='koeinsloot')
|
||||||
@@ -294,5 +296,3 @@ class URLTests(TestCase):
|
|||||||
[200,302,301])
|
[200,302,301])
|
||||||
else:
|
else:
|
||||||
tested.append(u)
|
tested.append(u)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
|||||||
|
{"boatspeed": {"name": "Boat Speed (m/s)", "metric": "velo", "unit": ""}, "cumdist": {"name": "Cumulative Distance (m)", "metric": "cumdist", "unit": ""}, "distance": {"name": "Distance (m)", "metric": "distance", "unit": ""}, "distanceperstroke": {"name": "Distance per Stroke (m)", "metric": "distanceperstroke", "unit": ""}, "pace": {"name": "Pace (/500m)", "metric": "pace", "unit": ""}, "spm": {"name": "Stroke Rate (spm)", "metric": "spm", "unit": ""}}
|
||||||
+7
-6
@@ -524,13 +524,14 @@ def do_sync(w,options):
|
|||||||
if ('upload_to_Strava' in options and not options['upload_to_Strava']):
|
if ('upload_to_Strava' in options and not options['upload_to_Strava']):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
if options['stravaid'] != 0:
|
||||||
message,id = stravastuff.workout_strava_upload(
|
try:
|
||||||
|
message,id = stravastuff.workout_strava_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
)
|
)
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
id = 0
|
id = 0
|
||||||
message = "Please connect to Strava first"
|
message = "Please connect to Strava first"
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)):
|
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)):
|
||||||
|
|||||||
+7
-1
@@ -104,7 +104,6 @@ def permissiondenied_view(request):
|
|||||||
|
|
||||||
|
|
||||||
def filenotfound_view(request):
|
def filenotfound_view(request):
|
||||||
print('aapje')
|
|
||||||
return rowers.views.error403_view(request)
|
return rowers.views.error403_view(request)
|
||||||
|
|
||||||
def response_error_handler(request, exception=None):
|
def response_error_handler(request, exception=None):
|
||||||
@@ -344,6 +343,13 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'),
|
||||||
# re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view),
|
# re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'),
|
||||||
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_create_view,
|
||||||
|
name='workout_video_create_view'),
|
||||||
|
re_path(r'^video/(?P<pk>\d+)/delete/$',views.VideoDelete.as_view(),name='video_delete'),
|
||||||
|
re_path(r'^video/(?P<id>\w.+)/$',views.workout_video_view,
|
||||||
|
name='workout_video_view'),
|
||||||
|
re_path(r'^videos/',views.list_videos,name='list_videos'),
|
||||||
|
re_path(r'^add-video/',views.video_selectworkout,name='video_selectworkout'),
|
||||||
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
|
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
|
||||||
re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'),
|
re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'),
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ from rowers.forms import (
|
|||||||
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
|
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
|
||||||
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
|
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
|
||||||
MetricsForm,DisqualificationForm,disqualificationreasons,
|
MetricsForm,DisqualificationForm,disqualificationreasons,
|
||||||
disqualifiers,SearchForm,BillingForm,PlanSelectForm
|
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
|
||||||
|
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
||||||
|
VideoAnalysisMetricsForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@@ -95,7 +97,8 @@ from rowers.models import (
|
|||||||
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
||||||
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
||||||
AlertEditForm, ConditionEditForm,
|
AlertEditForm, ConditionEditForm,
|
||||||
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser
|
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser,
|
||||||
|
VideoAnalysis
|
||||||
)
|
)
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
||||||
@@ -115,7 +118,7 @@ from rowers.models import (
|
|||||||
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
||||||
get_course_timezone,BaseConditionFormSet,
|
get_course_timezone,BaseConditionFormSet,
|
||||||
)
|
)
|
||||||
from rowers.metrics import rowingmetrics,defaultfavoritecharts,nometrics
|
from rowers.metrics import rowingmetrics,defaultfavoritecharts,nometrics,metricsgroups
|
||||||
from rowers import metrics as metrics
|
from rowers import metrics as metrics
|
||||||
from rowers import courses as courses
|
from rowers import courses as courses
|
||||||
import rowers.uploads as uploads
|
import rowers.uploads as uploads
|
||||||
@@ -1288,5 +1291,3 @@ def trydf(df,aantal,column):
|
|||||||
|
|
||||||
import rowers.teams as teams
|
import rowers.teams as teams
|
||||||
from rowers.models import C2WorldClassAgePerformance
|
from rowers.models import C2WorldClassAgePerformance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,267 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from rowers.views.statements import *
|
from rowers.views.statements import *
|
||||||
import rowers.teams as teams
|
import rowers.teams as teams
|
||||||
|
import rowers.mytypes as mytypes
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
|
def default(o):
|
||||||
|
if isinstance(o, numpy.int64): return int(o)
|
||||||
|
if isinstance(o, numpy.int32): return int(o)
|
||||||
|
raise TypeError
|
||||||
|
|
||||||
|
def get_video_id(url):
|
||||||
|
"""Returns Video_ID extracting from the given url of Youtube
|
||||||
|
|
||||||
|
Examples of URLs:
|
||||||
|
Valid:
|
||||||
|
'http://youtu.be/_lOT2p_FCvA',
|
||||||
|
'www.youtube.com/watch?v=_lOT2p_FCvA&feature=feedu',
|
||||||
|
'http://www.youtube.com/embed/_lOT2p_FCvA',
|
||||||
|
'http://www.youtube.com/v/_lOT2p_FCvA?version=3&hl=en_US',
|
||||||
|
'https://www.youtube.com/watch?v=rTHlyTphWP0&index=6&list=PLjeDyYvG6-40qawYNR4juzvSOg-ezZ2a6',
|
||||||
|
'youtube.com/watch?v=_lOT2p_FCvA',
|
||||||
|
|
||||||
|
Invalid:
|
||||||
|
'youtu.be/watch?v=_lOT2p_FCvA',
|
||||||
|
"""
|
||||||
|
|
||||||
|
if url.startswith(('youtu', 'www')):
|
||||||
|
url = 'http://' + url
|
||||||
|
elif 'http' not in url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
query = urlparse(url)
|
||||||
|
|
||||||
|
if 'youtube' in query.hostname:
|
||||||
|
if query.path == '/watch':
|
||||||
|
return parse_qs(query.query)['v'][0]
|
||||||
|
elif query.path.startswith(('/embed/', '/v/')):
|
||||||
|
return query.path.split('/')[2]
|
||||||
|
elif 'youtu.be' in query.hostname:
|
||||||
|
return query.path[1:]
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
|
# Show a video compared with data
|
||||||
|
def workout_video_view(request,id=''):
|
||||||
|
try:
|
||||||
|
id = encoder.decode_hex(id)
|
||||||
|
analysis = VideoAnalysis.objects.get(id=id)
|
||||||
|
except VideoAnalysis.DoesNotExist:
|
||||||
|
raise Http404("Video Analysis does not exist")
|
||||||
|
|
||||||
|
w = analysis.workout
|
||||||
|
delay = analysis.delay
|
||||||
|
|
||||||
|
if w.workouttype in mytypes.otwtypes:
|
||||||
|
mode = 'water'
|
||||||
|
else:
|
||||||
|
mode = 'erg'
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower)
|
||||||
|
rower = request.user.rower
|
||||||
|
else:
|
||||||
|
mayedit = False
|
||||||
|
rower = None
|
||||||
|
|
||||||
|
# get video ID and offset
|
||||||
|
if mayedit and request.method == 'POST':
|
||||||
|
form = VideoAnalysisCreateForm(request.POST)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
|
||||||
|
if form.is_valid() and metricsform.is_valid():
|
||||||
|
video_id = form.cleaned_data['url']
|
||||||
|
try:
|
||||||
|
video_id = get_video_id(form.cleaned_data['url'])
|
||||||
|
except (TypeError,ValueError):
|
||||||
|
pass
|
||||||
|
delay = form.cleaned_data['delay']
|
||||||
|
metricsgroups = metricsform.cleaned_data['groups']
|
||||||
|
if 'save_button' in request.POST:
|
||||||
|
analysis.name = form.cleaned_data['name']
|
||||||
|
analysis.video_id = video_id
|
||||||
|
analysis.delay = delay
|
||||||
|
analysis.metricsgroups = metricsgroups
|
||||||
|
analysis.save()
|
||||||
|
else:
|
||||||
|
video_id = id
|
||||||
|
delay = 0
|
||||||
|
elif mayedit:
|
||||||
|
form = VideoAnalysisCreateForm(
|
||||||
|
initial = {
|
||||||
|
'name':analysis.name,
|
||||||
|
'delay': analysis.delay,
|
||||||
|
'url': analysis.video_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(initial={'groups':analysis.metricsgroups},
|
||||||
|
mode=mode)
|
||||||
|
metricsgroups = analysis.metricsgroups
|
||||||
|
video_id = analysis.video_id
|
||||||
|
else:
|
||||||
|
form = None
|
||||||
|
metricsform = None
|
||||||
|
metricsgroups = analysis.metricsgroups
|
||||||
|
|
||||||
|
data, metrics, maxtime = dataprep.get_video_data(w,groups=metricsgroups,mode=mode)
|
||||||
|
hascoordinates = pd.Series(data['latitude']).std() > 0
|
||||||
|
# create map
|
||||||
|
if hascoordinates:
|
||||||
|
mapscript, mapdiv = leaflet_chart_video(data['latitude'],data['longitude'],
|
||||||
|
w.name)
|
||||||
|
else:
|
||||||
|
mapscript, mapdiv = interactive_chart_video(data)
|
||||||
|
data['longitude'] = data['spm']
|
||||||
|
data['latitude'] = list(range(len(data['spm'])))
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(analysis.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'embedded_video.html',
|
||||||
|
{
|
||||||
|
'workout':w,
|
||||||
|
'rower':rower,
|
||||||
|
'data': json.dumps(data,default=default),
|
||||||
|
'mapscript': mapscript,
|
||||||
|
'mapdiv': mapdiv,
|
||||||
|
'video_id': analysis.video_id,
|
||||||
|
'form':form,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'analysis':analysis,
|
||||||
|
'maxtime':maxtime,
|
||||||
|
'metrics':metrics,
|
||||||
|
'locked': True,
|
||||||
|
'metricsform':metricsform,
|
||||||
|
'metricsgroups': metricsgroups,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# Create a video compared with data
|
||||||
|
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
||||||
|
message="This functionality requires a Pro plan or higher",
|
||||||
|
redirect_field_name=None)
|
||||||
|
def workout_video_create_view(request,id=0):
|
||||||
|
# get workout
|
||||||
|
w = get_workout_permitted(request.user,id)
|
||||||
|
if w.workouttype in mytypes.otwtypes:
|
||||||
|
mode = 'water'
|
||||||
|
else:
|
||||||
|
mode = 'erg'
|
||||||
|
|
||||||
|
# get video ID and offset
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = VideoAnalysisCreateForm(request.POST)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
|
||||||
|
if form.is_valid() and metricsform.is_valid():
|
||||||
|
url = form.cleaned_data['url']
|
||||||
|
delay = form.cleaned_data['delay']
|
||||||
|
metricsgroups = metricsform.cleaned_data['groups']
|
||||||
|
video_id = get_video_id(url)
|
||||||
|
if 'save_button' in request.POST:
|
||||||
|
analysis = VideoAnalysis(
|
||||||
|
workout=w,
|
||||||
|
name=form.cleaned_data['name'],
|
||||||
|
video_id = video_id,
|
||||||
|
delay=delay,
|
||||||
|
metricsgroups=metricsgroups
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
analysis.save()
|
||||||
|
url = reverse('workout_video_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(analysis.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
except IntegrityError:
|
||||||
|
messages.error(request,'You cannot save two video analysis with the same YouTube video and Workout. Redirecting to your existing analysis')
|
||||||
|
analysis = VideoAnalysis.objects.filter(workout=w,video_id=video_id)
|
||||||
|
if analysis:
|
||||||
|
url = reverse('workout_video_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(analysis[0].id)})
|
||||||
|
else:
|
||||||
|
url = reverse('workout_video_create_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(w.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
video_id = None
|
||||||
|
delay = 0
|
||||||
|
metricsgroups = ['basic']
|
||||||
|
else:
|
||||||
|
form = VideoAnalysisCreateForm()
|
||||||
|
metricsform = VideoAnalysisMetricsForm(initial={'groups':['basic']},mode=mode)
|
||||||
|
video_id = None
|
||||||
|
delay = 0
|
||||||
|
metricsgroups = ['basic']
|
||||||
|
|
||||||
|
# get data
|
||||||
|
data, metrics, maxtime = dataprep.get_video_data(w,groups=metricsgroups,mode=mode)
|
||||||
|
hascoordinates = pd.Series(data['latitude']).std() > 0
|
||||||
|
|
||||||
|
# create map
|
||||||
|
if hascoordinates:
|
||||||
|
mapscript, mapdiv = leaflet_chart_video(data['latitude'],data['longitude'],
|
||||||
|
w.name)
|
||||||
|
else:
|
||||||
|
mapscript, mapdiv = interactive_chart_video(data)
|
||||||
|
data['longitude'] = data['spm']
|
||||||
|
data['latitude'] = list(range(len(data['spm'])))
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
analysis = {'delay':delay}
|
||||||
|
|
||||||
|
template = 'embedded_video.html'
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
template,
|
||||||
|
{
|
||||||
|
'workout':w,
|
||||||
|
'rower':request.user.rower,
|
||||||
|
'data': json.dumps(data,default=default),
|
||||||
|
'mapscript': mapscript,
|
||||||
|
'mapdiv': mapdiv,
|
||||||
|
'video_id': video_id,
|
||||||
|
'form':form,
|
||||||
|
'metricsform':metricsform,
|
||||||
|
'analysis':analysis,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'maxtime':maxtime,
|
||||||
|
'metrics':metrics,
|
||||||
|
'metricsgroups': metricsgroups,
|
||||||
|
'locked': False,
|
||||||
|
})
|
||||||
|
|
||||||
# Show the EMpower Oarlock generated Stroke Profile
|
# Show the EMpower Oarlock generated Stroke Profile
|
||||||
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
||||||
@@ -446,6 +707,205 @@ def workouts_join_view(request):
|
|||||||
url = reverse('workouts_join_select')
|
url = reverse('workouts_join_select')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
defaultoptions = {
|
||||||
|
'includereststrokes': False,
|
||||||
|
'workouttypes':['rower','dynamic','slides'],
|
||||||
|
'waterboattype': mytypes.waterboattype,
|
||||||
|
'rankingonly': False,
|
||||||
|
'function':'boxplot'
|
||||||
|
}
|
||||||
|
|
||||||
|
@user_passes_test(ispromember, login_url="/rowers/paidplans",
|
||||||
|
message="This functionality requires a Pro plan or higher",
|
||||||
|
redirect_field_name=None)
|
||||||
|
def video_selectworkout(request,userid=0,teamid=0):
|
||||||
|
r = getrequestrower(request, userid=userid)
|
||||||
|
user = r.user
|
||||||
|
userid = user.id
|
||||||
|
workouts = Workout.objects.filter(user=r).order_by('-date')
|
||||||
|
|
||||||
|
try:
|
||||||
|
theteam = Team.objects.get(id=teamid)
|
||||||
|
except Team.DoesNotExist:
|
||||||
|
theteam = None
|
||||||
|
|
||||||
|
|
||||||
|
if 'options' in request.session:
|
||||||
|
options = request.session['options']
|
||||||
|
else:
|
||||||
|
options=defaultoptions
|
||||||
|
|
||||||
|
options['userid'] = userid
|
||||||
|
try:
|
||||||
|
workouttypes = options['workouttypes']
|
||||||
|
except KeyError:
|
||||||
|
workouttypes = ['rower','dynamic','slides']
|
||||||
|
|
||||||
|
try:
|
||||||
|
modalities = options['modalities']
|
||||||
|
modality = modalities[0]
|
||||||
|
except KeyError:
|
||||||
|
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
|
||||||
|
modality = 'all'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
rankingonly = options['rankingonly']
|
||||||
|
except KeyError:
|
||||||
|
rankingonly = False
|
||||||
|
|
||||||
|
query = request.GET.get('q')
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
workouts = workouts.filter(
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(name__icontains=q) for q in query_list)) |
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(notes__icontains=q) for q in query_list))
|
||||||
|
)
|
||||||
|
searchform = SearchForm(initial={'q':query})
|
||||||
|
else:
|
||||||
|
searchform = SearchForm()
|
||||||
|
|
||||||
|
|
||||||
|
if 'startdate' in request.session:
|
||||||
|
startdate = iso8601.parse_date(request.session['startdate'])
|
||||||
|
else:
|
||||||
|
startdate=timezone.now()-datetime.timedelta(days=42)
|
||||||
|
|
||||||
|
if 'enddate' in request.session:
|
||||||
|
enddate = iso8601.parse_date(request.session['enddate'])
|
||||||
|
else:
|
||||||
|
enddate = timezone.now()
|
||||||
|
|
||||||
|
|
||||||
|
workouts = workouts.filter(date__gte=startdate,date__lte=enddate)
|
||||||
|
|
||||||
|
waterboattype = mytypes.waterboattype
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
thediv = get_call()
|
||||||
|
dateform = DateRangeForm(request.POST)
|
||||||
|
if dateform.is_valid():
|
||||||
|
startdate = dateform.cleaned_data['startdate']
|
||||||
|
enddate = dateform.cleaned_data['enddate']
|
||||||
|
startdatestring = startdate.strftime('%Y-%m-%d')
|
||||||
|
enddatestring = enddate.strftime('%Y-%m-%d')
|
||||||
|
request.session['startdate'] = startdatestring
|
||||||
|
request.session['enddate'] = enddatestring
|
||||||
|
|
||||||
|
form = WorkoutSingleSelectForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
cd = form.cleaned_data
|
||||||
|
selectedworkout = cd['workout']
|
||||||
|
url = reverse('workout_video_create_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(selectedworkout.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
id = 0
|
||||||
|
else:
|
||||||
|
form = WorkoutSingleSelectForm(workouts=workouts)
|
||||||
|
thediv = ''
|
||||||
|
dateform = DateRangeForm(initial={
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
negtypes = []
|
||||||
|
for b in mytypes.boattypes:
|
||||||
|
if b[0] not in waterboattype:
|
||||||
|
negtypes.append(b[0])
|
||||||
|
|
||||||
|
|
||||||
|
startdate = datetime.datetime.combine(startdate,datetime.time())
|
||||||
|
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
|
||||||
|
|
||||||
|
if enddate < startdate:
|
||||||
|
s = enddate
|
||||||
|
enddate = startdate
|
||||||
|
startdate = s
|
||||||
|
|
||||||
|
negtypes = []
|
||||||
|
for b in mytypes.boattypes:
|
||||||
|
if b[0] not in waterboattype:
|
||||||
|
negtypes.append(b[0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if theteam is not None and (theteam.viewing == 'allmembers' or theteam.manager == request.user):
|
||||||
|
workouts = Workout.objects.filter(team=theteam,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
elif theteam is not None and theteam.viewing == 'coachonly':
|
||||||
|
workouts = Workout.objects.filter(team=theteam,user=r,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
workouts = Workout.objects.filter(user=r,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
|
||||||
|
workouts = workouts.order_by(
|
||||||
|
"-date", "-starttime"
|
||||||
|
).exclude(boattype__in=negtypes)
|
||||||
|
|
||||||
|
|
||||||
|
if rankingonly:
|
||||||
|
workouts = workouts.exclude(rankingpiece=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
optionsform = AnalysisOptionsForm(initial={
|
||||||
|
'modality':modality,
|
||||||
|
'waterboattype':waterboattype,
|
||||||
|
'rankingonly':rankingonly,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
startdatestring = startdate.strftime('%Y-%m-%d')
|
||||||
|
enddatestring = enddate.strftime('%Y-%m-%d')
|
||||||
|
request.session['startdate'] = startdatestring
|
||||||
|
request.session['enddate'] = enddatestring
|
||||||
|
request.session['options'] = options
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/analysis',
|
||||||
|
'name':'Analysis'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('analysis_new',kwargs={'userid':userid}),
|
||||||
|
'name': 'Analysis Select'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return render(request, 'video_selectworkout.html',
|
||||||
|
{'workouts': workouts,
|
||||||
|
'dateform':dateform,
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
'rower':r,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'theuser':user,
|
||||||
|
'the_div':thediv,
|
||||||
|
'form':form,
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'searchform':searchform,
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(ispromember,login_url="/rowers/paidplans",
|
@user_passes_test(ispromember,login_url="/rowers/paidplans",
|
||||||
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality",
|
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality",
|
||||||
redirect_field_name=None)
|
redirect_field_name=None)
|
||||||
@@ -3530,6 +3990,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
videos = VideoAnalysis.objects.filter(workout=row)
|
||||||
|
|
||||||
# create interactive plot
|
# create interactive plot
|
||||||
f1 = row.csvfilename
|
f1 = row.csvfilename
|
||||||
@@ -3593,6 +4054,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
'workout':row,
|
'workout':row,
|
||||||
'teams':get_my_teams(request.user),
|
'teams':get_my_teams(request.user),
|
||||||
'graphs':g,
|
'graphs':g,
|
||||||
|
'videos':videos,
|
||||||
'breadcrumbs':breadcrumbs,
|
'breadcrumbs':breadcrumbs,
|
||||||
'rower':r,
|
'rower':r,
|
||||||
'indoorraces':indoorraces,
|
'indoorraces':indoorraces,
|
||||||
@@ -4495,6 +4957,45 @@ def team_workout_upload_view(request,message="",
|
|||||||
|
|
||||||
|
|
||||||
# A page with all the recent graphs (searchable on workout name)
|
# A page with all the recent graphs (searchable on workout name)
|
||||||
|
@login_required()
|
||||||
|
def list_videos(request):
|
||||||
|
r = getrequestrower(request)
|
||||||
|
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
|
||||||
|
query = request.GET.get('q')
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
workouts = workouts.filter(
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(name__icontains=q) for q in query_list)) |
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(notes__icontains=q) for q in query_list))
|
||||||
|
)
|
||||||
|
searchform = SearchForm(initial={'q':query})
|
||||||
|
else:
|
||||||
|
searchform = SearchForm()
|
||||||
|
|
||||||
|
g = VideoAnalysis.objects.filter(workout__in=workouts).order_by("-workout__date")
|
||||||
|
|
||||||
|
|
||||||
|
paginator = Paginator(g,8)
|
||||||
|
page = request.GET.get('page')
|
||||||
|
|
||||||
|
try:
|
||||||
|
g = paginator.page(page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
g = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
g = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
return render(request, 'list_videos.html',
|
||||||
|
{'analyses': g,
|
||||||
|
'searchform':searchform,
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
})
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def graphs_view(request):
|
def graphs_view(request):
|
||||||
request.session['referer'] = reverse('graphs_view')
|
request.session['referer'] = reverse('graphs_view')
|
||||||
@@ -5254,6 +5755,58 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
|
|||||||
'formvalues':formvalues,
|
'formvalues':formvalues,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
class VideoDelete(DeleteView):
|
||||||
|
login_required = True
|
||||||
|
model = VideoAnalysis
|
||||||
|
template_name = 'video_delete_confirm.html'
|
||||||
|
|
||||||
|
# extra parameters
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(VideoDelete, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(
|
||||||
|
self.request,
|
||||||
|
encoder.encode_hex(self.object.workout.id)),
|
||||||
|
'name': self.object.workout.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(self.object.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
},
|
||||||
|
{ 'url':reverse('video_delete',kwargs={'pk':str(self.object.pk)}),
|
||||||
|
'name': 'Delete'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
context['active'] = 'nav-workouts'
|
||||||
|
context['rower'] = getrower(self.request.user)
|
||||||
|
context['breadcrumbs'] = breadcrumbs
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
w = self.object.workout
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=w.id)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
return reverse('workouts_view')
|
||||||
|
|
||||||
|
return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
|
||||||
|
|
||||||
|
def get_object(self, *args, **kwargs):
|
||||||
|
obj = super(VideoDelete, self).get_object(*args, **kwargs)
|
||||||
|
if not checkaccessuser(self.request.user,obj.workout.user):
|
||||||
|
raise PermissionDenied('You are not allowed to delete this analysis')
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
class GraphDelete(DeleteView):
|
class GraphDelete(DeleteView):
|
||||||
login_required = True
|
login_required = True
|
||||||
|
|||||||
@@ -77,8 +77,12 @@ CACHES = {
|
|||||||
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
#STATIC_ROOT = 'C:/python/rowsandallapp'
|
|
||||||
STATIC_ROOT = BASE_DIR
|
STATIC_ROOT = BASE_DIR
|
||||||
|
#STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),
|
||||||
|
os.path.join(BASE_DIR, 'static/plots'),]
|
||||||
|
|
||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,16 @@ django.views.i18n.javascript_catalog = None
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
import django
|
import django
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^static/plots/(?P<path>.*)$',
|
||||||
|
django.views.static.serve,
|
||||||
|
kwargs={'document_root': settings.STATIC_ROOT+'plots/'})
|
||||||
|
]
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
# re_path(r'^__debug__/','debug_toolbar.urls'),
|
# re_path(r'^__debug__/','debug_toolbar.urls'),
|
||||||
re_path(r'^static/(?P<path>.*)$',
|
re_path(r'^static/(?P<path>.*)$',
|
||||||
django.views.static.serve,
|
django.views.static.serve,
|
||||||
kwargs={'document_root': settings.STATIC_ROOT,}
|
kwargs={'document_root': settings.STATIC_ROOT,
|
||||||
|
'show_indexes': True}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// var opts = {
|
||||||
|
// lines: 12,
|
||||||
|
// angle: 0.15,
|
||||||
|
// lineWidth: 0.44,
|
||||||
|
// pointer: {
|
||||||
|
// length: 0.9,
|
||||||
|
// strokeWidth: 0.035,
|
||||||
|
// color: '#000000'
|
||||||
|
// },
|
||||||
|
// limitMax: 'false',
|
||||||
|
// // percentColors: [[0.0, "#a9d70b" ], [0.50, "#a9d70b"], [1.0, "#a9d70b"]], // !!!!
|
||||||
|
// strokeColor: '#E0E0E0',
|
||||||
|
// generateGradient: true
|
||||||
|
// };
|
||||||
|
// var target = document.getElementById('angles');
|
||||||
|
// var gauge = new Gauge(target).setOptions(opts);
|
||||||
|
// gauge.maxValue = 90;
|
||||||
|
// gauge.minValue = -90;
|
||||||
|
// gauge.animationSpeed = 5;
|
||||||
|
// gauge.set(-75);
|
||||||
|
|
||||||
|
// https://github.com/bernii/gauge.js/issues/193
|
||||||
|
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
angle: 0, // The span of the gauge arc
|
||||||
|
lineWidth: 0.2, // The line thickness
|
||||||
|
radiusScale: 0.89, // Relative radius
|
||||||
|
pointer: {
|
||||||
|
length: 0.54, // // Relative to gauge radius
|
||||||
|
strokeWidth: 0.053, // The thickness
|
||||||
|
color: '#000000' // Fill color
|
||||||
|
},
|
||||||
|
limitMax: false, // If false, max value increases automatically if value > maxValue
|
||||||
|
limitMin: false, // If true, the min value of the gauge will be fixed
|
||||||
|
colorStart: '#6FADCF', // Colors
|
||||||
|
colorStop: '#8FC0DA', // just experiment with them
|
||||||
|
strokeColor: '#E0E0E0', // to see which ones work best for you
|
||||||
|
generateGradient: true,
|
||||||
|
highDpiSupport: true, // High resolution support
|
||||||
|
staticZones: [
|
||||||
|
{strokeStyle: "#00FF00", min: 0, max: 2}, // Red from 100 to 60
|
||||||
|
{strokeStyle: "#0000FF", min: 2, max: 3}, // Yellow
|
||||||
|
{strokeStyle: "#00FFFF", min: 3, max: 4}, // Green
|
||||||
|
{strokeStyle: "#FFDD00", min: 4, max: 5}, // Yellow
|
||||||
|
{strokeStyle: "#FF0000", min: 5, max: 6} // Red
|
||||||
|
],
|
||||||
|
|
||||||
|
};
|
||||||
|
var target = document.getElementById('basic'); // your canvas element
|
||||||
|
var gaugeboatspeed = new Gauge(target).setOptions(opts); // create sexy gauge!
|
||||||
|
gaugeboatspeed.maxValue = 6; // set max gauge value
|
||||||
|
gaugeboatspeed.setMinValue(0); // Prefer setter over gauge.minValue = 0
|
||||||
|
gaugeboatspeed.animationSpeed = 10; // set animation speed (32 is default value)
|
||||||
|
gaugeboatspeed.set(0); // set actual value
|
||||||
|
|
||||||
|
// Define set_basic(values) so that gauges can be set by metricsgroups
|
||||||
|
function set_basic() {
|
||||||
|
gaugeboatspeed.set(boatspeed_now);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user