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)