diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e1a1f0eb..0e214a83 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -26,6 +26,7 @@ from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation from bokeh.resources import CDN,INLINE from bokeh.embed import components from bokeh.layouts import layout,widgetbox +from bokeh.palettes import Category20c from bokeh.layouts import row as layoutrow from bokeh.layouts import column as layoutcolumn from bokeh.models import LinearAxis,LogAxis,Range1d,DatetimeTickFormatter,HoverTool @@ -37,6 +38,7 @@ from bokeh.models import ( ResetTool, TapTool,CrosshairTool,BoxZoomTool, Span, Label ) +from bokeh.transform import cumsum from bokeh.models.glyphs import ImageURL from bokeh.models import OpenURL, TapTool from rowers.opaque import encoder @@ -51,6 +53,7 @@ from rowers.courses import ( polygon_coord_center ) +from rowers import mytypes from rowers.models import course_spline import datetime @@ -234,7 +237,8 @@ def interactive_hr_piechart(df,rower,title): tools=TOOLS, ) - for start, end , legend, color in zip(source_starts, source_ends, source_legends, colors[0:len(source_starts)]): + for start, end , legend, color in zip(source_starts, source_ends, source_legends, + colors[0:len(source_starts)]): z.wedge(x=0, y=0, radius=1, start_angle=start, end_angle=end, color=color, legend=legend) @@ -249,6 +253,50 @@ def interactive_hr_piechart(df,rower,title): return components(z) +def pretty_timedelta(secs): + hours, remainder = divmod(secs,3600) + minutes, seconds = divmod(remainder,60) + + return '{}:{:02}:{:02}'.format(int(hours),int(minutes),int(seconds)) + +def interactive_workouttype_piechart(workouts): + if len(workouts) == 0: + return "","Not enough workouts to make a chart" + + datadict = {} + + + for w in workouts: + try: + label = mytypes.workouttypes_ordered[w.workouttype] + except KeyError: + labels = w.workouttype + try: + datadict[label] += 60*(60*w.duration.hour+w.duration.minute)+w.duration.second + except KeyError: + datadict[label] = 60*(60*w.duration.hour+w.duration.minute)+w.duration.second + + data = pd.Series(datadict).reset_index(name='value').rename(columns={'index':'type'}) + data['angle'] = data['value']/data['value'].sum() * 2*pi + data['color'] = Category20c[len(datadict)] + data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']]) + + p = figure(plot_height=350, title="Types", toolbar_location=None, + tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) + + p.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='type', ) + + p.axis.axis_label=None + p.axis.visible=False + p.grid.grid_line_color = None + p.outline_line_color = None + p.toolbar_location = 'right' + + return components(p) + + def interactive_boxchart(datadf,fieldname,extratitle='', spmmin=0,spmmax=0,workmin=0,workmax=0): diff --git a/rowers/templates/history.html b/rowers/templates/history.html index c7507a58..657b2ab9 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -21,6 +21,10 @@
+
+ {{ tscript|safe}} +
+
  • History for {{ rower.user.first_name }} {{ rower.user.last_name }}

    @@ -43,6 +47,8 @@

    +
  • +
  • All workouts

    @@ -52,7 +58,7 @@ Total Distance{{ totalsdict|lookup:"distance"}} meters - Total Duration{{ totalsdict|lookup:"duration"}} hours + Total Duration{{ totalsdict|lookup:"duration"}} Number of workouts{{ totalsdict|lookup:"nrworkouts"}} @@ -73,6 +79,9 @@

  • +
  • + {{ tdiv|safe }} +
  • {{ totaldiv|safe }} @@ -90,7 +99,7 @@ Total Distance{{ ddict|lookup:"distance"}} meters - Total Duration{{ ddict|lookup:"duration"}} hours + Total Duration{{ ddict|lookup:"duration"}} Number of workouts{{ ddict|lookup:"nrworkouts"}} diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index bb005175..68f3110d 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4697,8 +4697,9 @@ def history_view(request,userid=0): columns = ['hr','power','time'] + tscript,tdiv = interactive_workouttype_piechart(g_workouts) - totalmeters,totalhours, totalminutes = get_totals(g_workouts) + totalmeters,totalhours, totalminutes, totalseconds = get_totals(g_workouts) totalminutes = "{totalminutes:02d}".format(totalminutes=totalminutes) # meters, duration per workout type @@ -4715,7 +4716,7 @@ def history_view(request,userid=0): for wtype in wtypes: a_workouts = g_workouts.filter(workouttype=wtype) - wmeters, whours, wminutes = get_totals(a_workouts) + wmeters, whours, wminutes,wseconds = get_totals(a_workouts) ddict = {} ddict['id'] = wtype try: @@ -4723,9 +4724,10 @@ def history_view(request,userid=0): except KeyError: ddict['wtype'] = wtype ddict['distance'] = wmeters - ddict['duration'] = "{whours}:{wminutes:02d}".format( + ddict['duration'] = "{whours}:{wminutes:02d}:{wseconds:02d}".format( whours=whours, - wminutes=wminutes + wminutes=wminutes, + wseconds=wseconds, ) ddict['nrworkouts'] = a_workouts.count() listofdicts.append(ddict) @@ -4736,9 +4738,10 @@ def history_view(request,userid=0): totaldiv = get_call() totalsdict = {} - totalsdict['duration'] = "{totalhours}:{totalminutes}".format( + totalsdict['duration'] = "{totalhours}:{totalminutes}:{totalseconds}".format( totalhours=totalhours, - totalminutes=totalminutes + totalminutes=totalminutes, + totalseconds=totalseconds ) totalsdict['distance'] = totalmeters @@ -4768,6 +4771,8 @@ def history_view(request,userid=0): return render(request,'history.html', { + 'tscript':tscript, + 'tdiv':tdiv, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-analysis', @@ -4833,9 +4838,11 @@ def history_view_data(request,userid=0): df = dataprep.clean_df_stats(df,workstrokesonly=True, ignoreadvanced=True) - totalmeters,totalhours, totalminutes = get_totals(g_workouts) + totalmeters,totalhours, totalminutes,totalseconds = get_totals(g_workouts) totalminutes = "{totalminutes:02d}".format(totalminutes=totalminutes) + + # meters, duration per workout type wtypes = list(set([w.workouttype for w in g_workouts])) @@ -4848,18 +4855,18 @@ def history_view_data(request,userid=0): for wtype in wtypes: a_workouts = g_workouts.filter(workouttype=wtype) - wmeters, whours, wminutes = get_totals(a_workouts) + wmeters, whours, wminutes,wseconds = get_totals(a_workouts) ddict = {} try: ddict['wtype'] = mytypes.workouttypes_ordered[wtype] except KeyError: ddict['wtype'] = wtype - + ddict['id'] = wtype ddict['distance'] = wmeters - ddict['duration'] = "{whours}:{wminutes:02d}".format( + ddict['duration'] = "{whours}:{wminutes:02d}:{wseconds:02d}".format( whours=whours, - wminutes=wminutes + wminutes=wminutes,wseconds=wseconds, ) ddf = getsmallrowdata_db(columns,ids=[w.id for w in a_workouts]) try: diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 08c5fbab..695e5d91 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -260,16 +260,17 @@ from django_mailbox.models import Message,Mailbox,MessageAttachment from rules.contrib.views import permission_required, objectgetter def get_totals(workouts): - totalminutes = 0 + totalseconds = 0 totalmeters = 0 for w in workouts: totalmeters += w.distance - totalminutes += w.duration.hour*60+w.duration.minute + totalseconds += 60*(w.duration.hour*60+w.duration.minute)+w.duration.second - totalhour, totalminutes = divmod(totalminutes,60) + hours, remainder = divmod(totalseconds,3600) + minutes, seconds = divmod(remainder,60) - return totalmeters,totalhour, totalminutes + return totalmeters,hours, minutes, seconds # creating shareable views def allow_shares(view_func): diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index fa984049..f0ecb3d8 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2010,7 +2010,7 @@ def workouts_view(request,message='',successmessage='', g_enddate, stack=stack) - totalmeters,totalhours, totalminutes = get_totals(g_workouts) + totalmeters,totalhours, totalminutes,total_seconds = get_totals(g_workouts) totalminutes = '{totalminutes:02d}'.format(totalminutes=totalminutes)