index.html
<html>
<body>
<link rel="stylesheet" type="text/css" href="normalize.css">
<style type="text/css">
.avgPaceLabel,
.avgPaceLabel2,
.runTimeLabel,
.runTimeLabel2{
font-family:arial,sans-serif;
font-size:11px;
fill:#666;
}
.avgPaceLabel2,.runTimeLabel2{
font-weight:bold;
font-size:13px;
fill:rgb(60,146,186);
}
.paceBar{
stroke:rgba(0,0,0,.2);
stroke-width:1px;
shape-rendering:crispEdges;
stroke-dashArray: 2,4;
display:none;
}
.paceLabel{
font-family:arial,sans-serif;
font-size:11px;
fill:#aaa;
}
.mileMarkerText{
font-family:arial,sans-serif;
font-size:11px;
fill:#777;
}
.mileMarker{
stroke:rgba(0,0,0,.3);
stroke-width:1px;
shape-rendering:crispEdges;
stroke-dashArray: 2,4;
}
.block{
}
.positionCircle{
fill:#999;
}
#container{
width:1000px;
margin: 20px auto;
position:relative;
}
.paceline{
stroke:rgba(0,0,100,0);
stroke-width:1.5px;
}
.avg{
stroke: rgb(163,210,225);
opacity:.9;
shape-rendering:crispEdges;
stroke-width:1px;
fill:none;
}
.goal{
stroke:#f9a7a8;
opacity:1;
shape-rendering:crispEdges;
stroke-width:1px;
fill:none;
}
.area.above {
fill: rgb(163,210,225);
fill-opacity:.7;
}
.area.below {
fill: #f9a7a8;
fill-opacity:.8;
}
.elevationGraph{
fill: #ccc;
fill-opacity:.3;
}
.paceline-above{
stroke:#900;
}
.hed{
font-size:25px;
text-align:center;
margin-left:-85px;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
}
#faster{
font-size: 11px;
color: #999;
width: 50px;
height: 38px;
position: absolute;
z-index: 100;
background: url(arrowbottom.gif) no-repeat 20px top;
left: 53px;
top: 269px;
padding-top: 21px;
text-align: center;
text-transform:uppercase;
display:none;
}
#slower{
font-size: 11px;
color: #999;
width: 50px;
height: 38px;
position: absolute;
z-index: 100;
background: url(arrowtop.gif) no-repeat 20px bottom;
left: 53px;
top: 210px;
padding-top: 21px;
text-align: center;
text-transform:uppercase;
display:none;
}
.boxRight{
height:40px;
width:4px;
border:1px solid #ddd;
border-right:none;
position:absolute;
}
.boxLeft{
height:50px;
width:10px;
border:1px solid #ddd;
border-left:none;
position:absolute;
}
.altitude{
fill:#999;
fill-opacity:.7;
}
</style>
<div id="container">
<h3 class="hed">Race Pace Analysis</h3>
<div id="chart"></div>
<div id="faster">Faster</div>
<div id="slower">Slower</div>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="streams.js"></script>
<script>
var margin = {top: 20, right: 156, bottom: 150, left: 100 },
width = 900 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var chart = d3.select("#container").append("svg")
.attr('xml:space', "preserve")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear().range([0, width]).domain(d3.extent(desiredStream, function(d){ return d.x }))
var y = d3.scale.linear().range([height, 40]).domain(d3.extent(desiredStream, function(d){ return d.y }))
var xDistance = d3.scale.linear().range([0, width]).domain(d3.extent(distanceStream, function(d){ return d }))
var avg = d3.mean(desiredStream, function(d){ return d.y });
var goal = 480;
var altX = d3.scale.linear().range([0, width]).domain(d3.extent(elevationStream, function(d){ return d.x }))
var altY = d3.scale.linear().range([height, 70]).domain(d3.extent(elevationStream, function(d){ return d.y }))
chart.selectAll('.mileMarker')
.data([1,2,3,4,5,6,7,8]).enter()
.append('line')
.attr('class', 'mileMarker')
.style('opacity',0)
.attr('x1', function(d){ return xDistance(d) })
.attr('x2', function(d){ return xDistance(d) })
.attr('y1', 0)
.attr('y2', height)
.attr('clip-path', 'url(#clip-above)')
.transition()
.delay(function(d, i) { return i / 8 * 700; })
.style('opacity',1)
chart.selectAll('.mileMarkerText')
.data([1,2,3,4,5,6,7,8]).enter()
.append('text')
.attr('class', 'mileMarkerText')
.style('opacity',0)
.attr('x', function(d){ return xDistance(d) })
.attr('dx', -3)
.attr('dy', -6)
.attr('y', 0)
.text(function(d){ if(d !== 1){ return d+'mi.' }else{ return d+'mile'} })
.transition()
.delay(function(d, i) { return i / 8 * 700; })
.style('opacity',1)
var desiredPaces = [8,10,12,14],
maxln = d3.max(desiredPaces),
paddingDesiredPaces = desiredPaces.map(function(d){ return (" " + d).slice((-2)) })
chart.selectAll('.avgPaceLabel')
.data([avg])
.enter()
.append('text')
.attr('class','avgPaceLabel')
.attr('text-anchor','start')
.attr('x',width+11)
.attr('dy',-16)
.attr('y',function(d){ return y(d) })
.text('AVG. PACE')
chart.selectAll('.avgPaceLabel2')
.data([avg])
.enter()
.append('text')
.attr('class','avgPaceLabel2')
.attr('text-anchor','start')
.attr('x',width+11)
.attr('y',function(d){ return y(d) })
.attr('dy',1)
.text('9:07min/mile')
chart.selectAll('.runTimeLabel')
.data([goal])
.enter()
.append('text')
.attr('class','avgPaceLabel')
.attr('text-anchor','end')
.attr('x',0)
.attr('dx',-10)
.attr('y',function(d){ return y(d) })
.attr('dy',19)
.text('GOAL PACE')
chart.selectAll('.runTimeLabel2')
.data([goal])
.enter()
.append('text')
.attr('class','avgPaceLabel2')
.attr('text-anchor','end')
.attr('x',0)
.attr('dx',-10)
.attr('y',function(d){ return y(d) })
.attr('dy',5)
.text('8:15min/mi')
chart.selectAll('.paceBar')
.data(desiredPaces).enter()
.append('line')
.attr('class', 'paceBar')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', function(d){ return y(d * 60) })
.attr('y2', function(d){ return y(d * 60) })
var line = d3.svg.area()
.interpolate("basis")
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var area = d3.svg.area()
.interpolate("basis")
.x(function(d) { return x(d.x); })
.y1(function(d) { return y(goal); });
var altLine = d3.svg.area()
.interpolate("basis")
.x(function(d) { return altX(d.x); })
.y(function(d) { return altY(d.y); });
var altArea = d3.svg.area()
.interpolate("basis")
.x(function(d) { return altX(d.x); })
.y0(function(d) { return y(goal); })
.y1(function(d) { return altY(d.y); });
var meanData = desiredStream.map(function(d){ return { x: d.x, y: goal }; })
var startData = desiredStream.map(function(d){ return { x: paceData[0].x, y: goal }; })
var t0 = chart.transition().duration(600)
var pline = chart.append("path")
.datum(startData)
.attr("class", "paceline animateMe")
.attr("d", line)
.style('opacity',0)
var avgLine = d3.select('svg').append("svg:line")
.attr("x1", margin.left)
.attr("x2", 0)
.attr('class', 'avg animateMe')
.attr("y1", y(avg)+margin.top + 1)
.attr("y2", y(avg)+margin.top + 1)
.style('opacity',0)
.transition()
.duration(600)
.style('opacity',1)
.attr('x2',width + margin.left)
var avgLine = d3.select('svg').append("svg:line")
.attr("x1", margin.left)
.attr("x2", 0)
.attr('class', 'goal animateMe')
.attr("y1", y(goal)+margin.top)
.attr("y2", y(goal)+margin.top)
.style('opacity',0)
.transition()
.duration(600)
.style('opacity',1)
.attr('x2',width + margin.left)
pline.datum(meanData);
var t1 = chart.transition().duration(900).delay(200)
t1.selectAll('.paceline').attr('d',line).style('opacity',1)
var altGraph = chart.append("path")
.datum(elevationStream)
.attr("id", "elevationGraph")
.attr("class", "elevationGraph")
.attr("d", altArea);
var c1_clip = chart.append("clipPath")
.datum(meanData)
.attr("id", "clip-above")
.append("path")
.attr("d", area.y0(0));
var c1_clip2 = chart.append("path")
.datum(meanData)
.attr("id", "area-above")
.attr("class", "area above")
.attr("clip-path", "url(#clip-above)")
.attr("d", area.y0(function(d) { return y(d.y); }));
var c3 = chart.append("path")
.datum(meanData)
.attr("class", "area below")
.attr("clip-path", "url(#clip-below)")
.attr("d", area);
var c4 = chart.append("clipPath")
.datum(meanData)
.attr("id", "clip-below")
.append("path")
.attr("d", area.y1(height))
pline.datum(desiredStream);
c1_clip.datum(desiredStream);
c1_clip2.datum(desiredStream);
var t2 = t1.transition()
t2.selectAll('.paceline').attr('d',line);
t2.selectAll('#clip-above').attr("d", area.y0(0));
t2.selectAll('#area-above').attr("d", area.y0(function(d) { return y(d.y); }));
c3.datum(desiredStream);
c4.datum(desiredStream);
t2.selectAll('#clip-below').attr("d", area.y1(height - margin.top - margin.bottom));
t2.selectAll('.area.below').attr("d", area);
d3.select('#container').append('div')
.attr('class','boxRight')
.attr('style','left:'+(margin.left + width + 3)+'px; top: '+(margin.top+y(avg) + 21)+'px;')
</script>
</body>
</html>