Saturday, December 19, 2015

Radial Bar Chart Using D3.js - Part 1

I recently received an email regarding a class on learning data visualization using D3.js. On a linked webpage that gave more information about the class, there was an image of a chart (see below) that really resonated with me. I liked how it displayed a variable number of questions in equally weighted categories, similar to the layout of some survey data that I deal with at work. I thought this would be a great basis for building my own chart with some features specific to my own data. Because the image was on a page advertising a class on D3.js, I naturally thought it was created with D3.js.

Why People Go Online - Ruder Finn Intent Index

However, when I started looking on the web for a D3 version of the above chart, I stumbled upon a blog post (and two others) that contained the exact same image. The posts were all written in July of 2009 (which explains the distribution of the answers) about the Ruder-Finn Intent Index on Why People Go Online. Links to the original chart on the www.ruderfinn.com site were no longer valid, but after a little more searching I found a different version (written in Flash) at www.intentindex.com, which enables one to experience the chart animations and interactivity.

With the knowledge that a D3 version of this chart didn't exist, I decided that a good exercise (and the basis for this series of blog posts) would be to try to replicate the look of the Ruder-Finn Intent Index. I could then use it to come up with a chart that would better fit my own data.

Searching through D3 samples online, I came across a Radial Bar Chart which I felt was a good starting point. This contained many of the elements that I would need to replicate the Ruder-Finn Intent Index chart, such as the gridlines, radiating bars and a simple, but effective, animation.
 


Based on that code, I was able to piece together the above background for my chart. Nothing really groundbreaking, but a decent replica of the Intent Index chart.

In Part 2 of this series, I'll add the question gridlines and bars within each category. In Part 3, I'll add the category and question labels.

For a complete listing of the code and a working example, check out the Plunker: 

Friday, April 3, 2015

AngularJS Expand/Collapse All Rows in a Hierarchical Table

I've been working with AngularJS lately as an alternative to the traditional way of displaying and editing data in SharePoint. In particular I've been using this framework with custom web services as a quick way of accessing and editing information not stored in SharePoint.

One of these external sources of data which I needed to display within my SharePoint site consists of dealership information, including the brands and product types that they are active in. So, to begin with, I created a web service that returned hierarchical JSON consisting of dealers and their brand/product combinations. A somewhat simplified example looked like the following:

[
  {
    "DealerID": 1,
    "Dealer Name": "Dealer 1",
    "Address": "123 Main St",
    "City": "Irvine",
    "State": "CA",
    "ZIP Code": 92603,
    "Brands": [
      {
        "Brand": "Brand A",
        "Product I": "",
        "Product II": "X",
        "Product III": "X",
        "Product IV": "X",
        "Product V": "X"
      },
      {
        "Brand": "Brand B",
        "Product I": "",
        "Product II": "",
        "Product III": "X",
        "Product IV": "",
        "Product V": "X"
      }
    ]
  },
  {
    "DealerID": 2,
    "Dealer Name": "Dealer 2",
    "Address": "456 2nd Av",
    "City": "Portland",
    "State": "OR",
    "ZIP Code": 97225,
    "Brands": [
      {
        "Brand": "Brand A",
        "Product I": "X",
        "Product II": "X",
        "Product III": "X",
        "Product IV": "",
        "Product V": "X"
      },
      {
        "Brand": "Brand C",
        "Product I": "",
        "Product II": "",
        "Product III": "X",
        "Product IV": "",
        "Product V": "X"
      },
      {
        "Brand": "Brand D",
        "Product I": "",
        "Product II": "",
        "Product III": "X",
        "Product IV": "X",
        "Product V": "X"
      }
    ]
  }, ...

In order to display this data I decided to use a hierarchical table layout, where one row would show the dealer information and the next row would contain a child table that would display the brand information. This was easily accomplished using Angular's ng-repeat-start for the first row and ng-repeat-end for the second, repeating this two-row pattern for all dealers.

Because ng-repeat produces a separate child scope for each dealer row, I could add a button element to a cell in the dealer row with an ng-click event that toggles an "expanded" property on the scope. The related brands table row could then be shown or hidden by setting the ng-show property equal to the scope's "expanded" property. In preparation for the expand/collapse all capability, I also added a button to the with an ng-click event that toggles an "allExpanded" property of the rootscope (which is largely impotent at this point). Something similar to the following:


<table>
    <tr>
        <th>
            <button type="button" ng-click="allExpanded = !allExpanded">
                <span ng-bind="allExpanded ? '-' : '+'"></span>
            </button>
        </th>
        <th>Dealer</th>
        <th>Address</th>
        <th>City</th>
        etc...
    </tr>
    <tr ng-repeat-start="dealer in ctrl.dealers>
        <td>
            <button ng-click="expanded = !expanded">
                <span ng-bind="expanded ? '-' : '+'"></span>
            </button>
         </td>
        <td>{{dealer.Dealer}}</td>
        <td>{{dealer.Address}}</td>
        <td>{{dealer.City}}</td>
        etc...
    </tr>
    <tr ng-repeat-end ng-show="expanded">
        <td colspan=5>
            <table>
                <tr>
                    <th>Brand</th>
                    <th>Product 1</th>
                    <th>Product 2</th>
                    etc..
                </tr>
                <tr ng-repeat="brand in dealer.Brands">
                    <td>{{brand.Brand}}</td>
                    <td>{{brand.Product1}}</td>
                    <td>{{brand.Product2}}</td>
                    etc..
                </tr>
            </table>
        </td>
    </tr>
</table>

So far, so easy with AngularJS!

Now for the part that wasn't so apparent and the reason for this post. How to create the expand/collapse all functionality. Googling mostly led to examples where the expanded state was being stored in the data model itself. In my example, this would mean adding an "expanded" attribute to each dealer object in my model which would make creating an expandAll function that could loop through all objects in the dealer model and setting the "expanded" attribute of each dealer object trivial. But I had no reason to pollute my dealer model with view state information. What I needed was a way to change the expanded property of each dealer row's scope based on the "allExpanded" property.


Enter Angular's $scope.broadcast and $scope.on


$scope.broadcast dispatches an event name down the scope hierarchy notifying the registered/subscribed listeners and takes an event name and an optional list of arguments. Using this method, I could create an expandAll function within my main controller (called by the ng-click event of the expand all button) to broadcast a change in the allExpanded property to all child scopes:


self.expandAll = function (expanded) {
    $scope.$broadcast('onExpandAll', {expanded: expanded});
};

On the receiving end, the $scope.on method listens for those broadcasted events. $scope.on takes the name of the event to listen for (e.g. "onExpandAll"), and an event listener function in the form of function(event, args). The "args" argument is used to access the arguments broadcasted in the $scope.$broadcast method. One way to accomplish this for the child scopes created by ng-repeat, is to create an "expand" directive.

.directive('expand', function () {
    function link(scope, element, attrs) {
        scope.$on('onExpandAll', function (event, args) {
            scope.expanded = args.expanded;
        });
    }
    return {
        link: link
    };
});

This directive can then be placed as an attribute on an element within the ng-repeat... I chose the expand button that is created for each dealer row. 

<tr ng-repeat-start="dealer in ctrl.dealers">
    <td>
        <button ng-click="expanded = !expanded" expand>
            <span ng-bind="expanded ? '-' : '+'"></span>
        </button>
    </td>
    ...

And viola...



For a complete listing of the code and a working example, check out the Plunker: 
http://plnkr.co/edit/qqNGSbw6oLUZbKqVQVpG?p=preview

Note, for my project (and this example), I used Angular 1.2.28 because I needed to support Internet Explorer 8.

Finally, although it wasn't required for my project, an interesting next step would be to convert this logic to work with hierarchical JSON that went deeper than a single child level, allowing each child to expand all of its children and so on.