forked from emeeks's block: Ch. 7 Fig. 12 - D3.js in Action
forked from everett-wetchler's block: Globe pan
forked from emeeks's block: Ch. 7 Fig. 12 - D3.js in Action
forked from everett-wetchler's block: Globe pan
This is the code for Chapter 7, Figure 12 from D3.js in Action showing how to create a globe using the d3.geo.orthographic() projection as well as a modified zoom that changes the rotate of the projection based on mouse behavior.
label | population | country | x | y | |
---|---|---|---|---|---|
San Francisco | 750000 | USA | 37 | -122 | |
Fresno | 500000 | USA | 36 | -119 | |
Lahore | 12500000 | Pakistan | 31 | 74 | |
Karachi | 13000000 | Pakistan | 24 | 67 | |
Rome | 2500000 | Italy | 41 | 12 | |
Naples | 1000000 | Italy | 40 | 14 | |
Rio | 12300000 | Brazil | -22 | -43 | |
Sao Paolo | 12300000 | Brazil | -23 | -46 |
<html> | |
<head> | |
<title>D3 in Action Chapter 7 - Example 5</title> | |
<meta charset="utf-8" /> | |
<script src="http://d3js.org/d3.v3.min.js" type="text/JavaScript"></script> | |
<script src="http://d3js.org/d3.geo.projection.v0.min.js" type="text/JavaScript"></script> | |
<script src="http://d3js.org/queue.v1.min.js" type="text/JavaScript"></script> | |
<script src="http://d3js.org/colorbrewer.v1.min.js" type="text/JavaScript"></script> | |
</head> | |
<style> | |
svg { | |
border: 1px solid gray; | |
} | |
.countries { | |
fill: red; | |
fill-opacity: .5; | |
stroke: black; | |
stroke-width: 1px; | |
} | |
</style> | |
<body> | |
<div id="viz"> | |
</div> | |
<div id="controls" /> | |
</body> | |
<footer> | |
<script> | |
var width = 800; | |
var height = 500; | |
d3.select("#viz").append("svg").attr("width", width).attr("height", height); | |
queue() | |
.defer(d3.json, "world.geojson") | |
.defer(d3.csv, "cities.csv") | |
.await(function(error, file1, file2) { createMap(file1, file2); }); | |
function createMap(countries, cities) { | |
expData = countries; | |
projection = d3.geo.orthographic() | |
.scale((Math.min(width,height) / 2)) | |
.translate([width / 2, height / 2]) | |
.clipAngle(90); | |
geoPath = d3.geo.path().projection(projection); | |
var mapZoom = d3.behavior.zoom().translate(projection.translate()).scale(projection.scale()).on("zoom", zoomed); | |
d3.select("svg").call(mapZoom); | |
var rotateScale = d3.scale.linear() | |
.domain([0, width]) | |
.range([0, 180]); | |
var currentRotation = projection.rotate(); | |
d3.select("svg").on("mousedown", startRotating).on("mouseup", stopRotating); | |
function startRotating() { | |
var pStart = d3.mouse(this); | |
var currentLat = (currentRotation[1] + 360) % 360; | |
var xFactor = (currentLat > 90 && currentLat < 270) ? -1.0 : 1.0; | |
d3.select("svg").on("mousemove", function() { | |
var p = d3.mouse(this); | |
projection.rotate([ | |
currentRotation[0] + xFactor * (rotateScale(p[0] - pStart[0])), | |
currentRotation[1] + rotateScale(pStart[1] - p[1])]); | |
zoomed(); | |
}); | |
} | |
function stopRotating() { | |
currentRotation = projection.rotate(); | |
console.log(currentRotation); | |
d3.select("svg").on("mousemove", null); | |
} | |
function zoomed() { | |
var currentRotate = projection.rotate()[0]; | |
projection.scale(mapZoom.scale()); | |
d3.selectAll("path.graticule").attr("d", geoPath); | |
d3.selectAll("path.countries").attr("d", geoPath); | |
d3.selectAll("circle.cities") | |
.attr("cx", function(d) {return projection([d.y,d.x])[0]}) | |
.attr("cy", function(d) {return projection([d.y,d.x])[1]}) | |
.style("display", function(d) {return parseInt(d.y) + currentRotate < 90 && parseInt(d.y) + currentRotate > -90 ? "block" : "none"}) | |
} | |
function zoomButton(zoomDirection) { | |
if (zoomDirection == "in") { | |
var newZoom = mapZoom.scale() * 1.5; | |
var newX = ((mapZoom.translate()[0] - (width / 2)) * 1.5) + width / 2; | |
var newY = ((mapZoom.translate()[1] - (height / 2)) * 1.5) + height / 2; | |
} | |
else if (zoomDirection == "out") { | |
var newZoom = mapZoom.scale() * .75; | |
var newX = ((mapZoom.translate()[0] - (width / 2)) * .75) + width / 2; | |
var newY = ((mapZoom.translate()[1] - (height / 2)) * .75) + height / 2; | |
} | |
mapZoom.scale(newZoom).translate([newX,newY]) | |
zoomed(); | |
} | |
d3.select("#controls").append("button").on("click", function () {zoomButton("in")}).html("Zoom In"); | |
d3.select("#controls").append("button").on("click", function () {zoomButton("out")}).html("Zoom Out"); | |
featureSize = d3.extent(countries.features, function(d) {return geoPath.area(d)}); | |
countryColor = d3.scale.quantize().domain(featureSize).range(colorbrewer.Reds[7]); | |
var graticule = d3.geo.graticule(); | |
d3.select("svg").append("path") | |
.datum(graticule) | |
.attr("class", "graticule line") | |
.attr("d", geoPath) | |
.style("fill", "none") | |
.style("stroke", "lightgray") | |
.style("stroke-width", "1px"); | |
d3.select("svg").selectAll("path.countries").data(countries.features) | |
.enter() | |
.append("path") | |
.attr("d", geoPath) | |
.attr("class", "countries") | |
.style("fill", function(d) {return countryColor(geoPath.area(d))}) | |
.style("stroke-width", 1) | |
.style("stroke", "black") | |
.style("opacity", .5) | |
.on("mouseover", centerBounds) | |
.on("mouseout", clearCenterBounds) | |
d3.select("svg").selectAll("circle").data(cities) | |
.enter() | |
.append("circle") | |
.attr("class", "cities") | |
.style("fill", "black") | |
.style("stroke", "white") | |
.style("stroke-width", 1) | |
.attr("r", 3) | |
.attr("cx", function(d) {return projection([d.y,d.x])[0]}) | |
.attr("cy", function(d) {return projection([d.y,d.x])[1]}) | |
function centerBounds(d,i) { | |
thisBounds = geoPath.bounds(d); | |
thisCenter = geoPath.centroid(d); | |
d3.select("svg") | |
.append("rect") | |
.attr("class", "bbox") | |
.attr("x", thisBounds[0][0]) | |
.attr("y", thisBounds[0][1]) | |
.attr("width", thisBounds[1][0] - thisBounds[0][0]) | |
.attr("height", thisBounds[1][1] - thisBounds[0][1]) | |
.style("fill", "none") | |
.style("stroke-dasharray", "5 5") | |
.style("stroke", "black") | |
.style("stroke-width", 2) | |
.style("pointer-events", "none") | |
d3.select("svg") | |
.append("circle") | |
.attr("class", "centroid") | |
.attr("r", 5) | |
.attr("cx", thisCenter[0]).attr("cy", thisCenter[1]) | |
.style("pointer-events", "none") | |
} | |
function clearCenterBounds() { | |
d3.selectAll("circle.centroid").remove(); | |
d3.selectAll("rect.bbox").remove(); | |
} | |
} | |
</script> | |
</footer> | |
</html> |