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 = ( analysischoices = (
('boxplot', 'Box Chart'), ('boxplot', 'Box Chart'),
('trendflex', 'Trend Flex'), # ('trendflex', 'Trend Flex'),
('histo', 'Histogram'), ('histo', 'Histogram'),
('flexall', 'Cumulative Flex Chart'), ('flexall', 'Cumulative Flex Chart'),
('stats', 'Statistics'), ('stats', 'Statistics'),

View File

@@ -4454,22 +4454,19 @@ def interactive_flex_chart2(id, r, promember=0,
trendline=False, trendline=False,
mode='rower'): 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, columns = [name for name, d in metrics.rowingmetrics]
'ftime', 'distance', 'fpace', columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic']
'power', 'hr', 'spm', 'driveenergy', columns = columns + ['spm', 'driveenergy', 'distance']
'time', 'pace', 'workoutstate'] columns_basic = columns_basic + ['spm', 'driveenergy', 'distance']
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, datadf = pd.DataFrame()
workstrokesonly=workstrokesonly) 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 if r.usersmooth > 1: # pragma: no cover
for column in columns: for column in columns:
@@ -4484,10 +4481,14 @@ def interactive_flex_chart2(id, r, promember=0,
try: try:
if len(rowdata) < 2: if len(rowdata) < 2:
rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], if promember:
doclean=False, rowdata = dataprep.getsmallrowdata_db(columns, ids=[id],
workstrokesonly=False) doclean=False,
workstrokesonly = 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 except (KeyError, TypeError): # pragma: no cover
workstrokesonly = False workstrokesonly = False
try: try:
@@ -4517,7 +4518,7 @@ def interactive_flex_chart2(id, r, promember=0,
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if rowdata.empty: if rowdata.empty:
return "", "No valid data", '', '', workstrokesonly return "", "No valid data", workstrokesonly
workoutstatesrest = [3] workoutstatesrest = [3]
@@ -4530,7 +4531,7 @@ def interactive_flex_chart2(id, r, promember=0,
try: try:
tseconds = rowdata.loc[:, 'time'] tseconds = rowdata.loc[:, 'time']
except KeyError: # pragma: no cover except KeyError: # pragma: no cover
return '', 'No time data - cannot make flex plot', '', '', workstrokesonly return '', 'No time data - cannot make flex plot', workstrokesonly
try: try:
rowdata['x1'] = rowdata.loc[:, xparam] rowdata['x1'] = rowdata.loc[:, xparam]
@@ -4578,27 +4579,6 @@ def interactive_flex_chart2(id, r, promember=0,
y1mean = rowdata['y1'].mean() y1mean = rowdata['y1'].mean()
y2mean = rowdata['y2'].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: try:
rowdata['xname'] = axlabels[xparam] rowdata['xname'] = axlabels[xparam]
@@ -4627,420 +4607,25 @@ def interactive_flex_chart2(id, r, promember=0,
rowdata['ytrend'] = ytrend rowdata['ytrend'] = ytrend
except TypeError: # pragma: no cover except TypeError: # pragma: no cover
rowdata['ytrend'] = y1 rowdata['ytrend'] = y1
# prepare data
source = ColumnDataSource(
rowdata
)
# second source for filtering data_dict = rowdata.to_dict("records")
source2 = ColumnDataSource(
rowdata.copy()
)
# Add hover to this comma-separated string and see what changes metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics]
if (promember == 1):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' chart_data = {
else: 'title': row.name,
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' '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, script, div = get_chart("/flex", chart_data)
tools=TOOLS, toolbar_location='above',
toolbar_sticky=False, width=800, height=600,
)
#plot.sizing_mode = 'stretch_both'
# add watermark return script, div, workstrokesonly
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]
def thumbnails_set(r, id, favorites): def thumbnails_set(r, id, favorites):

View File

@@ -8,16 +8,6 @@
{% localtime on %} {% localtime on %}
{% block main %} {% 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> <p>
{% if workout|previousworkout:rower.user %} {% if workout|previousworkout:rower.user %}
@@ -35,9 +25,7 @@
<ul class="main-content"> <ul class="main-content">
<li class="grid_4"> <li class="grid_4">
<div id="theplot" class="flexplot"> {{ the_div|safe }}
{{ the_div|safe }}
</div>
</li> </li>
<li class="grid_2"> <li class="grid_2">
<form enctype="multipart/form-data" <form enctype="multipart/form-data"
@@ -96,6 +84,10 @@
</li> </li>
</ul> </ul>
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_script |safe }}
{% endblock %} {% endblock %}
{% endlocaltime %} {% endlocaltime %}

View File

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

View File

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

View File

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