|
<!DOCTYPE html> |
|
<meta charset='utf-8'> |
|
<style> |
|
body { |
|
background: white; |
|
} |
|
svg { |
|
font: 12px sans-serif; |
|
} |
|
.title { |
|
font: 20px sans-serif; |
|
} |
|
.compass { |
|
font: 17px sans-serif; |
|
text-anchor: end; |
|
fill: #aaa; |
|
} |
|
text { |
|
text-anchor: middle; |
|
} |
|
</style> |
|
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<body> |
|
<script> |
|
// Basic dimensions. |
|
var margin = { top: 70, right: 40, bottom: 40, left: 40 }, |
|
html = document.documentElement, |
|
width = html.clientWidth - margin.left - margin.right, |
|
height = html.clientHeight - margin.top - margin.bottom, |
|
aspect = height / width; |
|
|
|
var format = d3.time.format("%Y-%m-%d"); |
|
|
|
function parseRow ( d ) { |
|
return { |
|
'time': format.parse(d.GMT), |
|
'speed': +d[' Mean Wind SpeedKm/h'], |
|
'dir': +d.WindDirDegrees |
|
}; |
|
} |
|
|
|
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 ]})` ); |
|
|
|
var legend = svg.append( 'g' ) |
|
.attr( 'transform', 'translate('+[ width/2, -margin.top * .70 ]+')' ); |
|
legend.append( 'text' ) |
|
.attr( 'class', 'title' ) |
|
.attr( 'dy', '.32em' ) |
|
.text( '2015 Reykjavík Wind Map' ); |
|
legend.append( 'text' ) |
|
.attr( 'y', 20 ) |
|
.attr( 'dy', '.32em' ) |
|
.text( 'A path travelled by an object pushed around by the wind.' ); |
|
|
|
svg.append( 'text' ) |
|
.attr( 'y', -margin.top / 2 ) |
|
.attr( 'x', width ) |
|
.attr( 'class', 'compass' ) |
|
.text( '↑N' ); |
|
|
|
d3.csv( 'data.csv', parseRow, data => { |
|
|
|
data.forEach((d, i) => { |
|
var pos = data[i - 1] || { x: 0, y: 0 }, |
|
a = ( d.dir - 90 - 180 ) * Math.PI / 180, |
|
r = d.speed, |
|
dx = r * Math.cos( a ), |
|
dy = r * Math.sin( a ); |
|
d.x = pos.x + dx; |
|
d.y = pos.y + (dy / aspect); |
|
}); |
|
|
|
var first = data[0], |
|
last = data[data.length - 1]; |
|
|
|
var yrange = d3.extent( data, d => d.y ), |
|
xrange = d3.extent( data, d => d.x ), |
|
yspan = Math.abs( yrange[0] - yrange[1] ), |
|
xspan = Math.abs( xrange[0] - xrange[1] ), |
|
span = Math.max( yspan, xspan ), |
|
yoffs = (( span - yspan ) / 2), |
|
xoffs = (( span - xspan ) / 2); |
|
|
|
var x = d3.scale.linear() |
|
.domain([ xrange[0] - xoffs, xrange[0] + span - xoffs ]) |
|
.range([ 0, width ]); |
|
var y = d3.scale.linear() |
|
.domain([ yrange[0] - yoffs, yrange[0] + span - yoffs ]) |
|
.range([ 0, height ]); |
|
|
|
var line = d3.svg.line().x( d => x( d.x ) ).y( d => y( d.y ) ); |
|
|
|
var path = svg.append( 'path' ) |
|
.attr( 'd', line( data ) ) |
|
.style( 'stroke', 'black' ) |
|
.style( 'fill', 'none' ); |
|
|
|
var totalLength = path.node().getTotalLength(); |
|
path.attr( 'stroke-dasharray', totalLength + ' ' + totalLength ) |
|
.attr( 'stroke-dashoffset', totalLength ) |
|
.transition() |
|
.duration( 4000 ) |
|
.ease( 'linear' ) |
|
.attr( 'stroke-dashoffset', 0 ) |
|
.each( 'end', plotFinal ); |
|
|
|
svg.append( 'text' ) |
|
.attr( 'x', x( first.x ) ) |
|
.attr( 'y', y( first.y ) ) |
|
.attr( 'class', 'label' ) |
|
.attr( 'dy', '1.1em' ) |
|
.text( 'Jan. 1st' ); |
|
svg.append( 'circle' ) |
|
.attr( 'cx', x( first.x ) ) |
|
.attr( 'cy', y( first.y ) ) |
|
.attr( 'class', 'start' ) |
|
.attr( 'r', 2 ); |
|
|
|
function plotFinal () { |
|
svg.append( 'text' ) |
|
.attr( 'x', x( last.x ) ) |
|
.attr( 'y', y( last.y ) ) |
|
.attr( 'class', 'label' ) |
|
.attr( 'dy', '-.32em' ) |
|
.text( 'Dec. 31st' ); |
|
svg.append( 'circle' ) |
|
.attr( 'cx', x( last.x ) ) |
|
.attr( 'cy', y( last.y ) ) |
|
.attr( 'class', 'start' ) |
|
.attr( 'r', 2 ); |
|
} |
|
|
|
}); |
|
</script> |