Search code examples
javascriptd3.jsjavascript-objectslinear-interpolation

Confusion about d3.interpolateObject


I was just investigating the D3 Interpolate Object function, and I noticed some strange behavior. However, I'm not very familiar with D3, so it could be that I'm simply misunderstanding something. Given the following data and interpolation function:

var a = {"Country": "Ireland", "Year": 2010, "Data": 10};
var b = {"Country": "Ireland", "Year": 2015, "Data": 50};

var iFunc = d3.interpolateObject(a, b);

The following results are as expected:

console.log(iFunc(0.2)) // Returns: { Country: "Ireland", Year: 2011, Data: 18 }
console.log(iFunc(0.4)) // Returns: { Country: "Ireland", Year: 2012, Data: 26 }

However, when both function calls are included in the same console log, like this:

console.log(iFunc(0.2), iFunc(0.4)) 

The output is just the second Object twice:

{ Country: "Ireland", Year: 2012, Data: 26 } { Country: "Ireland", Year: 2012, Data: 26 }

And, when the function calls are put inside an array like so:

console.log([iFunc(0.2), iFunc(0.4)])

The previous output gets multiplied by two:

[{ Country: "Ireland", Year: 2014, Data: 42 }, { Country: "Ireland", Year: 2014, Data: 42 }]

What is going on here?

The reason I am investigating this is that I'd like to create a series of intermediate objects using something like:

var iVals = d3.range(0, 1, 0.2).map( iFunc );

If anybody can show me how I could achieve this, I'd really appreciate it!


Solution

  • This is an interesting problem. The explanation can be found in the own documentation:

    Note: no defensive copy of the template object is created; modifications of the returned object may adversely affect subsequent evaluation of the interpolator. No copy is made for performance reasons; interpolators are often part of the inner loop of animated transitions. (emphasis mine)

    As you can see, if you use the same interpolator you get the weird result you described (open your browser's console, don't use the snippet's one):

    var a = {
      "Country": "Ireland",
      "Year": 2010,
      "Data": 10
    };
    var b = {
      "Country": "Ireland",
      "Year": 2015,
      "Data": 50
    };
    
    var iFunc = d3.interpolateObject(a, b);
    
    var iVals = d3.range(0, 1, 0.2).map(iFunc);
    
    console.log(iVals)
    <script src="https://d3js.org/d3.v5.min.js"></script>

    So, the simplest solution is defining the interpolator function inside the map():

    var iVals = d3.range(0, 1, 0.2).map(function(d) {
      return d3.interpolateObject(a, b)(d)
    });
    

    Here is the demo:

    var a = {
      "Country": "Ireland",
      "Year": 2010,
      "Data": 10
    };
    var b = {
      "Country": "Ireland",
      "Year": 2015,
      "Data": 50
    };
    
    var iVals = d3.range(0, 1, 0.2).map(function(d) {
      return d3.interpolateObject(a, b)(d)
    });
    
    console.log(iVals)
    <script src="https://d3js.org/d3.v5.min.js"></script>

    Alternatively, create a function that returns the interpolator:

    var iFunc = function(d) {
        return d3.interpolateObject(a, b)(d)
    };
    
    var iVals = d3.range(0, 1, 0.2).map(iFunc);
    

    Here is the corresponding demo:

    var a = {
      "Country": "Ireland",
      "Year": 2010,
      "Data": 10
    };
    var b = {
      "Country": "Ireland",
      "Year": 2015,
      "Data": 50
    };
    
    var iFunc = function(d) {
      return d3.interpolateObject(a, b)(d)
    };
    
    var iVals = d3.range(0, 1, 0.2).map(iFunc);
    
    console.log(iVals)
    <script src="https://d3js.org/d3.v5.min.js"></script>


    PS: Not related to your question, but the "stop" value in d3.range() is not inclusive. So, if you want to get the values in the object b, it should be:

    d3.range(0, 1.2, 0.2)
    

    Here it is:

    var a = {
      "Country": "Ireland",
      "Year": 2010,
      "Data": 10
    };
    var b = {
      "Country": "Ireland",
      "Year": 2015,
      "Data": 50
    };
    
    var iVals = d3.range(0, 1.2, 0.2).map(function(d) {
      return d3.interpolateObject(a, b)(d)
    });
    
    console.log(iVals)
    <script src="https://d3js.org/d3.v5.min.js"></script>