Add a Distance/Time toggle to the profile chart's x-axis
TrackStats.build_profile now also computes elapsed_secs per point (relative to the first known timestamp), alongside the existing cumulative distance. Point x-positions stay distance-based either way — only the shared axis strip's 5 tick labels (and the tooltip) switch between the two, so toggling is a cheap re-render rather than rebuilding the charts. The toggle only appears when a track actually has elapsed-time data to switch to.
This commit is contained in:
1 parent
4f7fe3c2a4
commit
a0476f1204
7 files changed
+138
-14
No files matched your search
@@ -55,9 +55,23 @@ export class TrackProfilePanel {
|
||||
this._tooltip.hidden = true;
|
||||
this._chartsContainer.appendChild(this._tooltip);
|
||||
|
||||
// Only offer the toggle when there's actually elapsed-time data to
|
||||
// switch to — plenty of GPX files have no <time> at all.
|
||||
this._hasTime = this._profile.some((p) => typeof p.elapsed_secs === "number");
|
||||
this._xAxisMode = "distance";
|
||||
if (this._hasTime) this._chartsContainer.appendChild(this._renderXAxisToggle());
|
||||
|
||||
this._activeSeries = SERIES.filter((s) => this._profile.some((p) => typeof p[s.key] === "number"));
|
||||
this._activeSeries.forEach((series) => this._chartsContainer.appendChild(this._renderChart(series)));
|
||||
|
||||
// One shared strip below the whole stack, not per-chart — every chart
|
||||
// above uses the same x-scale (distance-position), so a separate axis
|
||||
// per chart would just repeat itself.
|
||||
this._xAxisStripEl = document.createElement("div");
|
||||
this._xAxisStripEl.className = "profile-panel__xaxis";
|
||||
this._chartsContainer.appendChild(this._xAxisStripEl);
|
||||
this._renderXAxisTicks();
|
||||
|
||||
this._chartsContainer.addEventListener("pointermove", this._onPointerMove);
|
||||
this._chartsContainer.addEventListener("pointerleave", this._onPointerLeave);
|
||||
|
||||
@@ -108,6 +122,37 @@ export class TrackProfilePanel {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
_renderXAxisToggle() {
|
||||
const row = document.createElement("div");
|
||||
row.className = "profile-panel__xaxis-toggle";
|
||||
row.innerHTML = `
|
||||
<button type="button" class="profile-panel__xaxis-toggle-btn" data-mode="distance">Distance</button>
|
||||
<button type="button" class="profile-panel__xaxis-toggle-btn" data-mode="time">Time</button>
|
||||
`;
|
||||
row.querySelectorAll("button").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.mode === this._xAxisMode);
|
||||
btn.addEventListener("click", () => {
|
||||
if (btn.dataset.mode === this._xAxisMode) return;
|
||||
this._xAxisMode = btn.dataset.mode;
|
||||
row.querySelectorAll("button").forEach((b) => b.classList.toggle("active", b === btn));
|
||||
this._renderXAxisTicks();
|
||||
});
|
||||
});
|
||||
return row;
|
||||
}
|
||||
|
||||
_renderXAxisTicks() {
|
||||
const TICK_COUNT = 5;
|
||||
this._xAxisStripEl.innerHTML = Array.from({ length: TICK_COUNT }, (_, i) => {
|
||||
const fraction = i / (TICK_COUNT - 1);
|
||||
const point = this._profile[nearestIndex(this._profile, fraction * this._maxDistance)];
|
||||
const label = this._xAxisMode === "time" && typeof point.elapsed_secs === "number"
|
||||
? formatDuration(point.elapsed_secs)
|
||||
: `${(point.distance_meters / 1000).toFixed(1)} km`;
|
||||
return `<span class="profile-panel__xaxis-tick" style="left:${(fraction * 100).toFixed(2)}%">${escapeHtml(label)}</span>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
// Bound once (arrow fields, not prototype methods) so add/removeEventListener see the same reference.
|
||||
_onPointerMove = (event) => {
|
||||
const rect = this._chartsContainer.getBoundingClientRect();
|
||||
@@ -158,7 +203,8 @@ export class TrackProfilePanel {
|
||||
.filter((s) => typeof point[s.key] === "number")
|
||||
.map((s) => `<div><span class="profile-panel__tooltip-label">${escapeHtml(s.label)}</span> <strong>${formatValue(point[s.key], s)}</strong></div>`)
|
||||
.join("");
|
||||
this._tooltip.innerHTML = `<div class="profile-panel__tooltip-distance">${(point.distance_meters / 1000).toFixed(2)} km</div>${rows}`;
|
||||
const timeSuffix = typeof point.elapsed_secs === "number" ? ` · ${formatDuration(point.elapsed_secs)}` : "";
|
||||
this._tooltip.innerHTML = `<div class="profile-panel__tooltip-distance">${(point.distance_meters / 1000).toFixed(2)} km${timeSuffix}</div>${rows}`;
|
||||
this._tooltip.hidden = false;
|
||||
|
||||
const containerRect = this._chartsContainer.getBoundingClientRect();
|
||||
|
||||
Reference in new issue
Block a user