An HTML visualization example

We will visualize World Health Organization (WHO) global life expectancy data in a simple table.

This is available at http://apps.who.int/gho/data/node.main.688 by selecting JSON simplified structure. Save it to your data/ directory (or, if you did git checkout at the start of the chapter, will be in your data/ directory already).

Start off by creating a new file called index.js  inside lib/chapter2 and replacing all the code in main.js with the following:

import lifeExpectancyTable from './chapter2/index'; 
lifeExpectancyTable();

Add the following to chapter2/index.js:

import tableFactory from './table-factory'; 

export default async function lifeExpectancyTable() {
const getData = async () => {
try {
const response = await fetch('data/who-gho-life-expectancy.json');
const raw = await response.json();
} catch (e) {
console.error(e);
return undefined;
}
};
}

We're getting kind of crazy in here using one of the best ES7 features ever, which I want to introduce now because it's fantastic.

If you look where we defined the lifeExpectancyTable() and getData() functions, you will notice that we put a new keyword, async. This marks a function as asynchronous, meaning that it will always return a Promise. What's a Promise? We get into that later on in Chapter 3, Shape Primitives of D3, but for now, think of it as a special variable that may eventually have a value. This is cool, because that means async functions can then use the fancy-schmancy await keyword, which means wait until this variable has a value and then proceed. We also get to use the new Fetch API, which already returns a promise itself, meaning we can await all the things!
We'll generally use this pattern to load in data throughout the book (though we will look at a few other options in Chapter 3, Shape Primitives of D3). Begone, foul-smelling XMLHttpRequest of the previous chapter!

With data in hand, we can start constructing our epic Table... Of Life! (-expectancy values).

Add the following after const raw and before the catch block:

return raw.fact.filter(d => d.dim.GHO === 'Life expectancy at birth (years)' 
&& d.dim.SEX === 'Both sexes' && d.dim.YEAR === '2014')
.map(d => [
d.dim.COUNTRY,
d.Value,
]);

This changes the bizarre format used by the WHO into something much simpler, and gives us only the life expectancy at birth values, for both sexes, from 2014. Time to use our brand new tableBuilder.

Add the following at the end of lifeExpectancyTable, before the closing curly bracket:

  const data = await getData(); 
data.unshift(['Country', 'Life expectancy (years from birth)']);
return tableFactory(data);

This adds a header row to our table and passes it in to our tableFactory function. Your web browser should now have a really dull, unsorted table:

This is super gross, though the whole point of tables is being able to rapidly look up information, which this definitely does not allow you to do at present.

Let's try sorting it alphabetically. To do this, however, we need to update our tableFactory() function to be a little bit smarter. Let's try using some data joins like those we learned above.

Rewrite your tableFactory() function to resemble the following:

export default function tableFactory (_rows) { 
const rows = Array.from(_rows);
const header = rows.shift(); // Remove the first element for the header

const table = d3.select('body')
.append('table')
.attr('class', 'table');

table.append('thead')
.append('tr')
.selectAll('td')
.data(header)
.enter()
.append('th')
.text(d => d);

table.append('tbody')
.selectAll('tr')
.data(rows)
.enter()
.append('tr')
.selectAll('td')
.data(d => d)
.enter()
.append('td')
.text(d => d);

return {
table,
header,
rows,
};
}

Here, we use selection.data to create our table rows and all of our cells. Since D3 is now tracking all of our data and how it's attached to things on the screen, we can manipulate the data and have our table reflect it.

Change the last line of lifeExpectancyTable to the following:

  return tableFactory(data).table 
.selectAll('tr')
.sort();

Without doing anything else, this code will redraw the table with the new ordering--no refreshing the page, and no manually adding or removing elements. As all our data is joined to the HTML, we didn't even need a reference to the original tr selection or the data; pretty nifty, if you ask me.

The .sort() function takes only a comparator function. The comparator is given two pieces of data (arguments a and b) and must decide how to order them, returning -1 for a being less than b, 0 for both a and b being equal, and 1 for a being greater than b. If a and b are both numbers, you can merely subtract b from a to get ascending order (or vice versa to get descending). You can also use the d3.ascending and d3.descending comparators of D3, which saves some typing and has the added benefit of being much more immediately apparent about what you're trying to accomplish.

Think about somebody looking at our table in a newspaper or magazine, though. What is the reader more likely to be interested in? Finding the average life expectancy of a particular country, or finding which countries have the highest or lowest life expectancy? Particularly, in news media, readers tend to be concerned with the biggest and smallest numbers--who's the fastest, what's the biggest, and which company made the most money? In this instance, it probably makes more sense to sort by life expectancy, which we can do by replacing our .sort() line with the following:

.filter(i => i)
.sort(([countryA, yearsA], [countryB, yearsB]) => yearsA - yearsB);

There's some funky ES2015 stuff going on here, but it's really pretty simple. First, we filter it by identity to ensure that we don't have any undefined rows. Then, we write a sorting comparator function that takes two values--in our case, we know each datum is an array containing the country name and its average life expectancy. We use a new ES2015 feature called array destructuring assignment to assign the first element of our first argument to countryA, our first element of our second argument to countryB, our second element of our first argument to yearsA, and so on. We then subtract yearsB from yearsA to sort our values in an ascending order. If we wanted to sort in a descending order, we'd merely subtract yearsA from yearsB instead.
Boom! Awesome table, ahoy!:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset