Click States to test me !>Click States to test me !
Alternative of: Map zoom + static frame, with more details.
Exploration around automatic centering, zooming, and responsive framing. This gist have RESPONSIVE frames.
Here, only the width is provided, the minimal height is calculated from the svg hook’s width and the projected shape ratio. This tied frame is the relevant approach when waste of the webpage’s space is not acceptable.
Inputs are the topojson file, a target feature (shape), and the svg canevas width=300px
. Height, if missing (as in this demo), is recalculated elegantly. There is always a 5% margin on each side of the shape.
The innovative part compared to Mike Bostock’s Centering a map given a GeoJSON object is the responsive frame.
It is also projection proof. Which is not the case of the more common frame adjustment using the lat / lon dimensions’ ratio, which works only with the common but hightly deforming rectangular projections.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background: #C6ECFF;
border: 2px solid #AAA; }
.land { fill: #E0E0E0; }
.coast {
fill: none;
stroke: #0978AB;
stroke-width: 1.5px; }
.mesh{ /* borders*/
fill: none;
stroke: #646464;
stroke-width: 1.5px;
stroke-dasharray: 8,4; }
.focus {
fill: #FEFEE9;
stroke: #646464;
stroke-width: 2px; }
</style>
<body>
<script src="./d3.v3.min.js"></script>
<script src="./topojson.v1.min.js"></script>
<script>
var width = 500, centered, selected=51;
var svg = d3.select("body").append("svg")
.attr("width", width);
var projection = d3.geo.mercator()
.scale(1)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
d3.json("./us.json", function(error, us) {
var states = topojson.feature(us, us.objects.states),
state = states.features.filter(function(d) { return d.id === selected; })[0];
zoomon(state, width);
//landmass
svg.selectAll("path")
.data(states.features)
.enter().append("path")
.attr("class", function(d) {
if(d.id === selected ){ return "land focus"}
else { return "land";}
})
.attr("d", path)
.on("click", function(d,i){ clicked(d,width)});
//inland borders lines
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "mesh")
.attr("d", path);
//coast lines
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a == b; }))
.attr("class", "coast")
.attr("d", path);
});
function zoomon(d, width){
t = getTransform(d);
projection
.scale(t.scale)
.translate(t.translate);
svg.attr("height", t.height);
}
function clicked(d, width) {
t = getTransform(d);
if (!d || centered === d) { // reset center to init
projection
.translate([width / 2, t.height / 2])
.center(projection.invert(path.centroid(d)))
.scale(400);
centered = null;
t.height=width;
} else { // center on clicked shape
projection.translate(t.translate).scale(t.scale);
centered = d;
}
// Frame transition
svg.transition("Frame")
.duration(1000)
.attr("height", t.height);
// Transition to the new projection.
svg.selectAll("path").transition("Redraw")
.duration(1000)
.attr("d", path);
// Focus
svg.selectAll("path")
.classed("focus", centered && function(d) { return d === centered; });
}
var getTransform = function(d) {
/* RESET & PROFILE ************************************* */
projection
.scale(1)
.translate([0, 0]);
/* GEOJSON PROFILING *********************************** */
var b = path.bounds(d); // [left, bottom], [right, top] // W S E N
// b.w = b[0][0]; b.s = b[0][1]; b.e = b[1][0]; b.n = b[1][1];
b.dx = Math.abs(b[1][0] - b[0][0]); // distance x = E-W
b.dy = Math.abs(b[1][1] - b[0][1]); // distance y = N-S
b.cx = (b[1][0] + b[0][0]) / 2; // center x
b.cy = (b[1][1] + b[0][1]) /2; // center y
//compute meaningful ratio, scale, translation
var t={};
t.ratio = ( b.dy / b.dx );
if (typeof height==="undefined"){ t.height=width*t.ratio; console.log("no height");}
else { t.height = height; console.log("height");};
t.scale = .9 * Math.min( width/b.dx, t.height/b.dy); // = .9 * ( width / b.dx)
t.translate = [(width/2- t.scale * b.cx), (t.height/2 - t.scale * b.cy) ]; //translation
return t;
}
</script>
topojson=function(){function t(t,e){function n(e){var n=t.arcs[e],r=n[0],o=[0,0];return n.forEach(function(t){o[0]+=t[0],o[1]+=t[1]}),[r,o]}var r={},o={},a={};e.forEach(function(t){var e=n(t);(r[e[0]]||(r[e[0]]=[])).push(t),(r[e[1]]||(r[e[1]]=[])).push(~t)}),e.forEach(function(t){var e,r,i=n(t),u=i[0],c=i[1];if(e=a[u])if(delete a[e.end],e.push(t),e.end=c,r=o[c]){delete o[r.start];var s=r===e?e:e.concat(r);o[s.start=e.start]=a[s.end=r.end]=s}else if(r=a[c]){delete o[r.start],delete a[r.end];var s=e.concat(r.map(function(t){return~t}).reverse());o[s.start=e.start]=a[s.end=r.start]=s}else o[e.start]=a[e.end]=e;else if(e=o[c])if(delete o[e.start],e.unshift(t),e.start=u,r=a[u]){delete a[r.end];var f=r===e?e:r.concat(e);o[f.start=r.start]=a[f.end=e.end]=f}else if(r=o[u]){delete o[r.start],delete a[r.end];var f=r.map(function(t){return~t}).reverse().concat(e);o[f.start=r.end]=a[f.end=e.end]=f}else o[e.start]=a[e.end]=e;else if(e=o[u])if(delete o[e.start],e.unshift(~t),e.start=c,r=a[c]){delete a[r.end];var f=r===e?e:r.concat(e);o[f.start=r.start]=a[f.end=e.end]=f}else if(r=o[c]){delete o[r.start],delete a[r.end];var f=r.map(function(t){return~t}).reverse().concat(e);o[f.start=r.end]=a[f.end=e.end]=f}else o[e.start]=a[e.end]=e;else if(e=a[c])if(delete a[e.end],e.push(~t),e.end=u,r=a[u]){delete o[r.start];var s=r===e?e:e.concat(r);o[s.start=e.start]=a[s.end=r.end]=s}else if(r=o[u]){delete o[r.start],delete a[r.end];var s=e.concat(r.map(function(t){return~t}).reverse());o[s.start=e.start]=a[s.end=r.start]=s}else o[e.start]=a[e.end]=e;else e=[t],o[e.start=u]=a[e.end=c]=e});var i=[];for(var u in a)i.push(a[u]);return i}function e(e,n,r){function a(t){0>t&&(t=~t),(l[t]||(l[t]=[])).push(f)}function i(t){t.forEach(a)}function u(t){t.forEach(i)}function c(t){"GeometryCollection"===t.type?t.geometries.forEach(c):t.type in d&&(f=t,d[t.type](t.arcs))}var s=[];if(arguments.length>1){var f,l=[],d={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(t){t.forEach(u)}};c(n),l.forEach(arguments.length<3?function(t,e){s.push(e)}:function(t,e){r(t[0],t[t.length-1])&&s.push(e)})}else for(var p=0,h=e.arcs.length;h>p;++p)s.push(p);return o(e,{type:"MultiLineString",arcs:t(e,s)})}function n(t,e){return"GeometryCollection"===e.type?{type:"FeatureCollection",features:e.geometries.map(function(e){return r(t,e)})}:r(t,e)}function r(t,e){var n={type:"Feature",id:e.id,properties:e.properties||{},geometry:o(t,e)};return null==e.id&&delete n.id,n}function o(t,e){function n(t,e){e.length&&e.pop();for(var n,r=h[0>t?~t:t],o=0,i=r.length,u=0,c=0;i>o;++o)e.push([(u+=(n=r[o])[0])*f+d,(c+=n[1])*l+p]);0>t&&a(e,i)}function r(t){return[t[0]*f+d,t[1]*l+p]}function o(t){for(var e=[],r=0,o=t.length;o>r;++r)n(t[r],e);return e.length<2&&e.push(e[0]),e}function i(t){for(var e=o(t);e.length<4;)e.push(e[0]);return e}function u(t){return t.map(i)}function c(t){var e=t.type;return"GeometryCollection"===e?{type:e,geometries:t.geometries.map(c)}:e in v?{type:e,coordinates:v[e](t)}:null}var s=t.transform,f=s.scale[0],l=s.scale[1],d=s.translate[0],p=s.translate[1],h=t.arcs,v={Point:function(t){return r(t.coordinates)},MultiPoint:function(t){return t.coordinates.map(r)},LineString:function(t){return o(t.arcs)},MultiLineString:function(t){return t.arcs.map(o)},Polygon:function(t){return u(t.arcs)},MultiPolygon:function(t){return t.arcs.map(u)}};return c(e)}function a(t,e){for(var n,r=t.length,o=r-e;o<--r;)n=t[o],t[o++]=t[r],t[r]=n}function i(t,e){for(var n=0,r=t.length;r>n;){var o=n+r>>>1;t[o]<e?n=o+1:r=o}return n}function u(t){function e(t,e){t.forEach(function(t){0>t&&(t=~t);var n=o[t];n?n.push(e):o[t]=[e]})}function n(t,n){t.forEach(function(t){e(t,n)})}function r(t,e){"GeometryCollection"===t.type?t.geometries.forEach(function(t){r(t,e)}):t.type in u&&u[t.type](t.arcs,e)}var o={},a=t.map(function(){return[]}),u={LineString:e,MultiLineString:n,Polygon:n,MultiPolygon:function(t,e){t.forEach(function(t){n(t,e)})}};t.forEach(r);for(var c in o)for(var s=o[c],f=s.length,l=0;f>l;++l)for(var d=l+1;f>d;++d){var p,h=s[l],v=s[d];(p=a[h])[c=i(p,v)]!==v&&p.splice(c,0,v),(p=a[v])[c=i(p,h)]!==h&&p.splice(c,0,h)}return a}return{version:"1.1.4",mesh:e,feature:n,neighbors:u}}();