From e97d87f2d3113376e8a77a3f4bd243f29e235c72 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 22 Oct 2018 15:29:36 +0200
Subject: [PATCH] close #359
---
rowers/forms.py | 17 ++++-
rowers/templates/menu_workout.html | 4 ++
rowers/templates/workout_data.html | 29 +++++++++
rowers/urls.py | 1 +
rowers/views.py | 101 +++++++++++++++++++++++++++--
static/css/rowsandall2.css | 35 ++++++++++
static/css/styles2.css | 12 ++++
7 files changed, 193 insertions(+), 6 deletions(-)
create mode 100644 rowers/templates/workout_data.html
diff --git a/rowers/forms.py b/rowers/forms.py
index 31739429..66051470 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -30,7 +30,7 @@ class EmailForm(forms.Form):
botcheck = forms.CharField(max_length=5)
message = forms.CharField()
-
+
# Upload the CrewNerd Summary CSV
class CNsummaryForm(forms.Form):
@@ -105,6 +105,7 @@ from utils import (
defaultleft,defaultmiddle
)
+
# Form to change Workflow page layout
class WorkFlowLeftPanelForm(forms.Form):
@@ -533,6 +534,20 @@ ww = list(workouttypes)
ww.append(tuple(('all','All')))
workouttypes = tuple(ww)
+class DataFrameColumnsForm(forms.Form):
+ cols = ['ftime','cumdist','fpace','spm',
+ 'hr','power','driveenergy','drivelength','averageforce',
+ 'peakforce','distance','drivespeed','workoutstate',
+ 'catch','finish','peakforceangle','wash','slip','rhythm',
+ 'effectiveangle','totalangle','distanceperstroke','velo']
+
+ colchoices = [
+ (c, c) for c in cols
+ ]
+
+ cols = forms.MultipleChoiceField(choices=colchoices,
+ label='Table Columns')
+
# form to select modality and boat type for trend flex
class TrendFlexModalForm(forms.Form):
modality = forms.ChoiceField(choices=workouttypes,
diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html
index 12179e41..ec62b95e 100644
--- a/rowers/templates/menu_workout.html
+++ b/rowers/templates/menu_workout.html
@@ -259,6 +259,10 @@
Split Workout
+
+
+ Explore Raw Data
+
diff --git a/rowers/templates/workout_data.html b/rowers/templates/workout_data.html
new file mode 100644
index 00000000..a259bb35
--- /dev/null
+++ b/rowers/templates/workout_data.html
@@ -0,0 +1,29 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workout Data{% endblock %}
+
+{% block main %}
+Workout Data for {{ workout.name }}
+
+
+
+
+ {{ htmltable|safe }}
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_workout.html' %}
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 04b75c9a..59b9f769 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -246,6 +246,7 @@ urlpatterns = [
url(r'^workout/(?P\d+)/instroke/(?P\w+.*)$',views.instroke_chart),
url(r'^workout/(?P\d+)/instroke$',views.instroke_view),
url(r'^workout/(?P\d+)/stats$',views.workout_stats_view),
+ url(r'^workout/(?P\d+)/data$',views.workout_data_view),
url(r'^workout/(?P\d+)/otwsetpower$',views.workout_otwsetpower_view),
url(r'^workout/(?P\d+)/interactiveotwplot$',views.workout_otwpowerplot_view),
url(r'^workout/(?P\d+)/wind$',views.workout_wind_view),
diff --git a/rowers/views.py b/rowers/views.py
index 8fff77c9..39a35c80 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -44,7 +44,7 @@ from rowers.forms import (
PlannedSessionTeamForm,PlannedSessionTeamMemberForm,
VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm,
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
- FlexOptionsForm
+ FlexOptionsForm,DataFrameColumnsForm,
)
from django.core.urlresolvers import reverse, reverse_lazy
@@ -8093,6 +8093,100 @@ def cumstats(request,theuser=0,
return response
+
+# data explorer
+@login_required()
+def workout_data_view(request, id=0):
+
+ r = getrower(request.user)
+ w = get_workout(id)
+
+ if not checkworkoutuser(request.user,w):
+ raise PermissionDenied('Access Denied')
+
+ breadcrumbs = [
+ {
+ 'url':'/rowers/list-workouts',
+ 'name':'Workouts'
+ },
+ {
+ 'url':get_workout_default_page(request,id),
+ 'name': str(w.id)
+ },
+ {
+ 'url':reverse(workout_data_view,kwargs={'id':id}),
+ 'name': 'Data Explorer'
+ }
+
+ ]
+
+
+ datadf,row = dataprep.getrowdata_db(id=id)
+
+
+ datadf.sort_values(['ftime'],inplace=True)
+
+ datadf.drop(labels=[
+ 'id','time','hr_an','hr_at','hr_bottom','hr_max',
+ 'hr_tr','hr_ut1','hr_ut2','x_right',
+ ],inplace=True,axis=1)
+
+
+ cols = ['ftime','cumdist','fpace','spm',
+ 'hr','power','driveenergy','drivelength','averageforce',
+ 'peakforce','distance','drivespeed','workoutstate',
+ 'catch','finish','peakforceangle','wash','slip','rhythm',
+ 'effectiveangle','totalangle','distanceperstroke','velo']
+
+
+ tcols = ['ftime','cumdist','fpace','spm','hr','power']
+
+ datadf = datadf[cols]
+ datadf.loc[:,'hr'] = datadf['hr'].astype('int')
+ datadf.loc[:,'power'] = datadf['power'].astype('int')
+ datadf.loc[:,'distance'] = datadf['distance'].astype('int')
+ datadf.loc[:,'spm'] = 10*datadf['spm'].astype('int')/10.
+
+
+ if request.method == 'POST':
+ form = DataFrameColumnsForm(request.POST)
+ if form.is_valid():
+ tcols = form.cleaned_data['cols']
+
+ else:
+ form = DataFrameColumnsForm(initial = {'cols':tcols})
+
+ datadf = datadf[tcols]
+
+ for col in cols:
+ try:
+ if datadf[col].mean() == 0 and datadf[col].std() == 0:
+ datadf.drop(labels=[col],axis=1,inplace=True)
+ except (TypeError,KeyError):
+ pass
+
+ # pd.set_option('display.width', 1000)
+ pd.set_option('colheader_justify', 'left')
+
+ htmltable = datadf.to_html(
+ bold_rows=True,
+ show_dimensions=True,border=1,
+ classes='pandastable',justify='justify'
+ )
+
+ return render(request,
+ 'workout_data.html',
+ {
+ 'htmltable': htmltable,
+ 'form':form,
+ 'teams':get_my_teams(request.user),
+ 'workout': w,
+ 'breadcrumbs': breadcrumbs,
+
+ }
+ )
+
+
# Stats page
@login_required()
def workout_stats_view(request,id=0,message="",successmessage=""):
@@ -8130,10 +8224,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
# prepare data frame
datadf,row = dataprep.getrowdata_db(id=id)
if (checkworkoutuser(request.user,row)==False):
- message = "You are not allowed to see the stats of this workout"
- messages.error(request,message)
- url = reverse(workouts_view)
- return HttpResponseRedirect(url)
+ raise PermissionDenied('Access Denied')
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css
index 95b02e66..00567460 100644
--- a/static/css/rowsandall2.css
+++ b/static/css/rowsandall2.css
@@ -972,3 +972,38 @@ a.wh:hover {
width: auto !important;
max-width: 1px;
}
+
+.mystyle {
+ font-size: 11pt;
+ font-family: Arial;
+ border-collapse: collapse;
+ border: 1px solid silver;
+
+}
+
+.pandastable tr {
+ display: block;
+}
+
+.pandastable th, table td {
+ width: 100px;
+}
+
+.pandastable tbody {
+ display: block;
+ height: 300px;
+ overflow: auto;
+}
+
+.pandastable td, th {
+ padding: 5px;
+}
+
+.pandastable tr:nth-child(even) {
+ background: #E0E0E0;
+}
+
+.pandastable tr:hover {
+ background: silver;
+ cursor: pointer;
+}
diff --git a/static/css/styles2.css b/static/css/styles2.css
index 7a3e30fa..13cbbc5f 100644
--- a/static/css/styles2.css
+++ b/static/css/styles2.css
@@ -501,6 +501,18 @@
overflow: scroll;
}
+.maxheightwidth {
+ position: relative;
+ max-height: 300px;
+ overflow: scroll;
+}
+
+.maxheightwidthcontent {
+ position: relative;
+ max-width: 700px;
+ overflow: scroll;
+}
+
.main-content li.grid_4 {
grid-column-end: span 1;
}