9

My application displays HighCharts charts in various tabs (using AngularJS). Note that the charts are re-generated on the fly every time a tab is selected (Meaning that Angular "removes or recreates a portion of the DOM tree" every time).

My problem is that the size of the chart is correct only the first time I click on a tab. When I switch tabs, the charts that are created largely exceed the size of their container. Surprisingly, the charts are correctly resized after a window resize (i.e. when chart.reflow() is called).

So I tried the following, which did not help:

$(element).highcharts({
    chart: {
      type: 'scatter',
      zoomType: 'xy',
      events: {
        load: function () {
          this.reflow();
        }
      }
    },
    ...

Finally, the following did work, but it feels like a hack.

$(element).highcharts({
    chart: {
      type: 'scatter',
      zoomType: 'xy',
      events: {
        load: function () {
          var chart = this;
          setTimeout(function () { chart.reflow(); }, 0);
        }
      }
    },
    ...

Any idea where the problem comes from? I am spending hours on this, and this is pretty frustrating...

EDIT: I have another related question: By default my charts have a given size, but I have an "enlarge" button to make them take the full space. The button simply toggles a CSS class to do that. However, doing this does not trigger a resize event, and so does not trigger a reflow. So I have the same problem of charts not filling their container.

Here is a fiddle to demonstrate the issue: http://jsfiddle.net/swaek268/3/

Again, I have found a way around the problem, but it feels like an even bigger hack.

var width = 0;
var planreflow = function(){
  setTimeout(function(){
      if(width!==$('#container').width()){
          $('#container').highcharts().reflow(); 
          console.log('reflow!')
      }
      width = $('#container').width();
      planreflow();
  }, 10);
};
planreflow();
Eric Leibenguth
  • 4,167
  • 3
  • 24
  • 51

2 Answers2

7

I think I found solutions to both of my problems:

  1. Wrong size when chart finishes loading:

The problem does not come from Highcharts, but from the timing when the chart is created. I directly called .highcharts() from the link() function of my Angular directive. The JS looking essentially like this:

app.directive('dynamicView', function() {
  return {
    link: function(scope, element, attrs){
      $(element).highcharts({...});
    },    
  };
});

As my element also has a ng-if directive depending on whether the tab is selected or not, I imagine that when that condition turns true, angular calls the link() function before the dimension of the element is determined (I guess the HTML has to be ready in order to determine the size of the elements). So I think the problem was that I was calling .highcharts() before the document was ready. For some reason that I still don't understand, the size of the element is correct the first time ng-if turns true, but incorrect the next times.

Anyway, a solution (still not very elegant - but better than calling reflow()) is to delay the call to .highcharts() itself, to make sure that the HTML is ready when creating the charts:

link: function(scope, element, attrs){
  setTimeout(function () {
    $(element).highcharts({...});
  }, 0);
}

Thank you @Pavel Fus for your comments on the behaviour of .highcharts() and setTimeout().

  1. Chart not resizing when toggling a class

For this problem I took advantage of Angular's eventing system.

In my controller controlling the full-screen button:

$scope.toggleFullScreen = function(e){
  $scope.$broadcast('fullscreen');
}

This event is passed down the hierarchy, ending-up to my directives containing the charts. All I have to do, when creating my charts is to listen to this event, and trigger a reflow() accordingly:

scope.$on('fullscreen', function(){
  setTimeout(function () { 
    $(element).highcharts().reflow(); 
  }, 0);
});

Note that --again-- the trick only works if I delay the reflow with setTimeout(). Not sure why.

I hope this helps! Spent quite a bit of time on this...

Eric Leibenguth
  • 4,167
  • 3
  • 24
  • 51
  • can you please see my question maybe you have an idea http://stackoverflow.com/questions/37256487/i-want-to-change-the-chart-for-each-city-or-subcity-selected – Abderrahim May 17 '16 at 13:57
1

So, in fact you have two questions:

1) Incorrect size at second click. This is most probably caused by wrong width and height for a hidden (display: none) container. In that case, browsers report incorrect width/height (like 0px). The same question here and here.

2) Setting class for a container wouldn't fire resize (or rather reflow) event for a chart. That mean you need to call chart.reflow() on your own. I suggest to do that in the same place as you change your class.

Community
  • 1
  • 1
Paweł Fus
  • 44,795
  • 3
  • 61
  • 77
  • Thank you for your answer, but that will not work for me: 1) My implementation of tabs is not using `display: none`. I'm using the angular directive `ng-if`, which means the content of the tab is re-generated entirely on each click. So the problem somehow has to come from some kind of persistent/state-full behaviour of Highcharts. In other words, using `.highcharts()` does not produce exactly the same result every time. – Eric Leibenguth May 25 '15 at 09:46
  • 2) As mentionned in my Fiddle, I can't do that, because where I change the class I don't know a priori the content of the container. It could be a chart, but it could also be a table, or text, or any kind of jquery-plugin-generated content. So I don't have a handle on `chart` to call `chart.reflow()`. – Eric Leibenguth May 25 '15 at 09:50
  • 1) `.highcharts()` produces the same output. If width/height is incorrect then Highcharts uses default width/height. 2) You say you don't know, but you can call `planreflow()`? Anyway, can't you simply use: `if($("#container").highcharts()) { ... }`? – Paweł Fus May 25 '15 at 09:59
  • 1) If that's unquestionably the case, then I guess it's my angular directive that somehow behaves state-fully... There's a lot of code, so it's hard to share, unfortunately. 2) I can call `planreflow()` in the code that calls `.highchart()`, but the fullscreen-button does not know about this part of the code, nor about Highcharts. Could be potentially anything... – Eric Leibenguth May 25 '15 at 10:33
  • Also: 1) If the width/height *is* incorrect, then why does my hack (queuing the reflow) fixes the problem? `setTimeout(function () { chart.reflow(); }, 0);` – Eric Leibenguth May 25 '15 at 10:48
  • `setTimeout` creates new thread, and put on a stack - it's not 0ms, but rather a couple of ms delay. In other words, it allows browser to do some page reflows/redraws (non-js things) and most probably dimensions are proper at that point. Could you check your charts widths/heights? If those are 600x400 - then that are default values for width and height, when height/width is NaN/0. – Paweł Fus May 25 '15 at 12:50