An example zooming behavior for two-level choropleth maps. Click on a region to zoom in and navigate between regions. Inspired by http://bl.ocks.org/mbostock/2206590
Last active
March 29, 2018 09:43
-
-
Save serdaradali/11276011 to your computer and use it in GitHub Desktop.
Choropleth map zoom
This file contains hidden or 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
*.[oa] | |
*~ |
This file contains hidden or 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"> | |
<style> | |
.background { | |
fill: none; | |
pointer-events: all; | |
} | |
.choropleth { | |
margin: 20px 20px; | |
} | |
.cities { | |
stroke: #fff; | |
stroke-width: 1px; | |
stroke-linejoin: round; | |
} | |
.districts { | |
stroke: #fff; | |
stroke-width: 0.5px; | |
stroke-linejoin: round; | |
} | |
.choro_outline { | |
opacity: 1; | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
.tooltip { | |
width: 60px; | |
height:20px; | |
position: absolute; | |
opacity: 0; | |
padding: 6px 8px; | |
font: 8px/10px Arial, Helvetica, sans-serif; | |
background: white; | |
background: rgba(255,255,255,0.8); | |
box-shadow: 0 0 15px rgba(0,0,0,0.2); | |
border-radius: 5px; | |
} | |
.zoomout { | |
width: 50px; | |
height: 50px; | |
background: url("left-arrow-icon.jpg"); | |
background-size: contain; | |
position: absolute; | |
top: 10px; | |
left: 30px; | |
z-index: 5; | |
} | |
#switch{ | |
position:absolute; | |
left:650px; | |
top:130px; | |
width: 120px; | |
height: 30px; | |
padding: 6px 8px; | |
font: 14px/16px Arial, Helvetica, sans-serif; | |
} | |
</style> | |
<body> | |
<link type="text/css" rel="stylesheet" href="jquery.switchButton.css"/> | |
<script src="http://d3js.org/queue.v1.min.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> | |
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script> | |
<script src="jquery.switchButton.js"></script> | |
<div id="map"></div> | |
<div id="switch"><input type="checkbox" id = "levelSwitch" value="1" checked></div> | |
<script> | |
function randomData(n) | |
{ | |
var data = []; | |
for(var i=0; i<n;i++) | |
{ | |
data[i] = Math.floor((Math.random())*100); | |
} | |
return data; | |
} | |
var width=960,height=600,centered; | |
var div = document.createElement("div"); | |
var cityMapJSON = {}, districtMapJSON={}; | |
var selectedCity,selectedCityDistricts, selectedNeighborCity, selectedNeighborCityDistricts, districtVotes,turkeyTotal = [0.44,0.28,0.15,0.09,0.03,0.01]; | |
var cityScale = d3.scale.quantile(),districtScale = d3.scale.quantile(), barScale = d3.scale.linear().domain([0,100]).range([0,200]); | |
var colors7 = ["rgb(254,229,217)", "rgb(252,187,161)", "rgb(252,146,114)", "rgb(251,106,74)", "rgb(239,59,44)", "rgb(203,24,29)", "rgb(153,0,13)"]; | |
var projection = d3.geo.mercator() | |
.scale(1) | |
.translate([0, 0]); | |
var zoomedIn = false; | |
var path = d3.geo.path() | |
.projection(projection); | |
var svg,citiG,barSvg,districtGregCenters = []; | |
var cityPath,districtPath; | |
var country,cityFeatures,districtFeatures; | |
var mapMode = 1; // variable holding detail type: 1=city level, -1=district level | |
queue() | |
.defer(d3.json, "turkeyCitiesTopoSimplified.json") | |
.defer(d3.json, "turkeyDistrictsTopoSimplified.json") | |
.await(readMapData); | |
function readMapData(error,cityJSON,districtJSON) { | |
var cityData = randomData(cityJSON.objects.turkeyCities.geometries.length),districtData=randomData(districtJSON.objects.turkeyDistricts.geometries.length); | |
var svg = d3.select("#map").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.attr("class", "choropleth"); | |
var div = d3.select("#statDetails").html("<p align=\"center\">Turkiye Geneli</p>"); | |
svg.append("rect") | |
.attr("class", "background") | |
.attr("width", width) | |
.attr("height", height) | |
.on("click", clickedCity); | |
cityScale.domain(cityData) | |
.range(d3.range(6)); | |
country = topojson.mesh(cityJSON, cityJSON.objects.turkeyCities), | |
cityFeatures = topojson.feature(cityJSON, cityJSON.objects.turkeyCities).features, | |
districtFeatures = topojson.feature(districtJSON, districtJSON.objects.turkeyDistricts).features; | |
var b = path.bounds(country), | |
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height), | |
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2]; | |
projection | |
.scale(s) | |
.translate(t); | |
g = svg.append("g"); | |
// draw city geometry and fill colors according to city election data | |
cityPath = g.selectAll("path.cities") | |
.data(cityFeatures) | |
.enter().append("path") | |
.attr("class","cities") | |
.attr("fill", function(d,i){ | |
return colors7[cityScale(cityData[i])];}) | |
.attr("d", path) | |
.on("click", clickedCity) | |
.on("mouseover", function(d,i) { | |
d3.select("#statDetails").html("<p align=\"center\">" + d.properties.NAME_1 + "</p>"); | |
d3.select(this.parentNode.appendChild(this)).transition().duration(300) | |
.style({'stroke-opacity':1,'stroke':'#000'}); | |
}) | |
.on("mouseout", function(d,i) { | |
d3.select("#statDetails").html("<p align=\"center\">Turkiye Geneli</p>"); | |
d3.select(this.parentNode.appendChild(this)).transition().duration(300) | |
.style({'stroke-opacity':1,'stroke':'#FFF'}) | |
}); | |
districtScale.domain(districtData) | |
.range(d3.range(6)); | |
// draw district geometry and fill colors according to district election data. Hidden in the beginning | |
districtPath = g.selectAll("path.districts") | |
.data(districtFeatures) | |
.enter().append("path") | |
.attr("class","districts") | |
.attr("fill", function(m,i){ | |
return colors7[districtScale(districtData[i])];}) | |
.attr("d", path) | |
.on("mouseover", function(d,i) { | |
d3.select("#statDetails").html("<p align=\"center\">" + d.properties.NAME_2 + "," + d.properties.NAME_1 +"</p>"); | |
d3.select(this.parentNode.appendChild(this)).transition().duration(300) | |
.style({'stroke-opacity':1,'stroke':'#000'}); | |
}) | |
.on("mouseout", function(d,i) { | |
d3.select("#statDetails").html("<p align=\"center\">" + d.properties.NAME_1 + "</p>"); | |
d3.select(this.parentNode.appendChild(this)).transition().duration(300) | |
.style({'stroke-opacity':1,'stroke':'#FFF'}); | |
}) | |
.style("display","none"); | |
$("#levelSwitch").change(function(){ | |
mapMode *= -1; | |
if(mapMode == 1) | |
{ | |
d3.selectAll("path.districts").style("display","none"); | |
d3.selectAll("path.cities").style("display","inline"); | |
} | |
else if(mapMode == -1) | |
{ | |
d3.selectAll("path.cities").style("display","none"); | |
d3.selectAll("path.districts").style("display","inline"); | |
} | |
}); | |
} | |
function clickedCity(d,i) { | |
var x, y, k; | |
// if not already zoomed, zoom in. If clicked on a neighbor, change focus | |
if (d && centered !== d) { | |
d3.select("#switch").style("display","none"); | |
// fade out all cities except the clicked one | |
g.selectAll("path.cities") | |
.filter(function(m){ | |
return m.properties.ID_1 != d.properties.ID_1}) | |
.style("fill-opacity", 0.1); | |
// store selected city & its districts for later use | |
selectedCity = d3.selectAll("path.cities").filter(function(m){ return m == d}); | |
selectedCityDistricts = g.selectAll("path.districts") | |
.filter(function(m){return m.properties.ID_1 == d.properties.ID_1}); | |
// set translate&scale parameters for zooming into city | |
var centroid = path.centroid(d); | |
x = centroid[0]; | |
y = centroid[1]; | |
k = 4; | |
centered = d; | |
// if zoomed into a city from country level | |
if(!zoomedIn) | |
{ | |
// create(or show) zoom out div | |
d3.select("#map").append("div") | |
.attr("class","zoomout") | |
.on("click",clickedCity); | |
} | |
else //already zoomed in, clicked to a neighbor city | |
{ | |
// display previously hidden cities | |
d3.selectAll("path.cities").style("display","inline"); | |
} | |
// hide all districts | |
g.selectAll("path.districts") | |
.style("display","none") | |
.style("fill-opacity",0.1); | |
// Only display selected city districts | |
selectedCityDistricts.style("display","inline"); | |
// zoomed into district level | |
zoomedIn = true; | |
} | |
else { // zoom out | |
d3.select("#switch").style("display","inline"); | |
zoomedIn = false; | |
x = width / 2; | |
y = height / 2; | |
k = 1; | |
centered = null; | |
// hide all visible districts | |
selectedCityDistricts.style("display","none"); | |
g.selectAll("path.districts") | |
.style("fill-opacity",1); | |
// set display for hidden city | |
selectedCity.style("display","inline"); | |
// restore all city opacities | |
g.selectAll("path.cities") | |
.transition() | |
.duration(750) | |
.style("fill-opacity", 1); | |
// remove zoom out div | |
d3.select("div.zoomout").remove(); | |
} | |
g.selectAll("path") | |
.classed("active", centered && function(d) { return d === centered; }); | |
g.transition() | |
.duration(750) | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")") | |
.each("end",function(d) | |
{ | |
if(zoomedIn) | |
{ | |
// show all districts of selected city | |
selectedCityDistricts | |
.style("display","inline") | |
.style("fill-opacity", 1); | |
// slowly make zoomed city disappear so that disctrict can be seen. At the end, disable city for allowing mouse operations on districts | |
selectedCity.style("display","none") | |
.style("fill-opacity", 0.1) | |
.style("stroke-width", 1.5 / k + "px"); | |
} | |
}); | |
} | |
$("#levelSwitch").switchButton({ | |
on_label: 'District', | |
off_label: 'City', | |
checked: false | |
}); | |
d3.select(self.frameElement).style("height", height + "px"); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment