diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index cccd3486..9946f22c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr datadict = { - '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, + '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, '{ut2}'.format(ut2=hrzones[1]): frac_ut2, '{ut1}'.format(ut1=hrzones[2]): frac_ut1, '{at}'.format(at=hrzones[3]): frac_at, @@ -299,7 +299,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['angle'] = data['value']/data['value'].sum() * 2*pi data['color'] = colors data['zone'] = [ - '<{ut2}'.format(ut2=hrzones[1]), + '<{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]), '{ut1}'.format(ut1=hrzones[2]), '{at}'.format(at=hrzones[3]), @@ -309,24 +309,14 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']]) - TOOLS = 'save,hover' - - z = figure(title="HR "+title, x_range=(-0.5, 1), height=375, - tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime", - ) - - z.wedge(x=0, y=1, radius=0.4, - start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), - line_color='white', fill_color='color', source=data, legend_group='zone') - - z.axis.axis_label = None - z.axis.visible = False - z.grid.grid_line_color = None - z.outline_line_color = None - z.toolbar_location = 'right' - - return components(z) + data_dict = data.to_dict("records") + chart_data = { + 'data': data_dict, + 'title': "HR "+ title + } + script, div = get_chart("/hrpie", chart_data, debug=True) + return script, div def pretty_timedelta(secs): hours, remainder = divmod(secs, 3600) diff --git a/rowers/templates/disqualification_view.html b/rowers/templates/disqualification_view.html index dfacbe67..5a6406cb 100644 --- a/rowers/templates/disqualification_view.html +++ b/rowers/templates/disqualification_view.html @@ -102,14 +102,11 @@ {% endif %}
  • - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} +
  • diff --git a/rowers/templates/histo_single.html b/rowers/templates/histo_single.html index 4d6111d3..868f2f34 100644 --- a/rowers/templates/histo_single.html +++ b/rowers/templates/histo_single.html @@ -6,12 +6,9 @@ {% block main %} - - + + -{{ interactiveplot |safe }} @@ -24,6 +21,7 @@
  • +{{ interactiveplot |safe }} {% endif %} {% endblock %} diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 358b91a0..52aaa542 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -12,6 +12,7 @@
    +
    {% endblock %} diff --git a/rowers/templates/splitworkout.html b/rowers/templates/splitworkout.html index bd7c0130..845162b2 100644 --- a/rowers/templates/splitworkout.html +++ b/rowers/templates/splitworkout.html @@ -23,7 +23,7 @@
  • - + diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 4117c89d..886194e6 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -8,26 +8,26 @@ {% block scripts %} {% include "monitorjobs.html" %} - - - + + + {% endblock %} {% block main %} @@ -42,78 +42,75 @@ {% endif %}

    - - - - - - - - - - +
    Name{{ workout.name }}
    Distance:{{ workout.distance }}m
    Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
    Public link to this workout + + + + + + + + + + -
    Name{{ workout.name }}
    Distance:{{ workout.distance }}m
    Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
    Public link to this workout https://rowsandall.com/rowers/workout/{{ workout.id|encode }} - -
    -

    +
    +
    +

    Edit Workout Interval Data

    - Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest - intervals. To save your work, press the Save button above the updated summary. + Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest + intervals. To save your work, press the Save button above the updated summary.

      -
    • -

      Updated Summary

      -
      - {% csrf_token %} - - - {% for key,value in formvalues.items %} - - {% endfor %} -

      - -

      - - Reset to last saved -   - Restore Original data - -
      -

      -

      -					{{ intervalstats }}
      -				
      -

      -
    • -
    • - - - - {{ interactiveplot |safe }} - - {{ the_div |safe }} -
    • -
    • -

      Feeling lucky?

      -

      This new, experimental feature tries to find intervals by looking for patterns in the data. It does not - autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand - section below, where you can edit it. -

      - - - {{ ruptureform.as_table }} -
      - {% csrf_token %} - -
      -

      -
    • -
    • +
    • +

      Updated Summary

      +
      + {% csrf_token %} + + + {% for key,value in formvalues.items %} + + {% endfor %} +

      + +

      + + Reset to last saved +   + Restore Original data + +
      +

      +

      +	{{ intervalstats }}
      +      
      +

      +
    • +
    • + + + {{ the_div |safe }} + {{ interactiveplot |safe }} + +
    • +
    • +

      Feeling lucky?

      +

      This new, experimental feature tries to find intervals by looking for patterns in the data. It does not + autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand + section below, where you can edit it. +

      + + + {{ ruptureform.as_table }} +
      + {% csrf_token %} + +
      +

      +
    • +
    • Interval Shorthand

      See the how-to at the bottom of this page for details on how to use this form. @@ -125,26 +122,26 @@ {% csrf_token %} -

    • -
    • +
    • +
    • Intervals by Power/Pace

      - +

      With this form, you can specify a power or pace level. Everything faster/harder than the specified pace/power will become a work interval. Everything slower will become a rest interval. Use the slider to limit the active range. Everything outside the active range will - become rest (warming up and cooling down). + become rest (warming up and cooling down).

      - +
      -
      -

      - - -

      +
      +

      + + +

      {{ powerupdateform.as_table }}
      - + {% csrf_token %}
      @@ -153,11 +150,11 @@

      Interval Shorthand How-To

      This is a quick way to enter the intervals using a special mini-language.

      You enter something like 8x500m/3min, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.

      - +

      Special characters are x (times), + and / (denotes a rest interval), as well as ( and ). Units are min (minutes), sec (seconds), m (meters) and km (km).

      - +

      A typical interval is described as "10min/5min", with the work part before the "/" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "1km" or "1000m". The basic units can be combined with "+" and "Nx". You can use parentheses as in the example below.

      - +

      Here are a few examples

      @@ -182,32 +179,32 @@
    • - {% if courses %} -
    • -

      Interval by Course

      -

      - This functionality allows you to record a time on a set course that you've rowed during the workout. - The summary will be updated to show time on course, and you can compare this with other - attempts. -

      -

      - {% if rower.share_course_results %} - You are currently sharing your course results with all Rowsandall users. - Click here to hide your course results. - {% else %} - You are currently hiding your course results (except for your participation in online challenges). - Click here to hide your course results. - {% endif %} -

      -
      - - {{ courseselectform.as_table }} -
      - {% csrf_token %} - -
      -
    • - {% endif %} + {% if courses %} +
    • +

      Interval by Course

      +

      + This functionality allows you to record a time on a set course that you've rowed during the workout. + The summary will be updated to show time on course, and you can compare this with other + attempts. +

      +

      + {% if rower.share_course_results %} + You are currently sharing your course results with all Rowsandall users. + Click here to hide your course results. + {% else %} + You are currently hiding your course results (except for your participation in online challenges). + Click here to hide your course results. + {% endif %} +

      +
      + + {{ courseselectform.as_table }} +
      + {% csrf_token %} + +
      +
    • + {% endif %}
    {% endblock %} diff --git a/rowers/templates/withdraw_view.html b/rowers/templates/withdraw_view.html index a5df85ce..2929761c 100644 --- a/rowers/templates/withdraw_view.html +++ b/rowers/templates/withdraw_view.html @@ -98,14 +98,11 @@
  • {% endif %}
  • - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} +
  • diff --git a/rowers/urls.py b/rowers/urls.py index 2ebcf93f..4fdbf66b 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -443,9 +443,6 @@ urlpatterns = [ views.trainingzones_view_data, name="trainingzones_view_data"), re_path(r'^trainingzones/data/$', views.trainingzones_view_data, name="trainingzones_view_data"), - re_path(r'^ote-bests2/user/(?P\d+)/$', - views.rankings_view2, name='rankings_view2'), - re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'), re_path(r'^analysisdata/user/(?P\d+)/$', views.analysis_view_data, name='analysis_view_data'), re_path(r'^analysisdata/$', views.analysis_view_data, @@ -470,8 +467,6 @@ urlpatterns = [ views.workout_upload_view, name='workout_upload_view'), re_path(r'^workout/upload/$', views.workout_upload_view, name='workout_upload_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view, - name='workout_histo_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view, name='workout_forcecurve_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/(?P\d+)/$', views.workout_forcecurve_view, diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 2ebdbb72..03bedb87 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1382,398 +1382,6 @@ def ajax_agegrouprecords(request, ) -# Show ranking distances including predicted paces -@login_required() -@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) -def rankings_view2(request, userid=0, - startdate=timezone.now()-datetime.timedelta(days=365), - enddate=timezone.now(), - deltadays=-1, - startdatestring="", - enddatestring=""): - - if deltadays > 0: # pragma: no cover - startdate = enddate-datetime.timedelta(days=int(deltadays)) - - if startdatestring != "": # pragma: no cover - startdate = iso8601.parse_date(startdatestring) - - if enddatestring != "": # pragma: no cover - enddate = iso8601.parse_date(enddatestring) - - if enddate < startdate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - - if userid == 0: - userid = request.user.id - else: - lastupdated = "1900-01-01" - - promember = 0 - r = getrequestrower(request, userid=userid) - theuser = r.user - - wcdurations = [] - wcpower = [] - - lastupdated = "1900-01-01" - userid = 0 - if 'options' in request.session: - options = request.session['options'] - try: - wcdurations = options['wcdurations'] - wcpower = options['wcpower'] - lastupdated = options['lastupdated'] - except KeyError: # pragma: no cover - pass - try: - userid = options['userid'] - except KeyError: # pragma: no cover - userid = 0 - else: - options = {} - - lastupdatedtime = arrow.get(lastupdated).timestamp() - current_time = arrow.utcnow().timestamp() - - deltatime_seconds = current_time - lastupdatedtime - recalc = False - if str(userid) != str(theuser) or deltatime_seconds > 3600: - recalc = True - options['lastupdated'] = arrow.utcnow().isoformat() - else: # pragma: no cover - recalc = False - - options['userid'] = theuser.id - - if r.birthdate: - age = calculate_age(r.birthdate) - else: - age = 0 - - agerecords = CalcAgePerformance.objects.filter( - age=age, - sex=r.sex, - weightcategory=r.weightcategory) - - if len(agerecords) == 0: - recalc = True - wcpower = [] - wcdurations = [] - else: - wcdurations = [] - wcpower = [] - for record in agerecords: - wcdurations.append(record.duration) - wcpower.append(record.power) - - options['wcpower'] = wcpower - options['wcdurations'] = wcdurations - if theuser: - options['userid'] = theuser.id - - request.session['options'] = options - - result = request.user.is_authenticated and ispromember(request.user) - if result: - promember = 1 - - # get all indoor rows in date range - - # process form - if request.method == 'POST' and "daterange" in request.POST: - dateform = DateRangeForm(request.POST) - deltaform = DeltaDaysForm(request.POST) - if dateform.is_valid(): - startdate = dateform.cleaned_data['startdate'] - enddate = dateform.cleaned_data['enddate'] - if startdate > enddate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover - deltaform = DeltaDaysForm(request.POST) - if deltaform.is_valid(): - deltadays = deltaform.cleaned_data['deltadays'] - if deltadays: - enddate = timezone.now() - startdate = enddate-datetime.timedelta(days=deltadays) - if startdate > enddate: - s = enddate - enddate = startdate - startdate = s - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - else: - dateform = DateRangeForm() - deltaform = DeltaDaysForm() - - else: - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - deltaform = DeltaDaysForm() - - # get all 2k (if any) - this rower, in date range - try: - r = getrower(theuser) - except Rower.DoesNotExist: # pragma: no cover - r = 0 - - uu = theuser - - # test to fix bug - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - startdate = arrow.get(startdate).datetime - enddate = arrow.get(enddate).datetime - - thedistances = [] - theworkouts = [] - thesecs = [] - - rankingdistances.sort() - rankingdurations.sort() - - for rankingdistance in rankingdistances: - - workouts = Workout.objects.filter( - user=r, distance=rankingdistance, - workouttype__in=['rower', 'dynamic', 'slides'], - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('duration') - if workouts: - thedistances.append(rankingdistance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-6*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - for rankingduration in rankingdurations: - - workouts = Workout.objects.filter( - user=r, duration=rankingduration, - workouttype='rower', - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('-distance') - if workouts: - thedistances.append(workouts[0].distance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-5*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - thedistances = np.array(thedistances) - thesecs = np.array(thesecs) - - thevelos = thedistances/thesecs - theavpower = 2.8*(thevelos**3) - - # create interactive plot - if len(thedistances) != 0: - res = interactive_cpchart( - r, thedistances, thesecs, theavpower, - theworkouts, promember=promember, - wcdurations=wcdurations, wcpower=wcpower - ) - script = res[0] - div = res[1] - paulslope = res[2] - paulintercept = res[3] - p1 = res[4] - message = res[5] - else: - script = '' - div = '

    No ranking pieces found.

    ' - paulslope = 1 - paulintercept = 1 - p1 = [1, 1, 1, 1] - message = "" - - if request.method == 'POST' and "piece" in request.POST: # pragma: no cover - form = PredictedPieceForm(request.POST) - if form.is_valid(): - value = form.cleaned_data['value'] - hourvalue, value = divmod(value, 60) - if hourvalue >= 24: - hourvalue = 23 - pieceunit = form.cleaned_data['pieceunit'] - if pieceunit == 'd': - rankingdistances.append(value) - else: - rankingdurations.append(datetime.time( - minute=int(value), hour=int(hourvalue))) - else: - form = PredictedPieceForm() - - rankingdistances.sort() - rankingdurations.sort() - - predictions = [] - cpredictions = [] - - for rankingdistance in rankingdistances: - # Paul's model - p = paulslope*np.log10(rankingdistance)+paulintercept - velo = 500./p - t = rankingdistance/velo - pwr = 2.8*(velo**3) - try: - pwr = int(pwr) - except (ValueError, AttributeError): # pragma: no cover - pwr = 0 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - - # CP model - - pwr2 = p1[0]/(1+t/p1[2]) - pwr2 += p1[1]/(1+t/p1[3]) - - if pwr2 <= 0: # pragma: no cover - pwr2 = 50. - - velo2 = (pwr2/2.8)**(1./3.) - - if np.isnan(velo2) or velo2 <= 0: # pragma: no cover - velo2 = 1.0 - - t2 = rankingdistance/velo2 - - pwr3 = p1[0]/(1+t2/p1[2]) - pwr3 += p1[1]/(1+t2/p1[3]) - - if pwr3 <= 0: # pragma: no cover - pwr3 = 50. - - velo3 = (pwr3/2.8)**(1./3.) - if np.isnan(velo3) or velo3 <= 0: # pragma: no cover - velo3 = 1.0 - - t3 = rankingdistance/velo3 - p3 = 500./velo3 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t3), - 'pace': timedeltaconv(p3), - 'power': int(pwr3)} - cpredictions.append(a) - - for rankingduration in rankingdurations: - t = 3600.*rankingduration.hour - t += 60.*rankingduration.minute - t += rankingduration.second - t += rankingduration.microsecond/1.e6 - - # Paul's model - ratio = paulintercept/paulslope - - u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope - - d = 500*t*np.log(10.) - d = d/(paulslope*lambertw(u)) - d = d.real - - velo = d/t - p = 500./velo - pwr = 2.8*(velo**3) - try: - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - except: # pragma: no cover - pass - - # CP model - pwr = p1[0] / (1 + t / p1[2]) - pwr += p1[1] / (1 + t / p1[3]) - - if pwr <= 0: # pragma: no cover - pwr = 50. - - velo = (pwr / 2.8)**(1. / 3.) - - if np.isnan(velo) or velo <= 0: # pragma: no cover - velo = 1.0 - - d = t * velo - p = 500. / velo - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - cpredictions.append(a) - - if recalc: - wcdurations = [] - wcpower = [] - durations = [1, 4, 30, 60] - distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - sex=r.sex, - weightcategory=r.weightcategory - ).values() - ) - ) - - jsondf = df.to_json() - - job = myqueue(queue, - handle_getagegrouprecords, - jsondf, distances, durations, age, r.sex, r.weightcategory) - try: - request.session['async_tasks'] += [(job.id, 'agegrouprecords')] - except KeyError: - request.session['async_tasks'] = [(job.id, 'agegrouprecords')] - - messages.error(request, message) - return render(request, 'rankings.html', - {'rankingworkouts': theworkouts, - 'interactiveplot': script, - 'the_div': div, - 'predictions': predictions, - 'cpredictions': cpredictions, - 'nrdata': len(thedistances), - 'form': form, - 'dateform': dateform, - 'deltaform': deltaform, - 'id': theuser, - 'theuser': uu, - 'rower': r, - 'active': 'nav-analysis', - 'age': age, - 'sex': r.sex, - 'recalc': recalc, - 'weightcategory': r.weightcategory, - 'startdate': startdate, - 'enddate': enddate, - 'teams': get_my_teams(request.user), - }) - - @login_required() def otecp_toadmin_view(request, theuser=0, startdate=timezone.now() - datetime.timedelta(days=365), diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index edf0b572..7fb0c248 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -630,50 +630,6 @@ def otw_use_gps(request, id=0): return HttpResponseRedirect(url) -# Show Stroke power histogram for a workout -@login_required() -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -def workout_histo_view(request, id=0): - w = get_workoutuser(id, request) - r = getrequestrower(request) - - mayedit = 0 - if w.user == r: - mayedit = 1 - - res = interactive_histoall([w], 'power', False) - script = res[0] - div = res[1] - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_histo_view', kwargs={'id': id}), - 'name': 'Histogram' - } - - ] - - return render(request, - 'histo_single.html', - {'interactiveplot': script, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'workout': w, - 'rower': r, - 'the_div': div, - 'id': id, - 'mayedit': mayedit, - 'teams': get_my_teams(request.user), - }) - # add a workout manually @login_required()