D3 is designed and built using functional style JavaScript, which might come as a bit unfamiliar or even alien to someone who is more comfortable with the procedural or object-oriented JavaScript styles. This recipe is designed to cover some of the most fundamental concepts in functional JavaScript required to make sense of D3, and furthermore enable you to write your visualization code in the D3 style.
Let's dig a little deeper into the good part of JavaScript, the more functional side. Take a look at the following code snippet:
function SimpleWidget(spec) {
var instance = {}; // <-- A
var headline, description; // <-- B
instance.render = function () {
var div = d3.select('body').append("div");
div.append("h3").text(headline); // <-- C
div.attr("class", "box")
.attr("style", "color:" + spec.color) // <-- D
.append("p")
.text(description); // <-- E
return instance; // <-- F
};
instance.headline = function (h) {
if (!arguments.length) return headline; // <-- G
headline = h;
return instance; // <-- H
};
instance.description = function (d) {
if (!arguments.length) return description;
description = d;
return instance;
};
return instance; // <-- I
}
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget")
.description("This is a simple widget demonstrating
functional javascript.");
widget.render();
The preceding code snippet generates the following simple widget on your web page:
Despite its simplicity, the interface of this widget has this undeniable similarity to the D3 style of JavaScript. This is not by coincidence but rather by leveraging a JavaScript programming paradigm called functional objects. Like many interesting topics, this is another topic that can fill an entire book by itself; nevertheless, I will try to cover the most important and useful aspects of this particular paradigm in this section, so you can not only understand D3's syntax but you'll also be able to create your code in this fashion. As stated on D3's project Wiki, this functional programming style gives D3 much of its flexibility:
D3's functional style allows code reuse through a diverse collection of components and plugins.
-D3 Wiki (2016, August)
Functions in JavaScript are objects. Like any other object in JavaScript, function object is just a collection of name and value pair. The only difference between a function object and a regular object is that function can be invoked and additionally associated with the following two hidden properties: function context and function code. This might come as a surprise and seem unnatural, especially if you come from a more procedural programming background. Nevertheless, this is the critical insight most of us need in order to make sense of some of the strange ways that D3 uses functions.
Note
JavaScript in its pre-ES6 form is generally not considered very object oriented; however, function object is probably one aspect where it outshines some of its other more object-oriented cousins.
Now, with this insight in mind, let's take a look at the following code snippet again:
var instance = {}; // <-- A
var headline, description; // <-- B
instance.render = function () {
var div = d3.select('body').append("div");
div.append("h3").text(headline); // <-- C
div.attr("class", "box")
.attr("style", "color:" + spec.color) // <-- D
.append("p")
.text(description); // <-- E
return instance; // <-- F
};
At the lines marked as A
, B
, and C
, we can clearly see that instance
, headline
, and description
are all internal private variables belonging to the SimpleWidget
function object. While the render
function is a function associated with the instance
object which itself is defined as an object literal. Since functions are just objects, they can also be stored in an object/function, referred to by variables, contained in an array, and being passed as function arguments. The result of the execution of the SimpleWidget
function is the returning of object instance at line I
as follows:
function SimpleWidget(spec) {
...
return instance; // <-- I
}
Note
The render
function uses some of the D3 functions that we have not covered yet, but let's not pay too much attention to them for now since we will cover each of them in depth in the next couple of chapters. Also, they basically just render the visual representation of this widget and do not have much to do with our topic on hand.
Curious readers will probably be asking by now how the variable scoping is resolved in this example since the render function has seemingly strange access to not only the instance
, headline
, and description
variables but also the spec
variable that is passed into the base SimpleWidget
function. This seemingly strange variable scoping is actually determined by a simple static scoping rule. This rule can be thought as the following: whenever the runtime searches for a variable reference, the search will be first performed locally. When a variable declaration is not found (as in the case of headline
on line C
), the search continues toward the parent object (in this case, the SimpleWidget
function is its static parent and the headline
variable declaration is found at line B
). If still not found, then this process will continue recursively to the next static parent, so on and so forth, till it reaches a global variable definition; if it is still not found, then a reference error will be generated for this variable. This scoping behavior is very different from variable resolution rules in some of the most popular languages, such as Java and C#.
It might take some time to get used to; however, don't worry too much about it if you still find it confusing. With more practice and by keeping the static scoping, rule in mind, you will be comfortable with this kind of scoping in no time.
Note
One word of caution here, again for folks from Java and C# backgrounds, is that JavaScript does not implement block scoping. The static scoping rule we described only applies to function/object but not at the block level, as shown in the following code:
for(var i = 0; i < 10; i++){
for(var i = 0; i < 2; i++){
console.log(i);
}
}
You might be inclined to think this code should produce 20 numbers. However, in JavaScript, this code creates an infinite loop. This is because JavaScript does not implement block scoping, so the variable i
in the inner loop is the same variable i
used by the outer loop. Therefore, it gets reset by the inner loop and can never end the outer loop.
This pattern is usually referred as functional when compared with the more popular prototype-based Pseudo-classical pattern. The advantage of the functional pattern is that it provides a much better mechanism for information hiding and encapsulation since the private variables, in our case, the headline
and description
variables, are only accessible by nested functions through the static scoping rule; therefore, the object returned by the SimpleWidget
function is flexible yet more tamper-proof and durable.
If we create an object in the functional style, and if all of the methods of the object make no use of this, then the object is durable. A durable object is simply a collection of functions that act as capabilities.
Let's take a look at the following code; something strange has happened on line G
:
instance.headline = function (h) {
if (!arguments.length) return headline; // <-- G
headline = h;
return instance; // <-- H
};
You might be asking where this arguments
variable on line G
came from. It was never defined anywhere in this example. The arguments
variable is a built-in hidden parameter that is available to functions when they are invoked. The arguments
variable contains all arguments for a function invocation in an array.
Note
In fact, arguments
is not really a JavaScript array object. It has length and can be accessed using an index; however, it does not have many of the methods associated with a typical JavaScript array object, such as slice
or concat
. When you need to use a standard JavaScript array method on arguments
, you will need to use the following apply invocation pattern:
var newArgs = Array.prototype.slice.apply(arguments);
This hidden parameter when combined with the ability to omit function argument in JavaScript allows you to write a function such as instance.headline
with unspecified number of parameters. In this case, we can either have one argument h
or none. Because arguments.length
returns 0
when no parameter is passed, the headline
function returns headline
if no parameter is passed, otherwise it turns into a setter if parameter h
is provided. To clarify this explanation, let's take a look at the following code snippet:
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget"); // set headline
console.log(widget.headline()); // prints "Simple Widget"
Here, you can see how the headline function can be used as both setter and getter with different parameters.
The next interesting aspect of this particular example is the capability of chaining functions to each other. This is also the predominant function invocation pattern that the D3 library deploys since most of the D3 functions are designed to be chainable to provide a more concise and contextual programming interface. This is actually quite simple once you understand the variable, parameter function concept. Since a variable-parameter function, such as the headline
function, can serve as setter and getter at the same time, returning the instance
object when it's acting as a setter allows you to immediately invoke another function on the invocation result, hence the chaining.
Let's take a look at the following code:
var widget = SimpleWidget({color: "#6495ed"})
.headline("Simple Widget")
.description("This is ...")
.render();
In this example, the SimpleWidget
function returns the instance
object (as on line I
).
function SimpleWidget(spec) {
...
instance.headline = function (h) {
if (!arguments.length) return headline; // <-- G
headline = h;
return instance; // <-- H
};
...
return instance; // <-- I
}
Then, the headline
function is invoked as a setter, which also returns the instance
object (as on line H
). The description
function can then be invoked directly on its return, which again returns the instance
object. Finally, the render
function can be called.
Now, with the knowledge of functional JavaScript and a working ready-to-go D3 data visualization development environment, we are ready to dive into the rich concepts and techniques that D3 has to offer. However, before we take off, I would like to cover a few more important areas, how to find and share code and how to get help when you are stuck.