Drag & drop geographic game.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//code.jquery.com/jquery-2.1.0.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.1.0/topojson.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js"></script>
<script src="../js/wikiatlas.js"></script>
<style>svg { border: 1px solid #BBB; }
#hook {
margin:10px 10%;
padding:20px;
border:2px solid #d0d0d0;
border-radius: 5px;
height:100%;
clear:both;
}
#score{
float:right;
padding:4px 0;
font-weight:bold;
}
#info{
float:left;
font-weight:bold;
padding:4px 0;
width:80%;
}
#top{
margin:10px 10%;
}
.country{
display:none;
fill:#555;
stroke:#555;
}
.country-back{
fill:#ddd;
stroke:#fff;
}
.correct {
fill:#61aa61;
fill-opacity: 0.6;
}
.error{
fill:#B10000;
fill-opacity: 0.6;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
#top .start{
position: absolute;
top: 43%;
left: 45%;
background: #fff;
width: auto;
padding: 4px 10px;
text-align: center;
font-size: 140%;
}
#options{
margin-bottom:10px;
}
</style>
Check out the source or go <a href="//techslides.com/geography-game-with-d3/">back to article</a>.</p>
<div id="options">
<label>Country Borders?</label>
<input type="checkbox" id="borders" name="borders" />
</div>
<div id="info" class="start">Loading...</div>
<div id="score"></div>
<div class="clear"></div>
</div>
<div id="hook"></div>
<script>
/* ****************************************************** */
/* INIT************************************************** */
var width = document.getElementById('hook').offsetWidth-60;
var height = width / 2;
var topo,all,tries=0,score=0;
var projection = d3.geo.equirectangular().translate([0, 0]).scale(width / 2 / Math.PI);
var path = d3.geo.path().projection(projection);
var graticule = d3.geo.graticule();
/* ****************************************************** */
/* SVG ************************************************** */
var svg = d3.select("#hook").append("svg")
.attr("width", width)
.attr("height", height);
var outterg = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = outterg.append("g").attr("id", "innerg");
g.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
g.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
g.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
g.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
/* ****************************************************** */
/* GIS data injection *********************************** */
d3.json("./world-geogame-110m.json", function(error, world) {
topo = topojson.feature(world, world.objects.countries).features;
var neighbors = topojson.neighbors(world.objects.countries.geometries);
//filter and place borders into topo
topo = topo.filter(function(f,i){
if(path.area(f)>100){
var b = neighbors[i];
var t = 'Nearby... ';
if(b.length>0){
b.forEach(function(g){
t = t + topo[g].properties.name+', ';
});
f.properties.borders = t.substr(0,t.length-2);
} else {
f.properties.borders = 'No nearby countries';
}
return f;
}
});
//lets shuffle topo so that it shows different countries on each reload
d3.shuffle(topo);
var country = d3.select("#innerg").selectAll(".country").data(topo);
//ofsets (useless !)
var offsetL = document.getElementById('hook').offsetLeft+30;
var offsetT =document.getElementById('hook').offsetTop-30;
d3.select("#innerg").insert("path")
.datum(topojson.feature(world, world.objects.countries))
.attr("class", "country-back")
.attr("d", path);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return 'b'+d.id; })
.attr("title", function(d,i) { return d.properties.id; })
.style("stroke", "#111")
.attr("transform", function(d,i) {
var cx = Number(-1*path.centroid(d)[0]);
var cy = Number(-1*path.centroid(d)[1]);
var coord = [cx,cy];
return "translate(" + coord + ")"
})
.attr("x", function(d,i) {
var cx = Number(-1*path.centroid(d)[0]);
return cx;
})
.attr("y", function(d,i) {
var cy = Number(-1*path.centroid(d)[1]);
return cy;
})
.call(drag);
//all used to keep track
all = d3.selectAll(".country")[0];
d3.select("#info").text("World Map");
//start
go();
});
/* ****************************************************** */
/* GAME FUNCTION **************************************** */
/* Start ************************************************ */
function go(){
d3.select("#info").attr("class","start");
var missed = topo.length - all.length - score;
d3.select("#score").text("Score: "+score+"/"+topo.length+" | Missed: "+missed);
if(all.length>0){
var obj = d3.select(all.shift());
obj.style("display","block");
d3.select("#info").text(obj.attr("title"));
window.setTimeout(function(){
d3.select("#info").attr("class","");
}, 1500);
} else {
d3.select("#info").text("All Done. Restart to try again.");
d3.select(".country-back").transition().duration(1500).style("fill", function(){
return "#666";
});
}
}
/* Borders or not *************************************** */
d3.select("#borders").on("click",function(){
if(this.checked){ d3.select(".country-back").style("stroke","#ddd"); }
else { d3.select(".country-back").style("stroke","#fff"); }
});
/* ****************************************************** */
/* DRAG & DROP ****************************************** */
var drag = d3.behavior.drag()
.on("drag", dragmove)
.on("dragstart", dragstart)
.on("dragend", dragend);
function dragstart(d) {
//d3.event.sourceEvent.stopPropagation(); // silence other listeners
d.x = Number(d3.select(this).attr("x"));
d.y = Number(d3.select(this).attr("y"));
}
function dragmove(d) {
//only if its new country
if(d3.select('#b'+d.id).attr("class") == "country"){
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
});
}
}
function dragend(d) {
//only if its new country
if(d3.select('#b'+d.id).attr("class") == "country"){
//snap to position if you are 10 pixels or closer
if(d.x > -10 && d.x < 10 && d.y > -10 && d.y < 10 ){
tries = 0; score++;
d3.select(this)
.attr("class","correct")
.attr("transform", function(d,i){ return "translate([0,0])"; });
go(); // go next
} else { tries++; /* wrong ********************** */
if(tries == 1){ // move shape back to Null Island.
d3.select("#info").text(d.properties.borders.split(",").slice(0,3));
//put back in the middle!
var cx = Number(-1*path.centroid(d)[0]);
var cy = Number(-1*path.centroid(d)[1]);
var coord = [cx,cy];
d3.select(this).transition().duration(500).attr("transform", function(d,i){
return "translate(" + coord + ")"
});
} else { //wrong again, move element to correct position and mark red!
d3.select(this).transition().duration(500).attr("class","error").attr("transform", function(d,i){
return "translate([0,0])";
});
tries = 0;
go();//go to next!
}
}
}
}
</script>
</body>
</html>