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).

Wednesday 1 January 2014

Rickshaw toolkit in an Angularjs application

The topic of this article is to show how to integrate smoothly the Rickshaw toolkit in an Angularjs application. The aim is to take advantage of this graphing toolkit within the Angularjs concepts. In order to get the display more user-friendly, Bootstrap CSS and JQuerywill 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 Rickshaw toolkit
  • PART 2: The Angularjs application
  • PART 3: The strategy
  • PART 4: Defining Angularjs directives
  • PART 5: Integrating the directives in the application
  • PART 6: Getting a working example


PART 1: A short presentation of Rickshaw toolkit

Rickshaw is an opensource Javascript toolkit based on d3.js for creating interactive graphs. It is developed by Shutterstock and as it is opensource the code is available on Github.

The first and simplest example given by Shutterstock for creating a graph with Rickshaw is to define an empty div element in an HTML page (after loading d3js and Rickshaw scripts) and render a graph built with this div element through rickshaw graph class.
<script src="/vendor/d3.min.js"></script> 
<script src="/vendor/d3.layout.min.js"></script> 
<script src="/rickshaw.min.js"></script>
 
<div id="chart"></div>

<script> 
var graph = new Rickshaw.Graph( {
    element: document.querySelector("#chart"), 
    width: 300, 
    height: 200, 
    series: [{
        color: 'steelblue',
        data: [ 
            { x: 0, y: 40 }, 
            { x: 1, y: 49 }, 
            { x: 2, y: 38 }, 
            { x: 3, y: 30 }, 
            { x: 4, y: 32 } ]
    }]
});
 
graph.render();
</script>
You can find a working example of this code here.

As you can see in the code snippet above, Rickshaw is based on DOM manipulation (with jquery for instance). Rickshaw will populate the empty div element with some SVG tags and will style it with some CSS.


PART 2: The Angularjs application

The Angularjs application will display a simple graph with 4 forms below for changing some properties (the colour, the width and the height) of the graph and add new points in the graph.
The application is based on a single html page. At the first load of the page, the graph will be empty (only one point at 0,0). In order to display something in the graph, you need to first add a point: add a value in the field dedicated to adding a data point and then click the button "Add a data point".


PART 3: The strategy

In an Angularjs application, it is not recommended to directly manipulate the DOM elements as it is explained in the Rickshaw example. The best strategy for manipulating DOM elements in Angularjs application is to use directives which are HTML tags or HTML attributes that are interpreted by the framework for rendering or behaving the way they have been designed. For more information on directives you can read directly the dedicated page in Angularjs Developer Guide here.

The strategy for displaying a graph with Rickshaw in an Angularjs application will be to create a custom directive and use it in our HTML page. The directive will be an HTML tag <rickshawgraph></rickshawgraph> with the following attributes: color, width, height and graphicaldata.


PART 4: Defining Angularjs directives

The custom directive will be defined in an angularjs module dedicated to implementing custom directives for integrating Rickshaw toolkit. The directive will replace the rickshawgraph tag by an empty div element. When the framework will attach the behaviour of the directive to the div element (calling the link function defined in the directive), a new rickshaw graph will be created based on the div element thanks to the Graph class provided by the Rickshaw toolkit and the graph render function will be called to display the graph.

In order to bind the attribute values (color, graphicaldata, width and height) to the data scope of the application and update the view when the data change, some callbacks will be attached to the attributes thanks to the $watch and $watchCollection functions of the scope object. These callbacks will update the Rickshaw graph object and call its update function.
var angularrickshawmodule = angular.module('angularrickshaw', ['ngRoute', 'angularrickshaw.tpl']);
angularrickshawmodule.directive('rickshawgraph', function() {
  return {
    restrict: 'E',
    templateUrl: 'rickshawgraphDirective.html',
    replace: true,
    link: function(scope, element, attrs) {
      var graph = null;
      var series = [{
        color: 'black',
        data: [{x:0,y:0}]
      }];
      
      scope.$watch(attrs.color, function(value){
        if (graph != null) {
          graph.series[0].color = value;
          graph.update();
        }
      });
      
      scope.$watch(attrs.height, function(value){
        if (graph != null) {
          graph.setSize({height: value, width: graph.width});
          graph.update();
        }
      });
      
      scope.$watch(attrs.width, function(value){
        if (graph != null) {
          graph.setSize({height: graph.height, width: value});
          graph.update();
        }
      });
      
      scope.$watchCollection(attrs.graphicaldata, function(value){
        if (graph != null) {
          graph.series[0].data = value;
          graph.update();
        }
      });
      
      graph = new Rickshaw.Graph( {
          element: element[0],
          width: 200,
          height: 100,
          series: series
      });
      graph.render();
    }
  };
});
Here are few comments on the code snippet displayed above:

  • The restrict property is set for using the directive as an attribute ('A' value) or an element ('E' value) or both ('AE' value).
  • The templateUrl property set the url of the HTML template to use. In our case, rickshawgraphDirective.html is only an empty div element.
  • The replace property set whether the element(s) defined in the template should replace the directive element (the rickshawgraph tag in our case).
  • The link property is a function with 3 parameters which are the scope of the directives, the DOM element retrieved by jqlite or jquery and the attributes represented by an object.
  • The function used for listening to changes of graphicaldata attributes is $watchCollection because the value that would be passed to this attribute should be a function that returns an array or a reference to an array.


PART 5: Integrating the directives in the application

The first step for integrating the custom directives in the application is to use them in the HTML makup. You can see below in the code snippet of the index.html page that a <rickshawgraph> tag is used:
<div class="graphic-content">
    <rickshawgraph color="graphcolor" graphicaldata="graphdata" height="graphheight" width="graphwidth"></rickshawgraph>
    <form class="form-inline" role="form">
        <!-- The form for modifying one attribute of the custom directive -->
    </form>
    <form class="form-inline" role="form">
        <!-- The form for modifying one attribute of the custom directive -->
    </form>
    ...
</div>
The second step is to inject the dependency of the angularrickshaw module (the module in which the custom directives are defined) into the rickshawapp module which is the application main module. The scope used for defining the data injected in the attributes of the custom HTML tag (color, graphicaldata, width and height) will also be defined in a controller attached to the rickshawapp module.
var rickshawapp = angular.module('rickshawapp', ['ngRoute', 'angularrickshaw']);
rickshawapp.controller('MainCtrl', function MainCtrl($scope) {
  $scope.text = "The aim of this code example is to show how to implement some angular directives for integrating Rickshaw framework in Angularjs.";
  $scope.title = "Test Angular Rickshaw";
  $scope.graphcolor = "steelblue";
  $scope.graphheight = 100;
  $scope.graphwidth = 200;
  $scope.graphdata = [{x:0,y:0}];
  ...
});


PART 6: Getting a working example

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