Hierarchy in Selections and Data

Tutorial

It's quite common for data and visualizations exhibit hierarchy. Anytime there is grouping there is a potential for hiearchy. Since grouping is fundamental to how humans understand the world, it is no surprise that it is frequently seen in hierarchy. Some examples include:

Consider the data below about cancer incidence rates.

U.S. Cancer Statistics Working Group. United States Cancer Statistics: 1999–2011 Incidence and Mortality Web-based Report. Atlanta: U.S. Department of Health and Human Services, Centers for Disease Control and Prevention and National Cancer Institute; 2014. Available at: www.cdc.gov/uscs.

var cancerData = [{
 country: "United States", rate: 507.5, 
 regions: [
  {
    region: "Northeast", rate: 550.0,
    states: [
      { state: "Connecticut", rate: 539.8 },
      { state: "Rhode Island", rate: 516.8 },
      { state: "Vermont", rate: 484.2 },
      { state: "New York", rate: 561.7 }
    ]
  },
  {
    region: "South", rate: 503.9,
    states: [
      { state: "District of Columbia", rate: 584.8 },
      { state: "Virginia", rate: 460.5 },
      { state: "Lousiana", rate: 583.0 },
      { state: "Kentucky", rate: 576.4 }
    ]
  }
 ]
}];

Above, I have a listing of the data used to create the nested bullets above. Unlike the data we have looked at so far, this data is hierarchical, that is, there are data points that contain data points. In this case, the country level data contains a dataset about regions, and each region dataset contains a dataset about states.

How do we create a table?

Take a second and see if you can think of how to make an HTML table from data using D3. No really, try to write down the D3 code you would use. For reference an HTML table at it's simplest looks like the following:

row1-col1 row1-col2
row2-col1 row2-col2
<table>
  <tr>
    <td>row1-col1</td>
    <td>row1-col2</td>
  </tr>
  <tr>
    <td>row2-col1</td>
    <td>row2-col2</td>
  </tr>
</table>

"tr" stands for table row and "td" stands for table data. And, let's say our data looks like this:

var tableData = [ [ "row1-col1", "row1-col2" ], 
                  [ "row2-col1", "row2-col2" ] ];

Notice how both the data and the table itself have hierarchy? For the table there is a tr for each row and a td for each column within a row. For the data there is an array of columns for each "row" of data.

With what we learned so far, we run directly into a problem:

d3.select("table")
  .selectAll("tr")
  .data(tableData)
  .enter()
  .append("tr")
  .text();         // uh oh, how do we add a td for each column? (for each tr)

What we really need is another selectAll, data, enter, append chain, but for the td's. The key to doing this is that D3 also allows an accessor function to be passed to the data function. When we create the tr's, we bind a row of data to each tr, then what we want is for each tr to bind a column of data to each td.

d3.select("table")
  .selectAll("tr")
  .data(tableData)
  .enter()
  .append("tr")
                  // After append, we have a selection of the given tr.
                  // We want several td's per tr, So we select all td's within a given tr
  .selectAll("td")
                  // tableData is an array of arrays. Each tr's data is an array.
                  // Therefore we just return the tr's array as it is an array of the td text.
  .data(function(d) { return d; })
  .enter()
  .append("td")
  .text(function(d) { return d; });

This produces exactly the table we desired. At the .append("tr") step, the return value is a selection of tr's. When we do selectAll() after this, we change the appendPoint (d3 calls it the parent node) from the "table" to each "tr". In the data function, the input instead of being all the data, is each data point, which at this point is an array of columns. Since what we want is an array of columns, we simply return this. We could have selected specific columns and returned only them as an array.

Selecting from a Hierarchy

Now that you know how to create hierarchy in documents from data, we also need to be able to make selections in hierarchies. A simple example is wanting to highlight a particular column of a table. This involves changing the styling of tds in every row based on the position of the td. For example, below we highlight the second column by making all of those td's members of the "highlightedColumn" class.

row1-col1 row1-col2
row2-col1 row2-col2
d3.selectAll("tr")
  .selectAll("td")
  .classed("highlightedColumn", function(d,i) { return i==1; }); // 1 because we count from 0

Each successive call to selectAll creates another level of nesting to the selection. When we use the (d,i) accessor it applies to the final level of the nesting.

Selection Theory Again

A while back I mentioned that selections were D3's wrappers around the DOM. I also stated that d3.select("body")[0][0] would give us access to the body element of a document. The reason that the body selection looks like two nested arrays is because that is essentially what it is. In reality it is two nested selections, which are D3 wrappers around arrays. In this case of just selecting the body there is no point to it, but it allows D3 to support nesting. For example our sample table has 2 rows and 2 columns. A nested selection of the tr then td has the dimensions [2][2].

Quiz

var data = [{a: 10, b:12}, {c: 13, b:14}];
The data above exhibits hierarchy. True/False.

A new selectAll() is required in the method chain for each level of
the hierarchy. True/False.

Things to do

  1. Using the nested data below, change the code to output the examples as an unordered list underneath each of the food types.
  2. Italicize the second example of each food type. For reference the CSS style for italicize is "font-style: italic".

Extra Credit

Change the code on the left. Once you've made a change, the page will render on the right.