Change matplotlib backend to speed up plottng time (~0.25 to ~0.03 seconds)

This commit is contained in:
Peter Stockings
2023-10-19 22:19:24 +11:00
parent fa1e76df47
commit dafc23af49

97
app.py
View File

@@ -1,3 +1,11 @@
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from dateutil.relativedelta import relativedelta
import humanize
from dateutil.parser import isoparse
import sparklines
import os
import time
from sqlalchemy import func from sqlalchemy import func
from datetime import datetime, timedelta from datetime import datetime, timedelta
import io import io
@@ -5,13 +13,8 @@ from flask_sqlalchemy import SQLAlchemy
from flask import Flask, make_response, render_template, request, jsonify from flask import Flask, make_response, render_template, request, jsonify
import jinja_partials import jinja_partials
from flask_htmx import HTMX from flask_htmx import HTMX
import matplotlib.pyplot as plt import matplotlib
import matplotlib.dates as mdates matplotlib.use('Agg')
import os
import sparklines
from dateutil.parser import isoparse
import humanize
from dateutil.relativedelta import relativedelta
app = Flask(__name__) app = Flask(__name__)
@@ -358,63 +361,66 @@ def get_workouts_for_user(user, workouts):
return [get_workout_view_data(workout, user) for workout in workouts if get_workout_view_data(workout, user)] return [get_workout_view_data(workout, user) for workout in workouts if get_workout_view_data(workout, user)]
def get_workout_view_data(workout, user): def format_workout_data(workout, user):
duration = timedelta( """
seconds=int(workout.duration)) if workout.duration else timedelta(seconds=0) Formats the workout data for view.
average_rpm = workout.average_rpm if workout.average_rpm else 0
min_rpm = workout.min_rpm if workout.min_rpm else 0
max_rpm = workout.max_rpm if workout.max_rpm else 0
calories = workout.calories if workout.calories else 0
distance = workout.distance if workout.distance else 0
average_bpm = workout.average_bpm if workout.average_bpm else 0
min_bpm = workout.min_bpm if workout.min_bpm else 0
max_bpm = workout.max_bpm if workout.max_bpm else 0
is_heart_rate_available = workout.is_heart_rate_available
is_cadence_available = workout.is_cadence_available
start_time = workout.started_at
selected_graph_types = ['speed'] Parameters:
if is_heart_rate_available: - workout: The workout object.
selected_graph_types.append('heart_rate') - user: The user associated with the workout.
Returns:
- dict: A dictionary with formatted workout data.
"""
duration = timedelta(seconds=int(workout.duration or 0))
if is_cadence_available or is_heart_rate_available:
return { return {
'id': workout.id, 'id': workout.id,
'user_id': user.id, 'user_id': user.id,
'user_name': user.name, 'user_name': user.name,
'start_time': format_date_with_ordinal(start_time, '%#H:%M %B %dth %Y'), 'start_time': format_date_with_ordinal(workout.started_at, '%#H:%M %B %dth %Y'),
'start_time_date': start_time, 'start_time_date': workout.started_at,
'start_time_ago': humanize.naturaltime(start_time), 'start_time_ago': humanize.naturaltime(workout.started_at),
'duration': humanize.naturaldelta(duration), 'duration': humanize.naturaldelta(duration),
'duration_minutes': duration.total_seconds() / 60, 'duration_minutes': duration.total_seconds() / 60,
'average_rpm': int(average_rpm), 'average_rpm': int(workout.average_rpm or 0),
'min_rpm': int(min_rpm), 'min_rpm': int(workout.min_rpm or 0),
'max_rpm': int(max_rpm), 'max_rpm': int(workout.max_rpm or 0),
'calories': int(calories), 'calories': int(workout.calories or 0),
'distance': int(distance), 'distance': int(workout.distance or 0),
'bike_display_name': workout.bike.display_name, 'bike_display_name': workout.bike.display_name,
'selected_graph_types': selected_graph_types, 'selected_graph_types': ['speed'] + (['heart_rate'] if workout.is_heart_rate_available else []),
'is_heart_rate_available': is_heart_rate_available, 'is_heart_rate_available': workout.is_heart_rate_available,
'is_cadence_available': is_cadence_available, 'is_cadence_available': workout.is_cadence_available,
'average_bpm': int(average_bpm), 'average_bpm': int(workout.average_bpm or 0),
'min_bpm': int(min_bpm), 'min_bpm': int(workout.min_bpm or 0),
'max_bpm': int(max_bpm), 'max_bpm': int(workout.max_bpm or 0),
} }
else:
def get_workout_view_data(workout, user):
"""
Retrieve view data for a single workout.
Parameters:
- workout: The workout object.
- user: The user associated with the workout.
Returns:
- dict or None: A dictionary containing view data if cadence or heart rate is available, otherwise None.
"""
if workout.is_cadence_available or workout.is_heart_rate_available:
return format_workout_data(workout, user)
return None return None
def create_graph(x_values, y_values, y_label, filename, x_label='Time'): def create_graph(x_values, y_values, y_label, filename, x_label='Time'):
# Plotting
fig, ax = plt.subplots() fig, ax = plt.subplots()
ax.plot(x_values, y_values) ax.plot(x_values, y_values)
ax.set_xlabel(x_label) ax.set_xlabel(x_label)
ax.set_ylabel(y_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")) ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
# set the y-axis limits to start at 0
ax.set_ylim(bottom=0) ax.set_ylim(bottom=0)
# Save the graph to a bytes buffer # Save the graph to a bytes buffer
@@ -422,6 +428,7 @@ def create_graph(x_values, y_values, y_label, filename, x_label='Time'):
plt.savefig(buffer, format='png', plt.savefig(buffer, format='png',
transparent=True, bbox_inches='tight') transparent=True, bbox_inches='tight')
buffer.seek(0) buffer.seek(0)
# Create a response object with the graph image # Create a response object with the graph image
response = make_response(buffer.getvalue()) response = make_response(buffer.getvalue())
response.headers['Content-Type'] = 'image/png' response.headers['Content-Type'] = 'image/png'