3

What needs to be done that the second transition of the green circle behaves like the second transition of the blue circle?

I would have expected that the transitions of both circles behave the same. However it seems that the first transition of the green circle is applied in place of the second transition.

const svg = d3.select("svg");

const blueCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 10)
  .attr("r", 5)
  .style("fill", "blue");

const greenCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 30)
  .attr("r", 5)
  .style("fill", "green");

blueCircle
  .transition()
    .duration(4000)
    .ease(d3.easeLinear)
    .attr("cx", 100)
  .transition()
    .duration(2000)
    .ease(d3.easeElastic)
    .attr("cx", 200);

const firstTransition = d3.transition()
  .duration(4000)
  .ease(d3.easeLinear);

const secondTransition = d3.transition()
  .duration(2000)
  .ease(d3.easeElastic);

greenCircle
  .transition(firstTransition)
    .attr("cx", 100)
  .transition(secondTransition)
    .attr("cx", 200);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>

Update

Thanks to Coola's answer and this question, I found a possibility to make the second transition of the green circle work as expected:

const greenCircle = svg.append("circle")
  .attr("class", "green")
  .attr("cx", 10)
  .attr("cy", 30)
  .attr("r", 5)
  .style("fill", "green");

const firstTransition = d3.transition()
  .duration(4000)
  .ease(d3.easeLinear);

const secondTransition = firstTransition.transition()
  .duration(2000)
  .ease(d3.easeElastic);

firstTransition
  .select("circle.green")
  .attr("cx", 100);

secondTransition
  .select("circle.green")
  .attr("cx", 200); 

However, this code has still the following flaws:

  • The transitions are not independent, thus cannot be reused in a different order.
  • You cannot insert an already selected element (i.e. greenCircle) into the select method of the transition (it results in a "Failed to execute 'querySelector' on 'Element': '[object Object]' is not a valid selector." exception).
  • The typical method chaining concept of D3.js is not used.

Does anybody know a solution without these issues, especially for the first point?

F1refly
  • 336
  • 2
  • 7

1 Answers1

1

In order to chain the transitions you have to use the on("end", function(){<do something>}).

You can read more about advanced control flows in the documentation.

greenCircle
  .transition(firstTransition)
    .attr("cx", 100)
    .on("end", () => {
      greenCircle.transition(secondTransition)
      .attr("cx", 200);
    });

Full Snippet:

const svg = d3.select("svg");

const blueCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 10)
  .attr("r", 5)
  .style("fill", "blue");

const greenCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 30)
  .attr("r", 5)
  .style("fill", "green");

blueCircle
  .transition()
    .duration(4000)
    .ease(d3.easeLinear)
    .attr("cx", 100)
  .transition()
    .duration(2000)
    .ease(d3.easeElastic)
    .attr("cx", 200);

const firstTransition = d3.transition()
  .duration(4000)
  .ease(d3.easeLinear);

const secondTransition = d3.transition()
  .duration(2000)
  .ease(d3.easeElastic);

greenCircle
  .transition(firstTransition)
    .attr("cx", 100)
    .on("end", () => {
      greenCircle.transition(secondTransition)
      .attr("cx", 200);
    })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>

UPDATE:

The above code solves only part of the problem. You will notice the animation is not exactly identical between the blue and green circles.

You also need to chain the firstTransition into the secondTransition const. Like:

const secondTransition = firstTransition.transition()
  .duration(2000)
  .ease(d3.easeElastic);

Full snippet:

const svg = d3.select("svg");

const blueCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 10)
  .attr("r", 5)
  .style("fill", "blue");

const greenCircle = svg.append("circle")
  .attr("cx", 10)
  .attr("cy", 30)
  .attr("r", 5)
  .style("fill", "green");

blueCircle
  .transition()
    .duration(4000)
    .ease(d3.easeLinear)
    .attr("cx", 100)
  .transition()
    .duration(2000)
    .ease(d3.easeElastic)
    .attr("cx", 200);

const firstTransition = d3.transition()
  .duration(4000)
  .ease(d3.easeLinear);

const secondTransition = firstTransition.transition()
  .duration(2000)
  .ease(d3.easeElastic);

greenCircle
  .transition(firstTransition)
    .attr("cx", 100)
    .on("end", function () {
      greenCircle.transition(secondTransition)
      .attr("cx", 200);
    })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
Coola
  • 2,934
  • 2
  • 19
  • 43
  • Please check updated answer to get identical animation between blue and green circles. The first part of the answer only solves part of the problem. – Coola Nov 28 '19 at 18:58
  • Thank you for your fast answer, it definitively solves the problem! However according to the comments to [this question](https://stackoverflow.com/questions/26319711/what-is-the-correct-way-of-chaining-transitions-in-d3), the "end" event may complicate things. I've posted an alternative solution without listening to the "end" event, see the update in my question. However, also as in your solution, the transitions are dependent of each other, thus cannot be used as 'mixins'. I'm wondering whether there is a more elegant/intuitive solution. – F1refly Nov 29 '19 at 14:49
  • @F1refly I see the issue. But I don't think I have a solution for that, and it may be worth raising an issue on the D3js Github for Mike Bostock. – Coola Nov 29 '19 at 15:20