- Data Visualization with D3 4.x Cookbook(Second Edition)
- Nick Zhu
- 424字
- 2021-07-09 19:26:22
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 would 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-v2/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 take a look at how we can do that:
var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8]; // <- A function render(data) { // <- B var bars = d3.select("body").selectAll("p.h-bar") // <- C .data(data); // Update <- D // Enter bars.enter() // <- E .append("p") // <- F .attr("class", "h-bar") // <- G .merge(bars) // Enter + Update <- H .style("width", function (d) { return (d * 3) + "px"; // <- I }) .text(function (d) { return d; // <- J }); // Exit bars.exit() // <- K .remove(); } setInterval(function () { // <- L data.shift(); data.push(Math.round(Math.random() * 100)); render(data); }, 1500); render(data);
This recipe generates the following visual output:

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 A
in the preceding code. The render
function is defined on the line marked as B
so that it can be repeatedly invoked to update our visualization. The selection starts on the line marked as C
, which selects all p
elements on the web page with the h-bar
CSS class. You are probably wondering why we are selecting these p
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 essentially declare that there should be a set of the p.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 dataset to the to-be-created visual elements. Once the two sets are defined, the enter()
function on line E
can be used to select all pieces of 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:
var bars = d3.select("body") .selectAll("p.h-bar") // <- C .data(data); // Update <- D // Enter bars.enter() // <- E .append("p") // <- F .attr("class", "h-bar") // <- G
On line F
, a new p
element is created and appended to the body
element of each data element selected in the enter
function; this essentially creates one p
element for each datum. Finally, on line G
, we set its CSS class to h-bar
. At this point, we basically created the skeleton of our visualization, including the empty p
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 dataset, 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.

As illustrated by the preceding screenshot, this is very useful to know when debugging.
In the following code snippet, on line H
, the merge function is invoked with the selection as its parameter. This function call essentially merges the enter selection with the update selection and returns the union of both selections, thus allowing us to chain modifiers for both enter and update scenarios. Without the merge function, we would need to repeat the code here for both enter and update scenarios. Then, on line I
, we applied a dynamic style attribute width
to be three times the integer value associated with each visual element, as shown in the following code snippet:
bars.enter() // <- E .append("p") // <- F .attr("class", "h-bar") // <- G .merge(bars) // Enter + Update <- H .style("width", function (d) { return (d * 3) + "px"; // <- I }) .text(function (d) { return d; // <- J });
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 p
bar has the value 10
associated as its datum, 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 element's width
in pixels. While, on line J
, we used a similar approach to change the text content of the p
element to the datum value associated with each element.
Note
The dynamic modifier function actually accepts two parameters, d
and i
. The first parameter d
is the associated datum we just discussed, and i
is a zero-based index number for the current element. Some recipes in the previous chapter relied on this index, and in the rest of this chapter, we will take look at other recipes that utilize this index in different ways.
The following is the raw HTML code that resulted from this update process:
<p class="h-bar" style="width: 30px;"> 10 </p> <p class="h-bar" style="width: 45px;"> 15 </p> .... <p class="h-bar" style="width: 24px;"> 8 </p>
The following last section, Exit
section, is fairly simple:
bars.exit() // <- K .remove();
Note
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 K
in the preceding code snippet, 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 you call the render()
function after you change our data, you can always ensure that our visual representation and data are kept synchronized.
Now, let's implement the following last block of code as follows:
setInterval(function () { // <- L data.shift(); data.push(Math.round(Math.random() * 100)); render(data); }, 1500);
On line L
, a simple anonymous 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 to keep it synchronized with the new dataset. This is what gives our example its animated bar chart look.
- Expert C++
- Ceph Cookbook
- Java EE框架整合開發入門到實戰:Spring+Spring MVC+MyBatis(微課版)
- Rust編程從入門到實戰
- C語言程序設計教程(第2版)
- C語言程序設計
- Raspberry Pi Home Automation with Arduino(Second Edition)
- Practical Game Design with Unity and Playmaker
- OpenCV 3 Blueprints
- 多模態數據分析:AGI時代的數據分析方法與實踐
- 并行編程方法與優化實踐
- C語言程序設計
- Manage Your SAP Projects with SAP Activate
- Three.js Essentials
- Scratch 3.0少兒積木式編程(6~10歲)