Click States to test me !
Alternative of: Map zoom + responsive frame, with more details.
Exploration around automatic centering, zooming. This one has STATIC frame, the frame’s dimensions are not answering to the central shape.
Here, both the width and height are provided, definitively setting the canevas’ dimensions, which may be a relevant approach when waste of the webpage’s space is not important.
It is also projection proof.
Inspired by Centering a map given a GeoJSON object is the responsive frame.
<!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, height=500, centered, selected=51;
var svg = d3.select("body").append("svg")
.attr("width", width);
var projection = d3.geo.albersUsa()
.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, width*t.ratio / 2]).scale(300);
centered = null;
} 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) {
console.log(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}}();