block by mbostock 09dd5ad7d6bfd40187e0

Map Pan & Zoom III

Full Screen

This example uses d3.behavior.zoom with a canvas transform and pre-projected geometry for map panning and zooming. This approach is faster than reprojecting, but still slower than an SVG transform. A faster variation of this approach is to use dynamic simplification and viewport clipping.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

canvas {
  background: #eee;
}

</style>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<body>
<script>

var width = 960,
    height = 960;

var zoom = d3.behavior.zoom()
    .translate([0, 0])
    .scale(1)
    .scaleExtent([1, 8]);

var canvas = d3.select("body").append("canvas")
    .attr("width", width)
    .attr("height", height);

var context = canvas.node().getContext("2d");

context.lineJoin = "round";
context.lineCap = "round";
context.strokeStyle = "#fff";

var path = d3.geo.path()
    .projection(null)
    .context(context);

d3.json("world.json", function(error, world) {
  if (error) throw error;

  var sphere = topojson.feature(world, world.objects.sphere),
      land = topojson.feature(world, world.objects.land),
      boundary = topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; });

  canvas
      .call(zoom.on("zoom", zoomed))
      .call(zoom.event);

  function zoomed() {
    var t = zoom.translate(),
        s = zoom.scale();

    context.clearRect(0, 0, width, height);

    context.save();
    context.translate(t[0], t[1]);
    context.scale(s, s);
    context.lineWidth = 1 / s;

    context.beginPath();
    path(sphere);
    context.fillStyle = "#fff";
    context.fill();

    context.beginPath();
    path(land);
    context.fillStyle = "#000";
    context.fill();

    context.beginPath();
    path(boundary);
    context.stroke();

    context.restore();
  }
});

d3.select(self.frameElement).style("height", height + "px");

</script>

Makefile

GENERATED_FILES = \
	world.json

all: $(GENERATED_FILES)

clean:
	rm -rf -- %(GENERATED_FILES)

.PHONY: all clean

build/ne_50m_admin_0_countries.zip:
	mkdir -p $(dir $@)
	curl -o $@ 'http://www.nacis.org/naturalearth/50m/cultural/$(notdir $@)'

build/ne_50m_admin_0_countries.shp: build/ne_50m_admin_0_countries.zip
	unzip -d $(dir $@) $<
	touch $@

world.json: build/ne_50m_admin_0_countries.shp sphere.json
	node_modules/.bin/topojson \
		-q 1e5 \
		--projection='width = 960, height = 960, d3.geo.mercator() \
				.translate([width / 2, height / 2]) \
				.scale((width - 1) / 2 / Math.PI)' \
		-- \
		countries=build/ne_50m_admin_0_countries.shp \
		sphere=sphere.json | \
			node_modules/.bin/topojson-merge \
				-o $@ \
				--io=countries \
				--oo=land

package.json

{
  "name": "anonymous",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "topojson": "1"
  }
}