block by mbostock 5180185

Zipdecode

Full Screen

Another (minimalist) take on Ben Fry’s zipdecode, inspired by Nelson Minar’s reimplementation. This one uses cross-fading canvas elements to render transitions efficiently.

index.html

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

input,
canvas {
  position: absolute;
}

input {
  background: transparent;
  border: none;
  margin: 10px 384px;
  width: 180px;
  font-size: 18px;
  padding: 6px;
  text-align: center;
}

</style>
<input autofocus maxlength=5 placeholder="type a zipcode">
<script src="//d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
    height = 500;

var projection = d3.geo.albersUsa()
    .scale(1000)
    .translate([width / 2, height / 2]);

d3.tsv("zipcodes.tsv", function(error, zipcodes) {
  if (error) throw error;

  var zip0;

  // Compute projected locations of each zipcode.
  zipcodes.forEach(function(d) {
    var p = projection([+d.lon, +d.lat]);
    if (p) d.x = Math.round(p[0]), d.y = Math.round(p[1]);
  });

  var input = d3.select("input")
      .on("cut", function() { setTimeout(change, 10); })
      .on("paste", function() { setTimeout(change, 10); })
      .on("change", change)
      .on("keyup", change);

  change();

  function change() {
    var zip1 = input.property("value");
    if (zip0 === zip1) return;
    zip0 = zip1;

    // Select old canvases to remove after fade.
    var canvas0 = d3.selectAll("canvas");

    // Add a new canvas, initially with opacity 0, to show the new zipcodes.
    var canvas1 = d3.select("body").insert("canvas", "input")
        .attr("width", width)
        .attr("height", height)
        .style("opacity", 0);

    var context = canvas1.node().getContext("2d");
    context.fillStyle = "#fff";
    context.fillRect(0, 0, width, height);

    // Render the inactive zipcodes.
    context.globalAlpha = .4;
    context.fillStyle = zip1 ? "#aaa" : (zip1 = "*", "#000");
    zipcodes.forEach(function(d) {
      for (var i = 0, n = zip1.length; i < n; ++i) {
        if (d.zip[i] !== zip1[i]) {
          context.fillRect(d.x, d.y, 1, 1);
          return;
        }
      }
    });

    // Render the active zipcodes.
    context.globalAlpha = 1;
    context.fillStyle = "#f00";
    zipcodes.forEach(function(d) {
      for (var i = 0, n = zip1.length; i < n; ++i) {
        if (d.zip[i] !== zip1[i]) {
          return;
        }
      }
      context.fillRect(d.x, d.y, 1, 1);
    });

    // Use a transition to fade-in the new canvas.
    // When this transition finishes, remove the old canvases.
    canvas1.transition()
        .duration(350)
        .style("opacity", 1)
        .each("end", function() { canvas0.remove(); });
  }
});

</script>