Front-End

Leveling Up D3: Events and Refactorings

This is the third and final post in the Leveling Up D3 series. In the first post, we talked about the Reusable Chart API and its advantages. And in the second article, we test-drove a basic bar char. In this post, we will carry out some refactoring and optimizations made possible by our new, shiny, and high-quality code.

Personal Note

I love refactoring and evolving my code, as I greatly value being proud of the code I produce. In general terms, in the Software Development field, it is pretty common to introduce bugs when new features are added to a product. This obviously isn’t ideal, but people have gotten used to it.

However, when we talk about improving a piece of code that is already on production, organizations do not like adding bugs when pushing those changes. And that’s why the previous steps were so important. Without the tests generated in the last post, we wouldn’t have the necessary confidence to refactor our charts code once it had been shipped.

Accessors Refactoring

The first refactor I want to show you deals with the accessor functions for our public interface. Consider this code:

We can easily spot a repetition pattern here, right? These methods do almost the same thing but for different parameters. Additionally, as the public API of the bar chart grows, the repetition will be even more flagrant.

There is something else we should take a look at. Check this code section at the top of the file:

It is not clear which ones of these variables are public (and therefore accessible via accessor functions) and which ones are private.

Let’s fix it! The idea is to have two lists of attributes: public and private. We will loop over the public attributes list and dynamically generate an accessor function for each item in that list.

The first step is to pick an attribute and unify its interface inside our chart. For that, we need to swap the mentions of the attribute with a call to its accessor (instead of accessing height, we call exports.height()). If we chose margin, this is how it would look like in code:

We will do this for each public attribute (height, width, and margin), checking that our unit tests still work. But this call to exports still look a bit weird, doesn’t it? So let’s also change the exports name and move it to chart, resulting in the following code:

/**
* Generate accessors for each element in attributes
* We are going to check if it's an own property and if the accessor
* wasn't already created
*/
for (var attr in publicAttributes) {
if ((!chart[attr]) && (publicAttributes.hasOwnProperty(attr))) {
chart[attr] = generateAccessor(attr);
}
}

And that’s it! We have generated accessors in our chart! A nice advantage of this code is that we highlight which attributes are public and which are private. We can also change this segregation by just moving attributes from one group to the other, super easy!

Adding Events

Now that we have a fully refactored bar chart, we can start thinking about communicating our chart with other components. Maybe we want to implement a fully-fledged tooltip, or maybe just an interactive legend or some other functionality that would react to a ‘hover’ event on one of the bars of our chart.

In order to do this, we would need to make our chart trigger events when these actions happen. But first, we are going to write the test we want to pass. It looks like this:

I have highlighted two lines in the previous code. The first is the API that we want for our bar chart events. This might feel really familiar as it looks exactly the same as what we would get using jQuery or other general purpose JavaScript libraries.

The second emphasized line shows how we can use an specific property of d3 which appends the blinded events of an element directly onto the DOM with the prefix ‘__’, to trigger an event on the DOM element.

NOTE: Do you know why the third argument of the hover callback is 0? I couldn’t find it anywhere!

In order to make this test pass, the first thing we need to do is to declare the events that our chart will trigger, and for that, we will use d3’s dispatch object. Consider the following:

Now for the event to be accessible from the outside, it needs to be bound to the chart. Since it is an object, we could use a method like $.extend (on jQuery) or _.extend (in underscore), but it happens that there is a d3 method that does something similar: d3.rebind. This is how it looks in our code:

// Copies the method "on" from dispatch to exports,
// making it accessible from outside
d3.rebind(chart, dispatch, "on");

And those are all the modifications necessary inside our chart. In order to use this, we will do:

barChart.on('customHover', function(){ /* user code ... */ });

Now we can easily send the highlighted element’s information to a tooltip, highlight an element of a legend, or any other interaction that we want to achieve. All this without breaking the encapsulation of our bar chart or coupling it with another object!

Summary

And with this post, we have arrived to the end of this series! If I would have to describe what having tests on my D3 code brings me, that would be “peace of mind.” I could say this whole series is about that: achieving peace when developing D3 charts, and I wanted to share it with you.

I hope that you all have learned about other ways of building d3.js charts and, at the very least, that you will contemplate writing some tests for your next implementation.

Do you know other ways of doing this? Tell us about your attempts at testing D3 code by leaving a comment or pinging me @golodhros.