diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..fd6a80c9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3-alpine
+COPY ./requirements.txt ./
+WORKDIR /usr/src/app/
+RUN pip install --no-cache-dir -r requirements.txt
+COPY . .
+EXPOSE 8000
+CMD ["python", "./manage.py", "runserver"]
diff --git a/rowers/metrics.py b/rowers/metrics.py
index 3e08f76e..ab32687b 100644
--- a/rowers/metrics.py
+++ b/rowers/metrics.py
@@ -258,7 +258,7 @@ rowingmetrics = (
'ax_min': 0,
'ax_max': 30,
'default': 0,
- 'sigfigs': 0,
+ 'sigfigs': 1,
'mode':'water',
'type': 'pro',
'group': 'stroke'}),
diff --git a/rowers/templates/embedded_video.html b/rowers/templates/embedded_video.html
index 298577e6..7d66b3de 100644
--- a/rowers/templates/embedded_video.html
+++ b/rowers/templates/embedded_video.html
@@ -29,6 +29,8 @@
{% block meta %}
{% leaflet_js %}
{% leaflet_css %}
+
+
{% endlanguage %}
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index d7f9dd4a..b7f3be2f 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -48,7 +48,7 @@ def sigdig(value, digits = 3):
# return integers as is
if value % 1 == 0:
return value
-
+
places = digits - order - 1
if places > 0:
fmtstr = "%%.%df" % (places)
@@ -83,7 +83,7 @@ def strfdelta(tdelta):
seconds=seconds,
tenths=tenths,
)
-
+
return res
from rowers.teams import rower_get_managers
@@ -91,13 +91,13 @@ from rowers.teams import rower_get_managers
@register.filter
def alertstatspercentage(list,i):
alertstats = list[i-1]
-
+
return alertstats["percentage"]
@register.filter
def alertstartdate(list,i):
alertstats = list[i-1]
-
+
return alertstats["startdate"]
@register.filter
@@ -109,7 +109,7 @@ def alertnperiod(list,i):
@register.filter
def alertenddate(list,i):
alertstats = list[i-1]
-
+
return alertstats["enddate"]
@register.filter
@@ -121,6 +121,37 @@ def is_coach(rower,rowers):
return True
+@register.filter
+def waterpower(x,rower):
+ return int(x*(100-rower.otwslack)/100.)
+
+@register.filter
+def round20(x):
+ return int(20.*(1+int(int(x)/20)))
+
+@register.filter
+def round100(x):
+ return int(100.*(1+int(int(x)/100)))
+
+@register.filter
+def majorticks(maxval):
+ ticks = range(1+int(maxval/100.))
+ newticks =[]
+ for t in ticks:
+ newticks.append(t*100)
+
+ return newticks
+
+@register.filter
+def hrmajorticks(maxval,minval):
+ ticks = range(int((maxval-minval)/20.)-1)
+ newticks =[]
+ for t in ticks:
+ newticks.append(100+t*20)
+
+ print(newticks)
+ return newticks
+
def strfdeltah(tdelta):
hours, rest = divmod(tdelta.seconds,3600)
minutes,seconds = divmod(rest,60)
@@ -131,7 +162,7 @@ def strfdeltah(tdelta):
seconds=seconds,
tenths=tenths,
)
-
+
return res
def secondstotimestring(tdelta):
@@ -142,7 +173,7 @@ def secondstotimestring(tdelta):
minutes=minutes,
seconds=seconds,
)
-
+
return res
@register.filter
@@ -218,7 +249,7 @@ def nextperiodstart(timeperiod):
newenddate = newstartdate+timedelta
return newstartdate.strftime("%Y-%m-%d")
-
+
@register.filter
def previousperiodend(timeperiod):
startdate,enddate = getstartenddate(timeperiod)
@@ -243,7 +274,7 @@ def previousperiodstart(timeperiod):
newstartdate = startdate-timedelta-datetime.timedelta(days=1)
return newstartdate.strftime("%Y-%m-%d")
-
+
@register.filter
def paceprint(d):
if (d == None):
@@ -288,11 +319,11 @@ def rkuserid(user):
rkuserid = runkeeperstuff.get_userid(thetoken)
return rkuserid
-
+
@register.filter
def courselength(course):
return course_length(course)
-
+
@register.filter(is_safe=True)
def jsdict(dict,key):
s = dict.get(key)
@@ -306,7 +337,7 @@ def lookup(dict, key):
s = dict.get(key)
except KeyError:
return None
-
+
if isinstance(s,string_types) and len(s) > 22:
s = s[:22]
return s
@@ -317,7 +348,7 @@ def lookuplong(dict, key):
s = dict.get(key)
except KeyError:
return None
-
+
return s
@register.filter
@@ -329,11 +360,11 @@ def ualookup(dict, key):
if key=='duration':
s = secondstotimestring(int(s))
-
+
if key=='starttime':
s = dateutil.parser.parse(s)
-
+
return s
from rowers.models import PlannedSession
@@ -343,7 +374,7 @@ def is_session_manager(id,user):
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
return False
-
+
return ps.manager == user
from rowers.models import checkworkoutuser
@@ -358,7 +389,7 @@ def may_edit(workout,request):
return mayedit
-
+
@register.filter
def mayeditplan(obj,request):
@@ -376,11 +407,11 @@ def mayeditplan(obj,request):
rr = Rower.objects.get(user=request.user)
if checkaccessuser(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']:
mayedit = True
-
+
return mayedit
-
+
@register.filter(name='times')
def times(number):
return range(number)
@@ -480,7 +511,7 @@ def team_members(user):
@register.filter
def openactions(user):
myteams = Team.objects.filter(manager=user)
-
+
invites = TeamInvite.objects.filter(user=user).count()
requests = TeamRequest.objects.filter(user=user).count()
myrequests = TeamRequest.objects.filter(team__in=myteams).count()
@@ -491,7 +522,7 @@ def openactions(user):
coachrequests = CoachRequest.objects.filter(coach=user.rower).count()
return invites+requests+myrequests+myinvites+mycoachoffers+coachoffers+mycoachrequests+coachrequests
-
+
@register.filter
def team_rowers(user):
@@ -518,7 +549,7 @@ def coach_rowers(user):
else:
thelist = [c for c in coach_getcoachees(user.rower)]
return thelist
-
+
@register.filter
def verbosetimeperiod(timeperiod):
@@ -538,7 +569,7 @@ def verbosetimeperiod(timeperiod):
verbose = timeperiod
return verbose
-
+
from datetime import date
@ register.filter
@@ -557,8 +588,8 @@ def is_future_date(the_date):
def amount(value):
vs = '{v}.00'.format(v=int(value))
return vs
-
-
+
+
@register.filter
def date_dif(the_date):
@@ -569,7 +600,7 @@ def date_dif(the_date):
else:
return 1
-
+
@register.filter
def can_register(race,r):
return race_can_register(r,race)
@@ -618,7 +649,7 @@ def userurl(path,member):
tpattern = re.compile('team\/\d+/')
if tpattern.search(path) is not None:
path = tpattern.sub('',path)
-
+
if pattern.search(path) is not None:
replaced = pattern.sub(userstring,path)
else:
@@ -635,7 +666,7 @@ def teamurl(path,team):
upattern = re.compile('\/user\/\d+/')
if upattern.search(path) is not None:
path = upattern.sub('/',path)
-
+
if pattern.search(path) is not None:
replaced = pattern.sub(teamstring,path)
@@ -726,13 +757,13 @@ def nextworkout(workout,user):
).exclude(id=workout.id)
except ValueError:
return 0
-
+
if ws:
return encoder.encode_hex(ws[0].id)
else:
return 0
-
+
@register.filter
def previousworkout(workout,user):
@@ -762,6 +793,3 @@ def previousworkout(workout,user):
return encoder.encode_hex(ws[0].id)
else:
return 0
-
-
-
diff --git a/static/js/videogauges.js b/static/js/videogauges.js
index f6c068e4..42ac8ff9 100644
--- a/static/js/videogauges.js
+++ b/static/js/videogauges.js
@@ -1,60 +1,209 @@
-// 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);
+google.charts.load('current', {'packages':['gauge','corechart']});
+google.charts.setOnLoadCallback(drawSPMChart);
+google.charts.setOnLoadCallback(drawSpeedChart);
+google.charts.setOnLoadCallback(drawPowerChart);
+google.charts.setOnLoadCallback(drawHRChart);
+google.charts.setOnLoadCallback(drawStrokeAngleChart);
-// https://github.com/bernii/gauge.js/issues/193
+var spmdata = [
+ ['Label', 'Value'],
+ ['SPM', 21],
+];
+var speeddata = [
+ ['Label', 'Value'],
+ ['V m/s', 0],
+];
-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}, // Greem
- {strokeStyle: "#0000FF", min: 2, max: 3}, // Blue`
- {strokeStyle: "#00FFFF", min: 3, max: 4}, // Cyan
- {strokeStyle: "#FFDD00", min: 4, max: 5}, // Orange
- {strokeStyle: "#FF0000", min: 5, max: 6} // Red
- ],
+var powerdata = [
+ ['Label','Value'],
+ ['PWR',150],
+]
- };
- 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
+var hrdata = [
+ ['Label','Value'],
+ ['HR',110],
+]
-// Define set_basic(values) so that gauges can be set by metricsgroups
- function set_basic() {
- gaugeboatspeed.set(boatspeed_now);
+var angledata = [
+ ['Angle', 'deg'],
+ ['slip', 10 ],
+ ['load', 50],
+ ['unload', 50],
+ ['wash', 10],
+ ['recovery',240 ]
+]
+
+var anglesoptions = {
+ title: 'Stroke Angles',
+ legend: 'none',
+ pieHole: 0.5,
+ chartArea: { width: "100%" },
+ pieStartAngle: 35,
+ pieSliceText: 'value',
+ pieSliceTextStyle: {color:'black',fontSize:12},
+ slices: {
+ 0: { color: 'lightblue' },
+ 1: { color: 'lightgreen' },
+ 2: { color: 'yellow'},
+ 3: { color: 'orange'},
+ 4: { color: 'transparent', textStyle: {color:'transparent'}}
}
+};
+
+
+var hroptions = {
+ min: 100, max: 200,
+ width: 400, height: 120,
+ greenFrom: 100, greenTo: 135,
+ yellowFrom: 135,yellowTo: 157,
+ redFrom: 157, redTo: 200,
+ minorTicks: 5
+};
+
+
+var spmoptions = {
+ min:0, max: 50,
+ width: 400, height: 120,
+ greenFrom: 20, greenTo: 30,
+ yellowFrom: 30,yellowTo: 40,
+ redFrom: 40, redTo: 50,
+ majorTicks: ['0','10','20','30','40','50'],
+ minorTicks: 10
+};
+
+var speedoptions = {
+ min:0, max: 6,
+ width: 400, height: 120,
+ greenFrom: 2, greenTo: 4,
+ yellowFrom: 4,yellowTo: 5,
+ redFrom: 5, redTo: 6,
+ majorTicks: ['0','1','2','3','4','5','6'],
+ minorTicks: 10
+};
+
+var poweroptions = {
+ min: 0, max: 1000,
+ width: 400, height: 120,
+ greenFrom: 100, greenTo: 200,
+ yellowFrom: 200,yellowTo: 400,
+ redFrom: 400, redTo: 1000,
+ majorTicks: ['0','200','400','600','800','1000'],
+ minorTicks: 5
+};
+
+
+var dataspm = null;
+var dataspeed = null;
+var spmchart = null;
+var speedchart = null;
+var powerchart = null;
+var datapower = null;
+var dataangles = null;
+
+// SPM chart
+function drawSPMChart() {
+ console.log('first draw SPM chart');
+ dataspm = new google.visualization.arrayToDataTable(spmdata);
+
+
+ try {
+ spmchart = new google.visualization.Gauge(document.getElementById('basic_spm'));
+ spmchart.draw(dataspm,spmoptions);
+} catch(err) {
+
+}
+ // spmchart.draw(data, spmoptions);
+
+ // Define set_basic(values) so that gauges can be set by metricsgroups
+
+};
+
+// Speed Chart
+function drawSpeedChart() {
+ dataspeed = new google.visualization.arrayToDataTable(speeddata);
+
+ try {
+ speedchart = new google.visualization.Gauge(document.getElementById('basic_boatspeed'));
+ speedchart.draw(dataspeed,speedoptions);
+} catch(err) {
+
+}
+}
+
+// Power chart
+function drawPowerChart() {
+ datapower = new google.visualization.arrayToDataTable(powerdata);
+ try {
+ powerchart = new google.visualization.Gauge(document.getElementById('forcepower_power'));
+ powerchart.draw(datapower,poweroptions)
+} catch(err) {
+
+}
+
+}
+
+// HR chart
+function drawHRChart() {
+ datahr = new google.visualization.arrayToDataTable(hrdata);
+ try {
+ hrchart = new google.visualization.Gauge(document.getElementById('athlete_hr'));
+ hrchart.draw(datahr,hroptions);
+ } catch(err) {
+ console.log('no hr div');
+ }
+
+}
+
+// Stroke angle chart
+function drawStrokeAngleChart() {
+ dataangles = new google.visualization.arrayToDataTable(angledata);
+ try {
+ angleschart = new google.visualization.PieChart(document.getElementById('stroke_angles'));
+ angleschart.draw(dataangles,anglesoptions);
+ } catch(err) {
+ console.log('no angles div');
+ }
+
+}
+
+function set_basic() {
+ dataspm.setCell(0,1,spm_now);
+ spmchart.draw(dataspm, spmoptions);
+
+ dataspeed.setCell(0,1,boatspeed_now);
+ speedchart.draw(dataspeed,speedoptions);
+}
+
+function set_athlete() {
+ datahr.setCell(0,1,hr_now);
+ try {
+ hrchart.draw(datahr,hroptions);
+ } catch(err) {}
+}
+
+function set_stroke() {
+ var piestartangle = 90+catch_now;
+ var load = Math.max(-catch_now-slip_now+peakforceangle_now);
+ var unload = Math.max(finish_now-wash_now-peakforceangle_now) ;
+ var recovery = Math.max(360+catch_now-finish_now);
+ // console.log('load ',load,'; unload ',unload,'; recovery ',recovery,'; pie start angle ',piestartangle);
+ dataangles.setCell(0,1,slip_now);
+ dataangles.setCell(1,1,load);
+ dataangles.setCell(2,1,unload);
+ dataangles.setCell(3,1,wash_now);
+ dataangles.setCell(4,1,recovery);
+ anglesoptions.pieStartAngle = piestartangle;
+ try {
+ angleschart.draw(dataangles,anglesoptions);
+ } catch(err) {
+ console.log('failed: ',err);
+ }
+}
+
+function set_forcepower() {
+ datapower.setCell(0,1,power_now);
+ try {
+ powerchart.draw(datapower,poweroptions);
+} catch(err) {}
+}