From dafc23af49fafc99f6661d76acf81af67a8d07f6 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Thu, 19 Oct 2023 22:19:24 +1100 Subject: [PATCH] Change matplotlib backend to speed up plottng time (~0.25 to ~0.03 seconds) --- app.py | 113 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/app.py b/app.py index 766c33b..07b7c8d 100644 --- a/app.py +++ b/app.py @@ -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 datetime import datetime, timedelta import io @@ -5,13 +13,8 @@ from flask_sqlalchemy import SQLAlchemy from flask import Flask, make_response, render_template, request, jsonify import jinja_partials from flask_htmx import HTMX -import matplotlib.pyplot as plt -import matplotlib.dates as mdates -import os -import sparklines -from dateutil.parser import isoparse -import humanize -from dateutil.relativedelta import relativedelta +import matplotlib +matplotlib.use('Agg') 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)] +def format_workout_data(workout, user): + """ + Formats the workout data for view. + + Parameters: + - workout: The workout object. + - user: The user associated with the workout. + + Returns: + - dict: A dictionary with formatted workout data. + """ + duration = timedelta(seconds=int(workout.duration or 0)) + + return { + 'id': workout.id, + 'user_id': user.id, + 'user_name': user.name, + 'start_time': format_date_with_ordinal(workout.started_at, '%#H:%M %B %dth %Y'), + 'start_time_date': workout.started_at, + 'start_time_ago': humanize.naturaltime(workout.started_at), + 'duration': humanize.naturaldelta(duration), + 'duration_minutes': duration.total_seconds() / 60, + 'average_rpm': int(workout.average_rpm or 0), + 'min_rpm': int(workout.min_rpm or 0), + 'max_rpm': int(workout.max_rpm or 0), + 'calories': int(workout.calories or 0), + 'distance': int(workout.distance or 0), + 'bike_display_name': workout.bike.display_name, + 'selected_graph_types': ['speed'] + (['heart_rate'] if workout.is_heart_rate_available else []), + 'is_heart_rate_available': workout.is_heart_rate_available, + 'is_cadence_available': workout.is_cadence_available, + 'average_bpm': int(workout.average_bpm or 0), + 'min_bpm': int(workout.min_bpm or 0), + 'max_bpm': int(workout.max_bpm or 0), + } + + def get_workout_view_data(workout, user): - duration = timedelta( - seconds=int(workout.duration)) if workout.duration else timedelta(seconds=0) - 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 + """ + Retrieve view data for a single workout. - selected_graph_types = ['speed'] - if is_heart_rate_available: - selected_graph_types.append('heart_rate') + Parameters: + - workout: The workout object. + - user: The user associated with the workout. - if is_cadence_available or is_heart_rate_available: - return { - 'id': workout.id, - 'user_id': user.id, - 'user_name': user.name, - 'start_time': format_date_with_ordinal(start_time, '%#H:%M %B %dth %Y'), - 'start_time_date': start_time, - 'start_time_ago': humanize.naturaltime(start_time), - 'duration': humanize.naturaldelta(duration), - 'duration_minutes': duration.total_seconds() / 60, - 'average_rpm': int(average_rpm), - 'min_rpm': int(min_rpm), - 'max_rpm': int(max_rpm), - 'calories': int(calories), - 'distance': int(distance), - 'bike_display_name': workout.bike.display_name, - 'selected_graph_types': selected_graph_types, - 'is_heart_rate_available': is_heart_rate_available, - 'is_cadence_available': is_cadence_available, - 'average_bpm': int(average_bpm), - 'min_bpm': int(min_bpm), - 'max_bpm': int(max_bpm), - } - else: - return None + 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 def create_graph(x_values, y_values, y_label, filename, x_label='Time'): + # Plotting 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 @@ -422,6 +428,7 @@ def create_graph(x_values, y_values, y_label, filename, x_label='Time'): 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'