From 95739145a98b19f8bf8fdc7816378a6bf578d3e3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 9 May 2020 15:12:32 +0200 Subject: [PATCH 1/3] first version workouttype chart --- rowers/interactiveplots.py | 65 ++++++++++++++++++++++++++++++++++- rowers/templates/history.html | 7 ++++ rowers/views/analysisviews.py | 9 +++-- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e1a1f0eb..9568069c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -51,6 +51,7 @@ from rowers.courses import ( polygon_coord_center ) +from rowers import mytypes from rowers.models import course_spline import datetime @@ -234,7 +235,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 +251,67 @@ def interactive_hr_piechart(df,rower,title): return components(z) +def interactive_workouttype_piechart(workouts): + if len(workouts) == 0: + return "","Not enough workouts to make a chart" + + datadict = {} + labels = [] + types = [] + + for w in workouts: + try: + datadict[w.workouttype] += 3600*w.duration.hour+60*w.duration.minute+w.duration.second + except KeyError: + datadict[w.workouttype] = 3600*w.duration.hour+60*w.duration.minute+w.duration.second + types += [w.workouttype] + try: + labels += [mytypes.workouttypes_ordered[w.workouttype]] + except KeyError: + labels += [w.workouttype] + + total = 0 + source_starts = [0] + source_ends = [] + for type in types: + total += datadict[type] + source_starts.append(total) + source_ends.append(total) + + source_ends.append(total) + + source_starts = pd.Series(source_starts)*2*pi/total + source_ends = pd.Series(source_ends)*2*pi/total + + size = 350 + TOOLS = 'save' + + z = figure(title="Workout Types", x_range=(-1,1), y_range=(-1,1), width=size, height=size, + tools=TOOLS, + ) + + colors = palette + + print(source_ends) + print(labels) + + for start, end , legend, color in zip(source_starts, source_ends, labels, + colors[0:len(source_starts)]): + print(start,end,color,legend) + z.wedge(x=0, y=0, radius=1, start_angle=start, end_angle=end, color=color, legend=legend) + + + z.toolbar_location = 'right' + z.legend.location = 'top_right' + #z.legend.visible = False + z.axis.visible = False + z.xgrid.grid_line_color = None + z.ygrid.grid_line_color = None + z.outline_line_color = None + + return components(z) + + 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..9f7c87b2 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 }}

    @@ -73,6 +77,9 @@

  • +
  • + {{ tdiv|safe }} +
  • {{ totaldiv|safe }} diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index bb005175..89cd0075 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4697,7 +4697,8 @@ def history_view(request,userid=0): columns = ['hr','power','time'] - + tscript,tdiv = interactive_workouttype_piechart(g_workouts) + totalmeters,totalhours, totalminutes = get_totals(g_workouts) totalminutes = "{totalminutes:02d}".format(totalminutes=totalminutes) @@ -4768,6 +4769,8 @@ def history_view(request,userid=0): return render(request,'history.html', { + 'tscript':tscript, + 'tdiv':tdiv, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-analysis', @@ -4836,6 +4839,8 @@ def history_view_data(request,userid=0): totalmeters,totalhours, totalminutes = 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])) @@ -4854,7 +4859,7 @@ def history_view_data(request,userid=0): ddict['wtype'] = mytypes.workouttypes_ordered[wtype] except KeyError: ddict['wtype'] = wtype - + ddict['id'] = wtype ddict['distance'] = wmeters ddict['duration'] = "{whours}:{wminutes:02d}".format( From 24750be04e2f74b0ad7fc39f14a11b19fc1285f4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 9 May 2020 15:50:52 +0200 Subject: [PATCH 2/3] different pie chart for types --- rowers/interactiveplots.py | 75 ++++++++++++++--------------------- rowers/templates/history.html | 6 ++- rowers/views/analysisviews.py | 24 ++++++----- rowers/views/statements.py | 9 +++-- 4 files changed, 52 insertions(+), 62 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9568069c..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 @@ -251,65 +253,48 @@ 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 = {} - labels = [] - types = [] + for w in workouts: try: - datadict[w.workouttype] += 3600*w.duration.hour+60*w.duration.minute+w.duration.second + label = mytypes.workouttypes_ordered[w.workouttype] except KeyError: - datadict[w.workouttype] = 3600*w.duration.hour+60*w.duration.minute+w.duration.second - types += [w.workouttype] - try: - labels += [mytypes.workouttypes_ordered[w.workouttype]] - except KeyError: - labels += [w.workouttype] + 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 - total = 0 - source_starts = [0] - source_ends = [] - for type in types: - total += datadict[type] - source_starts.append(total) - source_ends.append(total) + 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']]) - source_ends.append(total) + p = figure(plot_height=350, title="Types", toolbar_location=None, + tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) - source_starts = pd.Series(source_starts)*2*pi/total - source_ends = pd.Series(source_ends)*2*pi/total + 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', ) - size = 350 - TOOLS = 'save' + p.axis.axis_label=None + p.axis.visible=False + p.grid.grid_line_color = None + p.outline_line_color = None + p.toolbar_location = 'right' - z = figure(title="Workout Types", x_range=(-1,1), y_range=(-1,1), width=size, height=size, - tools=TOOLS, - ) - - colors = palette - - print(source_ends) - print(labels) - - for start, end , legend, color in zip(source_starts, source_ends, labels, - colors[0:len(source_starts)]): - print(start,end,color,legend) - z.wedge(x=0, y=0, radius=1, start_angle=start, end_angle=end, color=color, legend=legend) - - - z.toolbar_location = 'right' - z.legend.location = 'top_right' - #z.legend.visible = False - z.axis.visible = False - z.xgrid.grid_line_color = None - z.ygrid.grid_line_color = None - z.outline_line_color = None - - return components(z) + return components(p) diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 9f7c87b2..657b2ab9 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -47,6 +47,8 @@

    +
  • +
  • All workouts

    @@ -56,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"}} @@ -97,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 89cd0075..68f3110d 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4698,8 +4698,8 @@ 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 @@ -4716,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: @@ -4724,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) @@ -4737,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 @@ -4836,7 +4838,7 @@ 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) @@ -4853,7 +4855,7 @@ 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] @@ -4862,9 +4864,9 @@ def history_view_data(request,userid=0): 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): From fba28baf7401a2d04f56cf72846a7760d216ef1d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 9 May 2020 16:00:37 +0200 Subject: [PATCH 3/3] fix --- rowers/views/workoutviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)