function make_elevation_chart(trkpts, map) {
let size = d3.min([window.innerWidth, window.innerHeight]);
let w = size < 600 ? 0.98 * size : 600;
let h = w / 3;
let pad_left = 40;
let pad_bottom = 20;
let svg = d3
.create("svg")
.attr("class", "elevation_chart")
.attr("width", w)
.attr("height", h);
svg
.append("rect")
.attr("width", w)
.attr("height", h)
.attr("fill", "white")
.attr("opacity", 0.5);
let lngLats = trkpts;
let elevation_path = [];
let cummulative_length = 0;
let R = 3;
let distance_to_point_map = new Map();
for (let i = R; i < trkpts.length - R; i++) {
cummulative_length =
cummulative_length + d3.geoDistance(lngLats[i - 1], lngLats[i]) * 3958.8;
let elevation = d3.mean(trkpts.slice(i - R, i + R).map((pt) => pt.ele));
elevation_path.push([cummulative_length, elevation]);
distance_to_point_map.set(cummulative_length, lngLats[i]);
}
let path_length = elevation_path.slice(-1)[0][0];
let elevations = trkpts.map((o) => o.ele);
let min_elevation = d3.min(elevations);
let max_elevation = d3.max(elevations);
let elevation_pad = 500;
let elevation_path2 = [[0, min_elevation - elevation_pad]]
.concat(elevation_path)
.concat([[path_length, min_elevation - elevation_pad]]);
let x_scale = d3.scaleLinear().domain([0, path_length]).range([pad_left, w]);
let y_scale = d3
.scaleLinear()
.domain([min_elevation - elevation_pad, max_elevation + elevation_pad])
.range([h - pad_bottom, 0]);
let pts_to_path = d3
.line()
.x((d) => x_scale(d[0]))
.y((d) => y_scale(d[1]));
svg
.append("path")
.attr("d", pts_to_path(elevation_path2))
.style("stroke", "black")
.style("stroke-width", "0px")
.style("stroke-linejoin", "round")
.style("opacity", 0.7)
.style("fill", "#eee");
svg
.append("path")
.attr("d", pts_to_path(elevation_path))
.style("stroke", "black")
.style("stroke-width", "3px")
.style("stroke-linejoin", "round")
.style("fill", "none");
svg
.append("g")
.attr("transform", `translate(0, ${h - pad_bottom})`)
.call(d3.axisBottom(x_scale));
svg
.append("g")
.attr("transform", `translate(${pad_left})`)
.call(d3.axisLeft(y_scale));
let position_marker = svg
.append("g")
.attr("class", "position_marker")
.style("opacity", 0);
position_marker
.append("line")
.attr("stroke-width", "1px")
.attr("stroke", "black")
.attr("y1", 0)
.attr("y2", h)
.attr("x1", w / 2)
.attr("x2", w / 2);
position_marker
.append("circle")
.attr("r", "5px")
.attr("cx", w / 2)
.attr("cy", h / 2)
.attr("fill", "#0ff")
.attr("stroke", "black");
let lengths = elevation_path.map((pt) => pt[0]);
svg
.on("touchmove", (e) => e.preventDefault()) // prevent scrolling
.on("pointerenter", function () {
position_marker.style("opacity", 1);
map.addSource("point", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Point",
coordinates: trkpts[20]
}
}
});
map.addLayer({
id: "point",
type: "circle",
source: "point",
paint: {
"circle-radius": 6,
"circle-color": "cyan"
// "stroke-color": "black"
}
});
})
.on("pointermove", function (evt) {
evt.preventDefault();
let distance = x_scale.invert(d3.pointer(evt)[0]);
let i = binarySearch(lengths, distance);
let x = x_scale(distance);
let elevation = elevations[i];
let y = y_scale(elevation);
position_marker.select("line").attr("x1", x).attr("x2", x);
position_marker.select("circle").attr("cx", x).attr("cy", y);
// global.gpx_path.getLayers()[1].setLatLng(trkpts[i]);
map.getSource("point").setData({
type: "Feature",
geometry: {
type: "Point",
coordinates: trkpts[i]
}
});
})
.on("pointerleave", function () {
position_marker.style("opacity", 0);
map.removeLayer("point");
map.removeSource("point");
});
if (trkpts[0].time) {
svg
.append("text")
.attr("x", 50)
.attr("y", 20)
.text(formatTime(trkpts[0].time));
}
svg
.append("text")
.attr("x", 50)
.attr("y", 40)
.text(`${d3.format("0.1f")(path_length)} miles`);
d3.select(elevation_chart_container).selectAll(".elevation_chart").remove();
d3.select(elevation_chart_container).append(() => svg.node());
}