NickJan NickJan - 1 month ago 34
Javascript Question

Tweening numbers in D3 v4 not working anymore like in v3

I'm trying to figure out why my tweening numbers (counting up or down) code in Version 4 of D3 doesn't function any more.

Here is my code:

var pieText = svg4.append("text")
.attr("class", "pieLabel")
.attr("x", 0)
.attr("y", 0)
.text(0)
.attr("dy", "0.2em")
.style("font-size", 19)
.style("fill", "#46596b")
.attr("text-anchor", "middle");

d3.selectAll(".pieLabel").transition()
.delay(500)
.duration(1000)
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d.value);
return function(t) {
this.textContent = form(i(t));
};
});


The console.log tells me that the interpolation works fine.
So what has changed? And how do I get it to work?

Thanks for your help.

Answer Source

The problem here is just this inside the inner function, which will no longer work as it worked in v3.

Let's prove it. Have a look at the console here, using D3 v3, this is the DOM element:

d3.select("p").transition()
  .tween("foo", function(d) {
    var i = d3.interpolate(0, 1);
    return function(t) {
      console.log(this)
    };
  });
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>

Now the same snippet, using D3 v4... this is now the window object:

d3.select("p").transition()
  .tween("foo", function(d) {
    var i = d3.interpolate(0, 1);
    return function(t) {
      console.log(this)
    };
  });
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>

Solution: keep the reference to this in the outer function as a variable (traditionally named self, but here I'll name it node):

d3.selectAll(".pieLabel").transition()
    .delay(500)
    .duration(1000)
    .tween("text", function(d) {
        var node = this;
        //keep a reference to 'this'
        var i = d3.interpolate(node.textContent, d.value);
        return function(t) {
            node.textContent = form(i(t));
            //use that reference in the inner function
        };
    });

Here is your code with that change only:

var widthpie = 250,
  heightpie = 300,
  radius = Math.min(widthpie, heightpie) / 2;

var data = [{
  antwort: "A",
  value: 0.5
}, {
  antwort: "B",
  value: 0.4
}];

var form = d3.format(",%");

var body4 = d3.select("#chart1");

var svg4 = body4.selectAll("svg.Pie")
  .data(data)
  .enter()
  .append("svg")
  .attr("width", widthpie)
  .attr("height", heightpie)
  .append("g")
  .attr("transform", "translate(" + widthpie / 2 + "," + heightpie / 2 + ")");

var pieText = svg4.append("text")
  .attr("class", "pieLabel")
  .attr("x", 0)
  .attr("y", 0)
  .text(0)
  .attr("dy", "0.2em")
  .style("font-size", 19)
  .style("fill", "#46596b")
  .attr("text-anchor", "middle");

d3.selectAll(".pieLabel").transition()
  .delay(500)
  .duration(1000)
  .tween("text", function(d) {
    var node = this;
    var i = d3.interpolate(node.textContent, d.value);
    return function(t) {
      node.textContent = form(i(t));
    };
  });
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart1"></div>

PS: I'm using Firebug in the snippets because S.O. snippet don't log the window object correctly.