block by arrayjam 5362978

Australia Postcode Decoder

Full Screen

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
text, label {
  font-family: sans-serif;
}

label {
  position: absolute;
  text-align: center;
  font-weight: bold;
  padding: 0 10px;
  line-height: 50px;
}

input {
  text-align: center;
  width: 5em;
  padding: 5px;
}

.feature {
  stroke: none;
}

.divider {
  stroke: #dedede;
  stroke-width: 0.5px;
}

</style>
<body>
<label for="postcode">
  Enter a Postcode:
  <input id="postcode" autofocus maxlength=4 placeholder="Postcode" type="text" />
</label>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v0.min.js"></script>
<script>
  var width = 750,
    height = 700;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height + 50);

var g = svg.append("g")
  .attr("transform", "translate(0, 50)")
    .append("g");

var color = d3.scale.category10();

var loading = svg.append("text")
  .attr("x", width / 2)
  .attr("y", height / 2)
  .attr("dy", ".35em")
  .attr("text-anchor", "middle")
  .style("font-size", 40)
  .text("Loading...");

d3.json("b53aa41b4c61df6272fd67fd2691517e87af63a5/postcodes.json", function(error, postcodes) {
  var postcodesgeo = topojson.object(postcodes, postcodes.objects.postcodesgeo).geometries;

  var mainlandWidth = 236;
  var leftMargin = 186;
  var rightMargin = 64;

  var allAus = mainlandWidth + leftMargin + rightMargin;
  var translateWidth = width / (allAus / mainlandWidth);

  var projection = d3.geo.albers()
    .translate([translateWidth, height / 2])
    .scale(1100)
    .rotate([-132.5, 0])
    .center([0, -26.5]) // Center of Australia, accounting for tasmania
    .parallels([-36, -18]);

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

  var input = d3.select("input")
      .on("cut", change)
      .on("paste", change)
      .on("change", change)
      .on("keyup", change);

  var feature = g.selectAll("path")
      .data(postcodesgeo)
    .enter().append("path")
      .attr("class", "feature")
      .attr("id", function (d, i) {
            // Here we can compute "id" using datums
            console.log(d);
            return i;
          })
      .attr("d", path);

  loading.remove();

  svg.append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("height", 50)
      .attr("width", width)
      .style("fill", "white");

  svg.append("line")
      .attr("class", "divider")
      .attr("x1", 0)
      .attr("x2", width)
      .attr("y1", 51)
      .attr("y2", 51);

  var legends = svg.append("g").attr("class", "legends");

  svg.selectAll("text.legend")
      .data([0,1,2,3,4,5,6,7,8,9])
    .enter().append("text")
      .attr("class", "legend")
      .attr("y", 25)
      .attr("x", function(d) { return width - (10 - d) * 50 + 25; })
      .style("fill", "white")
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .style("font-size", 20)
      .text(String);

  var prefix = "POA";
  var selectedPostcode;

  change();

  function updateLegend(legend) {
    var container = legends.selectAll("rect.legend")
        .data(legend, function(d) { return d; });

    container
      .transition().duration(750)
        .attr("x", function(d) { return width - (10 - d) * 50; })
        .style("fill", function(d) { return color(d); })
        .attr("y", 0);

    container.enter().append("rect")
        .attr("class", "legend")
        .attr("y", -50)
        .attr("width", 50)
        .attr("height", 50)
        .attr("x", function(d) { return width - (10 - d) * 50; })
      .transition()
        .duration(750)
        .ease("bounce")
        .style("fill", function(d) { return color(d); })
        .attr("y", 0);

    container.exit().transition().duration(400)
        .ease("backs")
        .attr("y", -50)
        .remove();

  }

  function setFoundStyle(selection, postcode) {
    selection.style("fill", color(postcode.slice(-1)));
  }

  function unzoom(selection) {
    selection.transition().duration(750).attr("transform", "");
  }

  function zoom(selection, b, maxScale) {
    var scale = 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
    var usedScale = Math.min(scale, maxScale);
    selection.transition().duration(750).attr("transform",
        "translate(" + projection.translate() + ")" +
        "scale(" + usedScale + ")" +
        "translate(" + -(b[1][0] + b[0][0]) / 2 + "," + -(b[1][1] + b[0][1]) / 2 + ")");
  }

  function change() {
    var lastPostcode = selectedPostcode;
    selectedPostcode = input.property("value");
    if (lastPostcode === selectedPostcode) {
      return;
    }

    var shouldUnzoom = selectedPostcode === "";
    var shouldZoom = selectedPostcode.match(/\d{1,4}/);

    if (!shouldUnzoom && !shouldZoom) {
      return;
    }

    var matchString = prefix + selectedPostcode;
    var findBounds = findMinMaxBounds();
    var bounds;
    var legendObj = {};
    var matching = feature.filter(function(d) {
      var match = matchingFeature(d, matchString);
      var next = nextDigit(d, matchString);
      this.style.fill = match ? color(+next) : "#222";
      if (match) {
        if (next) { legendObj[+next] = true; }
        bounds = findBounds(path.bounds(d));
      }
      return match;
    });

    updateLegend(d3.keys(legendObj).map(function(d) { return +d; }));
    if (matching[0].length === 0) { return; }
    if (selectedPostcode.length === 4 && matching[0].length === 1) { matching.call(setFoundStyle, matchString); }

    var zoomers = function() {
      if (shouldZoom) { g.call(zoom, bounds, 100); }
      if (shouldUnzoom) { g.call(unzoom); }
    };
    setTimeout(zoomers, 0);

  }

  function nextDigit(feature, string) {
    return feature.id.charAt(string.length);
  }

  function matchingFeature(feature, string) {
    return feature.id.indexOf(string) !== -1;
  }

  function findMinMaxBounds() {
    var topBound    = -Infinity;
    var rightBound  = -Infinity;
    var bottomBound =  Infinity;
    var leftBound   =  Infinity;
    return function (bound) {
      leftBound = Math.min(bound[0][0], leftBound);
      bottomBound = Math.min(bound[0][1], bottomBound);
      rightBound = Math.max(bound[1][0], rightBound);
      topBound = Math.max(bound[1][1], topBound);
      return [[leftBound, bottomBound], [rightBound, topBound]];
    };
  }
});

d3.select(self.frameElement).style("height", height + "px");
</script>