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="http://d3js.org/d3.v3.js"></script> | |
| <script src="http://d3js.org/d3.geo.projection.v0.min.js"></script> | |
| <script src="http://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", "http://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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment