diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-25 08:46:58 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-25 08:46:58 +0200 |
| commit | 077aa1700fbcaf3f499c2178e0e58e89ae9dcaf8 (patch) | |
| tree | 020d0d8b34ab7b47cf551b50034c1e086a247af3 /web/frontend/climb-attempts-logic.ts | |
| parent | 90614736f2a0c556f31eca5361b3c032f5f309f6 (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.ts | 30 |
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 }; +}; |
