Follow me if you want to grow your full-stack JavaScript skills with my screencasts and courses.

Using D3 with Rickshaw and Angular

Resources

We created a whole course for D3.js! Check it out: Understanding D3.js

Setting up the environment

We quickly generate our angular app using yeoman.

terminal
    
    $ yo angular
    
  

We're going to massage our data with underscorejs, so let's install it with bower.

terminal
    
    $ bower install underscore --save
    
  

We install rickshaw in the same way, this also installs d3 for us.

terminal
    
    $ bower install rickshaw --save
    
  

We need to add references to everything we just installed to the index.html file, in order to get it loaded into the angular app.

/app/index.html
    
    <script src="bower_components/underscore/underscore.js"></script>
    <script src="bower_components/rickshaw/rickshaw.js"></script>
    <script src="bower_components/rickshaw/vendor/d3.v3.js"></script>
    <script src="bower_components/rickshaw/vendor/d3.v3.js"></script>
    
  

Rickshaw also comes with a bunch of css which we need to pull in.

/app/index.html
    
    <link rel="stylesheet" href="bower_components/rickshaw/rickshaw.css">
    
  

Now let's start the dev server using grunt.

terminal
    
    $ grunt serve
    
  

OK, it looks like our stack is ready.

Pull in test data

We've prepared some data for this screencast which you can download if you are following along. The data consists of dates of reported ufo sightings in 2008, so its uite interesting.

terminal
    
    $ curl -o app/data/sightings.json http://tagtree.tv/ufos_in_2008.json
    
  

We need to wire up the data in our controller. I'm injecting the http service, making a call to our dev server and setting the results on scope, so that we can check it out.

/scripts/controllers/main.js
    
    angular.module('angularRickshawApp')
    .controller('MainCtrl', function ($scope, $http) {
      $http.get('data/sightings.json').success(function(result){
        $scope.sightings = result;
      })
    });
    
  

We quickly wire the view up to the data to test that it was set up correctly. We clear out the generated stuff, now we can set a heading for our view. And we want to repeat a div for each item in our sightings htmlarray.

    
    <div class="header">
      <h3 class="text-muted">UFO sightings in 2008</h3>

      <div ng-repeat="sighting in sightings">
        {{sighting.reportedAt.$date}}
      </div>
    </div>
    
  

Custom graph directive

In order to let d3 play well with angulars weird and wonderful run loop, we're creating a custom directive that will render our graph.

terminal
    
    $ yo angular:direct rickshawChart
    
  
/app/scripts/directives/rickshawChart.js
    
    angular.module('angularRickshawApp')
    .directive('rickshawChart', function () {
      return {
        template: '<div></div>',
        restrict: 'E',
        link: function postLink(scope, element, attrs) {
          element.text('this is the rickshawChart directive');
        }
      };
    });
    
  

We wire up our newly minted directive in the view, just to make sure everything is cool.

/app/views/main.html
    
    <div class="header">
      <h3 class="text-muted">UFO sightings in 2008</h3>

      <rickshaw-chart></rickshaw-chart>
    </div>
    
  

We want to start fleshing out the directive and start using rickshaw to render our d3 graph. We have to bind two variables from the controllers's scope. Allowing the containing view to point the directive to the correct variables in the form of attributes set on the directive . This interesting equals syntax does all that for us, I'm using this for both of the atrributes that we want to wire up in this manner.

/app/scripts/directives/rickshawChart.js
    
    angular.module('angularRickshawApp')
    .directive('rickshawChart', function () {
      return {
        scope: {
          data: '=',
          renderer: '='
        },
        template: '<div></div>',
        restrict: 'E',
        link: function postLink(scope, element, attrs) {
          element.text('this is the rickshawChart directive');
        }
      };
    });
    
  

In our link function we want to make sure that our directive rerenders everytime that the data or renderer values change. We use the nifty watchcollection function for this. $watchcollection takes two parameters, the first is a string that angular can eval on it's scope to create a collection consisting of all the items it should watch. The second is a function that should execute when a value in the collection has changed. We don't want to resume if the data was not set on the scope, so we return if it's undefined.

/app/scripts/directives/rickshawChart.js
    
    link: function postLink(scope, element, attrs) {
      $scope.$watchCollection('[data, renderer]', function(newVal, oldVal){
        if(!newVal[0]){
          return;
        }

      });
    }
    
  

Finally we're getting to the good stuff, the rickshaw code!

We need to pull out the first div in the directive's template, we're going to target this div with the rickshaw graph. At the moment this template only has one div, but we'll be adding more later on.

We call the rickshaw graph constructor function, and specify our first div as the target element.

For the width and height arguments we just pull the width and height values from the attributes set on the directive from the view. We'll make sure to specify them in the view soon.

And now we set the data that the graph will bind to. We can pass multiple series to the graph, but we're going to keep it simple and bind only one series to it. So we wrap our data in an array structure which rickshaw expects.

The last argument to the graph constructor is the render that it should use.

The renderer is just a fancy name for chart type.

Lastly we just call the render function on the graph object.

/app/scripts/directives/rickshawChart.js
    
    angular.module('angularRickshawApp')
    .directive('rickshawChart', function () {
      return {
        scope: {
          data: '=',
          renderer: '='
        },
        template: '<div></div>',
        restrict: 'E',
        link: function postLink(scope, element, attrs) {
          scope.$watchCollection('[data, renderer]', function(newVal, oldVal){
            if(!newVal[0]){
              return;
            }

            element[0].innerHTML ='';

            var graph = new Rickshaw.Graph({
              element: element[0],
              width: attrs.width,
              height: attrs.height,
              series: [{data: scope.data, color: attrs.color}],
              renderer: scope.renderer
            });

            graph.render();
          });
        }
      };
    });
    
  

Prepping the data for rickshaw

Our directive expects two values to be set on its scope, the renderer which we default to line and the data it'll bind to. We utilize underscore to format the data correctly for rickshaw.

First we use countby which counts the number of occurences with the same date. countby takes a function which is used to determine the grouping of each item in the collection, in our case the sighting's date.

At this stage we have an object with attributes for each date it found with the number of occurences as values.

We use the pairs function to morph this structure into a nested array format.

No we can map it to the sctructure expected by rickshaw. It expects an array populated with objects that have x and y attributes that it can use to plot the x and y axes of the graph.

In our case we want the x value to be the date, and the y value the number of sightings for that date.

Rickshaw also expects the data to be sorted according to the x values. So we use the sortby function to sort the items accordingly.

Because we instructed underscore to start a chain of commands, we need to tell it that we want the value to be calculated now.

/app/scripts/controllers/main.js
    
    angular.module('angularRickshawApp')
    .controller('MainCtrl', function ($scope, $http) {
      $http.get('data/sightings.json').success(function(result){
        $scope.sightings = result;

        $scope.renderer = 'line';

        $scope.sightingsByDate = _(result)
          .chain()
          .countBy(function(sighting){return sighting.sightedAt.$date;})
          .pairs()
          .map(function(pair){
            return {
              x: new Date(parseInt(pair[0])).getTime()/1000,
              y: pair[1]
            };
          })
          .sortBy(function(dateCount){return dateCount.x;})
          .value();

      })
    });
    
  

in our view we want to wire up the correct values for the directive to function.The most important value is the pointer to the data on our controller scope. We're going to specify blue for the color, and point it to the renderer value we set on the controller scope. We give it some sensible values for the width and height attributes, and we've got a d3 chart bound to some data with almost no effort!

/app/views/main.html
    
    <div class="header">
      <h3 class="text-muted">UFO sightings in 2008</h3>

      <rickshaw-chart
        data="sightingsByDate"
        color="blue"
        renderer="renderer"
        width="750"
        height="450"
      ></rickshaw-chart>
    </div>
    
  

I'd like to show you something cool quickly. Remember we made the renderer an attribute on the directive? By changing this value, we can quickly change this from a line graph to a bar graph.

/app/scripts/controllers/main.js
    
    $scope.renderer = 'bar';
    
  

Adding axes to the graph

We've got a pretty naked graph at the moment. Let's spruce it up with an x axis that displays the date. Luckily rickshaw makes this easy, all we've gotta do is construct a time axis, associate it with our graph and render the axis.

/app/scripts/directives/rickshawChart.js
    
      renderer: scope.renderer
    });


    var xAxis = new Rickshaw.Graph.Axis.Time({graph: graph});
    xAxis.render();

    graph.render();
    
  

There, now we've got an x axis. It's not very visible at the moment, but we'll remedy that soon.

We'd also like a y axis that displays the number of sightings for the day. The code looks exactly like the x axis, but we use a Y axis object instead of a time axis object.

/app/scripts/directives/rickshawChart.js
    
    var xAxis = new Rickshaw.Graph.Axis.Time({graph: graph});
    xAxis.render();

    var yAxis = new Rickshaw.Graph.Axis.Y({graph: graph});
    yAxis.render();

    graph.render();
    
  

Styling the graph with CSS

We've got the y axis hooked up, but the graph really doesn't look great. Luckily we can do something about that.

That blue is a bit hectic, so let's tone it down a bit. This can be any valid html color, including hex codes.

/app/views/main.html
    
     <rickshaw-chart
       data="sightingsByDate"
       color="steelblue"
       renderer="renderer"
       width="750"
       height="450"
     ></rickshaw-chart>
     
  

Looks a bit better, but we can still improve a lot. What's nice about d3 is that it generates svg markup, and we can style that with CSS.

I'm targeting the x axis first. It's easy to figure out how to target the elements by having a look at the markup that d3 generates for us. Let's make a couple of changes here so that it's fully opaque, has a better color and that it's positioned below the graph to make it more readable.

/app/styles/main.scss
    
    .rickshaw_graph .x_tick .title {
        opacity: 1 !important;
        position: relative !important;
        color: #646464;
        margin-top:10px;
    }
     
  

Thats looking much better. I also want the values in the y axis to be fully opaque.

/app/styles/main.scss
    
    .rickshaw_graph .y_ticks text {
        opacity: 1 !important;
        position: relative !important;
    }
     
  

This should give you an idea of how easy it is to style a rickshaw generated d3 graph with CSS.

Adding hover detail interactivity

Let's make our graph more interactive by adding some info that appears when you hover over the data points., using rickshaw's hoverdetail function.

As with the axes we need to provide it with a graph it should attach to.

The next argument is totally optional,but i wanted to illustrate the flexibility of the rickshaw controls by providing a custom value for the tooltip that will show when you hover over a data point in the graph.

This we achieve by passing a function as the formatter argument, which uses the values of the datapoints to build a label for the tooltip.

/app/scripts/directives/rickshawChart.js
    
    var yAxis = new Rickshaw.Graph.Axis.Y({graph: graph});
    yAxis.render();

    var hoverDetail = new Rickshaw.Graph.HoverDetail({
      graph: graph,
      formatter: function(series, x, y, formattedX, formattedY){
        return y + " sightings on " + formattedX;
      }
    });
    graph.render();
    
  

There ya go, a custom tooltip!

Dynamic graph types

Instead of hardcoding the chart type on the controller, we'll make it a bit more dynamic. First we need to specify the type options in an array and set it on the scope.

/scripts/controllers/main.js
    
        $scope.sightings = result;

        $scope.renderers = ['line', 'bar', 'scatterplot', 'area'];

        $scope.renderer = 'bar';
    
  

We bind the view to the array in the form of a dropdown.

Luckily angular makes this really easy wigth the ng options directive.

/app/views/main.html
    
     <h3 class="text-muted">UFO sightings in 2008</h3>
     <select ng-options="renderer for renderer in renderers"
        ng-model="renderer" />

     <rickshaw-chart
       data="sightingsByDate"
       color="steelblue"
    
  

Now we can easily change the chart type directly from the view.

Zooming and panning

Its a bit difficult to discern individual dates on the graph, so lets add a way to zoom in on the data by using the slider functionality provided by rickshaw.

Unfortunately this requires some widgets from jquery ui, so we need to pull that into our project with bower.

We need to reference the correct javascript and css from jquery ui to support our slider widget.

terminal
    
    $ bower install jquery-ui
    
  
/app/index.html
    
    <script src="bower_components/rickshaw/vendor/d3.v3.js"></script>
    <script src="bower_components/rickshaw/vendor/d3.v3.js"></script>
    <script src="bower_components/jquery-ui/ui/jquery-ui.js"></script>
    <script src="bower_components/jquery-ui/ui/jquery.ui.slider.js"></script>
    <!-- endbuild -->
    
  

We need to do a couple of things in our directive to pull in the slider.

First we construct the slider similar to how we constructed the previous chart controls.

We specify the graph it shoiuld be attached to, and we also need to specify an element that should contain the slider.

We hook it up to a child element of our template with a class called slider.

/app/scripts/directives/rickshawChart.js
    
    var slider = new Rickshaw.Graph.RangeSlider( {
      graph: graph,
      element: element.find('.slider')
    });

    graph.render();
    
  

Because our template doesn't have such an element yet, we quickly create it in the template argument to the directive.

/app/scripts/directives/rickshawChart.js
    
    scope: {
      data: '=',
      renderer: '='
    },
    template: '<div></div><div class="slider"></div>',
    restrict: 'E',
    
  

And now we've got a functional slider that allows us to zoom in and out and also move backwards and forwards on the graph. But I don't like its position, so I'll tweak it in the CSS.

I think the slider should be positioned below the chart and not over it.

/app/styles/main.scss
    
    .slider {
        margin-top: 20px;
    }
    
  

That looks better to me!

Wrap Up

I love the flexibility D3 gives us, but I think when you want to work with graphs you don't want to mess around with low level code. Rickshaw does a great job in providing us with a graphing specific API, with a reasonable level of flexibility. What I like is that it still results in D3 generated SVG which we can style with CSS!

See you next time!