block by wboykinm f8857b6b4e90f70d633eec138da126e9

Bivariate cartogram

Full Screen

An adaptation of the NPR hexgrid that sets hexagon size in play as a thematic variable.

Still needs a legend, but:

index.html

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

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<style>
  body { background-color: #fff; }
</style>
<svg id="cartogram" width="800" height="500"></svg>

<script>

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

// Map and projection
var projection = d3.geoMercator()
    .scale(600) // This is the zoom
    .translate([1500, 750]); // You have to play with these values to center your map
	
// Path generator
var path = d3.geoPath()
    .projection(projection)

// Colors
var lowColor = '#ffffff'
var highColor = '#bc2a66'

// load in the data
d3.queue()
    .defer(d3.json, "us_states_hexgrid.geojson")
    .defer(d3.csv, "sn.csv")
    .await(ready);

function ready(error, geo, sn) {
  if (error) throw error;

  // get min/max of everything
  var getArray = function(data,name) {
    var dataArray = [];
  	for (var d = 0; d < data.length; d++) {
  		dataArray.push(parseInt(data[d][name]))
  	}
    return dataArray
  }
  var min1 = d3.min(getArray(sn,'population'))
  var max1 = d3.max(getArray(sn,'population'))
  var min2 = d3.min(getArray(sn,'avg_sn'))
  var max2 = d3.max(getArray(sn,'avg_sn'))
  
  // create color scale
  var colorRamp = d3.scaleSqrt()
    .domain([min2,max2])
    .range([lowColor,highColor])
  
  // Join geojson and CSV (gotta be a better way of doing this)
  for (var i = 0; i < sn.length; i++) {
    // Grab State Name
    var dataState = sn[i].state;
    // Grab grouped record count 
    var dataPop = sn[i].population;
    var dataSn = sn[i].avg_sn
    // Find the corresponding state inside the GeoJSON
    for (var j = 0; j < geo.features.length; j++) {
      var geoState = geo.features[j].properties.abbrv;
      if (dataState == geoState) {
        // Copy the data value into the JSON
        geo.features[j].properties.population = parseInt(dataPop);
        geo.features[j].properties.avg_sn = parseInt(dataSn);
        // Stop looking through the JSON
        break;
      }
    }
  }
      
  // Draw the scaled features
  svg.append("g")
      .selectAll("path")
      .data(geo.features)
      .enter()
      .append("path")
          .attr("fill", function(d) { return colorRamp(d.properties.avg_sn) || 'rgba(255,255,255,0.2)' })
          .attr("d", path)
          .attr("stroke", "rgba(100,100,100,0.9)")
          // create resize scale (via //bl.ocks.org/rveciana/5928736)
          .attr("transform", function(d) {
            scale_factor = Math.sqrt(d.properties.population/(max1-min1));
            var centroid = path.centroid(d),
                x = centroid[0],
                y = centroid[1];
            return "translate(" + x + "," + y + ")"
                + "scale(" + scale_factor + ")"
                + "translate(" + -x + "," + -y + ")";
          });
          
  // Draw the mesh
  svg.append("g")
      .selectAll("path")
      .data(geo.features)
      .enter()
      .append("path")
          .attr("fill", "none")
          .attr("d", path)
          //.attr("stroke", "rgba(50,50,50,0.2)")

  // Add the labels
  svg.append("g")
      .selectAll("labels")
      .data(geo.features)
      .enter()
      .append("text")
        .attr("x", function(d){return path.centroid(d)[0]})
        .attr("y", function(d){return path.centroid(d)[1]})
        .text(function(d){ return d.properties.abbrv})
        .attr("text-anchor", "middle")
        .attr("alignment-baseline", "central")
        .style("font-size", 11)
        .style("fill", "rgba(25,25,25,0.6)")
        .attr("transform","translate(0,-15)")
}

</script>

sn.csv

id,state,population,avg_sn
2,AK,733375,
1,AL,4830620,72
5,AR,2958208,65
4,AZ,6641928,89
6,CA,38421464,83
8,CO,5278906,84
9,CT,3593222,73
11,DC,647484,64
10,DE,926454,72
12,FL,19645772,79
13,GA,10006693,68
15,HI,1406299,87
19,IA,3093526,75
16,ID,1616547,77
17,IL,12873761,73
18,IN,6568645,76
20,KS,2892987,71
21,KY,4397353,76
22,LA,4625253,75
25,MA,6705586,69
24,MD,5930538,75
23,ME,1329100,71
26,MI,9900571,62
27,MN,5419171,72
29,MO,6045448,77
28,MS,2988081,73
30,MT,1014699,77
37,NC,9845333,70
38,ND,721640,73
31,NE,1869365,78
33,NH,1324201,71
34,NJ,8904413,72
35,NM,2084117,86
32,NV,2798636,91
36,NY,19673174,77
39,OH,11575977,77
40,OK,3849733,77
41,OR,3939233,75
42,PA,12779559,77
44,RI,1053661,78
45,SC,4777576,76
46,SD,843190,70
47,TN,6499615,70
48,TX,26538614,80
49,UT,2903379,79
51,VA,8256630,75
50,VT,626604,77
53,WA,6985464,71
55,WI,5742117,70
54,WV,1851420,79
56,WY,579679,76