D3 on Angular: How to use d3.js visualizations in an angular.js web app?

You always wanted to include D3 in your Angular.js application? This article shows you how to setup a D3 on Angular Seed project with Yeoman.

At first you will need node.js and npm to be installed on your system.

Install Yeoman

Yeoman helps you to start new projects as modern web apps. Therefore it provides a generator ecosystem for different kinds of projects. The “yo” command lets you scaffold whole projects or just useful components. Yeoman includes three types of tools like bower and grunt in its workflow to provide best practices and a productive environment. For more information see Yeoman Website.

Install Yeoman globally: npm install –g yo

Install angular-generator

Yeoman provides a lot of generators to build various kinds of projects. Since this tutorial uses angular as its main framework, it would be great to find a proper generator to scaffold a new project.

Install Angular Generator globally: npm install -g generator-angular

See Angular Generator Website for more insights.

Create new project

Now we can start to create a new project. Type the following commands:

cd [your projects directory]
mkdir [new project directory]
cd [new project directory]
yo angular [optional yourAppName]

The process of creation will ask you for some optional project settings. You are free to make your own decisions here.

Run grunt for building and grunt serve to start your web app in a browser window.

Install d3.js

After scaffolding our Angular project it is time to include d3. Your new project comes with bower as package manager. To install d3.js type: bower install d3 --save

Include d3.js

yo angular:factory d3Service

This creates a new factory called d3Service.js. Open this file and you will see:

angular.module('myAppName')
  .factory('d3Service', function () {
   // Service logic
   // ...

   var meaningOfLife = 42;

   // Public API here
   return {
     someMethod: function () {
       return meaningOfLife;
     }
   };
 });

Change it to:

angular.module('d3', [])
  .factory('d3Service', ['$document', '$q', '$rootScope', function($document, $q, $rootScope) {
    var d = $q.defer();
    function onScriptLoad() {
      // Load client in the browser
      $rootScope.$apply(function() { d.resolve(window.d3); });
    }
    // Create a script tag with d3 as the source
    // and call our onScriptLoad callback when it
    // has been loaded
    var scriptTag = $document[0].createElement('script');
    scriptTag.type = 'text/javascript';
    scriptTag.async = true;
    scriptTag.src = 'bower_components/d3/d3.js';
    scriptTag.onreadystatechange = function () {
    if (this.readyState === 'complete') { onScriptLoad(); }
  };
  scriptTag.onload = onScriptLoad;

  var s = $document[0].getElementsByTagName('body')[0];
  s.appendChild(scriptTag);

  return {
    d3: function() { return d.promise; }
  };
}]);

In your app.js add d3 as dependency:

angular.module('myAppName', ['d3']);

There may be already other dependencies beside d3.

Create your first d3 directive

With the next command you create a new directive called simpleLineChart: yo angular:directive simpleLineChart

Open the new simplelinechart.js and change its content to:

angular.module(' myAppName ')
  .directive('simpleLineChart', ['d3Service', function(d3Service) {
    return {
      restrict: 'EA',
      scope: {},
      link: function(scope, element, attrs) {
        d3Service.d3().then(function(d3) {

          var margin = {top: 20, right: 20, bottom: 30, left: 50},
            width = 600 - margin.left - margin.right,
            height = 700 - margin.top - margin.bottom;

          var parseDate = d3.time.format('%d-%b-%y').parse;

          var x = d3.time.scale()
            .range([0, width]);

          var y = d3.scale.linear()
            .range([height, 0]);

          var xAxis = d3.svg.axis()
            .scale(x)
            .orient('bottom');

          var yAxis = d3.svg.axis()
            .scale(y)
            .orient('left');

          var line = d3.svg.line()
            .x(function(d) { return x(d.date); })
            .y(function(d) { return y(d.close); });

          var svg = d3.select(element[0]).append('svg')
           .attr('width', width + margin.left + margin.right)
           .attr('height', height + margin.top + margin.bottom)
           .append('g')
           .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

          // Hard coded data
          scope.data = [
            {date: '4-Apr-12', close: 34},
            {date: '5-Apr-12', close: 45},
            {date: '6-Apr-12', close: 37},
            {date: '7-Apr-12', close: 56},
            {date: '8-Apr-12', close: 50},
            {date: '9-Apr-12', close: 77}
          ];

          scope.data.forEach(function(d) {
            d.date = parseDate(d.date);
            d.close = +d.close;
          });

          x.domain(d3.extent(scope.data, function(d) { return d.date; }));
          y.domain(d3.extent(scope.data, function(d) { return d.close; }));

          svg.append('g')
            .attr('class', 'x axis')
            .attr('transform', 'translate(0,' + height + ')')
            .call(xAxis);

          svg.append('g')
            .attr('class', 'y axis')
            .call(yAxis)
            .append('text')
            .attr('transform', 'rotate(-90)')
            .attr('y', 6)
            .attr('dy', '.71em')
            .style('text-anchor', 'end')
            .text('Price ($)');

          svg.append('path')
            .datum(scope.data)
            .attr('class', 'line')
            .attr('d', line);
        });
      }};
    }]);

In your main.css add to stylize your new chart:

/* D3 */

.axis path,
.axis line {
 fill: none;
 stroke: #000;
 shape-rendering: crispEdges;
}

.x.axis path {
 display: none;
}

.line {
 fill: none;
 stroke: steelblue;
 stroke-width: 1.5px;
}

Finally add the following tag in your main.html to use your new directive:

 <div simple-line-chart></div>

Run grunt serve and you can see your line chart in a browser.

Visit Angular Newsletter: D3 on Angular for optional setup hints. The line chart example was taken from Mike Bostocks bl.ocks.org.

You can continue this article like a series of articles by reading the next blog post for implementing line graphs as Small Multiples.

Feel free to give feedback and improvements for this tutorial. Furthermore you can find a GitHub repository with the working result and installing instructions on my GitHub Page.

rwieruch

rwieruch

Hi, I'm Robin. I am working as a software engineer at a Berlin based company called Small Improvements. Aside I blog about software development, contribute in open source and read a lot. Follow me on GitHub or Twitter to get quality tweets and the recent updates. As alternative you can subscribe for the Newsletter. Feedback is very much appreciated! I would like to hear from you!
rwieruch
  • eoin

    Really great article Robin, thanks for this. One question how do I create a dist that includes the d3 dependencies? I created my yeoman+d3 app & it works great locally when I run “grunt serve”. However when I try “grunt server: dist”, I get “Failed to load resource: the server responded with a status of 404 (Not Found) , http://localhost:9000/bower_components/d3/d3.js“.

    I thought I could sort it by doing “bower install d3 –save”, and then running “grunt build” before trying “grunt server: dist” again but with no joy. I’m trying to prepare my app for pushing to Heroku, any help would be greatly appreciated.

  • if you encounter an error installing yeoman, try this command instead, with a different hyphen character:

    npm install -g yo

    I found a solution here:

    https://github.com/Medium/phantomjs/issues/98

    Looks like npm is trying to install a package called ‘–g’.

    decodeURIComponent('%E2%80%93g') -> "–g"
    "–g".charCodeAt(0) -> 8211

    Make sure to use a real hyphen, which is charCode 45.

  • Kishore Babu

    Very nice article Robin. It would be helpful if you can extend this line chart directive to include the date range slider with date series. Some thing like http://bl.ocks.org/mbostock/1667367#index.html. Is it possible?

    • wrobin

      Hi. In future I wanted to do this anyway. I will write a new article to extend the Small Multiple Article with that brushing effect from Mike Bostock. Have a look at the Small Multiples at first. I will tweet about the new article when it is finished.

  • Joe Keohan

    I had issues running grunt after creating the new project and found out that i needed to install the grunt-cli ( npm install -g grunt-cli ) as per http://www.hongkiat.com/blog/grunt-command-not-found/

  • Ramesh, Chennai

    Thanks a lot. It works great.

  • Lauren Zia

    Thank you!

  • Pingback: D3 on Angular: How to use d3-plugins in Angular - WRobin()