2

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

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
cirrusio
  • 580
  • 5
  • 28
  • If it was me I would try bypassing angular scope entirely for that sort of traffic so digests don't have to be performed and let d3 handle the data directly – charlietfl Sep 25 '15 at 15:22
  • I am not following this Charlie - are you suggesting that I remove the nvd3 directive? – cirrusio Sep 25 '15 at 17:01
  • Yeah you might need to roll-your-own. The next piece of code to read is `nvd3.lineChart` – Thomson Comer Sep 25 '15 at 17:05

2 Answers2

7

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

krispo
  • 526
  • 1
  • 4
  • 11
  • Krispo,Thanks for answering. Maybe I am totally not understanding the difference in implementation, but here is an example of NVD3 taking a lot of data at regular intervals and NVD3 seems to have no problem - http://plnkr.co/edit/oUTsyuvN4kdk23FyQErB. So what exactly do I need to do in relation to AngularJS that is different than this implementation. – cirrusio Sep 28 '15 at 17:50
  • 1
    The only difference is that directive has watcher for data updates. And if this watcher is active, it's triggered every time when $digest loop updates. And this leads to memory leaks. So we turn it off with `deepWatchData: false` and then just update the chart via `$scope.api.update()`. I've updated your example with angularjs: http://plnkr.co/edit/PqepCg – krispo Sep 29 '15 at 21:36
  • Awesome, @krispo - you beat me to the punch! My current version of angular-nvd3 has the ``deepWatchData`` flag set to ``true``, so I will try this out. I will let you all know what the results are... – cirrusio Sep 29 '15 at 21:58
  • Thanks, @krispo - looks a lot better. The browser is still getting clobbered (it's a little laggy), but there is likely some optimization that I am missing. Cheers, m – cirrusio Sep 30 '15 at 05:44
1

Are you using SVG? nvd3.lineChart is SVG so yeah, probably. If so, @mbostock has the answer for you: http://bl.ocks.org/mbostock/1276463. Use a canvas instead of SVG for lots more speed.

Most of the suggestions on https://www.safaribooksonline.com/blog/2014/02/20/speeding-d3-js-checklist/ are pretty solid.

Are you redrawing all 5000 points each second? If so, this is a job for webGL imo, not nvd3. canvas might be fast enough to do this, if canvas isn't fast enough then I'll stick to former answer.

What % of the time is it spending in d3_timer_step? It doesn't make sense that that function would be slow, it may just be called a great many times. Actually, d3_timer_frame is called by d3_timer_step, which could be the actual render code and would definitely take all your time. Try to do the canvas.

possible nvd3 performance improvements:

  1. Definitely disable useInteractiveGuideline if you haven't already.
Thomson Comer
  • 3,919
  • 3
  • 30
  • 32
  • It seems that svg shouldn't really have a problem until you get up there in terms of the number of points - I have seen something like 100k. 5k points doesn't seem like a lot to render. 5 to 6 seconds to render all of this? Seems like I am abusing the framework somehow. Did you see anything wrong with the setup? – cirrusio Sep 25 '15 at 17:00
  • Do you have a suggestion for using canvas with angular? – cirrusio Sep 25 '15 at 17:04
  • Try to paste @mbostock's canvas code directly into your angular app as pure html and js. Once you can get it to render, you can modify it to have your data. – Thomson Comer Sep 25 '15 at 17:06
  • When printing the charts on paper is a requirement, you might want to not use `canvas`. Just try and print any chart using a library using `canvas` instead of `svg`. – Christiaan Westerbeek Jan 24 '17 at 09:24