mgraham
mgraham

Reputation: 6207

Transition.each in d3 doesn't interpolate attributes

I'm missing something in my understanding here rather than a bug I think...

In d3, I'm trying to update multiple attribute values within a transition. Using multiple transition.attr declarations works fine. But I'd like to use .each as in the real world I have a big calculation I don't want to do twice or more... I tried using transition.each but the attribute values don't interpolate.

Is it the d3.select within the each? I tried d3.active but that didn't do anything.

(There is a https://github.com/d3/d3-selection-multi library that supplied .attrs that worked nicely up to v5, but not with the new v6...)

See https://jsfiddle.net/f679a4yj/3/ - what I can do to get the animation but through the .each, what am I not getting?

Upvotes: 1

Views: 174

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102188

The problem here is a misconception regarding transition.each: you cannot use it to wrap several transition.attr or transition.style methods. In fact, it is used only to invoke arbitrary code to each element in that transition, just like selection.each.

Therefore, when you do...

.each (function (d, i) {
    // complicated stuff with d I don't want to do twice
    d3.select(this)
       .attr("x", ((zz+i)%4) *20)
       .attr("y", ((zz+i)%4) *40)
})

... you are simply changing the attribute in the DOM element, and the element will immediately jump to the new x and y position, as expected. In other words, that d3.select(this).attr(... in your code is a selection.attr, not a transition.attr.

You have several alternatives (for avoiding redundant complex calculations). If you want to stick with transition.each, you can use it to create a new local variable, or maybe a new property. For that to work, we need to have objects as the data elements in the array, and then we just use transition.attr as usual:

.each(function(d, i) {
  d.complexValue = ((zz + i) % 4) * 20;
})
.attr("x", function(d) {
  return d.complexValue;
})
//etc...

Here is a demo:

const sel = d3.select("svg")
  .selectAll(null)
  .data([{
    color: "red"
  }, {
    color: "blue"
  }, {
    color: "green"
  }, {
    color: "yellow"
  }])
  .enter()
  .append("rect")
  .attr("width", 30)
  .attr("height", 30)
  .attr("x", function(d, i) {
    return i * 20;
  })
  .attr("y", function(d, i) {
    return i * 40;
  })
  .style("fill", function(d) {
    return d.color;
  });

let zz = 0;

function each() {
  zz++;
  d3.selectAll("rect")
    .transition()
    .duration(1000)
    .each(function(d, i) {
      d.complexValue = ((zz + i) % 4) * 20;
    })
    .attr("x", function(d) {
      return d.complexValue;
    })
    .attr("y", function(d) {
      return d.complexValue;
    })
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="demo"><svg width="360px"></svg></div>
<button onclick="each()">In an Each</button>

Upvotes: 2

Related Questions