Skip to content

Instantly share code, notes, and snippets.

@MaciejKus
Last active March 17, 2023 18:18
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save MaciejKus/61e9ff1591355b00c1c1caf31e76a668 to your computer and use it in GitHub Desktop.
Save MaciejKus/61e9ff1591355b00c1c1caf31e76a668 to your computer and use it in GitHub Desktop.
d3 map with states and countries

A d3 world map which shows the states of Canada and the USA on zoom. Also has rotation, but only when scale is set to 1 (zoomed out).

I combined a world topo.json file with a states topo.json file using something like this:

node_modules/.bin/topojson --allow-empty -p name -o combined2.json -- data/countries.topo.json data/states_usa.topo.json
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body {
background-color: white;
}
svg {
border: 2px solid black;
background-color: white;
}
.selected {
fill: red;
}
.boundary {
fill: #DEB887;
stroke: black;
stroke-width: 1px;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
border-radius: 3px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
padding: .2em;
text-shadow: #f5f5f5 0 1px 0;
opacity: 0.9;
position: absolute;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 962,
rotated = 90,
height = 502;
//countries which have states, needed to toggle visibility
//for USA/ etc. either show countries or states, not both
var usa, canada;
var states; //track states
//track where mouse was clicked
var initX;
//track scale only rotate when s === 1
var s = 1;
var mouseClicked = false;
var projection = d3.geo.mercator()
.scale(153)
.translate([width/2,height/1.5])
.rotate([rotated,0,0]); //center on USA because 'murica
var zoom = d3.behavior.zoom()
.scaleExtent([1, 20])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
//track where user clicked down
.on("mousedown", function() {
d3.event.preventDefault();
//only if scale === 1
if(s !== 1) return;
initX = d3.mouse(this)[0];
mouseClicked = true;
})
.on("mouseup", function() {
if(s !== 1) return;
rotated = rotated + ((d3.mouse(this)[0] - initX) * 360 / (s * width));
mouseClicked = false;
})
.call(zoom);
function rotateMap(endX) {
projection.rotate([rotated + (endX - initX) * 360 / (s * width),0,0])
g.selectAll('path') // re-project path data
.attr('d', path);
}
//for tooltip
var offsetL = document.getElementById('map').offsetLeft+10;
var offsetT = document.getElementById('map').offsetTop+10;
var path = d3.geo.path()
.projection(projection);
var tooltip = d3.select("#map")
.append("div")
.attr("class", "tooltip hidden");
//need this for correct panning
var g = svg.append("g");
//det json data and draw it
d3.json("combined2.json", function(error, world) {
if(error) return console.error(error);
//countries
g.append("g")
.attr("class", "boundary")
.selectAll("boundary")
.data(topojson.feature(world, world.objects.countries).features)
.enter().append("path")
.attr("name", function(d) {return d.properties.name;})
.attr("id", function(d) { return d.id;})
.on('click', selected)
.on("mousemove", showTooltip)
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
})
.attr("d", path);
usa = d3.select('#USA');
canada = d3.select('#CAN');
//states
g.append("g")
.attr("class", "boundary state hidden")
.selectAll("boundary")
.data(topojson.feature(world, world.objects.states).features)
.enter().append("path")
.attr("name", function(d) { return d.properties.name;})
.attr("id", function(d) { return d.id;})
.on('click', selected)
.on("mousemove", showTooltip)
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
})
.attr("d", path);
states = d3.selectAll('.state');
});
function showTooltip(d) {
label = d.properties.name;
var mouse = d3.mouse(svg.node())
.map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(label);
}
function selected() {
d3.select('.selected').classed('selected', false);
d3.select(this).classed('selected', true);
}
function zoomed() {
var t = d3.event.translate;
s = d3.event.scale;
var h = 0;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
if(s === 1 && mouseClicked) {
rotateMap(d3.mouse(this)[0])
return;
}
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the stroke width based on zoom level
d3.selectAll(".boundary")
.style("stroke-width", 1 / s);
//toggle state/USA visability
if(s > 1.5) {
states
.classed('hidden', false);
usa
.classed('hidden', true);
canada
.classed('hidden', true);
} else {
states
.classed('hidden', true);
usa
.classed('hidden', false);
canada
.classed('hidden', false);
}
}
</script>
</body>
</html>
@Itachi3007
Copy link

Hey, I have a doubt, How do I use this code of yours to make this world map only focus on counties of US on clicking or zoom instead of US and Canada both .?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment