Graph speed, distance, calories, & power (crashes locally, possible revert, will attempt to combine graphs)

This commit is contained in:
Peter Stockings
2023-03-16 22:06:58 +11:00
parent c3bf6c7ee3
commit 3c5661e7b2
2 changed files with 69 additions and 43 deletions

98
app.py
View File

@@ -127,53 +127,46 @@ def workouts(user_id):
return jsonify({'message': 'Workout created successfully.'}), 201 return jsonify({'message': 'Workout created successfully.'}), 201
@app.route('/user/<int:user_id>/workout/<int:workout_id>', methods=['GET', 'DELETE']) @app.route('/user/<int:user_id>/workout/<int:workout_id>/<string:graph_type>', methods=['GET', 'DELETE'])
def workout(user_id, workout_id): def workout(user_id, workout_id, graph_type):
workout = Workout.query.filter_by(user_id=user_id, id=workout_id).first() workout = Workout.query.filter_by(user_id=user_id, id=workout_id).first()
if workout: if workout:
if request.method == 'GET': # Get the cadence readings for the workout
# Get the cadence readings for the workout cadence_readings = CadenceReading.query.filter_by(
cadence_readings = CadenceReading.query.filter_by( workout_id=workout_id).all()
workout_id=workout_id).all() if cadence_readings:
if cadence_readings: x_values = [reading.created_at for reading in cadence_readings]
# Create a graph of cadence readings if graph_type == 'cadence':
x_values = [reading.created_at for reading in cadence_readings]
y_values = [reading.rpm for reading in cadence_readings] y_values = [reading.rpm for reading in cadence_readings]
fig, ax = plt.subplots() return create_graph(x_values=x_values, y_values=y_values, y_label='Cadence (RPM)', filename='cadence'), 200
ax.plot(x_values, y_values) elif graph_type == 'speed':
ax.set_xlabel('Time') y_values = [reading.speed for reading in cadence_readings]
ax.set_ylabel('Cadence (RPM)') return create_graph(x_values=x_values, y_values=y_values, y_label='Speed (KPH)', filename='speed'), 200
elif graph_type == 'distance':
# ax.set_title( y_values = [reading.distance for reading in cadence_readings]
# 'Cadence Readings for Workout {}'.format(workout_id)) return create_graph(x_values=x_values, y_values=y_values, y_label='Distance (KM)', filename='distance'), 200
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M")) elif graph_type == 'calories':
y_values = [reading.calories for reading in cadence_readings]
# set the y-axis limits to start at 0 return create_graph(x_values=x_values, y_values=y_values, y_label='Calories (KCAL)', filename='calories'), 200
ax.set_ylim(bottom=0) elif graph_type == 'power':
y_values = [reading.power for reading in cadence_readings]
# Save the graph to a bytes buffer return create_graph(x_values=x_values, y_values=y_values, y_label='Power (WATTS)', filename='power'), 200
buffer = io.BytesIO() else:
plt.savefig(buffer, format='png', return jsonify({'message': 'No cadence readings for workout {}.'.format(workout_id)}), 404
transparent=True, bbox_inches='tight')
buffer.seek(0)
# Create a response object with the graph image
response = make_response(buffer.getvalue())
response.headers['Content-Type'] = 'image/png'
response.headers['Content-Disposition'] = 'attachment; filename=cadence.png'
return response, 200
else:
return jsonify({'message': 'No cadence readings for workout {}.'.format(workout_id)}), 404
elif request.method == 'DELETE':
# Delete the workout and its associated cadence readings
CadenceReading.query.filter_by(workout_id=workout_id).delete()
db.session.delete(workout)
db.session.commit()
workouts_data = get_workouts_for_user(user_id)
return render_template('workouts_list.html', workouts=workouts_data)
else: else:
return jsonify({'message': 'Workout {} not found for user {}.'.format(workout_id, user_id)}), 404 return jsonify({'message': 'Workout {} not found for user {}.'.format(workout_id, user_id)}), 404
@app.route('/user/<int:user_id>/workout/<int:workout_id>/delete', methods=['DELETE'])
def delete_workout(user_io, workout_id):
# Delete the workout and its associated cadence readings
CadenceReading.query.filter_by(workout_id=workout_id).delete()
db.session.delete(workout)
db.session.commit()
workouts_data = get_workouts_for_user(user_id)
return render_template('workouts_list.html', workouts=workouts_data)
@app.route('/user/<int:user_id>/workouts', methods=['GET', 'DELETE']) @app.route('/user/<int:user_id>/workouts', methods=['GET', 'DELETE'])
def workouts_for_user(user_id): def workouts_for_user(user_id):
workouts_data = get_workouts_for_user(user_id) workouts_data = get_workouts_for_user(user_id)
@@ -238,6 +231,31 @@ def get_workouts_for_user(user_id):
return workouts_data return workouts_data
def create_graph(x_values, y_values, y_label, filename, x_label='Time'):
fig, ax = plt.subplots()
ax.plot(x_values, y_values)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
# ax.set_title(
# 'Cadence Readings for Workout {}'.format(workout_id))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
# set the y-axis limits to start at 0
ax.set_ylim(bottom=0)
# Save the graph to a bytes buffer
buffer = io.BytesIO()
plt.savefig(buffer, format='png',
transparent=True, bbox_inches='tight')
buffer.seek(0)
# Create a response object with the graph image
response = make_response(buffer.getvalue())
response.headers['Content-Type'] = 'image/png'
response.headers['Content-Disposition'] = 'attachment; filename={filename}.png'
return response
def format_date_with_ordinal(d, format_string): def format_date_with_ordinal(d, format_string):
ordinal = {'1': 'st', '2': 'nd', '3': 'rd'}.get(str(d.day)[-1:], 'th') ordinal = {'1': 'st', '2': 'nd', '3': 'rd'}.get(str(d.day)[-1:], 'th')
return d.strftime(format_string).replace('{th}', ordinal) return d.strftime(format_string).replace('{th}', ordinal)

View File

@@ -19,11 +19,19 @@
</button> </button>
</h2> </h2>
<div class="!visible collapse {% if loop.index != 1 %}hidden{% endif %}"> <div class="!visible collapse {% if loop.index != 1 %}hidden{% endif %}">
<img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id) }}" loading="lazy" alt="No image" <img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id, graph_type='cadence') }}" loading="lazy"
class="mx-auto"> alt="No image" class="mx-auto">
<img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id, graph_type='speed') }}" loading="lazy"
alt="No image" class="mx-auto">
<img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id, graph_type='distance') }}" loading="lazy"
alt="No image" class="mx-auto">
<img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id, graph_type='calories') }}" loading="lazy"
alt="No image" class="mx-auto">
<img src="{{ url_for('workout', user_id=w.user_id, workout_id=w.id, graph_type='power') }}" loading="lazy"
alt="No image" class="mx-auto">
<button <button
class="mx-4 mb-4 bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center" class="mx-4 mb-4 bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center"
hx-delete="{{ url_for('workout', user_id=w.user_id, workout_id=w.id) }}" hx-delete="{{ url_for('delete_workout', user_id=w.user_id, workout_id=w.id) }}"
hx-confirm="Are you sure you wish to delete this {{ w.duration }} workout at {{ w.start_time }}" hx-confirm="Are you sure you wish to delete this {{ w.duration }} workout at {{ w.start_time }}"
hx-target="#container"> hx-target="#container">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"