Binding an array as data

One of the most common and popular ways to define data in D3 visualization is through the use of JavaScript arrays. For example, say you have multiple data elements stored in an array, and you want to generate corresponding visual elements to represent each and every one of them. Additionally, when the data array gets updated, you want your visualization to reflect such changes immediately. In this recipe, we will accomplish this common approach.

Getting Ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter3/array-as-data.html

How to do it...

The first and most natural solution that might come to mind is iterating through the data array elements and generating their corresponding visual elements on the page. This is definitely a valid solution and it will work with D3, however, the enter-update-exit pattern we discussed in the introduction provides a much easier and more efficient way to generate visual elements. Let's have a look at how we do that:

var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8]; // <- A

function render(data) { // <- B
        // Enter
        d3.select("body").selectAll("div.h-bar") // <- C
            .data(data) // <- D
            .enter() // <- E
            .append("div") // <- F
              .attr("class", "h-bar")
                .append("span"); // <- G

        // Update
        d3.select("body").selectAll("div.h-bar")
            .data(data) 
              .style("width", function (d) { // <- H
                return (d * 3) + "px";
              })
              .select("span") // <- I
                .text(function (d) {
                  return d;
                });
                
        // Exit
        d3.select("body").selectAll("div.h-bar")
            .data(data)
            .exit() // <- J
               .remove();        
 }

 setInterval(function () { // <- K
        data.shift();
        data.push(Math.round(Math.random() * 100));

        render(data);
 }, 1500);

 render(data);

This recipe generates the following visual output:

How to do it...

Data as Array

How it works...

In this example, data (a list of integers in this case) is stored in a simple JavaScript array as shown on the line marked as A with an arrow left of it. The render function is defined on the line marked as B so that it can be repeatedly invoked to update our visualization. The Enter selection implementation starts on the line marked as C, which selects all div elements on the web page with h-bar CSS class. You are probably wondering why we are selecting these div elements since they don't even exist on the web page yet. This is in fact true; however, the selection at this point is used to define the visual set we discussed in the introduction. By issuing this selection that we made in the previous line we are essentially declaring that there should be a set of div.h-bar elements on the web page to form our visual set. On the line marked as D, we invoke the data function on this initial selection to bind the array as our data set to the to-be-created visual elements. Once the two sets are defined, the enter() function can be used to select all data elements that are not yet visualized. When the render function is invoked for the very first time, it returns all elements in the data array, as shown in the following code snippet:

        d3.select("body").selectAll("div.h-bar") // <- C
             .data(data) // <- D
            .enter() // <- E
             .append("div") // <- F
               .attr("class", "h-bar")
              .append("span"); // <- G

On line F, a new div element is created and appended to the body element of each data element selected in the enter function; this essentially creates one div element for each data element. Finally, on line G, an element called span is created and appended to the div element and we set its CSS class to h-bar. At this point, we have basically created the skeleton of our visualization including empty div and span elements. Next step is to change the visual attributes of our elements based on the given data.

Tip

D3 injects a property to the DOM element named __data__ to make data sticky with visual elements so when selections are made using a modified data set, D3 can compute the difference and intersection correctly. You can see this property easily if you inspect the DOM element either visually using a debugger or programmatically.

How it works...

As illustrated by the preceding screenshot, this is a very useful fact to know when you are debugging your visualization implementation.

In the Update section of array-as-data.html, the first two lines are identical to what we have done in the Enter section, and this essentially defines our data set and visual set respectively. The major difference here is on line H. Instead of calling the enter function, as we did in the code mentioned under Enter in the previous paragraphs, in the Update mode we directly apply modifier functions to the selection made by the data function. In the Update mode, data function returns the intersection between the data set and visual set (A∩B). On line H, we apply a dynamic style attribute width to be three times the integer value associated with each visual element shown in the following code snippet:

        d3.select("body").selectAll("div.h-bar")
            .data(data) 
                .style("width", function (d) { // <- H
                    return (d * 3) + "px";
                })
                .select("span") // <- I
                    .text(function (d) {
                        return d;
                    });

All D3 modifier functions accept this type of dynamic function to compute its value on the fly. This is precisely what it means to "data drive" your visualization. Hence, it is crucial to understand what this function is designed to achieve in our example. This function receives a parameter d, which is the datum associated with the current element. In our example, the first div bar has the value 10 associated as its datum, while the second bar has 15, and so on. Therefore, this function essentially computes a numeric value that is three times the datum for each bar and returns it as the width in pixels.

Another interesting point worth mentioning here is on line I, where we mention the span attribute. The child span element can also use dynamic modifier functions and has access to the same datum propagated from its parent element. This is the default behavior of D3 data binding. Any element you append to a data-bound element automatically inherits the parent's datum.

Note

The dynamic modifier function actually accepts two parameters d and i. The first parameter d is the associated datum we have discussed here and i is a zero-based index number for the current element. Some recipes in the previous chapter have relied on this index, and in the rest of this chapter, we will see other recipes that utilize this index in different ways.

This is the raw HTML code resulted from this update process:

<div class="h-bar" style="width: 30px;">
<span>10</span>
</div>
<div class="h-bar" style="width: 45px;">
<span>15</span>
</div>
....
<div class="h-bar" style="width: 24px;">
<span>8</span>
   </div>

Tip

Elements created and appended in the enter mode, that is, on line F and G, are automatically added to the update set. So, there is no need to repeat visual attributes modification logic in both enter and update section of code.

The last section—Exit section—is fairly simple as shown here:

        d3.select("body").selectAll("div.h-bar")
            .data(data)
            .exit() // <- J
            .remove();

Tip

The selection returned by the exit() function is just like any other selection. Therefore, although remove is the most common action used against the exit selection, you can also apply other modifiers or transitions to this selection. We will explore some of these options in later chapters.

On line J, the exit() function is called to compute the set difference of all visual elements that are no longer associated with any data. Finally, the remove() function is called on this selection to remove all the elements selected by the exit() function. This way, as long as we call the render() function after we change our data, we can always ensure that our visual representation and data are kept synchronized.

Now, the last block of code is as follows:

setInterval(function () { // <- K
        data.shift();
        data.push(Math.round(Math.random() * 100));
        render(data);
 }, 1500);

On line K, a simple function called function() was created to remove the top element in the data array using the shift function, while appending a random integer to the data array using the push() function every 1.5 seconds. Once the data array is updated, the render() function is called again to update our visualization keeping it synchronized with the new data set. This is what gives our example its animated bar chart look.

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

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