D3 Income Chart

D3 is open source library for displaying powerful visualization on the web with help of javascript, HTML, and CSS. D3.js is a powerful library with many uses. This example specifically focuses on the map making the ability of D3.js and demonstrates how to make a web map using TopoJSON formatted geographic data, as well as introduce you to the wide range of projections that can be used to display a map in D3.js.

In this example, we are displaying the poverty and income based map in New York state. For this project, we first need the two data source and one ToponJson data.

  1. income.csv: Containing ID and Income
  2. poverty.csv: Containing ID and Poverty
  3. ny-quantize-topo.Json: Topjson data on New York

Step for creating New York Income Map in D3
Step 1. First, we need use scaleThreshold function to map input to output domain. Example as

var thresholdScale = d3.scaleThreshold()
    .domain([0, 50, 100])
    .range(['#ccc', 'lightblue', 'orange', '#ccc']);

In our example, we have to set color domain for each county in New York

// color 
var income_domain = [0, 10000, 50000, 70000, 80000, 150000, 290000, 360000]
var income_color = d3.scaleThreshold()
    .domain(income_domain)
    .range(d3.schemeGreens[7]);

d3.schemeGreens[7] is function from d3-scale-chromatic.v1.min.js for making chromatic color in our map. 

Step 2: We need to assign our data from file to variable and set it with d3.map function. The D3.map function map dictionary like structure that provides key-value storage. Here we are linking Id with Income.

// incomeData 
var incomeData = d3.map();
// povertyData 
var povertyData = d3.map();

Step 3: Load the TopoJSON data through d3.queue and defer functions. In our case, we will asynchronously load income.csv, poverty.CSV and TopoJSON data. Once we finishing loading all the data we will call the callback function.

In Step 3, we will have to follow sub-step, as

3.1 Read all the topojson data file and parse data to a variable and assign Geometry type and data.  Where the data, we are getting from the callback function.

    // new york data from  topojson
    var new_york = topojson.feature(data, {
        type: "GeometryCollection",
        geometries: data.objects.ny.geometries
    });

3.2 Assign the projection type and path

    // projection and path
    var projection = d3.geoAlbersUsa()
        .fitExtent([[20, 20], [460, 580]], new_york);;

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

Where 20, 20 is padding and 460 and 580 is width and height. The d3.geoPath() is going to be the workhorse of our geographic drawings. It’s similar to the SVG path generators, except it draws geographic data and is smart enough to decide whether to draw a line or an area.

D3 gives us three tools for geographic data:
1. Paths produce the final pixels
2. Projections turn sphere coordinates into Cartesian coordinates
3. Streams speed things up

3.3 Draw the New York map and bind income and poverty separately and assign both to separate SVG in HTML file.

Full code for ny.js


// color 
var income_domain = [0, 10000, 50000, 70000, 80000, 150000, 290000, 360000]
var income_color = d3.scaleThreshold()
    .domain(income_domain)
    .range(d3.schemeGreens[7]);

var poverty_domain = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
var poverty_color = d3.scaleThreshold()
    .domain(poverty_domain)
    .range(d3.schemeReds[4]);

// incomeData 
var incomeData = d3.map();

// povertyData 
var povertyData = d3.map();


// asynchronous tasks, load topojson maps and data
d3.queue()
    .defer(d3.json, "data/ny-quantize-topo.json")
    .defer(d3.csv, "data/income.csv", function(d) { 
        if (isNaN(d.income)) {
            incomeData.set(d.id, 0); 
        } else {
            incomeData.set(d.id, +d.income); 
        }
        
    })
    .defer(d3.csv, "data/poverty.csv", function(d) {
        if (d.poverty == '-') {
            povertyData.set(d.id, 0);
        } else {
            povertyData.set(d.id, +d.poverty); 
        }
        
    })
    .await(ready);



// callback function  
function ready(error, data) {

    if (error) throw error;

    // new york data from  topojson
    var new_york = topojson.feature(data, {
        type: "GeometryCollection",
        geometries: data.objects.ny.geometries
    });

    // projection and path
    var projection = d3.geoAlbersUsa()
        .fitExtent([[20, 20], [460, 580]], new_york);;

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

    // draw new york map and bind income data
    d3.select("svg.income").selectAll("path")
        .data(new_york.features)
        .enter()
        .append("path")
        .attr("d", geoPath)
        .attr("fill", "white")
        .transition().duration(2000)
        .delay(function(d, i) {
            return i * 5; 
        })
        .ease(d3.easeLinear)
        .attr("fill", function(d) { 
            var value = incomeData.get(d.properties.GEOID);
            return (value != 0 ? income_color(value) : "lightblue");  

        })
        .attr("class", "counties-income");
    
    // title
    d3.select("svg.income").selectAll("path")
        .append("title")
        .text(function(d) {
            return d.income = incomeData.get(d.properties.GEOID);
        });

    // draw new york map and bind poverty data
    d3.select("svg.poverty").selectAll("path")
        .data(new_york.features)
        .enter()
        .append("path")
        .attr("d", geoPath)
        .attr("fill", "white")
        .transition().duration(2000)
        .delay(function(d, i) {
            return i * 5; 
        })
        .ease(d3.easeLinear)
        .attr("fill", function(d) { 
            var value = povertyData.get(d.properties.GEOID);
            return (value != 0 ? poverty_color(value) : "lightblue");  

        })
        .attr("class", "counties-poverty");
        
    // title
    d3.select("svg.poverty").selectAll("path")
        .append("title")
        .text(function(d) {
            return d.income = incomeData.get(d.properties.GEOID);
        });
}

 

Add the following code in index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3js New York</title>

    <!-- bulma css-->
    <link rel="stylesheet"  type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.2/css/bulma.min.css">

    <!-- CSS stylesheet -->
    <link rel="stylesheet" type="text/css" href="stylesheet.css">

    <!-- D3.js CDN source -->
    <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
    <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
    <script src="https://d3js.org/topojson.v2.min.js"></script>
  </head>
  <body>
    <div class="container">
        <!-- Title -->
        <h1 class="title has-text-centered">New York</h1>
        <h2 class="subtitle has-text-centered">Income vs Poverty</h2>

        <div class="columns">
          <div class="column is-half">
            <svg class="income" width="480" height="600"></svg>
          </div>
           <div class="column is-half">
            <svg class="poverty" width="480" height="600"></svg>
          </div>
        </div>


        <!-- Your D3 code for ny maps -->
        <script type="text/javascript" src="js/ny.js"></script>
        

        <!-- Info -->
        <p class="subtitle is-5 has-text-centered">Created by: Venkata Karthik Thota</p>
    </div>
  </body>
</html>

Add following code in style.css

svg {
    margin-left: auto;
    margin-right: auto;
    display: block;
}