Search code examples
javascriptangularjsnvd3.jsangular-nvd3

nvd3 used with angular-nvd3 is slooooowwwww


I believe I have a problem that may be fairly easily addressed via something that I am missing, but I can't seem to see what the actual issue is. I have an application that returns 5000 points (5 array elements of 1000 x,y points) every second that I want to update on the client side using NVD3. This is an AngularJS application, so I am using krispos angular-nvd3 directive. However, it is bogging the whole application down, and it appears that, according to the timeline captured by Chrome's developer tools, the application seems to be waiting on d3_timer_step to return for 5-6 seconds.

I thought this problem was due to how we were updating the data, but the whole issue seems to be with the actual d3 portion. The code on the client side is

<nvd3 options="optionsRingdown" data="ringdownAvg" config="{refreshDataOnly:true}"></nvd3>

and in the controller the options are defined as follows

$scope.options = {
    chart: {
      type: 'lineChart',
      height: 300,
      margin: {
        top: 20,
        right: 40,
        bottom: 60,
        left: 75
      },
      x: function(d) {
        return d.x;
      },
      y: function(d) {
        return d.y;
      },
      useInteractiveGuideline: false,
      yAxis: {
        tickFormat: function(d) {
          return d3.format('0.01f')(d);
        },
        axisLabel: 'Testing'
      },
      xAxis: {
        tickFormat: function(d) {
          return d3.time.format('%X')(new Date(d));
        },
        rotateLabels: -45
      },
      transitionDuration: 0,
      showXAxis: true,
      showYAxis: true
    }
  };

and the data is defined in the following template

var ringdownT = [{
   values: [],
   key: 'Cell 0'
 }, {
   values: [],
   key: 'Cell 1'
 }, {
   values: [],
   key: 'Cell 2'
 }, {
   values: [],
   key: 'Cell 3'
 }, {
   values: [],
   key: 'Cell 4'
 }];

The data is updated via a function call on broadcast from a service using the following

function updateCRD(d){
   var dataOut = {
     "tauData": [],
     "rdFit": ringdownT,
     "rdAvg":ringdownT
   }
   for (k = 0; k < d.cell.length; k++) {
     dataOut.rdAvg[k].values = d.cell[k].avg_rd;
     dataOut.rdFit[k].values = d.cell[k].fit_rd;
   }

   return dataOut;
}

The function is called in a broadcast using the following (which is broadcast at 1 second intervals)

$scope.$on('dataAvailable', function() {

    $scope.data = Data.crd;

    var data = updateCRD(Data.crd);

    $scope.tauData = data.tauData;
    $scope.ringdownAvg = data.rdAvg;
    $scope.ringdownFit = data.rdFit;
});

Does anyone see something that looks obviously wrong here or that I should be doing differently? Is there an option that I am missing? Any help would be great.

Cheers, Matt


Solution

  • Try to add deepWatchData: false flag to config (it means that directive won't watch the data for updates) and update chart via api:

    <nvd3 options="optionsRingdown" data="ringdownAvg" api="apiRingdown" config="{refreshDataOnly:true, deepWatchData: false}"></nvd3>
    

    The directive watches options and complex data objects for any updates using $watch(watchExpression, listener, [objectEquality]) method. In our case deepWatchData is the objectEquality flag, while watching chart data for updates.

    According to the angular docs, inequality of the watchExpression is determined according to the angular.equals function. And to save the value of the object for later comparison, the angular.copy function is used. This therefore means that watching complex objects will have adverse memory and performance implications.

    In versions (1.0.2, 1.0.3) only, this flag is false by default.


    Then, to update chart, we can use apiRingdown.update method in your controller:

    $scope.$on('dataAvailable', function() {
    
        $scope.data = Data.crd;
    
        var data = updateCRD(Data.crd);
    
        $scope.tauData = data.tauData;
        $scope.ringdownAvg = data.rdAvg;
        $scope.ringdownFit = data.rdFit;
    
        //this line updates the chart
        $scope.apiRingdown.update();
    });
    

    UPDATED

    Some updates are added in the latest versions [1.0.4+]. Now flag deepWatchData means to use or not to use data watching at all (it's not objectEquality as before). And deepWatchData is true by default. But now we can manage the $watch depth with a new flag deepWatchDataDepth: 2, and thereby regulate performance. With this flag we can specify a change detection strategy (scope $watch depth) for data:

    0 - By Reference (the least powerful, but the most efficient)
    1 - By Collection Items
    2 - By Value (the most powerful, but also the most expensive; default value)
    

    Also, flag refreshDataOnly is true by default.

    So, the updated tag element may look like:

    <nvd3 options="optionsRingdown" data="ringdownAvg" api="apiRingdown" config="{deepWatchDataDepth: 0}"></nvd3>
    

    demo