Sunday 26 January 2014

Introduction to d3.js

The topic of this article is to present to you d3.js. D3.js is the framework on which relies the toolkit introduced in the previous article: Rickshaw. The aim is to get a quick look on the concepts introduced by d3.js and put them into pratice with a simple application. In order to get the display more user-friendly, Bootstrap CSS and JQuery will be used.
Explanations will be completed with code snippets and a link to a Github repository will be provided for testing a real example.

Here is the plan of this article:
  • PART 1: A short presentation of d3.js
  • PART 2: The link between data and DOM
  • PART 3: Utilitary classes and visualization components
  • PART 4: Use d3.js in an application
  • PART 5: Displaying the axis
  • PART 6: Displaying the circles
  • PART 7: Getting a working example


PART 1: A short presentation of d3.js

D3.js is a javascript library that allows the developers to manipulate documents based on data. This library provides a data-driven approach to DOM manipulation and also some visualization components. D3.js can support large datasets and dynamic behavior for interaction and animation. It is very usefull for displaying data through charts and graphs.

D3.js is well documented, you can find the documentation page here. You can get access to API reference, tutorials, examples, google groups...



PART 2: The link between data and DOM

In order to illustrate the way d3.js links data to DOM elements, we will take as an example a SVG element with some circles to add according to the data. D3.js dynamically handles the changes in data (removing and adding) to reflect them in the DOM elements.

You need first to retrieve the DOM elements with d3 and then bind them to a dataset. Then you need to specify what you want to do with the new data that have no associated DOM elements and what you want to do with the DOM elements that have no piece of data anymore. Finally you need to specify what to do with the DOM elements that are linked to data.

var myData = [{r : 5, x : 10, y : 10, color : "red"}, 
                {r : 25, x : 100, y : 100, color : "yellow"}];
var linkedData = d3.select("svg").selectAll("circle").data(myData);
linkedData.exit().remove();
linkedData.enter().append("svg:circle");
linkedData.attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; })
            .attr("r", function(d) { return d.r; })
            .attr("fill", function(d) { return d.color; });

As you can see in this code snippet, the 3rd line binds the circle SVG elements with the data "myData". Then when the SVG elements and the data are linked, the 4th and the 5th lines respectively describe what to do with SVG elements that have no data anymore and with pieces of data that have no corresponding SVG elements yet.
The last line explain what to do with SVG elements that have linked data (after updating). D3.js provides some usefull function to directly modify the attributes of the elements. In this case the "attr" function takes as arguments the name of the attribute and a function that will compute the value for this attribute.

The concept of data and elements binding under d3.js can be summarized with this schema:



PART 3: Utilitary classes and visualization components

D3.js provides many utilitary classes and visualization components. The utilitary classes allow the developer to work on DOM element selections, transitions, arrays, strings, mathematical functions, colors, scales, time and geographic features. The visualization components are SVG shapes, axis, geometric shapes and main graphs and charts. A complete list of the API references is available here.



PART 4: Use d3.js in an application

Let's put into practice the d3.js concepts described above with a simple application. Here is a screenshot of the application graphic we are going to implement:


The graph will be displayed in a DIV element which id is "graph". The javascript implementation of this graph is devided in 2 parts: one for adding the SVG elements with the horizontal and vertical axis and one for displaying the circles in the graph.

The SVG element is added to the "graph" DIV element thanks to d3.js utilities (space, height and width are 3 constants):
this.circles = [{r : 5, x : 10, y : 10, color : "red"}, 
                {r : 25, x : 100, y : 100, color : "yellow"},
                {r : 10, x : 400, y : 200, color : "green"},
                {r : 5, x : 50, y : 25, color : "purple"},
                {r : 50, x : 250, y : 300, color : "blue"}];
this.svgcontainer = d3.select("#graph").append("svg:svg")
            .attr("width", this.width + 2*this.space)
            .attr("height", this.height + 2*this.space);




PART 5: Displaying the axis

The axis creation is implemented in 3 steps. First you need to scale the axis (for instance from 0 to 500) to the axis length on the screen (in pixels for instance). Second you need to create the axis according to these scales and orient the vertical scale to the left part of the screen. Third you need to attach the axis to the SVG container. The piece of code in charge of the steps described above is:
//Create the Scale we will use for the Axis
var xAxisScale = d3.scale.linear().domain([0, this.width]).range([0, this.width]);
var yAxisScale = d3.scale.linear().domain([0, this.height]).range([this.height, 0]);
//Create the Axis
var xAxis = d3.svg.axis().scale(xAxisScale);
var yAxis = d3.svg.axis().scale(yAxisScale).orient("left");
//Create an SVG group Element for the Axis elements and call the xAxis function
var xAxisGroup = this.svgcontainer.append("svg:g")
    .attr("class", "x axis")
    .attr("transform", "translate(" + this.space + "," + (this.height + this.space) + ")")
    .call(xAxis);
var yAxisGroup = this.svgcontainer.append("svg:g")
    .attr("class", "y axis")
    .attr("transform", "translate(" + this.space + ","  + this.space + ")")
    .call(yAxis);

Here are few comments on the code above:
The scales are created by specifying the domain and the range.
The axis are created and scaled thanks to the scale function.
The Y axis is set vertically thanks to the orient function.
The axis are added to the SVG element by creating two G elements with a "transform" attribute for setting the axis at the correct position (in our case, the graph origin - bottom, left - is not the screen origin - top, left -) and applying the call function on these G elements with the axis as parameter.




PART 6: Displaying the circles

The concept used for displaying the circles is the same as the one described in PART 2. The code for displaying the circles is:
this.suite = this.svgcontainer.selectAll("circle");
this.bindCircles(this.suite.data(this.circles));

with the following functions:
    bindCircles : function(displayedData) {
        this.removeCircle(displayedData);
        this.appendCircle(displayedData);
        this.displayCircle(displayedData);
    },
    
    appendCircle : function(displayedData) {
        displayedData.enter().append("svg:circle");
    },
    
    removeCircle : function(displayedData) {
        displayedData.exit().remove();
    },
    
    displayCircle : function(displayedData) {
        displayedData.attr("cx", this.getX.bind(this))
            .attr("cy", this.getY.bind(this))
            .attr("r", function(d) { return d.r; })
            .attr("fill", function(d) { return d.color; });
    },

When you modify the data (for instance you want to add an element to the array this.circles), you need to call the 2 lines described in the first code section of this part for binding the data and the DOM elements and specify how to display the new circle, update the existing circle and remove the circle that do not exist in the dataset anymore.



PART 7: Getting a working example

You can find a working example in my github repository (the d3introduction folder).

No comments:

Post a Comment