block by dhoboy 7e1dfa5ed8ebebef8dfe

Jody's Journey: final map

Full Screen

This is a map of Jody from Tap Twice Tea‘s Journey across Asia. See a live version of this page! This map was made with d3. Thanks to Mike Bostock for the great Let’s Make a Map tutorial.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* map styles */
.country { fill: #B8B8B8; }
.interior-boundary {
	fill: none;
	stroke: #FFFFFF;
	stroke-dasharray: 2,2;
	stroke-linejoin: round;
}
.place, .place-label { fill: #444; }
text {
	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
	font-size: 10px;
	font-weight: bold;
}
.route {
	fill: none;
	stroke: red;
	stroke-width: 3px;
	stroke-linecap: round;
	stroke-opacity: .8;
}
/* title and legend styles */
.legend { 
	position: relative;
	margin: 0px 10px 80px 10px; 
}
.key {
	position: absolute;
	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
	font-size: 12px;
	font-weight: bold;
	margin: 0px 15px 0px 0px;
}
.Kolkata { left: 30px; }
.Darjeeling { left: 160px; }
.Nepal { left: 305px; }
.Myanmar { left: 425px; }
.Cambodia { left: 570px; }
.Laos { left: 725px; }
.Xishuangbanna { left: 850px; }
.Anhui { left: 30px; }
.Fujian { left: 160px; }
.Wuyi { left: 290px; }
.Qingdao { left: 390px; }
.Lishan { left: 515px; }
.Nantou { left: 640px; }
.Alishan { left: 775px; }
.Taiwan { left: 905px; }
.Shizuoka { left: 30px; }
.Uji { left: 180px; }
.Kyoto { left: 260px; }
.credit { left: 940px; top: 40px; font-size: 10px;}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.js"></script>
<script>
  
var width = 1000,
	height = 590,
	margin = { top: 40, left: 30, right: 150, bottom: 50 };

// draw svg
var svg = d3.select("body").append("svg")
	.attr("width", width)
	.attr("height", height);

// map projection
var projection = d3.geo.patterson()
	.center([58,54])
    .scale(520)
    .translate([0,0])
    .precision(.1);

// path generator
var path = d3.geo.path()
	.projection(projection)
	.pointRadius(3);

// coordinates are added to this when constructRoute is called 
var journey = [
	  {name:"Kolkata", dates:"Feb. 17-22"}, 
	  {name:"Darjeeling", dates:"Feb. 23-28"},
	  {name:"Nepal", dates:"March 1-6"},
	  {name:"Myanmar", dates:"March 7-10"},
	  {name:"Cambodia", dates:"March 11-13"},
	  {name:"Laos", dates:"March 14-18"},
	  {name:"Xishuangbanna", dates:"March 18-21"},
	  {name:"Anhui", dates:"March 22-28"},
	  {name:"Fujian", dates:"March 29-31"},
	  {name:"Wuyi", dates:"April 1-6"},
	  {name:"Qingdao", dates:"April 6-10"},
	  {name:"Lishan", dates:"April 11-14"},
	  {name:"Nantou", dates:"April 15-18"},
	  {name:"Alishan", dates:"April 18-20"},
	  {name:"Taiwan", dates:"April 21-26"},
	  {name:"Shizuoka", dates:"April 27-May 1"},
	  {name:"Uji", dates:"May 2-4"},
	  {name:"Kyoto", dest: "18", dates:"May 4-8"}
	];

d3.json("asia.json", function(error, asia) {
  
  var subunits = topojson.feature(asia, asia.objects.subunits),
  	  places = topojson.feature(asia, asia.objects.places),
	  route = constructRoute(journey, places.features);
	  	
  // draw the map
  svg.selectAll(".subunit")
  	  .data(subunits.features)
    .enter()
      .append("path")
      .attr("class", function(d) { return "country"; })
      .attr("d", path);
 
  // draw interior boundaries between countries
  svg.append("path")
  	.datum(topojson.mesh(asia, asia.objects.subunits, function(a, b) {
  		return a !== b;
  	}))
  	.attr("d", path)
  	.attr("class", "interior-boundary");
  
  // draw curved paths between places 
  svg.selectAll(".route")
  	  .data(route.coordinates)
  	.enter()
  	  .append("path")
  	  .attr("class", "route")
  	  .attr("d", function(d) {
  		var source = projection(d[0]);
  		var target = projection(d[1]);	
  		var dx = target[0] - source[0],
      		dy = target[1] - source[1],
      		dr = Math.sqrt(dx * dx + dy * dy);      	
  		return "M" + source[0] + "," + source[1] + "A" + dr + "," + dr + " 0 0,1 " + target[0] + "," + target[1];
  	  });
  
  // draw places on map
  svg.append("path")
  	.datum(places)
  	.attr("d", path)
  	.attr("class", "place");
 
  // set all place labels
  svg.selectAll(".place-label")
  	  .data(places.features)
    .enter()
      .append("text")
      .attr("class", function(d) { return "place-label " + d.properties.name; })
      .attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
	  .attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
	  .attr("dy", ".35em")
	  .style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start" : "end"; })
	  .text(function(d) { return d.properties.name; });
	  
  // tweak specific labels to remove overlap with other labels and routh paths
  svg.select(".place-label.Kyoto")
  	.attr("x", -33)
  	.attr("dy", "-.15em");
    
  svg.select(".place-label.Uji")
  	.attr("dy", "-.15em");
    
  svg.select(".place-label.Anhui")
  	.attr("x", 8);
  	
  svg.select(".place-label.Wuyi")
  	.attr("x", -29);
  
  svg.select(".place-label.Fujian")
  	.attr("x", -34);

  svg.select(".place-label.Nantou")
  	.attr("x", -40)
  	.attr("dy", ".01em");

  svg.select(".place-label.Alishan")
   	.attr("dy", ".7em");

  svg.select(".place-label.Lishan")
   	.attr("dy", ".5em");
  
  svg.select(".place-label.Nepal")
  	.attr("x", -33)
  	.attr("dy", ".25em");
  
  // add a key
  var key = d3.select("body").append("div")
	.attr("class", "legend");
  	
  	key.selectAll(".key")
  	  .data(journey)
  	.enter()
  	  .append("div")
  	  .attr("class", function(d) {
  	  	return "key " + d.name; 
  	  })
	  .style("top", function(d,i) {
	  	if (i >= 7 && i <= 14) {
	  		return "20px";
	  	} else if (i >= 15) {
	  		return "40px";
	  	}
	  })
	  .text(function(d) { 
	  	return d.name + ": " + d.dates; 
	  });
	  
    key.append("div")
	  .attr("class", "key credit")
      .text("Map by: ")
	  .append("a")
	  .attr("href", "//bl.ocks.org/dhoboy")
	  .attr("target", "_blank")
	  .text("dhoboy");
});

function constructRoute(journey, places) {	
  
  // get the coords of destinations in order
  var r = {  
  	type: "LineString", 
  	coordinates: []
  };
  
  journey.forEach(function(d) { 
	places.forEach(function(p) {
		if (d.name == p.properties.name) {
			d.coords = p.geometry.coordinates;
			p.dest = d.dest;
		}
	})
	r.coordinates.push(d.coords);
  });
 
  // form coords for curved path drawing 
  var route = {
  	type: "MultiLineString",
  	coordinates: []
  };
 
  for (var i = 0; i < r.coordinates.length - 1; i++) {
  	route.coordinates.push([r.coordinates[i], r.coordinates[i+1]]);
  }
  
  return route;
}
</script>