Drag & drop geographic game.
Last active
August 29, 2015 14:10
-
-
Save hugolpz/48af10320a4ab3c3c539 to your computer and use it in GitHub Desktop.
Geography game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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="http://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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment