aboutsummaryrefslogtreecommitdiff
path: root/web/frontend/climb-attempts-logic.ts
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-09-25 08:46:58 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-09-25 08:46:58 +0200
commit077aa1700fbcaf3f499c2178e0e58e89ae9dcaf8 (patch)
tree020d0d8b34ab7b47cf551b50034c1e086a247af3 /web/frontend/climb-attempts-logic.ts
parent90614736f2a0c556f31eca5361b3c032f5f309f6 (diff)
Chart power vs time for official climb attempts
Snapshot average power per attempt in a nullable average_power_w column (backfilled from stored rides), plot each powered attempt on the climb page as duration vs average watts with ride hover and click-through, and show avg power in the attempts table.
Diffstat (limited to 'web/frontend/climb-attempts-logic.ts')
-rw-r--r--web/frontend/climb-attempts-logic.ts30
1 files changed, 30 insertions, 0 deletions
diff --git a/web/frontend/climb-attempts-logic.ts b/web/frontend/climb-attempts-logic.ts
new file mode 100644
index 0000000..868545b
--- /dev/null
+++ b/web/frontend/climb-attempts-logic.ts
@@ -0,0 +1,30 @@
+import type { ClimbAttemptPoint } from "./types.js";
+
+// formatAttemptDuration renders an attempt duration as "mm:ss", or "h:mm:ss" past an hour.
+export const formatAttemptDuration = (durationS: number): string => {
+ const total = Math.max(0, Math.round(durationS));
+ const seconds = total % 60;
+ const minutes = Math.floor(total / 60) % 60;
+ const hours = Math.floor(total / 3600);
+ const two = (value: number): string => String(value).padStart(2, "0");
+ return hours > 0 ? `${hours}:${two(minutes)}:${two(seconds)}` : `${minutes}:${two(seconds)}`;
+};
+
+// formatPower renders an average power in whole watts.
+export const formatPower = (powerW: number): string => `${Math.round(powerW)} W`;
+
+export interface AttemptsDomain {
+ maxDurationS: number;
+ maxPowerW: number;
+}
+
+// attemptsDomain returns zero-based axis bounds padded above the data; empty or degenerate data still yields a drawable unit domain.
+export const attemptsDomain = (points: ClimbAttemptPoint[]): AttemptsDomain => {
+ let maxDurationS = 0;
+ let maxPowerW = 0;
+ for (const point of points) {
+ maxDurationS = Math.max(maxDurationS, point.durationS);
+ maxPowerW = Math.max(maxPowerW, point.averagePowerW);
+ }
+ return { maxDurationS: maxDurationS * 1.08 || 1, maxPowerW: maxPowerW * 1.08 || 1 };
+};