block by pstuffa 9da86640eb0f2090e00843bbbde7b854

Running VII

Full Screen

Elevation Profile by Distance Ran - Jan 2015 to August 2015

I got all the training run data from my Garmin Fenix 2. Notice some of the funkiness in the elevation profile readings. I added some notes of where I was training.

Part of my deck on ultrarunning. Give it a minute, as there’s about 730,000 rows.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
  background-color: #fff;
  font-size: 14px;
}

h2 {
  /*font 14px sans-serif;*/
  font-color: #bfbfbf;
}

.axis path,
.axis line {
  fill: none;
  stroke: #fff;
  shape-rendering: crispEdges;
}

.x.axis path {
  fill:none;
  stroke:#fff;
  shape-rendering: crispEdges;
}

.domain {
  stroke: none;
  fill: none;
}
.y.axis line {
  stroke: #000;
  stroke-opacity: .1;
  stroke-width: 2;
}

.runs {
  stroke:#000;
  fill: none;
  /*stroke-opacity: .25;*/
  stroke-width: .65;
}


</style>
<body>
<script src="https://d3js.org/d3.v3.js"></script>
<script>

var margin = {top: 20, right: 20, bottom: 100, left: 60},
    width = 900 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var elScale = d3.scale.linear()
    .domain([0,15000])
    .range([height,0]);

var distanceScale = d3.scale.linear()
    .domain([0.0,53])
    .range([0,width]);

var elAxis = d3.svg.axis()
    .scale(elScale)
    .tickValues([1000, 5000, 10000, 15000])
    .orient("left");

var distanceAxis = d3.svg.axis()
    .scale(distanceScale)
    .tickValues([8, 20, 30, 50])
    .orient("bottom");

var colorScale = d3.scale.ordinal()
    .domain(["new york", "bear mountain", "boulder", "leadville", "pike"])
    .range(["#045a8d", "#000000", "#8c6bb1", "#3690c0", "#000000"])

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


d3.json("processed.json", function(error, nestedHrData) {

console.log(nestedHrData)
  // // only want to look at data with HR
  // hrData = data.filter(function(d) { return d['heart_rate(bpm)'] > 0; })
  //              .filter(function(d) { return d['distance(m)'] > 0; })

  // hrData.forEach(function(d) {
  //   d['key'] = d['timestamp(s)'].split(" ")[0];
  //   d['heart_rate(bpm)'] = +d['heart_rate(bpm)'];
  //   d['distance(m)'] = +d['distance(m)']/1609.34;
  //   d['altitude(m)'] = +d['altitude(m)']*3.28084;
  // })

  // nestedHrData = d3.nest()
  //   .key(function(d) { return d['key']; })
  //   .entries(hrData)
  //   .filter(function(d) { return d.key.length == 10})

  svg.append("g")
    .attr("class", "x axis")
    .attr("transform","translate(0," + height + ")")
    .call(distanceAxis)
    .selectAll("text")
    .filter(function(d,i) { return d == 50 })
    .text(function(d) { return d + " mile run"})

  svg.append("g")
    .attr("class", "y axis")
    .call(elAxis);

  svg.selectAll(".runs")
      .data(nestedHrData)
    .enter().append("path")
      .attr("class","runs")
      .attr("d", function(d) { return d.value.line; })
      .style("stroke","#000")
      .style("stroke", function(d) { 
        if(d.value.maxEl > 14000) {
          return colorScale("pike")
        } else if(d.value.minEl > 9000) {
          return colorScale("leadville")
        } else if(d.value.maxEl > 9000) {
          return colorScale("leadville")
        } else if(d.value.minEl > 5000) {
          return colorScale("boulder")
        } else if(d.value.maxEl > 1000) {
          return colorScale("bear")
        } else if(d.value.minEl > -1000) {
          return colorScale("new york city")
        }
      })
      .on("mouseenter", function() { d3.select(this).style("stroke","red")})
      .on("mouseleave", function() { d3.select(this).style("stroke","#000")});

  svg.append("text")
    .attr("class","locations")
    .text("Pikes Peak")
    .style("fill", colorScale("pike"))
    .attr("y", elScale(14550))
    .attr("x", distanceScale(10));

  svg.append("text")
    .attr("class","locations")
    .text("Manitou Springs")
    .style("fill", colorScale("pike"))
    .attr("y", elScale(7400))
    .attr("x", distanceScale(23.5));

  svg.append("text")
    .attr("class","locations")
    .text("Leadville")
    .style("fill", colorScale("leadville"))
    .attr("y", elScale(11200))
    .attr("x", distanceScale(19));

  svg.append("text")
    .attr("class","locations")
    .text("Boulder")
    .style("fill", colorScale("boulder"))
    .attr("y", elScale(6000))
    .attr("x", distanceScale(19));

  svg.append("text")
    .attr("class","locations")
    .style("text-align", "center")
    .text("Bear Mountain")
    .style("fill", colorScale("bear"))
    .attr("y", elScale(1400))
    .attr("x", distanceScale(25));

  svg.append("text")
    .attr("class","locations")
    .style("fill", colorScale("new york city"))
    .text("New York City")
    .attr("y", elScale(350))
    .attr("x", distanceScale(8));


});



</script>

package.json

{
  "name": "running_vii",
  "version": "1.0.0",
  "description": "Elevation Profile by Distance Ran - Jan 2015 to August 2015",
  "main": "run.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@gist.github.com/9da86640eb0f2090e00843bbbde7b854.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://gist.github.com/9da86640eb0f2090e00843bbbde7b854"
  },
  "homepage": "https://gist.github.com/9da86640eb0f2090e00843bbbde7b854",
  "dependencies": {
    "d3": "^4.4.1"
  }
}

run.js

var d3 = require("d3");
var fs = require("fs");


var margin = {top: 20, right: 20, bottom: 2, left: 60},
    width = 900 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var data = d3.csvParse(fs.readFileSync('./data.csv').toString())

//   // only want to look at data with HR
data = data.filter(function(d) { return d['heart_rate(bpm)'] > 0; })
             .filter(function(d) { return d['distance(m)'] > 0; })

data.forEach(function(d) {
  d['key'] = d['timestamp(s)'].split(" ")[0];
  d['heart_rate(bpm)'] = +d['heart_rate(bpm)'];
  d['distance(m)'] = +d['distance(m)']/1609.34;
  d['altitude(m)'] = +d['altitude(m)']*3.28084;
})

var elScale = d3.scaleLinear()
    .domain([0,15000])
    .range([height,0]);

var distanceScale = d3.scaleLinear()
    .domain([0.0,53])
    .range([0,width]);

var line = d3.line()
    // .interpolate("linear")
    .y(function(d) { return elScale(+d['altitude(m)']); })
    .x(function(d) { return distanceScale(d['distance(m)']); });

var nestedHrData = d3.nest()
    .key(function(d) { return d['key']; })
    .rollup(function(leaves) { 
      return { 
        'line': line(leaves), 
        'minEl': d3.min(leaves, d => d['altitude(m)']),
        'maxEl': d3.max(leaves, d => d['altitude(m)']) 
      } 
    })
    .entries(data);

fs.writeFileSync("processed.json", JSON.stringify(nestedHrData));