block by armollica 71f9ff702ec99f3e11c5

Neighboring Geography Selection

Full Screen

Testing out selecting neighboring geographic units. Uses TopoJSON’s topojson.neighbors(objects) function.

Hover to highlight a county and it’s neighboring counties. This lets you see how a county compares to its immediate neighbors for a given variable, here the 2014 unemployment rate.

Data sources: Unemployment data from U.S. Bureau of Labor Statistics. Shapefile from U.S. Census Bureau, cleaned up with QGIS, simplified with mapshaper.org. Color palette from ColorBrewer

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Neighboring Geography Selection</title>
    <style>

      body {
        font: 9px sans-serif;
      }

      .county {
        stroke: slategrey;
        stroke-width: 1px;
      }

      .bar {
        stroke: lightgrey;
        stroke-width: .5px;
      }
      .q-0 { fill: #f7f7f7; }
      .q-1 { fill: #d9d9d9; }
      .q-2 { fill: #bdbdbd; }
      .q-3 { fill: #969696; }
      .q-4 { fill: #636363; }
      .q-5 { fill: #252525; }

      .hovered {
        webkit-transition: 250ms;
        -moz-transition: 250ms;
        -o-transition: 250ms;
        transition: 250ms;
        fill: red;
        font-weight: bold;
      }

      .neighbor {
        webkit-transition: 250ms;
        -moz-transition: 250ms;
        -o-transition: 250ms;
        transition: 250ms;
        fill: rgb(255, 129, 129);
        font-weight: bold;
      }

      .axis line {
        fill: none;
        stroke: #000;
        shape-rendering: crispEdges;
      }

      .axis path {
        display: none;
      }

      svg {
        position: absolute;
      }

      .map {
        left: 300px;
      }

    </style>
  </head>
  <body>
    <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
    <script>
      var margin = {top: 10, left: 70, bottom: 40, right: 20},
          width = 960/2 - margin.left - margin.right,
          height = 700 - margin.top - margin.bottom;

      var projection = d3.geo.conicConformal()
        .rotate([90, 0])
        .center([.2, 44.75])
        .parallels([29.5, 45.5])
        .scale(6000)
        .translate([
          (width + margin.left + margin.right) / 2,
          (height + margin.top + margin.bottom) / 2
        ])
        .precision(.1);

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

      var scale = {
        x: d3.scale.linear().range([0, width]),
        y: d3.scale.ordinal().rangeBands([height, 0], .1),
        color: d3.scale.quantize()
          .range(d3.range(6))
      };

      var axis = {
        x: d3.svg.axis().scale(scale.x).orient("bottom"),
        y: d3.svg.axis().scale(scale.y).orient("left")
      };

      var chart = d3.select("body").append("svg")
          .attr("class", "chart")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
        .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      var map = d3.select("body").append("svg")
        .attr("class", "map")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

      chart.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")");

      chart.append("g")
        .attr("class", "y axis");

      d3.json("wi.json", ready)

      function ready(error, wi) {
        if (error) throw error;

        var countyData = cleanData(wi);

        scale.x.domain([0, d3.max(countyData, function(d) { return d.unemployment_rate; })]);
        scale.y.domain(countyData.map(function(d) { return d.name; }));
        scale.color.domain(d3.extent(countyData, function(d) { return d.unemployment_rate; }));

        // Draw map
        var counties = map.selectAll(".county").data(countyData)
          .enter().append("path")
            .attr("class", function(d) { return "county q-" + scale.color(d.unemployment_rate); })
            .attr("d", path)
            .on("mouseenter", mouseenter)
            .on("mouseleave", mouseleave);

        // Draw bar chart
        var bars = chart.selectAll(".bar").data(countyData)
          .enter().append("rect")
            .attr("x", function(d) { return scale.x(0); })
            .attr("y", function(d) { return scale.y(d.name); })
            .attr("width", function(d) { return scale.x(d.unemployment_rate); })
            .attr("height", function(d) { return scale.y.rangeBand(); })
            .attr("class", function(d) { return "bar q-" + scale.color(d.unemployment_rate); })
            .on("mouseenter", mouseenter)
            .on("mouseleave", mouseleave);


        var labels = chart.selectAll(".label").data(countyData)
          .enter().append("text")
            .attr("x", function(d) { return scale.x(d.unemployment_rate); })
            .attr("y", function(d) { return scale.y(d.name); })
            .attr("dx", 3)
            .attr("dy", 7)
            .style("text-anchor", "start")
            .text(function(d) { return d.unemployment_rate; });


        chart.select(".x.axis")
            .call(axis.x)
          .append("text")
            .attr("x", width)
            .attr("y", 30)
            .style("text-anchor", "end")
            .text("Unemployment Rate (%)");

        chart.select(".y.axis")
          .call(axis.y);

        function mouseenter(hovered) {
          counties
            .classed("hovered", function(d) { return d === hovered; })
            .classed("neighbor", function(d) { return hovered.neighbors.indexOf(d) !== -1; });

          bars
            .classed("hovered", function(d) { return d === hovered; })
            .classed("neighbor", function(d) { return hovered.neighbors.indexOf(d) !== -1; });

          labels
            .classed("hovered", function(d) { return d === hovered; })
            .classed("neighbor", function(d) { return hovered.neighbors.indexOf(d) !== -1; });

          chart.select(".y.axis").selectAll("g")
            .classed("hovered", function(name) { return name === hovered.name; })
            .classed("neighbor", function(name) {
              return hovered.neighbors
                .filter(function(d) { return d.name === name; }).length > 0;
            });
        }

        function mouseleave() {
          counties
            .classed("hovered", false)
            .classed("neighbor", false);

          bars
            .classed("hovered", false)
            .classed("neighbor", false);

          labels
            .classed("hovered", false)
            .classed("neighbor", false);

          chart.select(".y.axis").selectAll("g")
            .classed("hovered", false)
            .classed("neighbor", false);
        }
      }

      function cleanData(wi) {
        var features = topojson.feature(wi, wi.objects.wi).features,
            neighbors = topojson.neighbors(wi.objects.wi.geometries),
            countyData = features
              .map(function(d,i) {
                d.neighbors = features.filter(function(d1, i1) {
                  return  neighbors[i].indexOf(i1) !== -1;
                });
                d.unemployment_rate = +d.properties["wi-data__2"];
                d.name = d.properties.NAME;
                return d;
              })
              .sort(function(a, b) {
                return b.unemployment_rate - a.unemployment_rate;
              });

        return countyData;
      }

      d3.select(self.frameElement)
        .style("width", 800 + "px")
        .style("height", (height + margin.top + margin.bottom) + "px");
    </script>
  </body>
</html>