3

The goal of this question

To understand why I have to wrap a highcharts load event handler call with a $timeout, before I can have full access to all the data the highcharts object embedded in the event is meant to provide. As stated in the topic I am using angularjs and highcharts-ng.

I find it really weird that the load event manages to fire before all of the data that is supposed to be present in the object is present. Especially since I am not using addSeries to populate the chart with data. I am manually constructing the entire config object. This approach is also what highcharts-ng prescribes.

I also find it hard to accept that I need to use a '$timeout' to deal with what seems to be a race condition. This is because I am from a C++ background, it might not be a race condition, but just one of the natural quirks of the nature of what is modern web development, javascript and Jquery. Of which I know very little.

If I can understand what is going on, I can move on with some confidence. If this issue is to do with our use of highcharts-ng I could ditch that and wrap highcharts myself. If there is a better way of solving my use-case (see below) that would be ok as well.

The behaviour I am seeing

I started working on solving my problem by printing out the highcharts object to the console, it showed all the fields when I expanded it. However, when I tried to begin working on my problem I started to see that some of the fields are actually undefined when I try to access them.

Ok so I discovered that this is because by the time I click expand in the console all values have been populated. But when I try to access them in my load handler they are undefined.

When I wrap my callback in angular's $timeout the problem goes away. But why ? highcharts should not have triggered the load callback till it has done all it's calculations, in fact it cannot render the chart without calculating some of the undefined fields. So what gives ? Is some html5 web worker multithreading going on ? :D

Highcharts Animation... Is an animation causing the race like behaviour ?

I found some references on this issues where the prescribed solution was to hook in to animation complete method. The chart has animation disabled. But I suppose some jquery animation could still be in use by highcharts ? I could try hooking into this handler if it would solve my problem and means I don't have to use a $timeout.

The problem I am trying to solve.

I am working on a complex visualisation app and I need to dynamically rescale the Y-Axis so that 2 key plotlines on the Y axis are always present. Hard coding the extremes is not an option as it prevents the charts from scaling out based on the range of the data.

Highcharts calculates various max and min values, I want to examine these, and if my plot lines do not fall within these max and min values I want to set new extremes and redraw the graph.

This question has some potential solutions to the problem. Sadly, the hacky answer seems the most robust and simplest --i.e., add a point on the plot line and hide it. I might go down this route to save time. Though I don't want to.

Community
  • 1
  • 1
Hassan Syed
  • 20,075
  • 11
  • 87
  • 171
  • 1
    $timeout will force a digest cycle after it executes the code. If this resolves the issue, I suspect that maybe you are calling a Highcharts API, or handling a jQuery event that is outside the angular context. If so, consider if you are using the highchargs-ng plugin as intended, or whether you are going straight to the highcharts itself; or possibly you might be handling a jquery event? Is it possible for you to post a basic fiddle that shows the issue? – Michael Kang Jun 15 '14 at 19:55
  • Yes I am trying to go straight to highcharts, and yes this is a jquery event that my handler is attaching to. I subscribe to the load event of the chart. I am fairly certain that the callback is triggering whilst angular is digesting, as I tried to wrap said callback in a $scope.$apply as I was tired and desperate and got an error from angular with "inprog" in it, which I guess means I am trying to $apply within an existing apply. Which is quite hard for me to understand. Shouldn't a jquery event only be able to get processing time when angular is not active ? – Hassan Syed Jun 15 '14 at 20:12
  • No, jquery events happens when it happens. There is no synchronization between jquery events and angular digest cycles. The reason you get a "inprog" error is because you are in code that angular knows about - angular will call $apply after your code is executed. The $timeout resolves the issue by adding an event to the event processing queue which runs after angular has finished rendering. Rendering in angular means finished all DOM manipulations and evaluating all angular expressions. $timeout ensures a digest cycle is triggered after the rendering process. – Michael Kang Jun 15 '14 at 20:31
  • You have given me something to think about, I wonder how angular "knows" about code that belongs to it. In either case reading [this](https://docs.angularjs.org/error/$rootScope/inprog) and [this](http://stackoverflow.com/questions/12729122/prevent-error-digest-already-in-progress-when-calling-scope-apply) gives me some more perspective. – Hassan Syed Jun 15 '14 at 21:05
  • Ok so here is my understanding at this point. The jqeury highcharts event is triggered by highcharts-ng which is in an apply or digest cycle, the event handler gets triggered before angular leaves it's apply cycle. Can jquery events be executed in another thread , or DOM events ? If this is the case that makes perfect sense. Especially how $timeout would fix said problem. That would also answer the source of confusion in my question. I'm operating under the assumption that javascript and DOM events / jquery are all running under the same single thread (like python). – Hassan Syed Jun 15 '14 at 21:07
  • Angular knows about code because it is the angular framework that is calling it-we're just supplying the callbacks. Angular makes heavy use of callback functions - you write the callback - and Angular will call it at the appropriate time. After it executes your callback, it can queue up a digest cycle at its discretion. – Michael Kang Jun 15 '14 at 21:10
  • I'll update that thought above to "can jquery callbacks occur in another special thread (DOM related or web worker) or can the call occur whilst the originator blocks (for example highcharts-ng inside an angular apply)". Again excuse my ignorance of DOM mechanisms, HTML5 and jquery. And I wish we didn't have to have this conversation in comments. And many thanks. – Hassan Syed Jun 15 '14 at 21:22
  • Here is what I think is happening. The load event is firing from the Highcharts plugin, because the DOM is technically ready. But angular is still in the process of manipulating the DOM. In fact, in Angular this is quite common for this to happen. When you use a $timeout, it forces another digest which queues up a digest cycle to update the views after all others have run. It depends, but I think this could still lead to a race condition. – Michael Kang Jun 15 '14 at 21:45
  • _"but I think this could still lead to a race consition"_ I don't think there is a race condition in the first place. JS is _mostly_ single threaded. @HassanSyed I am not familiar with Highcharts-ng, but I would be curious to know what happens if you replace `$timeout(...)`, by `$rootScope.$evalAsync(...)`. – gkalpak Jun 16 '14 at 10:27
  • Interesting that you mention that, since I was doing some more reading last night. `$timeout` without a duration, and `evalAsync` are meant to be functionally equivalent in this scenario. So I expect it will work. Unfortunately I have settled on the hacky solution for now, so I won't be able to tinker with the code anymore for now. – Hassan Syed Jun 16 '14 at 10:36
  • `evalAsync` is a better function to use however, as `$timeout` seems to be quite an overloaded concept and it leads to confusion when a non-angular pro looks at code. – Hassan Syed Jun 16 '14 at 10:45

1 Answers1

4

I'm the author of highcharts-ng.

I think the issue is that in highcharts-ng, it initializes the chart and then adds the series.

This could be fixed fairly easily. It would require a change to getMergedOptions: https://github.com/pablojim/highcharts-ng/blob/master/src/highcharts-ng.js#L245

At the moment getMergedOptions does not include the series objects and therefore when the chart is initialized it has no series. This behavior could be changed. There are some complications like ensuring that the series have ids. But it would be quite doable. I will get to doing it at some point, but pull requests gladly accepted in the meantime.

Another approach would be to dynamically set the x and y axis values on the highcharts-ng config object based on your data.

Pablojim
  • 8,542
  • 8
  • 45
  • 69
  • Hello Pablo, Thanks for posting an answer. It makes a lot of sense now. In this case I think the change you propose makes the most sense. I was considering hooking a user provided callback that could take the place of `processExtremes`. But your suggested method flows with the way the directive is meant to be used and will be robust to changes from upstream highcharts. – Hassan Syed Jun 16 '14 at 19:56