Skip to content

Commit

Permalink
Integrate Equiv Speed / hover trigger for attr dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
mayfield committed Dec 24, 2024
1 parent 4466c91 commit d2395e1
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 48 deletions.
3 changes: 2 additions & 1 deletion _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,5 +558,6 @@
"dashboard_exclude_commutes": {"message": "Skip Commutes"},
"dashboard_kudo_limit_reached": {"message": "Hourly Limit Reached"},
"dashboard_kudo_complete": {"message": "Everything Kudoed"},
"attribution_tp": {"message": "Normalized Power®, NP®, Training Stress Score®, TSS®, Intensity Factor®, IF® are trademarks of TrainingPeaks, LLC and are used with permission.\n\nLearn more at <a href=\"https://www.trainingpeaks.com/learn/articles/glossary-of-trainingpeaks-metrics/?utm_source=newsletter&utm_medium=partner&utm_term=sauce_trademark&utm_content=cta&utm_campaign=sauce\">https://www.trainingpeaks.com/learn/articles/glossary-of-trainingpeaks-metrics/</a>."}
"attribution_tp": {"message": "Normalized Power®, NP®, Training Stress Score®, TSS®, Intensity Factor®, IF® are trademarks of TrainingPeaks, LLC and are used with permission.\n\nLearn more at <a href=\"https://www.trainingpeaks.com/learn/articles/glossary-of-trainingpeaks-metrics/?utm_source=newsletter&utm_medium=partner&utm_term=sauce_trademark&utm_content=cta&utm_campaign=sauce\">https://www.trainingpeaks.com/learn/articles/glossary-of-trainingpeaks-metrics/</a>."},
"attribution_es": {"message": "Equivalent Speed (ES) is the distance weighted average speed. This compensates for climbing, traffic, etc. Discovered independently by <a href=\"https://lee-naish.github.io/src/posavespeed/\">Lee Naish</a> and <a href=\"https://www.researchgate.net/publication/353625461\">Scott Bollt</a>."}
}
22 changes: 21 additions & 1 deletion src/common/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -1736,11 +1736,31 @@ sauce.ns('pace', function() {
}


/*
* Equivalent Speed (ES) is the distance weighted average speed. This compensates for
* climbing, traffic, etc. Discovered independently by Lee Naish and Scott Bollt.
*
* See: https://lee-naish.github.io/src/posavespeed/
* See: https://www.researchgate.net/publication/353625461
*/
function equivalentSpeed(velocityStream) {
let sum = 0;
let sumSquared = 0;
for (let i = 0; i < velocityStream.length; i++) {
const v = velocityStream[i];
sum += v;
sumSquared += v * v;
}
return sumSquared / sum;
}


return {
RollingPace,
bestPace,
work,
createWattsStream,
RollingPace,
equivalentSpeed
};
});

Expand Down
85 changes: 62 additions & 23 deletions src/ext/boot.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,76 @@
/* global sauce, browser */


let _attrDialog;
function handleAttributionDialog() {
const hoverDelay = 500;
let dialogSingleton;
let hoverTimeout;
function makeAttrDialog(el, key) {
const dialog = document.createElement('dialog');
dialog.srcElement = el;
dialog.classList.add('sauce-attr');
sauce.adjacentNodeContents(dialog, 'beforeend',
browser.i18n.getMessage(`attribution_${key}`));
const pos = el.getBoundingClientRect();
if (pos.x || pos.y) {
dialog.style.setProperty('top', pos.y + pos.height + 'px');
dialog.style.setProperty('left', pos.x + 'px');
dialog.classList.add('anchored');
} else {
// Needs a note as to why/where this happens...
console.warn("XXX: Triage this context", el);
debugger;
}
dialog.addEventListener('click', ev => dialog.close());
dialog.addEventListener('close', ev => {
if (dialog === dialogSingleton) {
dialogSingleton = null;
}
dialog.remove();
});
document.body.append(dialog);
dialogSingleton = dialog;
return dialog;
}
document.documentElement.addEventListener('click', ev => {
if (dialogSingleton) {
dialogSingleton.close();
}
const attr = ev.target.closest('attr[for]');
if (!attr) {
return;
}
if (_attrDialog) {
_attrDialog.close();
} else {
const dialog = document.createElement('dialog');
dialog.classList.add('sauce-attr');
sauce.adjacentNodeContents(dialog, 'beforeend',
browser.i18n.getMessage(`attribution_${attr.getAttribute('for')}`));
const pos = attr.getBoundingClientRect(attr);
if (pos.left || pos.top) {
dialog.style.setProperty('top', pos.top + pos.height + 'px');
dialog.style.setProperty('left', pos.left + 'px');
dialog.classList.add('anchored');
}
dialog.addEventListener('click', ev => dialog.close());
dialog.addEventListener('close', ev => {
if (dialog === _attrDialog) {
_attrDialog = null;
dialog.remove();
const dialog = makeAttrDialog(attr, attr.getAttribute('for'));
dialog.persistent = true;
dialog.showModal();
});
document.documentElement.addEventListener('pointerover', ev => {
clearTimeout(hoverTimeout);
const attr = ev.target.closest('[attr-tooltip]');
if (!attr) {
if (dialogSingleton && !dialogSingleton.persistent) {
const rect = dialogSingleton.getBoundingClientRect();
const pad = 20;
const x = ev.pageX - scrollX;
const y = ev.pageY - scrollY;
if (x < (rect.x - pad) || x > (rect.x + rect.width + pad) ||
y < (rect.y - pad) || y > (rect.y + rect.height + pad)) {
console.info("left the rect", x, y, rect);
dialogSingleton.close();
} else {
console.info("inside the rect", ev.pageX, ev.pageY, rect);
}
});
_attrDialog = dialog;
document.body.append(dialog);
dialog.showModal();
}
return;
}
if (dialogSingleton) {
if (dialogSingleton.srcElement === attr) {
return;
}
dialogSingleton.close();
}
hoverTimeout = setTimeout(() => makeAttrDialog(attr, attr.getAttribute('attr-tooltip')).show(),
hoverDelay);
});
}

Expand Down
31 changes: 8 additions & 23 deletions src/site/analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -1241,10 +1241,7 @@ sauce.ns('analysis', ns => {
activeTime,
}),
pace: distance && {
// LEE possible temporary hack - replace average speed by ES
// I'm not sure if/where this gets used...
// avg: 1 / getEqvSpeed(startIdx, endIdx),
avg: 1 / (distance / elapsedTime), // ave. active speed
avg: 1 / (distance / elapsedTime),
max: 1 / sauce.data.max(streams.velocity_smooth),
gap: streams.grade_adjusted_distance &&
(1 / (streamDelta(streams.grade_adjusted_distance) / elapsedTime)),
Expand Down Expand Up @@ -2479,18 +2476,6 @@ sauce.ns('analysis', ns => {
return sauce.data.activeTime(timeStream, activeStream);
}

// LEE
// ES is essentially the speed averaged over the position rather
// than time (each meter travelled has the same weight rather than
// the more traditional each second travelled having the same
// weight. See https://lee-naish.github.io/src/posavespeed/
function getEqvSpeed(start, end) {
const velocity_smoothStream = _getStream('velocity_smooth', start, end);
const sumSpeed = sauce.data.sum(velocity_smoothStream);
const sumSpeedSquared = sauce.data.sum(velocity_smoothStream.map(v => v*v));
return sumSpeedSquared / sumSpeed;
}


function getStopCount(start, end) {
let stops = 0;
Expand Down Expand Up @@ -2598,7 +2583,6 @@ sauce.ns('analysis', ns => {
const activeTime = getActiveTime(start, end);
const elapsedTime = streamDelta(timeStream);
const distance = streamDelta(distStream);
const eqvSpeed = getEqvSpeed(start, end); // LEE
const tplData = {
logo: sauce.getURL('images/logo_vert_48x128.png'),
supportsRankBadge: pageView.activity().isRide(),
Expand Down Expand Up @@ -2652,14 +2636,15 @@ sauce.ns('analysis', ns => {
if (distance) {
const gradeDistStream = await fetchGradeDistStream({start, end});
const gradeDistance = gradeDistStream && streamDelta(gradeDistStream);
let equivSpeed;
if (pageView.isRide()) {
const velocityStream = await fetchStream('velocity_smooth', start, end);
equivSpeed = sauce.pace.equivalentSpeed(velocityStream);
}
tplData.pace = {
elapsed: 1 / (distance / elapsedTime),
// LEE temporary hack - replace average speed by ES
// XXX rename this to eqv or some such and change
// active_old back to active plus change other code so eqv
// is displayed (possibly with active also)
active: 1 / eqvSpeed, // ES - "equivalent speed"
active_old: 1 / (distance / activeTime), // ave. active speed
es: equivSpeed ? 1 / equivSpeed : undefined,
active: 1 / (distance / activeTime),
gap: gradeDistance && (1 / (gradeDistance / activeTime)),
};
}
Expand Down
7 changes: 7 additions & 0 deletions templates/analysis-stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
<value>{-humanPace(pace.gap, {html: true, suffix: true})-}</value>
</line>
<% } %>

<% if (pace.es) { %>
<line attr-tooltip="es">
<key>ES</key>
<value>{-humanPace(pace.es, {html: true, suffix: true})-}</value>
</line>
<% } %>
<% } %>

<% if (elapsed !== active) { %>
Expand Down

0 comments on commit d2395e1

Please sign in to comment.