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.
<!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>