Same as http://bl.ocks.org/jczaplew/6603431 but using Canvas instead of SVG. Seems to be faster in some browsers, but still remains unusably slow in mobile browsers. For full code see https://github.com/jczaplew/d3-acmp-lite
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Adaptive Composite Map Projection (Lite)</title>
<style>
body {
background: #fcfcfa;
height: 500px;
position: relative;
width: 960px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #000;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #fff;
stroke:#777;
stroke-width:.75px;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
</style>
</head>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
function pickProjection(scale, coords) {
if (scale <= 1.5) {
d3.select("#proj").text("Hammer");
currentProj = "hammer";
return d3.geo.hammer()
.coefficient(2)
.precision(0.3)
.scale(scale * 100)
.rotate([coords[0],0]);
} else if (scale <= 2.0) {
d3.select("#proj").text("Modified Hammer");
currentProj = "hammer";
return d3.geo.hammer()
.coefficient(2.0 - (scale-1.5) * 2.0)
.precision(0.3)
.scale(scale * 100)
.rotate([coords[0],0]);
} else if (scale <= 6.0) {
d3.select("#proj").text("Lambert azimuthal");
currentProj = "lambert";
return d3.geo.azimuthalEqualArea()
.scale(scale * 100)
.precision(0.3)
.clipAngle(180 - 1e-3)
.rotate(coords);
} else if (scale <= 12) {
d3.select("#proj").text("Albers");
currentProj = "albers";
coords = (coords[0] == 0) ? [0.01, 0.01] : coords;
return d3.geo.albers()
.rotate([coords[0], 0])
.center([0, -coords[1]])
.parallels([ -coords[1] - scale, -coords[1] + scale])
.scale(scale * 100)
.translate([width / 2, height / 2])
.precision(.1);
} else {
d3.select("#proj").text("Mercator");
return d3.geo.mercator()
.scale(scale * 95)
.translate([width / 2, height / 2])
.rotate([coords[0],0])
.center([0, -coords[1]]);
}
}
var width = 960,
height = 500,
scale = 1,
m0,
o0,
currentProj,
rotate = [0.1,0.1],
projection = pickProjection(1, [0,0]),
graticule = d3.geo.graticule(),
sphere = {type: "Sphere"};
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(projection)
.context(context);
var zoom = d3.behavior.zoom()
.center([width / 2, height / 2])
.on("zoomstart", function(){
m0 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY];
if (currentProj == "albers") {
var proj = projection.rotate(),
cent = projection.center();
o0 = [-proj[0], cent[1]];
} else {
var proj = projection.rotate();
o0 = [-proj[0],-proj[1]];
}
d3.event.sourceEvent.stopPropagation();
})
.on("zoom", function() {
if (m0) {
var constant = (scale < 4) ? 4 : scale * 2;
var m1 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY],
o1 = [o0[0] + (m0[0] - m1[0]) / constant, o0[1] + (m1[1] - m0[1]) / constant];
}
rotate = [-o1[0], -o1[1]];
scale = (d3.event.scale >= 1) ? d3.event.scale : 1;
projection = pickProjection(scale, rotate);
path = d3.geo.path().projection(projection);
context.clearRect(0, 0, width, height);
// Update sphere styles
context.beginPath();
path.context(context)(sphere);
context.fillStyle = "#000";
context.fill();
// Update graticule styles
context.beginPath();
path.context(context)(grid);
context.lineWidth = 0.5;
context.strokeStyle = "#ddd";
context.stroke();
// Update land styles
context.beginPath();
path.context(context)(land);
context.fillStyle = "#fff";
context.fill();
context.strokeStyle = "#000";
context.stroke();
});
d3.json("countries_1e5.json", function(error, data) {
land = topojson.feature(data, data.objects.countries),
grid = graticule();
context.clearRect(0, 0, width, height);
// Style sphere
context.beginPath();
path(sphere);
context.fillStyle = "#000";
context.fill();
// Style graticule
context.beginPath();
path(grid);
context.lineWidth = 0.5;
context.strokeStyle = "#ddd";
context.stroke();
// Style land
context.beginPath();
path(land);
context.fillStyle = "#fff";
context.fill();
context.strokeStyle = "#000";
context.stroke();
canvas.call(zoom);
d3.select("body").append("text")
.attr("id", "info");
d3.select("body").append("text")
.attr("id", "proj");
});
</script>
</body>
</html>