This is part of a series of posts related to my D3+ES6 Collection, a collection of pens that showcase how to leverage ES6 in D3. Each pen features a different aspect of ES6, and each one has an article to explain what's going on.

See the other articles here.

This article is about making a custom table in D3, with ES6 template literals.

What's the result?

It's important to display data in a table, and most data is in tabular format. On its own, D3 allows us to render tables quite easily, but it's difficult to have control over how that table is rendered as HTML.

What if we want to color a cell based on it's value? What if we want to format how another cell is displayed, without changing the underlying data.

One could do all of that with D3, but ES6 template literals offer a different approach.

Forked From

This example was forked from D3 Table (Basic). There are a few key differences here, which we will go over. Notice that now we are using Babel as our javascript preprocessor (you can change this in the settings for this pen).

You can also see the previous article for a more detailed look at this example.

What's Interesting About This?

We'll use the power of template literals to tell D3 exactly what we want to render to the browser. D3 selections have a .html() method, which will append HTML to the DOM.

If any of this is unfamiliar to you, I highly recommend brushing up on your D3.

Like most D3 methods, this one accepts a callback. That means we can pass our data to a callback, and write HTML with our data, and D3 will render it.

Consider the example below, where we want to append some HTML to a selector called needsTitle, bound to data with a title key:

  needsTitle
    .html(function(d) {
        return '<h1>' + d.title + '</h1>';
    });

This pattern can be cumbersome if you have something more complicated to render. This is how the same code looks with template literals (and an arrow function):

  needsTitle
    .html(d => `<h1>${d.title}</h1>`);

Here's a list of the libraries, concepts, and tools we'll be using:

  • D3, a javascript library for manipulating documents based on data
  • Babel, a javascript compiler--it allows us to use cutting-edge features of javascript that will work in environments that don't support it
  • Template Literals, a feature of ES6 that makes it easy to write javascript with strings
  • Arrow Functions, a new syntax for functions, presented in ES6
  • const variable declaration, a variable declaration introduced in ES6

The Code

If you saw the last example, you may have noticed we've done a complete overhaul of the javascript portion.

Now that we're using Babel, we can safely implement ES6 features. This means we'll use the const variable declaration instead of var (although you're welcome to keep using var), and we'll be using arrow functions instead of the usual function keyword.

You can always view the complied output in codepen! In the JS tab, click on the options arrow and select "View Compiled JS"

Aside from that, our D3 code is quite similar to that of the previous example. We're still appending a <table> and a <thead>, but things start to change once we append rows to the <tbody>.

In normal D3, we'd bind each row in our data to a <tr> element, and then bind all the data in that row to <td> elements. While nested bindings are allowed in D3, it can be a bit confusing to follow.

The previous example has the data binding shown here:

  var cells = rows
  .selectAll("td")
  .data(function(d) {
    return columns.map(function(k) {
      return d[k];
    });
  })
  .enter()
  .append("td")
  .text(function(d) {
    return d;
  });

Notice that we're actually binding the data to something new. We use a callback to go through each column and extract the data by their keys. Are you confused? I certainly was.

We can skip this step with template literals, avoiding that last data binding altogether. We can use the .html() methods to append html directly to our <tr>'s. We'll need a callback, of course.

Let's turn to our D3 code:

  d3.csv(url, (err, data) => {

  const table = d3.select("#viz").append("table");

  const header = table.append("thead")
    .selectAll('th')
    .data(data.columns)
    .enter()
    .append('th')
    .text(d => d);

  const rows = table.append("tbody")
    .selectAll("tr")
    .data(data)
    .enter()
    .append("tr")
    .html(rowTemplate);
})

Notice that in the rows variable, we are binding <tr>'s to data, which happens to be the Olympic Medal data. When we append a <tr>, we're calling .html(rowTemplate), where rowTemplate is a function that takes in some data from data.

D3 is smart enough to know that we'll be passing an entry of data to rowTemplate. That's all fine and dandy, but how are we defining rowTemplate?

You guessed it, we'll use template literals. Let's dive right into the code:

  const rowTemplate = (d) => {
  return `
  <td>${d["No."]}</td>
  <td>${d.Nation}</td>
  <td>${d.Games}</td>
  <td>${d.Gold}</td>
  <td>${d.Silver}</td>
  <td>${d.Bronze}</td>
  <td>${d.Total}</td>
  `;
};

Note that we have to specify the order that the columns appear, so be careful that the <td>'s are in the correct order.

Remember how I mentioned that template literals help us write complicated strings more easily? Image writing this as a string. It'd look something like this:

  const rowTemplate = (d) => {
  return ("<td>" + d["No."] + "</td>" +
    "<td>" + d.Nation + "</td>" +
    "<td>" + d.Games + "<td>" +
    "<td>" + d.Gold + "</td>" +
    "<td>" + d.Silver + "</td>" +
    "<td>" + d.Bronze + "</td>" +
    "<td>" + d.Total "</td>"
  );
};

Isn't that awful? Did you notice I made a mistake? Did you notice I made two mistakes?

I didn't fabricate these... I actually made this mistakes because it's hard to write code this way 😬

It's not easy to write templates this way, that's why template literals are so helpful. We can write HTML and javascript together.

Furthermore, any javascript can go into a template literal. Suppose we wanted to use the country code from the name and display that instead. We could use regular expressions to find the country code (e.g. USA), and then tell rowTemplate to render that instead of d.Nation (which has the country name and code).

Hint: this would be a good thing to try out!

We've effectively replaced a opaque block of code with one we can make sense of. We've eliminated the layer of abstraction that D3 puts on us in favor of (almost) writing the html directly.

Overall, this gives us more control over what we render, and is particularly helpful if you need to do something more complicated than simply writing your data to a table.

There are, of course, tradeoffs to this approach, which we'll cover now.

Pros and Cons

Pros

  • Gives complete control over how the HTML renders
  • Easy to style each <td>
  • Easy to include javascript for added functionality

Cons

  • Is not data-agnostic
    • i.e. You have to specify what your data is, and what order it's in
  • Lose power of D3 data binding on <td>'s

Summary

While D3 makes it (relatively) easy to render any table, it's hard to render custom behavior. D3 is designed so that we can leverage javascript to manipulate the DOM. With javascript growing every day, tools like Babel let us use cutting-edge features without compromising usability. One of these features is template literals, which offer a massive improvement over "the old way" of mixing strings and code.

What's Up Next?

Simply our code further and get into the data with object destructing 💻


3,794 0 24