Integrating dc.js with AngularJS & other charting libraries.

(Spanish version coming soon)
For an ongoing project we are using a mix of visualization technologies, most of it is done with dc.js and Crossfilter.

Crossfilter together with Reductio are a couple of nice libraries that let you slice and dice a dataset in no time.

dc.js binds them with D3 and the result is a set of visualizations that interact and react to changes in each other. You click on a pie chart element and without further ado all the other charts in the same group are automagically filtered by that value. dc.js takes care of applying the corresponding filters to a crossfilter Dimension and tells all the affected charts to redraw themselves.

But what if we wanted to use another visualization library, like amCharts? Or displaying some filtered result within an AngularJS application?

A naive solution:

We can get the filtered data from a dimension calling:

1
var filteredData = theDimension.top(Infinity);

and we can plug that into a watcher and call it a day

1
2
3
4
5
6
7
// Don't do this.
// If you do, perhaps using watchCollection can better suit your needs.
$scope.$watch(function () {
  return theDimension.top(Infinity);
}, function (newValue, oldValue) {
  // do something...
});

or just making an accessor and binding to that from a template:

1
2
3
vm.getFilteredValues = function () {
  return theDimension.top(Infinity);
}

However this is far from optimal, mainly for two reasons. First, we are polling for changes instead of being notified when they happen, incurring in redundant comparisons. Second, when the dataset size grows this starts to impact negatively the overall performance.

The Chart Registry

How does dc.js manage to be so snappy in spite of this?

dc.js keeps track of all the related charts by using a Registry. When you apply a filter from within dc.js all the charts that share the same group are redrawn (of course, if you filter a dimension from outside dc you have to call dc.redrawAll(‘the-group-name’);)

Knowing this we can register a fake chart and be notified when the User filters the dataset. This is very easy to do:

1
2
3
4
5
6
7
8
9
10
11
12
13
function onDatasetFiltered () {
  // Do something with the dimension.
  // We are being called from outside the digest cycle, so we need
  // to trigger one for Angular to pick up the changes.
  $scope.$apply();
}
 
var fakeChart = {
  render: onDatasetFiltered,
  redraw: onDatasetFiltered
}
 
dc.chartRegistry.register(fakeChart, 'the-group-name');

And all of a sudden we now react to changes instead of continuously polling.

Deja una respuesta