Script: FIT File Visualizer
You'll need uv to run this script. Copy the contents, save as a file
(fit-plot.py), make it executable (chmod +x fit-plot.py), and run it
(./fit-plot.py)
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "fitparse==1.2.0",
#   "matplotlib==3.8.2",
#   "numpy==1.26.4"
# ]
# ///
import fitparse
import os
import sys
import matplotlib.pyplot as plt
import numpy as np
def plot_hr_vs_power_over_time(filepath):
    """
    Reads a .fit file and creates a plot of heart rate vs. power
    with color indicating time.
    Args:
        filepath (str): The path to the .fit file.
    """
    if not os.path.exists(filepath):
        print(f"Error: File not found at '{filepath}'")
        return
    try:
        fitfile = fitparse.FitFile(filepath)
        # Data extraction
        timestamps, heart_rates, powers = [], [], []
        for record in fitfile.get_messages("record"):
            timestamps.append(record.get_value("timestamp"))
            heart_rates.append(record.get_value("heart_rate"))
            powers.append(record.get_value("power"))
        # Filter out None values
        valid_records = [
            (t, hr, p)
            for t, hr, p in zip(timestamps, heart_rates, powers)
            if all(x is not None for x in [t, hr, p])
        ]
        if not valid_records:
            print("No complete records found to generate the plot.")
            return
        timestamps, heart_rates, powers = zip(*valid_records)
        start_time = timestamps[0]
        # time in minutes
        time_deltas = [
            (t - start_time).total_seconds() / 60 for t in timestamps
        ]
        plt.figure(figsize=(10, 6))
        scatter = plt.scatter(
            powers, heart_rates, c=time_deltas, cmap="viridis", alpha=0.7
        )
        plt.title("Heart Rate vs. Power Over Time")
        plt.xlabel("Power (watts)")
        plt.ylabel("Heart Rate (bpm)")
        plt.grid(True)
        # Add colorbar
        cbar = plt.colorbar(scatter)
        cbar.set_label("Time (minutes)")
        plt.savefig("hr_vs_power_over_time.png")
        plt.close()
        print("Plot saved as hr_vs_power_over_time.png")
    except fitparse.FitParseError as e:
        print(f"Error parsing FIT file: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
    if len(sys.argv) > 1:
        fit_file_path = sys.argv[1]
        plot_hr_vs_power_over_time(fit_file_path)
    else:
        print(
            "Please provide the path to the .fit file as an argument."
        )
* * *