Responsive D3.js Modules with AngularJS

Trying to build a modular web application for data visualization using D3.js can be quite daunting. Certainly D3 offers event listeners, but arranging them in reusable modules for the requirements of todays interactive applications seems tedious. In such a scenario AngularJS can be of great help in creating responsive visualization for the web. By using AngularJS Directives nothing else but web standards like HTML, CSS, and SVG can be used to build powerful data driven applications.

Do demonstrate the possibilities of integrating D3.js as a Directive in an AngularJS analytics dashboard we are going to plot some access statistics of this blog, which I’ve exported from an analytics tool before.

A Simple Line Chart

Plotting the tracked access sessions of this blog in D3 results in a very simple line chart which could look similar to the one shown below.

Simple Line Chart with D3
Simple Line Chart

To draw a chart like this with D3, code similar to the following would be required."#graph").selectAll("svg").remove();

    var margin = {top: 10, right: 20, bottom: 30, left: 70};
    var width = 860 - margin.left - margin.right;
    var height = 400 - - margin.bottom;

    var url = 'data/blog-access-stats.csv';

    var parseDate = d3.time.format("%m/%d/%y").parse;
    var x = d3.time.scale().range([0, width]);
    var y = d3.scale.linear().range([height, 0]);
    var xAxis = d3.svg.axis()
    var yAxis = d3.svg.axis()
    var line = d3.svg.line()
            .x(function(d) { return x(; })
            .y(function(d) { return y(d.cnt); });
    var svg ="#graph").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + + margin.bottom)
            .attr("transform", "translate(" + margin.left + "," + + ")");
    d3.csv(url, function(error, data) {
        data.forEach(function(d) {
   = parseDate(d.Day);
            d.cnt = +d.Sessions;
        x.domain(d3.extent(data, function(d) { return; }));
        y.domain(d3.extent(data, function(d) { return d.cnt; }));

                .attr("class", "x axis")
                .attr("transform", "translate(0," + height + ")")
                .attr("class", "y axis").call(yAxis)
                .append("text").attr("transform", "rotate(-90)")
                .attr("y", 6).attr("dy", ".71em")
                .style("text-anchor", "end").text("#");
            .attr("class", "line").attr("d", line);

Going through the code step by step we see the following fixed dependencies that are incorporated into the the above example. For once we have the fixed scale of the box we are going to use for drawing the line chart. We would like to change this to dynamic to the window size of the user.
With url we have a fixed dependency to the data we want to show. We would prefer if the user can set the range of data he or she wants to view.
To re-factor this into a responsive module we would need to apply the appropriate event listeners. In case of the data dependency this might even have to be tied to specific elements of the UI like dropdown menus or check boxes.

This doesn’t sound overly complicated or bad, but as you will see with the use of the given environments $scope and $window of an AngularJS Directive this becomes much more convenient as well as portable. It’s a re-usable approach as Directives can be used as modules with clear dependencies given as HTML attributes or elements.

A Basic D3 Directive

Let’s go ahead by turning our initial line chart into a AngularJS Directive. The data will be filtered by date range to demonstrate the way D3 data bindings can be handle by AngularJS. In addition to that we’ll redraw the chart every time the window is resized to make it responsive. As a datetime picker Angular Datetime Picker is going to be used, which requires other dependencies as well.

The data will be filtered base on the date range. It contains click statistics in the range from 2013-09-01 to 2014-08-09. Filtering the data using D3 can be achieved with the following snippet:

d3.csv(url, function (error, in_data){
  data = in_data.filter(function(d){
    if(parseDate(d.Day) >= dateRange.startDate && 
         parseDate(d.Day) <= dateRange.endDate){
      return d;

Here data outside the given data range (start and end) will be excluded. Yet it is still unclear of how the date range is set.

A Directive in AngularJS is created by assigning it a name and a factory function to be returned. This is then passed to the directive  function of your module. AngularJS uses this name and collection of directives internally to bind the directive with the HTML code during “compilation”. This is an applied factory pattern common to AngularJS. In our case the directive definition looks as follows:

angular.module('ngD3Demo', [])
  .directive('simpleChart', ['$window', '$timeout', function ($window, $timeout) {

We create here a module named ngD3Demo  together with a directive simpleChart . By choosing a separate module we can re-use it in different places. From here on we can inject this directive into any other module where we would like to use it. For our dashboard application we can load this directive in the following way:

var analyticsDemo = angular.module('AnalyticsDemo', ['ngBootstrap', 'ngD3Demo']);

AnalyticsDemo  here is our AngularJS application, while ngBootstrap  is the module containing our datetime picker we choose. ng3Demo  is the module we have defined previously and which contains our line chart directive.

This gives us already the basic environment under which our line chart directive will function. As we want the line chart to be interactive to the selected date range we have to put the date range onto the scope of our application controller.

analyticsDemo.controller('AnalyticCtrl', function ($scope) {
        $scope.demoDateRange = { startDate: moment('2013-09-01'), endDate: moment('2014-08-09') };

The date range is initialized with default start and end ranges. In our case the scope of the controller and the application are going to be the same. But if you know AngularJS you also know that one application might have multiple controllers for different aspects of your page.

Here is the HTML layout to integrate everything in the dashboard:

<body data-ng-app="AnalyticsDemo" data-ng-controller="AnalyticCtrl">

By convention AngularJS translates between camelcase simpleChart between snakecase (simple-chart) in HTML. The remaining questions are now how the directive gets linked to the demoDateRange and how the graph gets rendered inside the div.

The way AngularJS resolves directives internally is associated with a factory module. Our directive therefor has to comply to the expected interface of this factory model. A minimum requirement is to return a restriction, a scope, and a link function. The restriction tells AngularJS on what kind of DOM parts a directive is restricted on. A directive can be for example be restricted to Element or Attribute (EA). The scope of you directive tells which are the members of application are part of this scope and how. Our directive is linked to the dateRange  in a bidirectional dependency (=). The link function is the with this directive associated execution module. It appends the defined functionality to the scope of the application.

Our final directive looks then as follows:

    (function (angular) {
        angular.module('ngD3Demo', [])
                .directive('simpleChart', ['$window', '$timeout', function ($window, $timeout) {
                    return {
                        restrict: 'EA',
                        scope: {
                            dateRange: "=",
                            label: '@',
                            onClick: '&'
                        link: function (scope, ele, attrs) {

                            var renderTimeout;
                            var margin = {top: 10, right: 10, bottom: 5, left: 20};

                            var svg =[0])
                                    .style('width', '100%');
                            $window.onresize = function() {

                            scope.$watch('dateRange', function(newVals, oldVals) {
                                return scope.render(newVals);
                            }, false);

                            scope.$watch('dateRange', function(newVals) {
                            }, false);

                            scope.$watch(function() {
                                return angular.element($window)[0].innerWidth;
                            }, function() {

                            scope.render = function (dateRange) {

                                if (!dateRange) return;
                                if (renderTimeout) clearTimeout(renderTimeout);

                                renderTimeout = $timeout(function () {
                                    var width =[0])[0][0].offsetWidth - margin.left - margin.right,
                                            height = 200 - - margin.bottom;
                                    var url = 'data/blog-access-stats.csv';

                                    var parseDate = d3.time.format("%m/%d/%y").parse;
                                    var x = d3.time.scale().range([0, width]);
                                    var y = d3.scale.linear().range([height, 0]);

                                    var xAxis = d3.svg.axis()
                                    var yAxis = d3.svg.axis()
                                    var line = d3.svg.line()
                                            .x(function (d) {
                                                return x(;
                                            .y(function (d) {
                                                return y(d.cnt);

                                    d3.csv(url, function (error, in_data) {
                                        data = in_data.filter(function(d){
                                            console.log(parseDate(d.Day) >= dateRange.startDate);
                                            if(parseDate(d.Day) >= dateRange.startDate &&


Further Readings


5 thoughts on “Responsive D3.js Modules with AngularJS

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s