block by hugolpz c89a76096c070a5a7d23

Map zoom + static frame

Full Screen

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.

index.html

<!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.v1.min.js

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}}();