import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs"; // Small-multiples profile chart: one row per metric the track actually has // (elevation always, speed/cadence/heart rate only when present — plenty of // GPX files have none of those), sharing one x-axis (distance) and one // crosshair. Elevation/speed/cadence/heart rate live on wildly different // scales, so this is deliberately several single-series charts stacked // rather than one chart with several y-axes (see dataviz skill: no // dual-axis charts). // // SVG viewBox is a fixed 1000-unit-wide coordinate space (not real pixels) // stretched to the container via CSS — mouse interaction converts // clientX through the container's current bounding rect on every move, so // this stays correct across container width without redrawing, but a // browser window resize while the panel is open won't reflow the drawn // paths until it's reopened. Acceptable for a detail panel that's normally // open briefly; add a resize listener if that turns out to matter. const VIEWBOX_WIDTH = 1000; const SERIES = [ { key: "elevation_meters", label: "Elevation", unit: "m", color: "#2a78d6", height: 110, area: true }, { key: "speed_kmh", label: "Speed", unit: "km/h", color: "#1baf7a", height: 70, area: false }, { key: "cadence_rpm", label: "Cadence", unit: "rpm", color: "#eda100", height: 70, area: false }, { key: "heart_rate_bpm", label: "Heart rate", unit: "bpm", color: "#e34948", height: 70, area: false }, ]; export class TrackProfilePanel { constructor(map) { this._map = map; this._profile = null; this._marker = new Marker({ color: "#7209b7" }); } mount() { this._panel = document.getElementById("profile-panel"); this._title = this._panel.querySelector(".profile-panel__title"); this._stats = this._panel.querySelector(".profile-panel__stats"); this._chartsContainer = this._panel.querySelector(".profile-panel__charts"); this._panel.querySelector(".profile-panel__close").addEventListener("click", () => this.hide()); } show(properties) { const profile = properties.profile; if (!profile || profile.length === 0) return; this._profile = profile.map((point) => ({ ...point, speed_kmh: typeof point.speed_mps === "number" ? point.speed_mps * 3.6 : null })); this._maxDistance = this._profile.at(-1).distance_meters || 1; this._title.textContent = properties.name || "Track"; this._stats.textContent = statsText(properties); this._chartsContainer.innerHTML = ""; this._tooltip = document.createElement("div"); this._tooltip.className = "profile-panel__tooltip"; 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