<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Gravel on M1CH's Log</title><link>https://blog.m1ch.eu/tags/gravel/</link><description>Recent content in Gravel on M1CH's Log</description><generator>Hugo</generator><language>en-us</language><copyright>Michael Klamminger</copyright><lastBuildDate>Tue, 28 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.m1ch.eu/tags/gravel/index.xml" rel="self" type="application/rss+xml"/><item><title>Meine ersten 200 km</title><link>https://blog.m1ch.eu/outdoor/20260428-200k/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.m1ch.eu/outdoor/20260428-200k/</guid><description>&lt;p&gt;Ich hab&amp;rsquo;s geschafft. Meine ersten 200 km mit dem Rad.&lt;/p&gt;





&lt;div id="gpxstats-be18726634407cf068ed7216b0858712" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:.4rem 1rem;font-size:.875rem;"&gt;&lt;/div&gt;

&lt;script&gt;
(function () {
 var src = "\/gpx\/2026\/2026-04-28.gpx";
 var color = "#e8500a";
 var elId = "gpxstats-be18726634407cf068ed7216b0858712";

 function haversine(a, b) {
 var R = 6371, dLat = (b.lat - a.lat) * Math.PI / 180, dLon = (b.lon - a.lon) * Math.PI / 180;
 var s = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
 Math.cos(a.lat * Math.PI / 180) * Math.cos(b.lat * Math.PI / 180) *
 Math.sin(dLon / 2) * Math.sin(dLon / 2);
 return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s));
 }

 function fmtElapsed(totalMin) {
 var h = Math.floor(totalMin / 60), m = Math.floor(totalMin % 60);
 return h + "h " + (m &lt; 10 ? "0" : "") + m + "m";
 }

 fetch(src)
 .then(function (r) { return r.text(); })
 .then(function (xml) {
 var doc = new DOMParser().parseFromString(xml, "application/xml");
 var nodes = Array.from(doc.querySelectorAll("trkpt"));
 var startMs = null, dist = 0, hasTime = false;
 var pts = [];

 nodes.forEach(function (pt, i) {
 var lat = parseFloat(pt.getAttribute("lat"));
 var lon = parseFloat(pt.getAttribute("lon"));
 var ele = parseFloat((pt.querySelector("ele") || { textContent: 0 }).textContent);
 var ts = (pt.querySelector("time") || { textContent: "" }).textContent;
 var ms = ts ? new Date(ts).getTime() : null;
 if (i === 0) { startMs = ms; if (ms) hasTime = true; }
 if (i &gt; 0) dist += haversine(pts[i - 1], { lat: lat, lon: lon });
 pts.push({
 lat: lat, lon: lon, ele: ele,
 dist: Math.round(dist * 100) / 100,
 elapsedMin: ms !== null &amp;&amp; startMs !== null ? (ms - startMs) / 60000 : i
 });
 });

 var last = pts[pts.length - 1];
 var distMax = last.dist;
 var timeMax = last.elapsedMin;

 

 var SMOOTH_WINDOW = 7;
 var half = Math.floor(SMOOTH_WINDOW / 2);
 var smoothedEle = pts.map(function (p, i) {
 var lo = Math.max(0, i - half), hi = Math.min(pts.length, i + half + 1);
 var sum = 0;
 for (var j = lo; j &lt; hi; j++) sum += pts[j].ele;
 return sum / (hi - lo);
 });

 

 var MAX_GAP_MIN = 3;
 var MOVING_THRESHOLD = 0.5;
 var speed = pts.map(function (p, i) {
 var lo = Math.max(0, i - half), hi = Math.min(pts.length - 1, i + half);
 var dDist = pts[hi].dist - pts[lo].dist;
 var dMin = pts[hi].elapsedMin - pts[lo].elapsedMin;
 return dMin &gt; 0 ? dDist / dMin * 60 : 0;
 });

 var maxEle = -Infinity, minEle = Infinity, gain = 0, loss = 0, movingMin = 0;

 for (var i = 0; i &lt; pts.length; i++) {
 if (pts[i].ele &gt; maxEle) maxEle = pts[i].ele;
 if (pts[i].ele &lt; minEle) minEle = pts[i].ele;
 if (i &gt; 0) {
 var dEleSmoothed = smoothedEle[i] - smoothedEle[i - 1];
 if (dEleSmoothed &gt; 0) gain += dEleSmoothed;
 else loss += -dEleSmoothed;
 var dMin = pts[i].elapsedMin - pts[i - 1].elapsedMin;
 if (dMin &gt; 0 &amp;&amp; dMin &lt;= MAX_GAP_MIN &amp;&amp; speed[i] &gt; MOVING_THRESHOLD) movingMin += dMin;
 }
 }

 var statsData = [
 { label: "Distance", value: distMax.toFixed(1) + " km" },
 { label: "Total time", value: hasTime ? fmtElapsed(timeMax) : "—" },
 { label: "Moving time", value: hasTime ? fmtElapsed(movingMin) : "—" },
 { label: "Elev. gain", value: "+" + Math.round(gain) + " m" },
 { label: "Elev. loss", value: "−" + Math.round(loss) + " m" },
 { label: "Max elev.", value: Math.round(maxEle) + " m" },
 { label: "Min elev.", value: Math.round(minEle) + " m" }
 ];

 document.getElementById(elId).innerHTML = statsData.map(function (s) {
 return '&lt;div style="padding:.3rem .5rem;border-left:2px solid ' + color + '"&gt;' +
 '&lt;div style="font-size:.7rem;opacity:.55;text-transform:uppercase;letter-spacing:.05em;"&gt;' + s.label + '&lt;/div&gt;' +
 '&lt;div style="font-weight:600;"&gt;' + s.value + '&lt;/div&gt;&lt;/div&gt;';
 }).join("");
 });
})();
&lt;/script&gt;

&lt;p&gt;






&lt;link rel="stylesheet" href="https://blog.m1ch.eu/js/leaflet.css"/&gt;
&lt;div id="gpxmap-be18726634407cf068ed7216b0858712" style="height:480px;width:100%;border-radius:4px;"&gt;&lt;/div&gt;
&lt;a href="https://blog.m1ch.eu/gpx/2026/2026-04-28.gpx" download style="display:inline-block;margin-top:.5rem;font-size:.875rem;"&gt;⬇ Download GPX&lt;/a&gt;

&lt;script&gt;
window.gpxLibs = window.gpxLibs || (function () {
 var loading = {};
 function load(url) {
 if (!loading[url]) {
 loading[url] = new Promise(function (resolve, reject) {
 var s = document.createElement("script");
 s.src = url;
 s.onload = resolve;
 s.onerror = function () { reject(new Error("could not load " + url)); };
 document.head.appendChild(s);
 });
 }
 return loading[url];
 }
 return function (urls) {
 return urls.reduce(function (p, url) {
 return p.then(function () { return load(url); });
 }, Promise.resolve());
 };
})();
&lt;/script&gt;

&lt;script&gt;
gpxLibs(["/js/leaflet.js", "/js/leaflet-gpx.js"]).then(function () {
 var color = "#e8500a";
 var src = "\/gpx\/2026\/2026-04-28.gpx";
 var hash = "be18726634407cf068ed7216b0858712";
 var mapId = "gpxmap-be18726634407cf068ed7216b0858712";

 var pts = null;
 var trackMarker = null; 
 var mapTooltip = null;

 function haversine(a, b) {
 var R = 6371, dLat = (b.lat - a.lat) * Math.PI / 180, dLon = (b.lon - a.lon) * Math.PI / 180;
 var s = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
 Math.cos(a.lat * Math.PI / 180) * Math.cos(b.lat * Math.PI / 180) *
 Math.sin(dLon / 2) * Math.sin(dLon / 2);
 return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s));
 }

 function fmtElapsed(totalMin) {
 var h = Math.floor(totalMin / 60), m = Math.floor(totalMin % 60);
 return h + "h " + (m &lt; 10 ? "0" : "") + m + "m";
 }

 
 var map = L.map(mapId, { scrollWheelZoom: true });
 L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
 maxZoom: 19,
 attribution: '&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors'
 }).addTo(map);

 new L.GPX(src, {
 async: true,
 polyline_options: { color: color, weight: 3, opacity: 0.9 },
 markers: { startIcon: null, endIcon: null }
 })
 .on("loaded", function (e) { map.fitBounds(e.target.getBounds(), { padding: [20, 20] }); })
 .addTo(map);

 
 mapTooltip = L.tooltip({ sticky: false, opacity: 0.92 });

 function notify(idx) {
 window.dispatchEvent(new CustomEvent("gpx-hover", { detail: { hash: hash, idx: idx, source: "map" } }));
 }

 map.on("mousemove", function (e) {
 if (!pts) return;
 var cur = e.containerPoint;
 var minSq = Infinity, minIdx = -1;
 for (var i = 0; i &lt; pts.length; i++) {
 var pp = map.latLngToContainerPoint(L.latLng(pts[i].lat, pts[i].lon));
 var sq = (pp.x - cur.x) * (pp.x - cur.x) + (pp.y - cur.y) * (pp.y - cur.y);
 if (sq &lt; minSq) { minSq = sq; minIdx = i; }
 }
 if (Math.sqrt(minSq) &gt; 24) {
 mapTooltip.remove();
 notify(null);
 return;
 }
 var pt = pts[minIdx];
 mapTooltip
 .setLatLng([pt.lat, pt.lon])
 .setContent(
 "&lt;b&gt;" + fmtElapsed(pt.elapsedMin) + "&lt;/b&gt;&lt;br&gt;" +
 pt.dist.toFixed(2) + " km ▸ " + Math.round(pt.ele) + " m"
 )
 .addTo(map);
 notify(minIdx);
 });

 map.on("mouseout", function () {
 mapTooltip.remove();
 notify(null);
 });

 
 window.addEventListener("gpx-hover", function (e) {
 if (e.detail.hash !== hash || e.detail.source === "map" || !pts) return;
 var idx = e.detail.idx;
 if (idx === null) return;
 var pt = pts[idx];
 if (!trackMarker) {
 trackMarker = L.circleMarker([pt.lat, pt.lon], {
 radius: 7, color: "#fff", weight: 2, fillColor: color, fillOpacity: 1
 }).addTo(map);
 } else {
 trackMarker.setLatLng([pt.lat, pt.lon]);
 }
 });

 window.addEventListener("gpx-flyto", function (e) {
 if (e.detail.hash !== hash || !pts) return;
 var pt = pts[e.detail.idx];
 map.flyTo([pt.lat, pt.lon], Math.max(map.getZoom(), 13));
 });

 
 fetch(src)
 .then(function (r) { return r.text(); })
 .then(function (xml) {
 var doc = new DOMParser().parseFromString(xml, "application/xml");
 var nodes = Array.from(doc.querySelectorAll("trkpt"));
 var startMs = null, dist = 0;

 pts = [];
 nodes.forEach(function (pt, i) {
 var lat = parseFloat(pt.getAttribute("lat"));
 var lon = parseFloat(pt.getAttribute("lon"));
 var ele = parseFloat((pt.querySelector("ele") || { textContent: 0 }).textContent);
 var ts = (pt.querySelector("time") || { textContent: "" }).textContent;
 var ms = ts ? new Date(ts).getTime() : null;
 if (i === 0) { startMs = ms; }
 if (i &gt; 0) dist += haversine(pts[i - 1], { lat: lat, lon: lon });
 pts.push({
 lat: lat, lon: lon, ele: ele,
 dist: Math.round(dist * 100) / 100,
 elapsedMin: ms !== null &amp;&amp; startMs !== null ? (ms - startMs) / 60000 : i
 });
 });
 });
}).catch(function (err) { console.error("gpx-map:", err); });
&lt;/script&gt;








&lt;div&gt;
 &lt;div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.4rem;gap:.5rem;flex-wrap:wrap;"&gt;
 &lt;div style="display:flex;flex-direction:column;gap:.35rem;"&gt;
 &lt;div style="display:flex;gap:.3rem;align-items:center;"&gt;
 &lt;span style="font-size:.7rem;opacity:.55;text-transform:uppercase;letter-spacing:.05em;width:1.2rem;"&gt;X&lt;/span&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-x-dist" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Distance&lt;/button&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-x-time" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Time&lt;/button&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-x-clock" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Time of day&lt;/button&gt;
 &lt;/div&gt;
 &lt;div style="display:flex;gap:.3rem;align-items:center;"&gt;
 &lt;span style="font-size:.7rem;opacity:.55;text-transform:uppercase;letter-spacing:.05em;width:1.2rem;"&gt;Y&lt;/span&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-y-ele" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Elevation&lt;/button&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-y-speed" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Speed&lt;/button&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-y-other" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;"&gt;Time&lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;button id="gpxgraph-be18726634407cf068ed7216b0858712-btn-reset" style="font-size:.8rem;padding:.2rem .7rem;border:1px solid currentColor;border-radius:3px;cursor:pointer;background:transparent;opacity:.6;"&gt;Reset zoom&lt;/button&gt;
 &lt;/div&gt;
 &lt;canvas id="gpxgraph-be18726634407cf068ed7216b0858712-chart" style="cursor:crosshair;"&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;script&gt;
window.gpxLibs = window.gpxLibs || (function () {
 var loading = {};
 function load(url) {
 if (!loading[url]) {
 loading[url] = new Promise(function (resolve, reject) {
 var s = document.createElement("script");
 s.src = url;
 s.onload = resolve;
 s.onerror = function () { reject(new Error("could not load " + url)); };
 document.head.appendChild(s);
 });
 }
 return loading[url];
 }
 return function (urls) {
 return urls.reduce(function (p, url) {
 return p.then(function () { return load(url); });
 }, Promise.resolve());
 };
})();
&lt;/script&gt;

&lt;script&gt;
gpxLibs(["/js/chart.js", "/js/chartjs-plugin-zoom.js"]).then(function () {
 var trackColor = "#e8500a";
 var src = "\/gpx\/2026\/2026-04-28.gpx";
 var hash = "be18726634407cf068ed7216b0858712";
 var id = "gpxgraph-be18726634407cf068ed7216b0858712";
 var tz = "Europe\/Vienna";

 var pts = null;
 var speed = null;
 var chart = null;
 var xMode = "dist"; 
 var mapHoverIdx = null; 

 

 var NEUTRAL_ACTIVE = { bg: "#333333", fg: "#ffffff" };

 function paintButton(btn, activeInfo) {
 if (activeInfo) {
 btn.style.background = activeInfo.bg;
 btn.style.color = activeInfo.fg;
 btn.style.opacity = "1";
 } else {
 btn.style.background = "transparent";
 btn.style.color = "";
 btn.style.opacity = "0.5";
 }
 }

 function fmtElapsed(totalMin) {
 var h = Math.floor(totalMin / 60), m = Math.floor(totalMin % 60);
 return h + "h " + (m &lt; 10 ? "0" : "") + m + "m";
 }

 
 function fmtClock(min) {
 var t = ((Math.floor(min) % 1440) + 1440) % 1440;
 var h = Math.floor(t / 60), m = t % 60;
 return (h &lt; 10 ? "0" : "") + h + ":" + (m &lt; 10 ? "0" : "") + m;
 }

 
 function clockMinutes(ms) {
 var parts = {};
 new Intl.DateTimeFormat("en-GB", { timeZone: tz, hour: "2-digit", minute: "2-digit",
 second: "2-digit", hourCycle: "h23" })
 .formatToParts(new Date(ms))
 .forEach(function (p) { parts[p.type] = parseInt(p.value, 10); });
 return parts.hour * 60 + parts.minute + parts.second / 60;
 }

 function haversine(a, b) {
 var R = 6371, dLat = (b.lat - a.lat) * Math.PI / 180, dLon = (b.lon - a.lon) * Math.PI / 180;
 var s = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
 Math.cos(a.lat * Math.PI / 180) * Math.cos(b.lat * Math.PI / 180) *
 Math.sin(dLon / 2) * Math.sin(dLon / 2);
 return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s));
 }

 
 window.addEventListener("gpx-hover", function (e) {
 if (e.detail.hash !== hash || e.detail.source === "graph") return;
 mapHoverIdx = e.detail.idx;
 if (!chart) return;
 if (mapHoverIdx === null) {
 chart.tooltip.setActiveElements([], { x: 0, y: 0 });
 } else {
 var meta = chart.getDatasetMeta(0);
 var point = meta.data[mapHoverIdx];
 if (point) chart.tooltip.setActiveElements([{ datasetIndex: 0, index: mapHoverIdx }], { x: point.x, y: point.y });
 }
 chart.update("none");
 });

 
 fetch(src)
 .then(function (r) { return r.text(); })
 .then(function (xml) {
 var doc = new DOMParser().parseFromString(xml, "application/xml");
 var nodes = Array.from(doc.querySelectorAll("trkpt"));
 var startMs = null, dist = 0;

 pts = [];
 nodes.forEach(function (pt, i) {
 var lat = parseFloat(pt.getAttribute("lat"));
 var lon = parseFloat(pt.getAttribute("lon"));
 var ele = parseFloat((pt.querySelector("ele") || { textContent: 0 }).textContent);
 var ts = (pt.querySelector("time") || { textContent: "" }).textContent;
 var ms = ts ? new Date(ts).getTime() : null;
 if (i === 0) { startMs = ms; }
 if (i &gt; 0) dist += haversine(pts[i - 1], { lat: lat, lon: lon });
 pts.push({
 lat: lat, lon: lon, ele: ele,
 dist: Math.round(dist * 100) / 100,
 elapsedMin: ms !== null &amp;&amp; startMs !== null ? (ms - startMs) / 60000 : i
 });
 });

 

 var SMOOTH_WINDOW = 7;
 var half = Math.floor(SMOOTH_WINDOW / 2);
 speed = pts.map(function (p, i) {
 var lo = Math.max(0, i - half), hi = Math.min(pts.length - 1, i + half);
 var dDist = pts[hi].dist - pts[lo].dist;
 var dMin = pts[hi].elapsedMin - pts[lo].elapsedMin;
 return dMin &gt; 0 ? dDist / dMin * 60 : 0;
 });

 var last = pts[pts.length - 1];
 var distMax = last.dist;
 var timeMax = last.elapsedMin;
 var hasTime = startMs !== null;
 

 var startClock = hasTime ? clockMinutes(startMs) : 0;

 

 function otherMode() { return xMode === "dist" ? "time" : "dist"; }
 function xMin() { return xMode === "clock" ? startClock : 0; }
 function xMax() {
 if (xMode === "dist") return distMax;
 if (xMode === "clock") return startClock + timeMax;
 return timeMax;
 }
 function xVal(p) {
 if (xMode === "dist") return p.dist;
 if (xMode === "clock") return startClock + p.elapsedMin;
 return p.elapsedMin;
 }

 

 var Y_METRICS = {
 ele: {
 label: function () { return "Elevation (m)"; },
 color: trackColor,
 active: { bg: trackColor, fg: "#ffffff" },
 value: function (i) { return Math.round(pts[i].ele); },
 tick: function (v) { return v + " m"; }
 },
 speed: {
 label: function () { return "Speed (km/h)"; },
 color: "#90ee90",
 active: { bg: "#90ee90", fg: "#1a1a1a" },
 value: function (i) { return Math.round(speed[i] * 10) / 10; },
 tick: function (v) { return v + " km/h"; }
 },
 other: {
 label: function () { return otherMode() === "dist" ? "Distance (km)" : "Time"; },
 color: "#89cff0",
 active: { bg: "#89cff0", fg: "#1a1a1a" },
 value: function (i) { return otherMode() === "dist" ? pts[i].dist : pts[i].elapsedMin; },
 tick: function (v) { return otherMode() === "dist" ? parseFloat(v.toFixed(2)) + " km" : fmtElapsed(v); }
 }
 };
 var yOrder = ["ele", "speed", "other"];
 var activeY = { ele: true, speed: false, other: false };

 function activeYKeys() {
 return yOrder.filter(function (k) { return activeY[k]; });
 }

 function xTickCb(v) {
 if (xMode === "dist") return parseFloat(v.toFixed(2)) + " km";
 if (xMode === "clock") return fmtClock(v);
 return fmtElapsed(v);
 }

 
 var crosshairPlugin = {
 id: "mapCrosshair",
 afterDraw: function (ch) {
 if (mapHoverIdx === null) return;
 var meta = ch.getDatasetMeta(0);
 var point = meta.data[mapHoverIdx];
 if (!point) return;
 var ctx = ch.ctx;
 ctx.save();
 ctx.beginPath();
 ctx.moveTo(point.x, ch.chartArea.top);
 ctx.lineTo(point.x, ch.chartArea.bottom);
 ctx.lineWidth = 1.5;
 ctx.strokeStyle = trackColor;
 ctx.setLineDash([4, 4]);
 ctx.stroke();
 ctx.restore();
 }
 };

 function buildDatasetsAndScales() {
 var keys = activeYKeys();
 var scales = {
 x: {
 type: "linear",
 min: xMin(), max: xMax(),
 title: { display: true, text: xMode === "dist" ? "Distance (km)" : xMode === "clock" ? "Time of day" : "Time" },
 ticks: { callback: xTickCb, maxTicksLimit: 12 },
 

 afterBuildTicks: xMode !== "clock" ? undefined : function (scale) {
 var span = scale.max - scale.min, step = 180;
 [15, 30, 60, 120, 180].some(function (st) { step = st; return span / st &lt;= 10; });
 var ticks = [];
 for (var v = Math.ceil(scale.min / step) * step; v &lt;= scale.max; v += step) ticks.push({ value: v });
 scale.ticks = ticks;
 }
 }
 };
 var datasets = keys.map(function (key, i) {
 var m = Y_METRICS[key];
 var scaleId = "y_" + key;
 scales[scaleId] = {
 type: "linear",
 position: i % 2 === 0 ? "left" : "right",
 title: { display: true, text: m.label() },
 ticks: { callback: m.tick },
 grid: { drawOnChartArea: i === 0 }
 };
 return {
 yAxisID: scaleId,
 data: pts.map(function (p, idx) { return { x: xVal(p), y: m.value(idx) }; }),
 borderColor: m.color,
 backgroundColor: m.color + "33",
 fill: keys.length === 1,
 pointRadius: 0,
 tension: 0.2,
 borderWidth: 2
 };
 });
 return { datasets: datasets, scales: scales };
 }

 function buildChart() {
 var built = buildDatasetsAndScales();
 var min = xMin();
 var max = xMax();

 if (chart) chart.destroy();

 chart = new Chart(document.getElementById(id + "-chart"), {
 type: "line",
 plugins: [crosshairPlugin],
 data: { datasets: built.datasets },
 options: {
 responsive: true,
 parsing: false,
 animation: false,

 onClick: function (event, activeElements, ch) {
 var els = activeElements.length
 ? activeElements
 : ch.getElementsAtEventForMode(event.native || event, "index", { intersect: false }, false);
 if (!els.length) return;
 window.dispatchEvent(new CustomEvent("gpx-flyto", { detail: { hash: hash, idx: els[0].index } }));
 },

 onHover: function (event, activeElements) {
 if (!activeElements.length) return;
 window.dispatchEvent(new CustomEvent("gpx-hover", { detail: { hash: hash, idx: activeElements[0].index, source: "graph" } }));
 },

 interaction: { mode: "index", intersect: false },

 plugins: {
 legend: { display: false },
 tooltip: {
 mode: "index",
 intersect: false,
 callbacks: {
 title: function (items) {
 var e = pts[items[0].dataIndex].elapsedMin;
 return fmtElapsed(e) + (hasTime ? " (" + fmtClock(startClock + e) + ")" : "");
 },
 label: function (item) {
 var i = item.dataIndex;
 return [
 "Distance: " + pts[i].dist.toFixed(2) + " km",
 "Elevation: " + Math.round(pts[i].ele) + " m",
 "Speed: " + (Math.round(speed[i] * 10) / 10) + " km/h"
 ];
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, mode: "x" },
 pan: { enabled: true, mode: "x" },
 limits: { x: { min: min, max: max, minRange: (max - min) * 0.02 } }
 }
 },

 scales: built.scales
 }
 });
 }

 buildChart();

 
 var btnXDist = document.getElementById(id + "-btn-x-dist");
 var btnXTime = document.getElementById(id + "-btn-x-time");
 var btnXClock = document.getElementById(id + "-btn-x-clock");
 if (!hasTime) btnXClock.style.display = "none";
 var btnYEle = document.getElementById(id + "-btn-y-ele");
 var btnYSpeed = document.getElementById(id + "-btn-y-speed");
 var btnYOther = document.getElementById(id + "-btn-y-other");
 var btnReset = document.getElementById(id + "-btn-reset");
 var yButtons = { ele: btnYEle, speed: btnYSpeed, other: btnYOther };

 function refreshXButtons() {
 paintButton(btnXDist, xMode === "dist" ? NEUTRAL_ACTIVE : null);
 paintButton(btnXTime, xMode === "time" ? NEUTRAL_ACTIVE : null);
 paintButton(btnXClock, xMode === "clock" ? NEUTRAL_ACTIVE : null);
 }

 function refreshYButtons() {
 btnYOther.textContent = otherMode() === "dist" ? "Distance" : "Time";
 yOrder.forEach(function (key) {
 paintButton(yButtons[key], activeY[key] ? Y_METRICS[key].active : null);
 });
 }

 function switchX(newMode) {
 if (newMode === xMode) return;
 xMode = newMode;
 refreshXButtons();
 refreshYButtons(); 
 buildChart();
 }

 function toggleY(key) {
 var willBeActive = !activeY[key];
 if (!willBeActive &amp;&amp; activeYKeys().length === 1) return; 
 activeY[key] = willBeActive;
 refreshYButtons();
 buildChart();
 }

 refreshXButtons();
 refreshYButtons();

 btnXDist.addEventListener("click", function () { switchX("dist"); });
 btnXTime.addEventListener("click", function () { switchX("time"); });
 btnXClock.addEventListener("click", function () { switchX("clock"); });
 btnYEle.addEventListener("click", function () { toggleY("ele"); });
 btnYSpeed.addEventListener("click", function () { toggleY("speed"); });
 btnYOther.addEventListener("click", function () { toggleY("other"); });
 btnReset.addEventListener("click", function () { chart.resetZoom(); });
 });
}).catch(function (err) { console.error("gpx-graph:", err); });
&lt;/script&gt;
&lt;/p&gt;</description></item></channel></rss>