Private
Public Access
1
0

v1 flex chart with chart server

This commit is contained in:
2024-03-19 12:21:05 +01:00
parent 1c4f6cc7ee
commit a8ad7e108b
6 changed files with 185 additions and 628 deletions

View File

@@ -1322,7 +1322,7 @@ palettechoices = tuple((p, p) for p in palettes.keys())
analysischoices = (
('boxplot', 'Box Chart'),
('trendflex', 'Trend Flex'),
# ('trendflex', 'Trend Flex'),
('histo', 'Histogram'),
('flexall', 'Cumulative Flex Chart'),
('stats', 'Statistics'),

View File

@@ -4454,22 +4454,19 @@ def interactive_flex_chart2(id, r, promember=0,
trendline=False,
mode='rower'):
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'
columns = [xparam, yparam1, yparam2,
'ftime', 'distance', 'fpace',
'power', 'hr', 'spm', 'driveenergy',
'time', 'pace', 'workoutstate']
columns = [name for name, d in metrics.rowingmetrics]
columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
columns = columns + ['spm', 'driveenergy', 'distance']
columns_basic = columns_basic + ['spm', 'driveenergy', 'distance']
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True,
workstrokesonly=workstrokesonly)
datadf = pd.DataFrame()
if promember:
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True,
workstrokesonly=workstrokesonly, for_chart=True)
else:
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True,
workstrokesonly=workstrokesonly, for_chart=True)
if r.usersmooth > 1: # pragma: no cover
for column in columns:
@@ -4484,10 +4481,14 @@ def interactive_flex_chart2(id, r, promember=0,
try:
if len(rowdata) < 2:
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id],
doclean=False,
workstrokesonly=False)
workstrokesonly = False
if promember:
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id],
doclean=False,
workstrokesonly=False, for_chart=True)
else:
rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=False,
workstrokesonly=False, for_chart=True)
workstrokesonly = False
except (KeyError, TypeError): # pragma: no cover
workstrokesonly = False
try:
@@ -4517,7 +4518,7 @@ def interactive_flex_chart2(id, r, promember=0,
row = Workout.objects.get(id=id)
if rowdata.empty:
return "", "No valid data", '', '', workstrokesonly
return "", "No valid data", workstrokesonly
workoutstatesrest = [3]
@@ -4530,7 +4531,7 @@ def interactive_flex_chart2(id, r, promember=0,
try:
tseconds = rowdata.loc[:, 'time']
except KeyError: # pragma: no cover
return '', 'No time data - cannot make flex plot', '', '', workstrokesonly
return '', 'No time data - cannot make flex plot', workstrokesonly
try:
rowdata['x1'] = rowdata.loc[:, xparam]
@@ -4578,27 +4579,6 @@ def interactive_flex_chart2(id, r, promember=0,
y1mean = rowdata['y1'].mean()
y2mean = rowdata['y2'].mean()
if xparam != 'time':
xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.
else:
xvals = np.arange(100)
# constant power plot
if yparam1 == 'driveenergy':
if xparam == 'spm': # pragma: no cover
yconstantpower = rowdata['y1'].mean()*rowdata['x1'].mean()/xvals
x_axis_type = 'linear'
y_axis_type = 'linear'
if xparam == 'time':
x_axis_type = 'datetime'
if yparam1 == 'pace':
y_axis_type = 'datetime'
try:
y1mean = rowdata.loc[:, 'pace'].mean()
except KeyError: # pragma: no cover
y1mean = 0
try:
rowdata['xname'] = axlabels[xparam]
@@ -4627,420 +4607,25 @@ def interactive_flex_chart2(id, r, promember=0,
rowdata['ytrend'] = ytrend
except TypeError: # pragma: no cover
rowdata['ytrend'] = y1
# prepare data
source = ColumnDataSource(
rowdata
)
# second source for filtering
source2 = ColumnDataSource(
rowdata.copy()
)
data_dict = rowdata.to_dict("records")
# Add hover to this comma-separated string and see what changes
if (promember == 1):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
else:
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
chart_data = {
'title': row.name,
'x': xparam,
'y1': yparam1,
'y2': yparam2,
'data': data_dict,
'metrics': metrics_list,
'trendline': trendline,
'plottype': plottype,
}
plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type,
tools=TOOLS, toolbar_location='above',
toolbar_sticky=False, width=800, height=600,
)
#plot.sizing_mode = 'stretch_both'
script, div = get_chart("/flex", chart_data)
# 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.sizing_mode = 'stretch_both'
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",
)
x1means = Span(location=x1mean, dimension='height', line_color='green',
line_dash=[6, 6], line_width=2)
y1means = Span(location=y1mean, dimension='width', line_color='blue',
line_dash=[6, 6], line_width=2)
y2means = y1means
try:
xlabeltext = axlabels[xparam]+": {x1mean:6.2f}".format(
x1mean=x1mean
)
except KeyError: # pragma: no cover
xlabeltext = xparam+": {x1mean:6.2f}".format(x1mean=x1mean)
xlabel = Label(x=50, y=80, x_units='screen', y_units='screen',
text=xlabeltext,
background_fill_alpha=.7,
background_fill_color='white',
text_color='green',
)
annolabel = Label(x=50, y=450, x_units='screen', y_units='screen',
text='',
background_fill_alpha=0.7,
background_fill_color='white',
text_color='black',
)
sliderlabel = Label(x=10, y=470, x_units='screen', y_units='screen',
text='',
background_fill_alpha=0.7,
background_fill_color='white',
text_color='black', text_font_size='10pt',
)
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover
plot.add_layout(x1means)
plot.add_layout(xlabel)
plot.add_layout(y1means)
plot.add_layout(annolabel)
plot.add_layout(sliderlabel)
try:
yaxlabel = axlabels[yparam1]
except KeyError: # pragma: no cover
yaxlabel = str(yparam1)+' '
try:
xaxlabel = axlabels[xparam]
except KeyError: # pragma: no cover
xaxlabel = xparam
y1label = Label(x=50, y=50, x_units='screen', y_units='screen',
text=yaxlabel+": {y1mean:6.2f}".format(y1mean=y1mean),
background_fill_alpha=.7,
background_fill_color='white',
text_color='blue',
)
if yparam1 != 'time' and yparam1 != 'pace': # pragma: no cover
plot.add_layout(y1label)
y2label = y1label
# average values
if yparam1 == 'driveenergy': # pragma: no cover
if xparam == 'spm':
plot.line(xvals, yconstantpower, color="green",
legend_label="Constant Power")
# trendline
if trendline: # pragma: no cover
plot.line('x1', 'ytrend', source=source2, legend_label=yaxlabel+' (trend)')
if plottype == 'line':
plot.line('x1', 'y1', source=source2, legend_label=yaxlabel)
elif plottype == 'scatter': # pragma: no cover
plot.scatter('x1', 'y1', source=source2, legend_label=yaxlabel, fill_alpha=0.4,
line_color=None)
try:
plot.title.text = row.name
except ValueError: # pragma: no cover
plot.title.text = ""
plot.title.text_font_size = "1.0em"
#plot.sizing_mode = 'stretch_both'
plot.xaxis.axis_label = xaxlabel
plot.yaxis.axis_label = yaxlabel
try:
yrange1 = Range1d(start=get_yaxminima(r, yparam1, mode),
end=get_yaxmaxima(r, yparam1, mode))
except KeyError: # pragma: no cover
yrange1 = Range1d(start=rowdata[yparam1].min(),
end=rowdata[yparam1].max())
plot.y_range = yrange1
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover
try:
xrange1 = Range1d(start=get_yaxminima(r, xparam, mode),
end=get_yaxmaxima(r, xparam, mode))
except KeyError:
xrange1 = Range1d(start=rowdata[xparam].min(),
end=rowdata[xparam].max())
plot.x_range = xrange1
if xparam == 'time':
xrange1 = Range1d(start=xaxmin, end=xaxmax)
plot.x_range = xrange1
plot.xaxis[0].formatter = DatetimeTickFormatter(
hours=["%H"],
minutes=["%M"],
seconds=["%S"],
days=["0"],
months=[""],
years=[""]
)
if yparam1 == 'pace':
plot.yaxis[0].formatter = DatetimeTickFormatter(
seconds=["%S"],
minutes=["%M"]
)
if yparam2 != 'None':
try:
yrange2 = Range1d(start=get_yaxminima(r, yparam2, mode),
end=get_yaxmaxima(r, yparam2, mode))
except KeyError: # pragma: no cover
yrange2 = Range1d(start=rowdata[yparam2].min(),
end=rowdata[yparam2].max())
plot.extra_y_ranges["yax2"] = yrange2
# = {"yax2": yrange2}
try:
axlegend = axlabels[yparam2]
except KeyError: # pragma: no cover
axlegend = str(yparam2)+' '
if plottype == 'line':
plot.line('x1', 'y2', color="red", y_range_name="yax2",
legend_label=axlegend,
source=source2)
elif plottype == 'scatter': # pragma: no cover
plot.scatter('x1', 'y2', source=source2, legend_label=axlegend,
fill_alpha=0.4,
line_color=None, color="red", y_range_name="yax2")
plot.add_layout(LinearAxis(y_range_name="yax2",
axis_label=axlegend), 'right')
y2means = Span(location=y2mean, dimension='width', line_color='red',
line_dash=[6, 6], line_width=2, y_range_name="yax2")
plot.add_layout(y2means)
y2label = Label(x=50, y=20, x_units='screen', y_units='screen',
text=axlegend+": {y2mean:6.2f}".format(y2mean=y2mean),
background_fill_alpha=.7,
background_fill_color='white',
text_color='red',
)
if yparam2 != 'pace' and yparam2 != 'time':
plot.add_layout(y2label)
hover = plot.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
('Time', '@ftime'),
('Distance', '@distance{int}'),
('Pace', '@fpace'),
('HR', '@hr{int}'),
('SPM', '@spm{1.1}'),
('Power', '@power{int}'),
])
hover.mode = 'mouse'
callback = CustomJS(args=dict(source=source, source2=source2,
x1means=x1means,
y1means=y1means,
y1label=y1label,
y2label=y2label,
xlabel=xlabel,
annolabel=annolabel,
sliderlabel=sliderlabel,
y2means=y2means,
), code="""
var data = source.data
var data2 = source2.data
var x1 = data['x1']
var y1 = data['y1']
var y2 = data['y2']
var spm1 = data['spm']
var time1 = data['time']
var ftime1 = data['ftime']
var pace1 = data['pace']
var hr1 = data['hr']
var fpace1 = data['fpace']
var distance1 = data['distance']
var power1 = data['power']
var driveenergy1 = data['driveenergy']
var xname = data['xname']
var yname1 = data['yname1']
var yname2 = data['yname2']
var workoutid1 = data['workoutid']
var workoutstate1 = data['workoutstate']
var ytrend = data['ytrend']
var annotation = annotation.value
var minspm = minspm.value
var maxspm = maxspm.value
var mindist = mindist.value
var maxdist = maxdist.value
var minwork = minwork.value
var maxwork = maxwork.value
sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0)
sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0)
sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0)
var xm = 0
var ym1 = 0
var ym2 = 0
data2['x1'] = []
data2['y1'] = []
data2['y2'] = []
data2['spm'] = []
data2['time'] = []
data2['ftime'] = []
data2['pace'] = []
data2['hr'] = []
data2['fpace'] = []
data2['distance'] = []
data2['power'] = []
data2['x1mean'] = []
data2['y1mean'] = []
data2['y2mean'] = []
data2['driveenergy'] = []
data2['workoutid'] = []
data2['workoutstate'] = []
data2['xname'] = []
data2['yname1'] = []
data2['yname2'] = []
data2['ytrend'] = []
for (var i=0; i<x1.length; i++) {
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) {
data2['x1'].push(x1[i])
data2['y1'].push(y1[i])
data2['y2'].push(y2[i])
data2['spm'].push(spm1[i])
data2['time'].push(time1[i])
data2['ftime'].push(ftime1[i])
data2['fpace'].push(fpace1[i])
data2['driveenergy'].push(driveenergy1[i])
data2['pace'].push(pace1[i])
data2['hr'].push(hr1[i])
data2['distance'].push(distance1[i])
data2['power'].push(power1[i])
data2['workoutid'].push(0)
data2['workoutstate'].push(0)
data2['xname'].push(0)
data2['yname1'].push(0)
data2['yname2'].push(0)
data2['ytrend'].push(ytrend[i])
xm += x1[i]
ym1 += y1[i]
ym2 += y2[i]
}
}
}
}
xm /= data2['x1'].length
ym1 /= data2['x1'].length
ym2 /= data2['x1'].length
for (var i=0; i<data2['x1'].length; i++) {
data2['x1mean'].push(xm)
data2['y1mean'].push(ym1)
data2['y2mean'].push(ym2)
}
x1means.location = xm
y1means.location = ym1
y2means.location = ym2
y1label.text = yname1[0]+': '+ym1.toFixed(2)
y2label.text = yname2[0]+': '+ym2.toFixed(2)
xlabel.text = xname[0]+': '+xm.toFixed(2)
annolabel.text = annotation
source2.change.emit();
""")
annotation = TextInput(
width=140, title="Type your plot notes here", value="")
annotation.js_on_change('value', callback)
callback.args["annotation"] = annotation
slider_spm_min = Slider(width=140, start=15.0, end=55, value=15.0, step=.1,
title="Min SPM")
slider_spm_min.js_on_change('value', callback)
callback.args["minspm"] = slider_spm_min
slider_spm_max = Slider(width=140, start=15.0, end=55, value=55.0, step=.1,
title="Max SPM")
slider_spm_max.js_on_change('value', callback)
callback.args["maxspm"] = slider_spm_max
slider_work_min = Slider(width=140, start=0.0, end=1500, value=0.0, step=10,
title="Min Work per Stroke")
slider_work_min.js_on_change('value', callback)
callback.args["minwork"] = slider_work_min
slider_work_max = Slider(width=140, start=0.0, end=1500, value=1500.0, step=10,
title="Max Work per Stroke")
slider_work_max.js_on_change('value', callback)
callback.args["maxwork"] = slider_work_max
try:
distmax = 100+100*int(rowdata['distance'].max()/100.)
except (KeyError, ValueError): # pragma: no cover
distmax = 100
slider_dist_min = Slider(width=140, start=0, end=distmax, value=0, step=50,
title="Min Distance")
slider_dist_min.js_on_change('value', callback)
callback.args["mindist"] = slider_dist_min
slider_dist_max = Slider(width=140, start=0, end=distmax, value=distmax,
step=50,
title="Max Distance")
slider_dist_max.js_on_change('value', callback)
callback.args["maxdist"] = slider_dist_max
thesliders = layoutcolumn([
annotation,
slider_spm_min,
slider_spm_max,
slider_dist_min,
slider_dist_max,
slider_work_min,
slider_work_max,
])
mylayout = layoutrow([thesliders, plot])
# layout.sizing_mode = 'stretch_both'
#mylayout.sizing_mode = 'stretch_both'
script, div = components(mylayout)
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
return [script, div, js_resources, css_resources, workstrokesonly]
return script, div, workstrokesonly
def thumbnails_set(r, id, favorites):

View File

@@ -8,16 +8,6 @@
{% localtime on %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<p>
{% if workout|previousworkout:rower.user %}
@@ -35,9 +25,7 @@
<ul class="main-content">
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
{{ the_div|safe }}
</li>
<li class="grid_2">
<form enctype="multipart/form-data"
@@ -96,6 +84,10 @@
</li>
</ul>
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_script |safe }}
{% endblock %}
{% endlocaltime %}

View File

@@ -101,11 +101,6 @@
<i class="fas fa-dumbbell fa-fw"></i>&nbsp;Force Curve
</a>
</li>
<li id="chart-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/interactiveotwplot/">
<i class="fal fa-calculator-alt fa-fw"></i>&nbsp;Corrected Pace Plot
</a>
</li>
{% endif %}
</ul>
</li>
@@ -304,6 +299,11 @@
<i class="fas fa-stream fa-fw"></i>&nbsp;Stream
</a>
</li>
<li id="chart-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/interactiveotwplot/">
<i class="fal fa-calculator-alt fa-fw"></i>&nbsp;Corrected Pace Plot
</a>
</li>
<li id="advanced-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/otwsetpower/">
<i class="fas fa-calculator-alt fa-fw"></i>&nbsp;OTW Power

View File

@@ -388,24 +388,6 @@
</script>
<div id="id_css_res">
</div>
<div id="id_js_res">
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.1.1.min.js"
crossorigin="anonymous"></script>
</div>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
</div>
@@ -439,144 +421,144 @@
{{ chartform.as_table }}
</table>
</li>
<li class="grid_2 maxheight">
<p>{{ searchform }}</p>
{% if workouts %}
<input type="checkbox" onClick="toggle(this)" /> Toggle All<br/>
<table width="100%" class="listtable">
{{ form.as_table }}
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</li>
<li class="grid_2">
{% csrf_token %}
<input name='optionsform' type="submit" value="Submit">
</form>
<li class="grid_2 maxheight">
<p>{{ searchform }}</p>
{% if workouts %}
<input type="checkbox" onClick="toggle(this)" /> Toggle All<br/>
<table width="100%" class="listtable">
{{ form.as_table }}
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</li>
{% if worldclass %}
{% if age and sex != 'not specified' %}
<li class="grid_4">
<h2>Gold Medal Standards</h2>
<p>The dashed lines are based on the
<a href="https://log.concept2.com/rankings">Concept2</a>
rankings for your age ({{ age }}), gender ({{ sex }})
and weight category ({{ weightcategory }}). The Gold Medal
Standard power is derived from the
<a href="http://www.concept2.com/indoor-rowers/racing/records/world">
World Record</a> for your category.
The percentile lines are estimates of where the percentiles
of the Concept2 rankings historically are for those of exactly
your age, gender and weight class.
</p>
<p>
For rowing on the water, the results are corrected for the expected
difference in power between the Concept2 indoor rower and power
values on the water.
</p>
</li>
<li>
<table width="100%" class="listtable">
<tbody>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/100m/">100m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/500m/">500m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1000m/">1000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/2000m/">2000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/5000m/">5000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/6000m/">6000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/10000m/">10000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/21097m/">Half Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/42195m/">Full Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1min/">1 minute
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/4min/">4 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/30min/">30 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/60min/">1 hour
</a>
</td>
</tr>
</tbody>
</table>
</li>
{% endif %}
{% endif %}
<li class="grid_2">
{% csrf_token %}
<input name='optionsform' type="submit" value="Submit">
</form>
</li>
{% if worldclass %}
{% if age and sex != 'not specified' %}
<li class="grid_4">
<h2>Gold Medal Standards</h2>
<p>The dashed lines are based on the
<a href="https://log.concept2.com/rankings">Concept2</a>
rankings for your age ({{ age }}), gender ({{ sex }})
and weight category ({{ weightcategory }}). The Gold Medal
Standard power is derived from the
<a href="http://www.concept2.com/indoor-rowers/racing/records/world">
World Record</a> for your category.
The percentile lines are estimates of where the percentiles
of the Concept2 rankings historically are for those of exactly
your age, gender and weight class.
</p>
<p>
For rowing on the water, the results are corrected for the expected
difference in power between the Concept2 indoor rower and power
values on the water.
</p>
</li>
<li>
<table width="100%" class="listtable">
<tbody>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/100m/">100m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/500m/">500m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1000m/">1000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/2000m/">2000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/5000m/">5000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/6000m/">6000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/10000m/">10000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/21097m/">Half Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/42195m/">Full Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1min/">1 minute
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/4min/">4 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/30min/">30 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/60min/">1 hour
</a>
</td>
</tr>
</tbody>
</table>
</li>
{% endif %}
{% endif %}
</ul>

View File

@@ -4367,7 +4367,7 @@ def workout_flexchart3_view(request, *args, **kwargs):
# create interactive plot
(
script, div, js_resources, css_resources, workstrokesonly
script, div, workstrokesonly
) = interactive_flex_chart2(
encoder.decode_hex(id), request.user.rower,
xparam=xparam, yparam1=yparam1,
@@ -4453,8 +4453,6 @@ def workout_flexchart3_view(request, *args, **kwargs):
'workout': row,
'chartform': flexaxesform,
'optionsform': flexoptionsform,
'js_res': js_resources,
'css_res': css_resources,
'teams': get_my_teams(request.user),
'id': id,
'xparam': xparam,