block by mbostock 5914438

Raster & Vector III

Full Screen

Using d3-tile to display raster image tiles underneath some TopoJSON vectors, and d3-zoom for pan & zoom. This version adjusts the transform and stroke-width to update the displayed vector data efficiently. You can instead reproject interactively, but changing the transform is typically much faster. Also see the static version.

Tiles copyright OpenStreetMap contributors.

Updated Example →

index.html

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

body {
  margin: 0;
}

path {
  fill: none;
  stroke: red;
  stroke-linejoin: round;
  stroke-width: 1.5px;
}

</style>
<svg></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/d3-tile.v0.0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>

var pi = Math.PI,
    tau = 2 * pi;

var width = Math.max(960, window.innerWidth),
    height = Math.max(500, window.innerHeight);

// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
    .scale(1 / tau)
    .translate([0, 0]);

var path = d3.geoPath()
    .projection(projection);

var tile = d3.tile()
    .size([width, height]);

var zoom = d3.zoom()
    .scaleExtent([1 << 11, 1 << 14])
    .on("zoom", zoomed);

var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height);

var raster = svg.append("g");

var vector = svg.append("path");

d3.json("/mbostock/raw/4090846/us.json", function(error, us) {
  if (error) throw error;

  // Compute the projected initial center.
  var center = projection([-98.5, 39.5]);

  // Apply a zoom transform equivalent to projection.{scale,translate,center}.
  svg
      .call(zoom)
      .call(zoom.transform, d3.zoomIdentity
          .translate(width / 2, height / 2)
          .scale(1 << 12)
          .translate(-center[0], -center[1]));

  vector
      .attr("d", path(topojson.mesh(us, us.objects.counties)));
});

function zoomed() {
  var transform = d3.event.transform;

  var tiles = tile
      .scale(transform.k)
      .translate([transform.x, transform.y])
      ();

  vector
      .attr("transform", transform)
      .style("stroke-width", 1 / transform.k);

  var image = raster
      .attr("transform", stringify(tiles.scale, tiles.translate))
    .selectAll("image")
    .data(tiles, function(d) { return d; });

  image.exit().remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "//" + "abc"[d[1] % 3] + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("x", function(d) { return d[0] * 256; })
      .attr("y", function(d) { return d[1] * 256; })
      .attr("width", 256)
      .attr("height", 256);
}

function stringify(scale, translate) {
  var k = scale / 256, r = scale % 1 ? Number : Math.round;
  return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}

</script>