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
forked from MaciejKus's block: d3 map with states and countries
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body {
background-color: white;
}
svg {
border: 2px solid black;
background-color: grey;
}
.selected {
fill: red;
}
.boundary {
fill: #b84d32;
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="https://d3js.org/d3.v3.min.js"></script>
<script src="https://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");
//circle
overlay.draw = function() {
var projection = this.getProjection(),
padding = 10;
var marker = layer.selectAll("svg")
.data(data)
.each(transform) // update existing markers
.enter().append("svg")
.each(transform)
.attr("class", "marker");
marker.append("circle")
.attr("r", 4.5)
.attr("cx", padding)
.attr("cy", padding);
//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>
Modified http://d3js.org/d3.v3.min.js to a secure url
Modified http://d3js.org/topojson.v1.min.js to a secure url
https://d3js.org/d3.v3.min.js
https://d3js.org/topojson.v1.min.js