在AngularJS选项卡中实现Highcharts动态(重新)调整大小

9
我的应用程序在各个选项卡中显示HighCharts图表(AngularJS)。 请注意,每次选择标签时都会重新生成图表(这意味着Angular会每次“删除或重新创建DOM树的一部分”)。
我的问题是,当我点击选项卡时,图表的大小只有第一次是正确的。 当我切换选项卡时,创建的图表大大超出了其容器的大小。 令人惊讶的是,调整窗口大小后,图表会正确地重新调整大小(即当调用chart.reflow()时)。
所以我尝试了以下内容,但没有帮助:
$(element).highcharts({
    chart: {
      type: 'scatter',
      zoomType: 'xy',
      events: {
        load: function () {
          this.reflow();
        }
      }
    },
    ...

最后,以下方法确实可行,但感觉有些取巧。
$(element).highcharts({
    chart: {
      type: 'scatter',
      zoomType: 'xy',
      events: {
        load: function () {
          var chart = this;
          setTimeout(function () { chart.reflow(); }, 0);
        }
      }
    },
    ...

有任何想法关于这个问题是从哪里来的吗?我已经花了几个小时在这上面,感到非常沮丧...
编辑:我还有另一个相关的问题:默认情况下,我的图表有固定的大小,但我有一个“放大”按钮,使它们占据整个空间。该按钮仅切换CSS类以进行操作。然而,这样做并没有触发调整大小事件,也没有触发回流。所以我遇到了相同的问题,即图表不能填充其容器。
这里有一个演示这个问题的fiddle:http://jsfiddle.net/swaek268/3/ 再次说明,我已经找到了解决问题的方法,但感觉像更大的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();
2个回答

7

我想我找到了解决我的两个问题的方法:

  1. 图表加载完成后尺寸错误:

问题不是来自Highcharts,而是来自创建图表的时机。我直接从我的Angular指令的link()函数中调用了.highcharts()。JS代码大致如下:

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

由于我的元素还有一个ng-if指令,取决于选项卡是否被选择,所以我想当该条件变为真时,Angular会在确定元素的尺寸之前调用link()函数(我猜想HTML必须就绪才能确定元素的大小)。所以我认为问题是我在文档准备好之前调用了.highcharts()。出于某种原因,我仍然不理解,第一次ng-if变为true时元素的大小是正确的,但下一次则不正确。
无论如何,一个解决方案(仍然不是很优雅,但比调用reflow()要好)是延迟对.highcharts()本身的调用,以确保在创建图表时HTML已经准备好:
link: function(scope, element, attrs){
  setTimeout(function () {
    $(element).highcharts({...});
  }, 0);
}

感谢@Pavel Fus.highcharts()setTimeout()行为的评论。

  1. 切换类时图表未重新调整大小

对于这个问题,我利用了Angular的事件系统。

在控制全屏按钮的控制器中:

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

这个事件会在层级结构中传递,最终到达包含图表的指令中。当我创建图表时,我只需要监听此事件,并相应地触发reflow()

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

请注意,这个技巧仅在我使用setTimeout()延迟回流时才会生效。不确定为什么会这样。
希望这可以帮到你!我花了很长时间来解决这个问题...

你能否看一下我的问题,也许你有想法。https://dev59.com/45ffa4cB1Zd3GeqP3BpQ - Abderrahim

1

因此,实际上您有两个问题:

1)第二次单击时大小不正确。这很可能是由于隐藏(display: none)容器的宽度和高度错误导致的。在这种情况下,浏览器报告不正确的宽度/高度(如0px)。同样的问题请参见这里这里

2)为容器设置类不会触发图表的resize(或者更确切地说是reflow)事件。这意味着您需要自己调用chart.reflow()。我建议您在更改类的同时执行此操作。


谢谢您的回答,但对我来说那不起作用: 1)我的选项卡实现没有使用display: none。我正在使用Angular指令ng-if,这意味着每次单击时选项卡的内容都会完全重新生成。因此,问题必须从Highcharts的某种持久/状态行为中产生。换句话说,使用.highcharts()不能每次都产生完全相同的结果。 - Eric Leibenguth
正如我在Fiddle中提到的那样,我无法这样做,因为在我更改类的位置,我事先不知道容器的内容。它可能是一个图表,但也可能是一个表格、文本或任何类型的jquery插件生成的内容。因此,我没有一个可以调用chart.reflow()chart句柄。 - Eric Leibenguth
  1. .highcharts() 会产生相同的输出。如果宽度/高度不正确,则 Highcharts 将使用默认的宽度/高度。
  2. 你说你不知道,但是你可以调用 planreflow() 吗?无论如何,你不能简单地使用:if($("#container").highcharts()) { ... } 吗?
- Paweł Fus
  1. 如果那确实是这种情况,那么我猜应该是我的Angular指令以某种有状态的方式运行... 代码很多,所以很遗憾无法分享。
  2. 我可以在调用.highchart()的代码中调用planreflow(),但全屏按钮不知道这部分代码,也不知道Highcharts。可能会出现任何情况...
- Eric Leibenguth
另外:1)如果宽度/高度不正确,那么为什么我的hack(排队回流)可以解决问题? setTimeout(function () { chart.reflow(); }, 0); - Eric Leibenguth
setTimeout 创建一个新的线程,并将其放置在堆栈上 - 它不是 0 毫秒,而是几毫秒的延迟。换句话说,它允许浏览器执行一些页面回流/重绘(非 JS 事件),最可能的是此时尺寸是正确的。你能检查一下图表的宽度/高度吗?如果它们是 600x400,则这些是当高度/宽度为 NaN/0 时的默认值。 - Paweł Fus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接