diff --git a/rowers/forms.py b/rowers/forms.py index bad4b1b6..76e6d66d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -10,7 +10,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from rowers.models import ( Workout, Rower, Team, PlannedSession, GeoCourse, VirtualRace, VirtualRaceResult, IndoorVirtualRaceResult, - PaidPlan + PaidPlan, InStrokeAnalysis ) from rowers.rows import validate_file_extension, must_be_csv, validate_image_extension, validate_kml from django.contrib.auth.forms import UserCreationForm @@ -1250,6 +1250,13 @@ class WorkoutSingleSelectForm(forms.Form): self.fields['workout'].queryset = workouts +class InStrokeMultipleCompareForm(forms.Form): + analyses = forms.ModelMultipleChoiceField( + queryset=InStrokeAnalysis.objects.all(), + widget=forms.CheckboxSelectMultiple() + ) + + class WorkoutMultipleCompareForm(forms.Form): workouts = forms.ModelMultipleChoiceField( queryset=Workout.objects.filter(), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 93366b79..771a5d79 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -5,7 +5,7 @@ from rowers.metrics import rowingmetrics, metricsdicts from scipy.spatial import ConvexHull, Delaunay from scipy.stats import linregress, percentileofscore from pytz import timezone as tz, utc -from rowers.models import course_spline, VirtualRaceResult +from rowers.models import course_spline, VirtualRaceResult, InStrokeAnalysis from bokeh.palettes import Category20c, Category10 from bokeh.layouts import layout, widgetbox from bokeh.resources import CDN, INLINE @@ -4078,6 +4078,81 @@ def interactive_streamchart(id=0, promember=0): return [script, div] +def instroke_multi_interactive_chart(selected): + df_plot = pd.DataFrame() + ids = [analysis.id for analysis in selected] + for analysis in selected: + #start_second, end_second, spm_min, spm_max, name + activeminutesmin = int(analysis.start_second/60.) + activeminutesmax = int(analysis.end_second/60.) + rowdata = rrdata(csvfile=analysis.workout.csvfilename) + data = rowdata.get_instroke_data( + analysis.metric, + spm_min=analysis.spm_min, + spm_max=analysis.spm_max, + activeminutesmin=activeminutesmin, + activeminutesmax=activeminutesmax, + ) + mean_vals = data.mean() + xvals = np.arange(len(mean_vals)) + xname = 'x_'+str(analysis.id) + yname = 'y_'+str(analysis.id) + df_plot[xname] = xvals + df_plot[yname] = mean_vals + + source = ColumnDataSource( + df_plot + ) + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' + plot = Figure(plot_width=920,tools=TOOLS, + toolbar_location='above', + toolbar_sticky=False) + + plot.sizing_mode = 'stretch_both' + + # add watermark + watermarkurl = "/static/img/logo7.png" + + watermarkrange = Range1d(start=0, end=1) + watermarkalpha = 0.6 + watermarkx = 0.99 + watermarky = 0.01 + watermarkw = 184 + watermarkh = 35 + watermarkanchor = 'bottom_right' + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl], watermarkx, watermarky, + watermarkw, watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name="watermark", + y_range_name="watermark", + ) + + colors = itertools.cycle(palette) + + try: + items = itertools.izip(ids, colors) + except AttributeError: + items = zip(ids, colors) + + for id, color in items: + xname = 'x_'+str(id) + yname = 'y_'+str(id) + analysis = InStrokeAnalysis.objects.get(id=id) + plot.line(xname,yname,source=source,legend_label=analysis.name, + line_width=2, color=color) + + script, div = components(plot) + + return (script, div) + def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, activeminutesmin, activeminutesmax, individual_curves, diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html index 3669a531..094145b0 100644 --- a/rowers/templates/instroke_analysis.html +++ b/rowers/templates/instroke_analysis.html @@ -6,16 +6,37 @@ {% block main %} +{{ js_res | safe }} +{{ css_res| safe }} + + + + + +{{ the_script |safe }} +

In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

- +
    + {% if the_div %} +
  • +
    + {{ the_div|safe }} +
    +
  • + {% endif %} {% if analyses %} {% for analysis in analyses %}
  • {{ analysis.date }}

    {{ analysis.name }}

    +
    + +
    @@ -57,6 +78,9 @@
  • {% endif %}
+{% csrf_token %} + + diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 01e942c5..3ae19152 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1852,6 +1852,19 @@ def instrokeanalysis_view(request, userid=0): analyses = InStrokeAnalysis.objects.filter(rower=r).order_by("-date","-id") + script = "" + div = "" + + if request.method == 'POST': + form = InStrokeMultipleCompareForm(request.POST) + + if form.is_valid(): + cd = form.cleaned_data + selected = cd['analyses'] + request.session['analyses'] = [a.id for a in selected] + # now should redirect to analysis + script, div = instroke_multi_interactive_chart(selected) + breadcrumbs = [ { 'url': '/rowers/analysis', @@ -1868,6 +1881,8 @@ def instrokeanalysis_view(request, userid=0): 'breadcrumbs': breadcrumbs, 'analyses': analyses, 'rower': r, + 'the_script': script, + 'the_div': div, }) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 628087b1..4a1e3f01 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -111,7 +111,7 @@ from rowers.forms import ( VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm, StravaChartForm, FitnessFitForm, PerformanceManagerForm, TrainingPlanBillingForm, InstantPlanSelectForm, - TrainingZonesForm, InstrokeForm + TrainingZonesForm, InstrokeForm, InStrokeMultipleCompareForm ) from django.urls import reverse, reverse_lazy diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css index a9086cc1..b58cf043 100644 --- a/static/css/rowsandall2.css +++ b/static/css/rowsandall2.css @@ -397,7 +397,7 @@ th.rotate > div > span { .analysiscontainer { display: grid; - grid-template-columns: 50px repeat(auto-fit, minmax(calc((100% - 100px)/6), 1fr)); + grid-template-columns: 50px repeat(auto-fit, minmax(calc((100% - 100px)/7), 1fr)); /* grid-template-columns: 50px repeat(auto-fit, minmax(100px, 1fr)) 50px; ????*/ padding: 5px; margin: 0px;