1

I have the following function which constructs one or more y-axes for a Highcharts chart, though my problem has little or nothing to do with the Highcharts API.

_constructYAxes: function(yAxes) {

    if (yAxes) {
        var highChartYAxes = [];

        for (var i = 0; i < yAxes.length; i++) {

            var kpiUnits = yAxes[i].units;

            var axisUnits = 'units ' + i;
            var axisLabel = 'label ' + i;

            // construct an Highcharts y-axis object
            var yAxis = {
                labels: {
                    formatter: function() {
                        return this.value + ' ' + axisUnits;
                    }
                },
                title: {
                    text: axisLabel
                },
            };
            highChartYAxes.push(yAxis);
        }
        // pass all the y-axes to the chart
        this.chartOpts.yAxis = highChartYAxes;
    }
}

The problem is that the same label is used for each y-axis, even though the title is displayed correctly. The difference betweent hese two is that the former is calculated by invoking a function

labels: {
    formatter: function() {
        return this.value + ' ' + axisUnits;
    }
},

So if I have a chart with 3 y-axes, the label "units 2" appears on all of them.

I think the problem is that the formatter function closes over the value of axisUnits in the last iteration of the loop, such that when the function is invoked (by Highcharts), it uses the same value of axisUnits each time.

What can I do to force the function to use the i-th value of axisUnits each time it is invoked instead?

Clearly my understanding of JavaScript scoping is somewhat lacking (I am frequently surprised by what this evaluates to in different contexts). If anyone knows of some good online resources that might help me to get this straight, please send them my way.

Dónal
  • 185,044
  • 174
  • 569
  • 824
  • 1
    possible duplicate of [How do I pass the value (not the reference) of a JS variable to a function?](http://stackoverflow.com/questions/2568966/how-do-i-pass-the-value-not-the-reference-of-a-js-variable-to-a-function) and *many, many* others. – Andy E Oct 14 '11 at 10:08

4 Answers4

2

Use an anonymous function wrapper:

formatter: function(units) { //"Private" variable axisUnits
    return function() {
        return this.value + ' ' + units;
    };
}(axisUnits)

Because of the anonymous function wrapper, each formatter function will now refer to the axisUnits variable at the time of creation.

Dónal
  • 185,044
  • 174
  • 569
  • 824
Rob W
  • 341,306
  • 83
  • 791
  • 678
2

axisLabel is passed as a string directly and does not change.

However, axisUnits is not directly passed. It's only fetched when the function is called. The problem is that a for loop does not create a new scope, so for all iterations there is only one axisUnits, which causes all labels to have an equal text.

You can solve this by creating a closure. This way, the string is directly passed and "freezed":

formatter: (function(x) { // x won't change
               return function() {
                   return this.value + ' ' + x;
               };
           })(axisUnits);
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • Very nice explanation. If I find out you're really 16 years old and not a native English speaker I'm giving up programming (and speaking) – Dónal Oct 14 '11 at 14:32
0

I think you're right about the closure and the loop problem

Please read here http://www.mennovanslooten.nl/blog/post/62 - does it help?

Guard
  • 6,816
  • 4
  • 38
  • 58
0

Use function.bind(this, args): https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

It's new and awesome and easy to provide if the browser doesn't have it. So in this case you'd write:

formatter: (function(axisUnits) {
     return this.value + ' ' + axisUnits;
}).bind(this, axisUnits);

As you can see somewhat less to write than an anonymous wrapping function and easier to understand.

w00t
  • 17,944
  • 8
  • 54
  • 62
  • Actually, this will probably not work as intended because the ``this`` value is being bound to your enclosing object instead of being left as is. So using ``bind()`` for currying only doesn't work and you'll need to create a wrapping function as explained in the other answers. Leaving answer for awareness. – w00t Feb 10 '12 at 09:57