Add line of best fit (adding dependency on numpy)

This commit is contained in:
Peter Stockings
2023-12-08 23:51:10 +11:00
parent ded5154acf
commit dd093e3819
3 changed files with 29 additions and 6 deletions

15
db.py
View File

@@ -1,5 +1,6 @@
import os import os
import psycopg2 import psycopg2
import numpy as np
from psycopg2.extras import RealDictCursor from psycopg2.extras import RealDictCursor
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse from urllib.parse import urlparse
@@ -505,6 +506,17 @@ class DataBase():
total_span = date_range.days or 1 total_span = date_range.days or 1
relative_positions = [(date - min_date).days / total_span for date in start_dates] relative_positions = [(date - min_date).days / total_span for date in start_dates]
# Convert relative positions and scaled estimated 1RM values to numpy arrays
x = np.array(relative_positions)
y = np.array(estimated_1rm_scaled)
# Calculate the slope (m) and y-intercept (b) of the line of best fit
m, b = np.polyfit(x, y, 1)
# Generate points along the line of best fit
y_best_fit = [m * xi + b for xi in x]
best_fit_points = zip(y_best_fit, relative_positions)
# Create messages and zip data for SVG plotting # Create messages and zip data for SVG plotting
messages = [f'{t["repetitions"]} x {t["weight"]}kg ({t["estimated_1rm"]}kg E1RM) on {t["start_date"].strftime("%d %b %y")}' for t in topsets] messages = [f'{t["repetitions"]} x {t["weight"]}kg ({t["estimated_1rm"]}kg E1RM) on {t["start_date"].strftime("%d %b %y")}' for t in topsets]
data_points = zip(estimated_1rm_scaled, relative_positions, messages) data_points = zip(estimated_1rm_scaled, relative_positions, messages)
@@ -514,5 +526,6 @@ class DataBase():
'exercise_name': topsets[0]['exercise_name'], 'exercise_name': topsets[0]['exercise_name'],
'vb_width': vb_width, 'vb_width': vb_width,
'vb_height': vb_height, 'vb_height': vb_height,
'data_points': list(data_points) 'data_points': list(data_points),
'best_fit_points': list(best_fit_points),
} }

View File

@@ -8,3 +8,4 @@ python-dateutil==2.8.2
minify-html==0.10.3 minify-html==0.10.3
jinja2-fragments==0.3.0 jinja2-fragments==0.3.0
Werkzeug==2.2.2 Werkzeug==2.2.2
numpy==1.19.5

View File

@@ -1,5 +1,5 @@
{% set fill = "#dcfce7" %} {% set fill = "#2ca02c" %}
{% set stroke = "#bbf7d0" %} {% set stroke = "#2ca02c" %}
{% set stroke_width = 4 %} {% set stroke_width = 4 %}
{% set margin = 2 %} {% set margin = 2 %}
@@ -11,6 +11,14 @@
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
{% macro path_best_fit(best_fit_points, vb_height) %}
{% for value, position in best_fit_points %}
{% set x = (position * vb_width)+margin %}
{% set y = (vb_height - value)+margin %}
{% if loop.first %}M{{ x }} {{ y }}{% else %} L{{ x }} {{ y }}{% endif %}
{% endfor %}
{% endmacro %}
{% macro circles(data_points, vb_height) %} {% macro circles(data_points, vb_height) %}
{% for value, position, message in data_points %} {% for value, position, message in data_points %}
{% set x = (position * vb_width)+margin %} {% set x = (position * vb_width)+margin %}
@@ -22,7 +30,7 @@
on mouseout on mouseout
add .hidden to #popover-{{ unique_id }}"> add .hidden to #popover-{{ unique_id }}">
</circle> </circle>
<circle cx="{{ x }}" cy="{{ y }}" r="1" stroke="blue" fill="blue"></circle> <circle cx="{{ x }}" cy="{{ y }}" r="1" stroke="{{ stroke }}" fill="{{ fill }}"></circle>
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
@@ -38,7 +46,8 @@
<!-- Popover content will be dynamically inserted here --> <!-- Popover content will be dynamically inserted here -->
</div> </div>
<svg viewBox="0 0 {{ vb_width + 4 }} {{ vb_height + 4 }}" preserveAspectRatio="none"> <svg viewBox="0 0 {{ vb_width + 4 }} {{ vb_height + 4 }}" preserveAspectRatio="none">
<path d="{{ path(data_points, vb_height) }}" stroke="blue" fill="none" /> <path d="{{ path_best_fit(best_fit_points, vb_height) }}" stroke="gray" stroke-dasharray="2,1" fill="none" stroke-opacity="40%"/>
<path d="{{ path(data_points, vb_height) }}" stroke="{{ stroke }}" fill="none" />
{{ circles(data_points, vb_height) }} {{ circles(data_points, vb_height) }}
</svg> </svg>
</div> </div>