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.
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.
The problem is caused by a change which is not documented in the changelog. D3 v3 calls the inner function returned by the callback passed to transition.tween()
on every tick passing the current node as the this
context:
tweens[--n].call(node, e);
As of v4, however, this is no longer the case when the inner function is called:
tween[i].call(null, t);
Passing null
to that function will be replaced with the global object. In this case this
in your function no longer points to current element as was true for v3. The remedy was already been laid out by Gerardo in his answer and is even suggested by the documentation on v4:
transition.tween("attr.fill", function() {
var node = this, i = d3.interpolateRgb(node.getAttribute("fill"), "blue");
return function(t) {
node.setAttribute("fill", i(t));
};
});
Hence, you need a closure to keep a reference to the current element as it is referenced as this
in the callback provided to transition.tween()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With