chrisle
chrisle

Reputation: 23

Wrapping D3 functions inside objects doesn't work. Why?

If I assign a d3.scale function to a variable I can pass in a number and it will scale that number correctly.

However, if I wrap that same function in an object it does not return the answer I expect. Why?

var applyScale1 = d3.scale.ordinal()
    .domain([0, 2])
    .rangePoints([0, 1024]);

console.log(typeof(applyScale1));     // "function"
console.log(applyScale1(1));          // 0
console.log(applyScale1(5000));       // 1024 *Correct*

// But wrapping that in an object doesn't work. Why?

var obj = {

  returnResult: function(n) {
    var fn = d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024]);

    return fn(n);
  },

  returnFunction: function() {
    return d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024]);
  },

  withCall: function(n) {
    var fn = d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024]);

    var nScaled = fn.call(n);
    return nScaled;
  },

  applySelf: function(n) {
    var self = this;
    var fn = d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024]);

    var nScaled = fn.apply(self, [n]);
    return nScaled;
  },

  usingCall: function(n) {
    return d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024])
      .call(n);
  },
};

console.log(obj.returnResult(1));         // = 0
console.log(obj.returnResult(5000));      // = 0 * Wrong *

console.log(obj.returnFunction()(1));     // = 0
console.log(obj.returnFunction()(5000));  // = 0 * Wrong *

console.log(obj.withCall(1));             // = 0
console.log(obj.withCall(5000));          // = 0 * Wrong *

console.log(obj.applySelf(1));            // = 0
console.log(obj.applySelf(5000));         // = 0 * Wrong *

console.log(obj.usingCall(1));            // = 0
console.log(obj.usingCall(5000));         // = 0 * Wrong *

Upvotes: 2

Views: 201

Answers (2)

cesutherland
cesutherland

Reputation: 546

You're using an ordinal scale, which is a scale with a discrete domain. In your case, that discrete domain is the set {0, 2}. I'm not sure how the behavior would be defined if you put in a number not in that set 1024, but I believe d3 leaves this undefined.

If you pass in 0 or 2, the results will be as expected.

One other issue is in this statement:

return d3.scale.ordinal()
  .domain([0, 2])
  .rangePoints([0, 1024])
  .call(n);

The first argument to call is the 'thisArg': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

That block should read:

return d3.scale.ordinal()
  .domain([0, 2])
  .rangePoints([0, 1024])
  .call(null, n);

Here's a fiddle: http://jsfiddle.net/cesutherland/5gY6Z/2/

Upvotes: 1

Josh
Josh

Reputation: 44906

It's clear that the function returned by d3 is maintaining some sort of state when calling it. However, you are recreating it each time you call it when assigned to your obj

Because no state is maintained it can't calculate the appropriate results.

If you are trying to call directly to the instance of your function, this might be a better approach:

var obj = {
   returnResult: d3.scale.ordinal()
      .domain([0, 2])
      .rangePoints([0, 1024])
}

This would allow you to do what you are doing now without recreating the object each time.

Upvotes: 0

Related Questions