d3: Use of the *name* argument in transition.tween

2019-04-28 09:27发布

问题:

According to the documentation for transition.tween(),

calling transition.tween(name, factory)...

Registers a custom tween for the specified name. When the transition starts, the specified factory function will be invoked for each selected element in the transition, being passed that element's data (d) and index (i) as arguments, with the element as the context (this).

What is the purpose of having a named tween? Since .tween needs a reference to a transition to be used, it seems unlikely that it would be useful for reusing the same tween on different transitions. As far as I can tell, this name has no purpose at all.

To illustrate my point, I recently used a custom transition.tween() to transition the text and path elements within a group simultaneously. HERE is a link to the example. The relevant code is:

function groupTween(transition, newAngle) {
  transition.tween("thisNameMeansNothing", function() {
    var d = d3.select(this).datum();
    var interpolate = d3.interpolate(d.endAngle, newAngle);
    return function(t) {
      d.endAngle = interpolate(t);
      d3.select(this).select("path")
        .attr("d", arc);
      d3.select(this).select("text")
        .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
        .text(function(d) {return formatLabel(d.endAngle);});
    };
  });
}

The name is never used except in registering the tween function, but must be given as it is a required first argument to .tween().

Question

Can this name be referenced later? If so, how? Where is such a reference stored? Also, any examples of this being used in the wild would be greatly appreciated.

回答1:

This is a great question.

I suspect that the quick answer for why the name parameter exists at all, is that all the other transition tweens are associated with some sort of name (e.g., of an attribute or style), and it was convenient for them to all work the same way.

The name is used in the background, in the tween objects that are set on each element. If you create a long enough transition, you can use your DOM inspector to see the changing DOM properties of each element. There will be something called __transition__ which stores an array with the information about the active transition on the element and any scheduled sub-transitions.

That transition information object is not the same as the transition selection that you create in your code. Instead, it contains the specific details required to calculate the transition for that particular element: the start time, duration, and the specific tween factory function that will be used to generate the tween for that element. For your custom tween, this is your function(d,i) that will return the function(t), but there are similar functions created by all the standard transition methods.

Those tween functions are stored in that transition information object by name, and your custom tween is stored using the name you give it. Which means the one real effect of setting a name is that if you use the same name twice on the same transition, you'll over-write the first version:

http://fiddle.jshell.net/9gPrY/

pTrans
  .style("color", "red")
  .style("color", "green") 
  .tween("countdown", function(d,i){
     var check;
     return function(t){
         this.textContent = percent(t);
         if (!check && t > 0.1) {
             console.log("That wasn't supposed to happen");
             check = true;
         }
     };
  }) 
  .tween("countdown", function(d,i){
    var check;
     return function(t){
         this.textContent = percent(t);
         if (!check && t > 0.1) {
             printTweens();
             check = true;
         }
     };
  });

The second color style transition call cancels out the previous one, and the second countdown tween likewise cancels out the first. Only two tween functions are printed out, one called style.color and one called countdown.

Now, I haven't done exhaustive testing, but that seems to be it.

So in a sense maybe this is a poor design, a bit of the internal guts of the library poking out into the API. But I suppose there could be cases where you would want to be able to over-write a tween function partway through, and having a name is useful. As Lars has discovered (see comments), the only practical use, beyond debugging, seems to be the ability to overwrite a tween factory function during the delay before a transition starts (approx 17ms if you haven't specified a longer delay).

It could have been designed as an optional parameter (e.g., .tween(function, [name])), but that would be inconsistent with the pattern used for all the other API functions.

So, in general, just use the name as a way to make your code more readable by describing what your tween function does. Unless you're obsessed with minification, in which case use a random single character.