Skip to content

Commit aa8e4fc

Browse files
committed
implements proper yd/m handling
1 parent 29c748b commit aa8e4fc

3 files changed

Lines changed: 137 additions & 59 deletions

File tree

swim_data_analyser/static/js/analyseView.js

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Plotly from 'plotly.js-basic-dist-min'
22
import { getItem, saveItem } from './storage.js';
3+
import * as Units from './units.js';
34

45
// Helper function for formatting seconds
56
function formatTime(secs, prec = 2) {
@@ -67,6 +68,8 @@ export async function renderSummary() {
6768
strokeData.avg_spm = (strokeData.totalLengths > 0) ? (strokeData.avg_spm / strokeData.totalLengths) : 0;
6869
}
6970

71+
const poolUnit = Units.getUnitLabel(sessionData);
72+
7073
// Initialize table with headers
7174
let tableHTML = `
7275
<table>
@@ -86,7 +89,7 @@ export async function renderSummary() {
8689

8790
// Variables to accumulate subtotal values
8891
let totalLengths = 0;
89-
let totalDistance = 0;
92+
let totalDistanceMeters = 0;
9093
let totalTime = 0;
9194
let totalSPM = 0;
9295
let totalSPL = 0;
@@ -97,13 +100,15 @@ export async function renderSummary() {
97100
if (grouped_data.hasOwnProperty(stroke)) {
98101

99102
const strokeData = grouped_data[stroke];
100-
const poolLength = sessionData.poolLength;
101-
const distance = strokeData.totalLengths * poolLength ;
102-
const pace = (strokeData.totalTime / distance) * 100;
103+
const poolLengthInternal = sessionData.poolLength;
104+
const distanceInternal = strokeData.totalLengths * poolLengthInternal;
105+
106+
const displayDistance = Units.toDisplayDistance(distanceInternal, sessionData);
107+
const paceSeconds = Units.getPaceSeconds(distanceInternal / strokeData.totalTime, sessionData);
103108

104109
// Accumulate totals
105110
totalLengths += strokeData.totalLengths;
106-
totalDistance += distance;
111+
totalDistanceMeters += distanceInternal;
107112
totalTime += strokeData.totalTime;
108113
totalSPM += strokeData.avg_spm;
109114
totalSPL += strokeData.avg_spl;
@@ -114,9 +119,9 @@ export async function renderSummary() {
114119
<tr class="${stroke}">
115120
<td>${stroke.charAt(0).toUpperCase() + stroke.slice(1)}</td>
116121
<td>${strokeData.totalLengths}</td>
117-
<td>${distance}m</td>
122+
<td>${Math.round(displayDistance)}${poolUnit}</td>
118123
<td>${formatTime(strokeData.totalTime, 0)}</td>
119-
<td>${formatTime(pace, 0)}</td>
124+
<td>${formatTime(paceSeconds, 0)}</td>
120125
<td>${strokeData.avg_spm.toFixed(2)}</td>
121126
<td>${strokeData.avg_spl.toFixed(2)}</td>
122127
</tr>
@@ -129,7 +134,8 @@ export async function renderSummary() {
129134
.filter(d => d.lengthType === 'idle') // Filter the data for 'idle' lengths
130135
.reduce((acc, curr) => acc + curr.totalElapsedTime , 0); // Sum the values
131136

132-
const totalPace = (totalTime/totalDistance) * 100;
137+
const displayTotalDistance = Units.toDisplayDistance(totalDistanceMeters, sessionData);
138+
const totalPaceSeconds = Units.getPaceSeconds(totalTime > 0 ? totalDistanceMeters / totalTime : 0, sessionData);
133139

134140
// Add subtotal row
135141
tableHTML += `
@@ -158,9 +164,9 @@ export async function renderSummary() {
158164
<tr class="interval">
159165
<td>Total</td>
160166
<td>${totalLengths}</td>
161-
<td>${totalDistance}m</td>
167+
<td>${Math.round(displayTotalDistance)}${poolUnit}</td>
162168
<td>${formatTime(totalTime + totalRest, 0)}</td>
163-
<td>${formatTime(totalPace, 0)}</td>
169+
<td>${formatTime(totalPaceSeconds, 0)}</td>
164170
<td>${(strokeCount > 0) ? (totalSPM / strokeCount).toFixed(2) : '0.00'}</td>
165171
<td>${(strokeCount > 0) ? (totalSPL / strokeCount).toFixed(2) : '0.00'}</td>
166172
</tr>
@@ -313,7 +319,10 @@ export async function renderBestTimes() {
313319
...l
314320
}));
315321

316-
const poolLen = data.sessionMesgs[0].poolLength;
322+
const sessionData = data.sessionMesgs[0];
323+
const poolLenInternal = sessionData.poolLength;
324+
const poolUnit = Units.getUnitLabel(sessionData);
325+
317326
const allowedDistances = new Set([50, 100, 200, 400, 800, 1500, 3000, 5000, 10000]);
318327

319328
const best = {}; // best[stroke][distance] = { time, count, lengths[] }
@@ -335,17 +344,19 @@ export async function renderBestTimes() {
335344
sum += curLengths[end].totalElapsedTime;
336345

337346
const count = end - start + 1;
338-
const distance = count * poolLen;
347+
const distanceInternal = count * poolLenInternal;
348+
const displayDistance = Math.round(Units.toDisplayDistance(distanceInternal, sessionData));
339349

340-
if (!allowedDistances.has(distance)) continue;
350+
if (!allowedDistances.has(displayDistance)) continue;
341351

342-
const existing = best[curStroke][distance];
352+
const existing = best[curStroke][displayDistance];
343353

344354
if (!existing || sum < existing.time) {
345-
best[curStroke][distance] = {
355+
best[curStroke][displayDistance] = {
346356
time: sum,
347357
count,
348-
lengths: curLengths.slice(start, end + 1)
358+
lengths: curLengths.slice(start, end + 1),
359+
distanceInternal
349360
};
350361
}
351362
}
@@ -364,10 +375,11 @@ export async function renderBestTimes() {
364375
flushStreak();
365376

366377
if (Object.keys(best).length === 0) {
378+
const displayPoolLen = Math.round(Units.toDisplayDistance(poolLenInternal, sessionData) * 100) / 100;
367379
document.getElementById('bestTimesTable').innerHTML = `
368380
<div class="no-best-times-message" style="text-align:center; font-style:italic; color:#555; padding:1em;">
369-
<p>No valid intervals found for a pool length of ${poolLen} m.</p>
370-
<p>The pool length must divide evenly into one of: 50, 100, 200, 400, 800, 1500, 3000, 5000, or 10000 m.</p>
381+
<p>No valid intervals found for a pool length of ${displayPoolLen} ${poolUnit}.</p>
382+
<p>The pool length must divide evenly into one of: 50, 100, 200, 400, 800, 1500, 3000, 5000, or 10000 ${poolUnit}.</p>
371383
</div>`;
372384
return;
373385
}
@@ -377,25 +389,26 @@ export async function renderBestTimes() {
377389
Object.keys(best).sort().forEach(stroke => {
378390
const entries = Object.entries(best[stroke])
379391
.map(([dist, info]) => ({
380-
distance: Number(dist),
392+
displayDistance: Number(dist),
381393
stroke,
382394
time: info.time,
383395
count: info.count,
384-
lengths: info.lengths
396+
lengths: info.lengths,
397+
distanceInternal: info.distanceInternal
385398
}))
386-
.sort((a, b) => a.distance - b.distance);
399+
.sort((a, b) => a.displayDistance - b.displayDistance);
387400

388401
entries.forEach(e => {
389-
const pace = (e.time / e.distance) * 100;
402+
const paceSeconds = Units.getPaceSeconds(e.distanceInternal / e.time, sessionData);
390403
const spm = e.lengths.reduce((s, l) => s + (l.avgSwimmingCadence || 0), 0) / e.count;
391404
const spl = e.lengths.reduce((s, l) => s + (l.totalStrokes || 0), 0) / e.count;
392405

393406
rows += `
394407
<tr class="${e.stroke}">
395-
<td>${e.distance}</td>
408+
<td>${e.displayDistance}${poolUnit}</td>
396409
<td>${e.stroke.charAt(0).toUpperCase() + e.stroke.slice(1)}</td>
397410
<td>${formatTime(e.time, 1)}</td>
398-
<td>${formatTime(pace, 0)}</td>
411+
<td>${formatTime(paceSeconds, 0)}</td>
399412
<td>${spm.toFixed(2)}</td>
400413
<td>${spl.toFixed(2)}</td>
401414
<td>${e.lengths[0].index}-${e.lengths[e.lengths.length - 1].index}</td>
@@ -428,7 +441,9 @@ export function renderPacePlot(data) {
428441

429442
// Filter data to include only entries where event is 'length' and lengthType is 'active'
430443
const lengthData = data.lengthMesgs.filter(d => d.event === 'length' && d.lengthType === 'active');
431-
const poolLength = data.sessionMesgs[0].poolLength;
444+
const sessionData = data.sessionMesgs[0];
445+
const poolLengthInternal = sessionData.poolLength;
446+
const poolUnit = Units.getUnitLabel(sessionData);
432447

433448
// Define a fixed Viridis color map for swim strokes
434449
const fixedStrokeColors = {
@@ -443,11 +458,11 @@ export function renderPacePlot(data) {
443458
// Create paceData with stroke-based colors for the bars
444459
const paceData = {
445460
x: lengthData.map((d, index) => index + 1), // X-axis as the length index
446-
y: lengthData.map(d => (d.totalElapsedTime / poolLength)*100 || 0), // Pace in minutes per 100m
461+
y: lengthData.map(d => Units.getPaceSeconds(poolLengthInternal / d.totalElapsedTime, sessionData) || 0), // Pace in seconds per 100 units
447462
name: '',
448463
type: 'bar',
449464
text: lengthData.map(d => `Stroke: ${d.swimStroke || 'Unknown'}<br>Pace:
450-
${formatTime((d.totalElapsedTime / poolLength)*100)}<br>SPM:
465+
${formatTime(Units.getPaceSeconds(poolLengthInternal / d.totalElapsedTime, sessionData))}/100${poolUnit}<br>SPM:
451466
${d.avgSwimmingCadence}<br>SPL: ${d.totalStrokes}`), // Hover text
452467
hoverinfo: 'text',
453468
textposition: 'none', // Prevent the text from being shown on the bars
@@ -537,7 +552,9 @@ export function renderStrokeRateStrokeCountPlot(data) {
537552
...d
538553
}));
539554
const filteredData = activeData.filter(d => d.swimStroke === stroke);
540-
const poolLength = data.sessionMesgs[0].poolLength;
555+
const sessionData = data.sessionMesgs[0];
556+
const poolLengthInternal = sessionData.poolLength;
557+
const poolUnit = Units.getUnitLabel(sessionData);
541558

542559
// Reverse the color scale to align with faster times being darker colors and slower times being lighter
543560
const paceColorScale = [
@@ -548,8 +565,8 @@ export function renderStrokeRateStrokeCountPlot(data) {
548565
[1, '#B9E1EC']
549566
];
550567

551-
// Calculate the pace values (min/100m) for the color bar
552-
const paceValues = filteredData.map(d => (d.totalElapsedTime / poolLength)*100 || 0);
568+
// Calculate the pace values for the color bar
569+
const paceValues = filteredData.map(d => Units.getPaceSeconds(poolLengthInternal / d.totalElapsedTime, sessionData) || 0);
553570
const paceMin = Math.min(...paceValues);
554571
const paceMax = Math.max(...paceValues);
555572

@@ -558,18 +575,17 @@ export function renderStrokeRateStrokeCountPlot(data) {
558575
x: filteredData.map(d => d.avgSwimmingCadence || 0), // X-axis: Stroke Rate (SPM)
559576
y: filteredData.map(d => d.totalStrokes || 0), // Y-axis: Stroke Count (SPL)
560577
mode: 'markers',
561-
text: filteredData.map(d => `Length: ${d.index} <br>Pace: ${formatTime((d.totalElapsedTime /
562-
poolLength)*100)} min/100m<br>SPM: ${d.avgSwimmingCadence}<br>SPL:
578+
text: filteredData.map(d => `Length: ${d.index} <br>Pace: ${formatTime(Units.getPaceSeconds(poolLengthInternal / d.totalElapsedTime, sessionData))}/100${poolUnit}<br>SPM: ${d.avgSwimmingCadence}<br>SPL:
563579
${d.totalStrokes}`), // Hover text
564580
hoverinfo: 'text',
565581
marker: {
566582
size: 12, // Size of the points
567-
color: paceValues, // Color: Pace (min/100m)
583+
color: paceValues, // Color: Pace
568584
colorscale: paceColorScale,
569585
cmin: paceMin,
570586
cmax: paceMax,
571587
colorbar: {
572-
title: 'Pace (min/100m)',
588+
title: `Pace (min/100${poolUnit})`,
573589
titleside: 'right',
574590
tickmode: 'array',
575591
tickvals: [paceMin, paceMax],
@@ -610,7 +626,8 @@ export async function renderIntervalSummaryTable() {
610626
const activeLengths = lengthData.filter(d => d.event === 'length' && d.lengthType === 'active');
611627
const activeLapsData = data.lapMesgs.filter(d => d.numActiveLengths > 0);
612628
const sessionData = data.sessionMesgs[0];
613-
const poolLength = sessionData.poolLength;
629+
const poolLengthInternal = sessionData.poolLength;
630+
const poolUnit = Units.getUnitLabel(sessionData);
614631

615632
// Define the intervals
616633
const intervals = activeLapsData.map((lap, index) => {
@@ -655,19 +672,19 @@ export async function renderIntervalSummaryTable() {
655672

656673
// Loop through each length within the interval
657674
intervalLengths.forEach(length => {
658-
const distance = poolLength; // Assuming each length is one pool length
675+
const displayDistance = Units.toDisplayDistance(poolLengthInternal, sessionData);
659676
const time = length.totalElapsedTime;
660-
const pace = (time / distance) * 100;
677+
const paceSeconds = Units.getPaceSeconds(poolLengthInternal / time, sessionData);
661678
const spm = length.avgSwimmingCadence || 0;
662679
const spl = (length.totalStrokes || 0);
663680

664681
tableHTML += `
665682
<tr class="length ${length.swimStroke}">
666683
<td>${lengthCounter}</td>
667-
<td>${distance}</td>
684+
<td>${Math.round(displayDistance)}${poolUnit}</td>
668685
<td>${length.swimStroke.charAt(0).toUpperCase() + length.swimStroke.slice(1)}</td>
669686
<td>${formatTime(time, 1)}</td>
670-
<td>${formatTime(pace, 0)}</td>
687+
<td>${formatTime(paceSeconds, 0)}</td>
671688
<td>${spm}</td>
672689
<td>${spl}</td>
673690
</tr>
@@ -699,11 +716,12 @@ export async function renderIntervalSummaryTable() {
699716

700717
// Calculate interval summary
701718
const totalLengths = intervalLengths.length;
702-
const intervalDistance = totalLengths * poolLength;
719+
const intervalDistanceInternal = totalLengths * poolLengthInternal;
720+
const intervalDistanceDisplay = Units.toDisplayDistance(intervalDistanceInternal, sessionData);
703721
const intervalTime = intervalLengths.reduce((sum, length) => sum + length.totalElapsedTime, 0);
704722
const intervalSPM = intervalLengths.reduce((sum, length) => sum + (length.avgSwimmingCadence || 0), 0) / totalLengths;
705723
const intervalSPL = intervalLengths.reduce((sum, length) => sum + (length.totalStrokes || 0), 0) / totalLengths;
706-
const intervalPace = (intervalTime / intervalDistance) * 100;
724+
const intervalPaceSeconds = Units.getPaceSeconds(intervalDistanceInternal / intervalTime, sessionData);
707725

708726
// Calculate interval stroke
709727
const strokesInInterval = intervalLengths.map(length => length.swimStroke);
@@ -714,10 +732,10 @@ export async function renderIntervalSummaryTable() {
714732
tableHTML += `
715733
<tr class="interval">
716734
<td>${ordinalSuffixOf(intervalIndex + 1)} Interval</td>
717-
<td>${intervalDistance}</td>
735+
<td>${Math.round(intervalDistanceDisplay)}${poolUnit}</td>
718736
<td>${intervalStroke}</td>
719737
<td>${formatTime(intervalTime, 1)}</td>
720-
<td>${formatTime(intervalPace, 0)}</td>
738+
<td>${formatTime(intervalPaceSeconds, 0)}</td>
721739
<td>${intervalSPM.toFixed(2)}</td>
722740
<td>${intervalSPL.toFixed(2)}</td>
723741
</tr>

swim_data_analyser/static/js/editView.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Plotly from 'plotly.js-basic-dist-min'
22
import { Encoder, Stream, Profile, Utils } from '@garmin/fitsdk';
33
import { getItem, saveItem } from './storage.js';
4+
import * as Units from './units.js';
45

56
let selectedLabels = [];
67

@@ -203,10 +204,12 @@ export async function loadMeta() {
203204
const daytime = date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
204205

205206
// Pace calculation
206-
const pace100mSec = speedMps > 0 ? 100 / speedMps : 0;
207-
const pace100ydSec = speedMps > 0 ? 91.44 / speedMps : 0;
208-
const isYards = sessionData.poolLengthUnit === 'statute';
209-
const poolUnit = isYards ? 'yd' : 'm';
207+
const paceSeconds = Units.getPaceSeconds(speedMps, sessionData);
208+
const poolUnit = Units.getUnitLabel(sessionData);
209+
210+
const displayPoolLength = Units.toDisplayDistance(metadata.poolLength, sessionData);
211+
const displayTotalDistance = Units.toDisplayDistance(metadata.totalDistance, sessionData);
212+
const displayAvgStrokeDistance = Units.toDisplayDistance(metadata.avgStrokeDistance, sessionData);
210213

211214
// Update the content dynamically with recalculated metadata
212215
document.getElementById('metadata-container').innerHTML = `
@@ -218,7 +221,7 @@ export async function loadMeta() {
218221
219222
<div class="metadata-box">
220223
<strong>Pool Length:</strong>
221-
<span>${metadata.poolLength}${poolUnit}</span>
224+
<span>${Math.round(displayPoolLength * 100) / 100}${poolUnit}</span>
222225
</div>
223226
224227
<div class="metadata-box">
@@ -233,17 +236,13 @@ export async function loadMeta() {
233236
234237
<div class="metadata-box">
235238
<strong>Total Distance:</strong>
236-
<span>${Math.round(metadata.totalDistance)}${poolUnit}</span>
239+
<span>${Math.round(displayTotalDistance)}${poolUnit}</span>
237240
</div>
238241
239242
<div class="metadata-box">
240243
<strong>Avg. Pace:</strong>
241244
<span>
242-
${
243-
isYards
244-
? `${formatTime(pace100ydSec)}/100yd`
245-
: `${formatTime(pace100mSec)}/100m`
246-
}
245+
${formatTime(paceSeconds)}/100${poolUnit}
247246
</span>
248247
</div>
249248
@@ -254,7 +253,7 @@ export async function loadMeta() {
254253
255254
<div class="metadata-box">
256255
<strong>Avg. Distance/Stroke:</strong>
257-
<span>${metadata.avgStrokeDistance.toFixed(2)}${poolUnit}</span>
256+
<span>${displayAvgStrokeDistance.toFixed(2)}${poolUnit}</span>
258257
</div>
259258
260259
<div class="metadata-box">
@@ -609,11 +608,11 @@ document.getElementById('confirmPoolSize').addEventListener('click', async funct
609608

610609
// Get the modified data from IndexedDB
611610
const modifiedData = await getItem('modifiedData');
611+
const sessionData = modifiedData.sessionMesgs[0];
612612

613-
614-
// Get the current pool size from the metadata
615-
const newPoolSize = parseFloat(document.getElementById('poolSizeEntered').value);
616-
const currentPoolSize = modifiedData.sessionMesgs[0].poolLength;
613+
// Get the current pool size from the metadata (assume user entered value in display units)
614+
let newPoolSizeDisplay = parseFloat(document.getElementById('poolSizeEntered').value);
615+
const newPoolSize = Units.toInternalDistance(newPoolSizeDisplay, sessionData);
617616

618617
// Hide the modal
619618
document.getElementById('poolSizeModal').style.display = 'none';

0 commit comments

Comments
 (0)