Skip to content

Instantly share code, notes, and snippets.

@serdaradali
Created April 29, 2014 02:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save serdaradali/11389411 to your computer and use it in GitHub Desktop.
Save serdaradali/11389411 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#map{
background-image:url("img/parchment2.jpg");
}
</style>
<body>
<link type="text/css" rel="stylesheet" href="worldMap.css"/>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src = "zoomableWorldMap.js"></script>
<div id="map" style="width: 1296px;"></div>
<script>
var sample,sample2,cMap,s_m;
function randomData()
{
var data = [];
for(var i=0; i<41;i++)
{
data[i] = Math.floor((Math.random())*100);
}
return data;
}
var dataArr = randomData();
var q = queue();
q.defer(d3.json, "data/worldTopo.json");
q.defer(d3.tsv,"data/boats.tsv");
q.await(readMapData);
function readMapData(error,countries,boats) {
cMap = WorldMap()
.mapJSON(countries);
// create array of arrays for boats
//var totalBoats = boats[boats.length-1].id-boats[0].id+1;
var pathCount = [205,166,206,231,455,443,209,229,118,177,73,275,205,176,245,312,214,335,444,64,397,216,262,393,293,257,123,400,217,184,180,165,245,403,51,181,322,271,411,353,208,331,271,374,230,327,459,82,325,152,361,352,83,366,266,203,133,378,93,120,203,62,184,191,374,225,287,231,171,149,274,167,343,500,367,94,148,166,147,365,246,133,300];
d3.select("#map")
.datum([boats,pathCount])
.call(cMap);
//map = new choroplethMap(collection,sample);
//map2 = new choroplethMap(collection,sample);
};
</script>
</body>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#globe{
background: #fcfcfa;
position: absolute;
top:70px;
width: 900px;
height: 500px;
margin-left: 350px;
}
#animDate{
background: #fcfcfa;
width: 700px;
height: 50px;
margin-left: 575px;
}
#about{
position: absolute;
top:20px;
left:10px;
width: 200px;
height: 200px;
font: 300 46px "Helvetica Neue";
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #222;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.year.label {
font: 300 46px "Helvetica Neue";
fill: #ddd;
}
</style>
<body>
<link type="text/css" rel="stylesheet" href="worldMap.css"/>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<div id="about">BARCELONA WORLD RACE 2010</div>
<div id="animDate"></div>
<div id="globe"></div>
<script>
var width = 680,
height = 680;
var boatPath = [],circles=[];
var pathCount = [205,166,206,231,455,443,209,229,118,177,73,275,205,176,245,312,214,335,444,64,397,216,262,393,293,257,123,400,217,184,180,165,245,403,51,181,322,271,411,353,208,331,271,374,230,327,459,82,325,152,361,352,83,366,266,203,133,378,93,120,203,62,184,191,374,225,287,231,171,149,274,167,343,500,367,94,148,166,147,365,246,133,300];
var projection = d3.geo.orthographic()
.scale(270)
.translate([width / 2, height / 2])
.clipAngle(90)
.precision(.1);
var zoom = d3.behavior.zoom()
.scaleExtent([1,6])
.on("zoom",zoomed);
var drag = d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0], y: -r[1]}; })
.on("drag", dragged)
.on("dragstart", dragstarted)
.on("dragend", dragended);
var path = d3.geo.path()
.projection(projection);
var pathLine = d3.svg.line()
.interpolate("cardinal")
.x(function(d) {
return projection([d.Lon, d.Lat])[0]; })
.y(function(d) {
return projection([d.Lon, d.Lat])[1]; });
var colors = d3.scale.category20();
var graticule = d3.geo.graticule();
var svg = d3.select("#globe").append("svg")
.attr("width", width)
.attr("height", height);
var pathG = svg.append("g");
var format = d3.time.format("%Y-%m-%d");
var label = d3.select("#animDate").append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", 10)
.attr("x", 10)
.text(format(new Date(1293801017000)));
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(zoom)
pathG.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
pathG.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
pathG.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
pathG.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var q = queue();
q.defer(d3.json, "data/worldTopo.json");
q.defer(d3.tsv,"data/boats.tsv");
q.await(readMapData);
function readMapData(error,world,boatFile) {
// to render meridians/graticules on top of lands, use insert which adds new path before graticule in the selection
pathG.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path)
pathG.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
pathG.attr("transform","translate(-1671.8701554571276,-859.1978421866252)scale(6)");
//drawBoatsAndPaths(boatFile);
animateBoatPathTrail(findBoatTrajectory(2,boatFile),15000);
animateBoatPathTrail(findBoatTrajectory(0,boatFile),13000);
animateBoatPathTrail(findBoatTrajectory(6,boatFile),12500);
animateBoatPathTrail(findBoatTrajectory(7,boatFile),13500);
label.transition()
.duration(15000)
.ease("linear")
.tween("text", tweenYear);
pathG.transition()
.delay(1000)
.duration(5500)
.ease("linear")
.attr("transform","translate(-66.55078689208563,-72.20082705511209)scale(1.1487080744842741)");
}
// apply transformations to map and all elements on it
function zoomed()
{
pathG.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//grids.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//geofeatures.select("path.graticule").style("stroke-width", 0.5 / d3.event.scale);
pathG.selectAll("path.boundary").style("stroke-width", 0.5 / d3.event.scale);
}
function dragstarted(d)
{
//stopPropagation prevents dragging to "bubble up" which triggers same event for all elements below this object
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged() {
projection.rotate([d3.event.x, -d3.event.y]);
pathG.selectAll("path").attr("d", path);
}
function dragended(d)
{
d3.select(this).classed("dragging", false);
}
function drawBoatsAndPaths(boats)
{
/* draw a boat path
for faster line drawing(without interpolation), instead of pathLine use .attr("d", function(d) { return "M" + d.map(projection).join("L"); });
*/
var temp = 0;
boatPaths = pathG.selectAll("path.boatPath")
.data(pathCount)
.enter().append("path")
.attr("d",function(d,i){
var l;
if(i == 0)
l = pathLine(boats.slice(0,d));
else if(i<20)
l = pathLine(boats.slice(temp,d+temp));
var trX = projection([boats[temp].Lon,boats[temp].Lat])[0];
var trY = projection([boats[temp].Lon,boats[temp].Lat])[1];
circles[i] = pathG.append("circle")
.attr("r", 2)
.attr("transform", "translate(" + trX + ","+ trY +")")
.attr("fill","blue");
temp += d;
return l;
})
.attr("class","boatPath")
.style("display","none");
}
function animateBoats()
{
for(var i=0;i<pathCount.length;i++)
{
circles[i].transition()
.duration(10000)
.attrTween("transform",translateAlong(boatPaths[0][i]));
}
}
function translateAlong(path)
{
var l = path.getTotalLength();
return function(d, i, a)
{
return function(t)
{
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
// creates trajectory made of line segments from our raw data. When we create json, this might change.
function findBoatTrajectory(bid,boats)
{
var temp = 0;
var trajArr = [];
if(bid == 0)
trajArr = boats.slice(0,pathCount[bid]);
else
{
var cum = 0;
for(var i=0;i<bid;i++)
cum += pathCount[i];
trajArr = boats.slice(cum,cum+pathCount[bid]);
}
return trajArr;
}
// animates boat trajectory. This is in use right now.
function animateBoatPathTrail(trajectory,speed)
{
var circle;
var tmpBoatTraj = pathG.append("path")
.attr("class","boatPath")
.attr("d",function(){
return pathLine(trajectory);
})
.on("mouseover",pathClicked);
var totalLength = tmpBoatTraj.node().getTotalLength();
tmpBoatTraj
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(speed)
.ease("linear")
.attr("stroke-dashoffset", 0);
// boat is not sync with path animation at the moment
/*var trX = projection([trajectory[0].Lon,trajectory[0].Lat])[0];
var trY = projection([trajectory[0].Lon,trajectory[0].Lat])[1];
circle = pathG.append("circle")
.attr("r", 2)
.attr("transform", "translate(" + trX + ","+ trY +")")
.attr("fill","blue");
circle.transition()
.duration(10000)
.attrTween("transform",translateAlong(tmpBoatTraj.node()));*/
}
function animateBoatVariableSpeed(circle,path,speed)
{
circle.transition()
.duration(500/+speed)
.attrTween("transform", translateAlong(path.node()))
.each("end", function()
{
if(index<boats.length)
{
//animateBoatVariableSpeed(circle,boatPath[index++], boats[index].speed);
}
});
}
function tweenYear() {
var date = d3.interpolate(0, 3042615000);
return function(t) {
displayDate(date(t)); };
}
// Updates the display to show the specified year.
function displayDate(msec) {
label.text(format(new Date(msec+1293801017000)));
}
function pathClicked()
{
d3.event.sourceEvent.stopPropagation();
d3.select(this).attr("opacity",0);
}
d3.select(self.frameElement).style("height", height + "px");
</script>
.choropleth {
}
.graticule {
fill: none;
stroke: #777;
stroke-opacity: .5;
stroke-width: .5px;
}
.overlay {
fill: none;
pointer-events: all;
}
/* region specific style */
.choro_mesh {
/*stroke: #9C1400;*/
stroke: #4A2A1B;
fill: #D8C5B6;
}
.boatPath {
fill: none;
stroke-width: 1px;
stroke-opacity: .8;
/*stroke-dasharray: 3,2;*/
stroke: #CB301E;
}
function WorldMap() {
var mapJSON = {};
var zoom = d3.behavior.zoom()
.scaleExtent([1,6])
.on("zoom",zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var geofeatures,grids,boatPath = [],circles=[];
var graticule = d3.geo.graticule();
var width = 1296,
height = 703;
var data;
var index = 0;
var projection = d3.geo.mercator();
var path = d3.geo.path()
.projection(projection);
var svg;
var totalGeoEl = 0;
function chart(selection)
{
selection.each(function(dataArr)
{
boats = dataArr[0];
pathCount = dataArr[1];
//Select svg if it exists in this DOM element
svg = d3.select(this).selectAll("svg.choropleth").data([mapJSON.objects]);
svg.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "choropleth");
//uncomment below if you want to draw meridians and parallels
/*grids = svg.append("g").append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);*/
geofeatures = svg.append("g");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(zoom);
var country = topojson.mesh(mapJSON, mapJSON.objects.countries);
var countries = topojson.feature(mapJSON, mapJSON.objects.countries);
//var state = countries.features.filter(function(d) { return +d.id === 7; })[0];
projection
.scale(1)
.translate([0, 0]);
// Calculate the scale and translate values automatically from boundaries of shapefile
var b = path.bounds(country),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// update projection
projection
.scale(s)
.translate(t);
// bind the geometry data and draw the world
/*geofeatures.selectAll("path.choro_mesh")
.data(countries.features)
.enter().append("path")
.attr("class","choro_mesh")
.attr("d", path);*/
// function to draw path lines on the map
var pathLine = d3.svg.line()
.interpolate("cardinal")
.x(function(d) {
return projection([d.Lon, d.Lat])[0]; })
.y(function(d) {
return projection([d.Lon, d.Lat])[1]; });
var colors = d3.scale.category20();
/* draw a boat path
for faster line drawing(without interpolation), instead of pathLine use .attr("d", function(d) { return "M" + d.map(projection).join("L"); });
*/
var temp = 0;
boatPaths = geofeatures.selectAll("path.boatPath")
.data(pathCount)
.enter().append("path")
.attr("d",function(d,i){
var l;
if(i == 0)
l = pathLine(boats.slice(0,d));
else if(i<20)
l = pathLine(boats.slice(temp,d+temp));
var trX = projection([boats[temp].Lon,boats[temp].Lat])[0];
var trY = projection([boats[temp].Lon,boats[temp].Lat])[1];
circles[i] = geofeatures.append("circle")
.attr("r", 2)
.attr("transform", "translate(" + trX + ","+ trY +")")
.attr("fill","blue");
temp += d;
return l;
})
.attr("class","boatPath")
.style("stroke",function(d,i){return colors[i];})
.style("display","none");
temp = 0;
for(var i=0;i<pathCount.length;i++)
{
circles[i].transition()
.duration(10000)
.attrTween("transform",translateAlong(boatPaths[0][i]));
}
/*for(var j=0;j<3;j++)
{
//index = 0;
animateBoatConstantSpeed(circles[j],boatPath[j]);
//animateBoatVariableSpeed(circles[j],boatPath[j][index++],boats[index].speed);
}*/
});
function animateBoatVariableSpeed(circle,path,speed)
{
circle.transition()
.duration(500/+speed)
.attrTween("transform", translateAlong(path.node()))
.each("end", function()
{
if(index<boats.length)
{
animateBoatVariableSpeed(circle,boatPath[index++], boats[index].speed);
}
});
}
function animateBoatConstantSpeed(circle,path)
{
circle.transition()
.duration(10000)
.attrTween("transform", translateAlong(path.node()));
}
function translateAlong(path)
{
var l = path.getTotalLength();
return function(d, i, a)
{
return function(t)
{
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
}
// apply transformations to map and all elements on it
function zoomed()
{
geofeatures.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//grids.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//geofeatures.select("path.graticule").style("stroke-width", 0.5 / d3.event.scale);
geofeatures.selectAll("path.choro_mesh").style("stroke-width", 0.8 / d3.event.scale);
}
function dragstarted(d)
{
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d)
{
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d)
{
d3.select(this).classed("dragging", false);
}
/**********************************
ACCESSORS & MODIFIERS OF THIS CHART
**********************************/
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;};
chart.intervalID = function(value) {
if (!arguments.length) return intervalID;
intervalID = value;
return chart;};
chart.mapJSON = function(value) {
if (!arguments.length) return mapJSON;
mapJSON = value;
return chart;};
chart.projection = function(value) {
if (!arguments.length) return projection;
projection = value;
return chart;};
chart.mapGroup = function(value){
if (!arguments.length) return geoFeatures;
geoFeatures = value;
return chart;};
return chart;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment