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) {} +}