tic
tic

Reputation: 4189

how to reuse two (or more) sequences of chained transitions in D3

I have to apply two very long sequences of chained transitions, which differ mainly on the order of transitions, and I'm looking for a compact way to code.

As a toy example, consider the case where the sequence orders should be a,b,c,d,e,f,g,h and e,f,g,h,a,b,c,d. I've tried with the code below, but it doesn't work. Notice that the transitions could have different properties (delay, duration, ease, and so on) and they could apply to different attributes (x, y, width, height or cx, cy, r, and so on) and styles. For example, transition a could refer to width, transition b to height, transition c to x, transition d to y, transition e to transform attribute, transition f to color style, etcetera.

P.S.: This question has the same aim of my previous one but there I used a too much simple coding case that misguided answers.

Is there any way to code this in a compact way?

var t1 = d3
.transition() // transition "a" specifications 
...
.transition() // transition "b" specifications 
...
.transition() // transition "c" specifications 
...
.transition() // transition "d" specifications 
...
;
var t2 = d3
.transition() // transition "e" specifications 
...
.transition() // transition "f" specifications 
...
.transition() // transition "g" specifications 
...
.transition() // transition "h" specifications 
...
;
someelement1
.transition(t1).transition(t2); 
someelement2
.transition(t2).transition(t1); 

Upvotes: 4

Views: 304

Answers (1)

i alarmed alien
i alarmed alien

Reputation: 9520

As noted in the comments, the principles for answering this question are the same as your previous question. In this case, you have a set of different transitions that might be applied in any order referred to by different keys. Let's store them in an object:

var transitions = {
  a: function(sel){ return sel.transition().duration(1000).delay(1000).attr('cy', 200) },
  b: function(sel){ return sel.transition().duration(2000).delay(0).attr('r', 40) },
  c: function(sel){ return sel.transition().duration(500).delay(1500).attr('fill', 'red') },
  d: function(sel){ return sel.transition().duration(1500).delay(500).attr('opacity', 0.5) },
  e: function(sel){ return sel.transition().duration(1000).delay(3000).attr('cy', 300) },
  f: function(sel){ return sel.transition().duration(2000).delay(0).attr('r', 60) },
  g: function(sel){ return sel.transition().duration(500).delay(1500).attr('fill', 'magenta') },
  h: function(sel){ return sel.transition().duration(1500).delay(500).attr('opacity', 0.25) }
};

Each function takes a d3.selection object and applies specific transition parameters and sets of transformations to it. These functions can be as long and complicated as you like. I am lazy with little imagination so they only do one transformation in this version.

There's already a bit of code repetition here, so let's take out the conversion of the selection to a transition, and use this instead of passing an argument:

var transitions = {
  a: function(){ return this.duration(1000).delay(1000).attr('cy', 200) },
  b: function(){ return this.duration(2000).delay(0).attr('r', 40) },
  c: function(){ return this.duration(500).delay(1500).attr('fill', 'red') },
  d: function(){ return this.duration(1500).delay(500).attr('opacity', 0.5) },
  e: function(){ return this.duration(1000).delay(3000).attr('cy', 300) },
  f: function(){ return this.duration(2000).delay(0).attr('r', 60) },
  g: function(){ return this.duration(500).delay(1500).attr('fill', 'magenta') },
  h: function(){ return this.duration(1500).delay(500).attr('opacity', 0.25) }
};

Now we can perform these transitions by calling code like

transitions['a'].call( selection.transition() )
transitions.f.call( d3.select('circle').transition() )

You want to specify an array of transitions to apply to a selection, something like this:

apply_transitions( group.select(":nth-child(1)"), ['a','b','c','d'] );
apply_transitions( group.select(":nth-child(2)"), ['e','f','g','h'] );

This could be implemented as follows:

/**
* apply a series of transitions to a selection
*
* @param selection - d3 selection
* @param tr_arr - array of transition identifiers, referring to functions in the `transitions` object
*/
function apply_transitions( selection, tr_arr ) {

  // turn the current selection into a d3.transition
  // call the transition function referred to by the first ID in the array
  // with the d3.transition as the `this` context
  // note that the function returns a transition object, so it can be chained
  transitions[ tr_arr[0] ].call( selection.transition() )
  // add a handler to be applied at the end of the transition
    .on('end', function(){
      // if there are more transitions to be applied, call
      // apply_transitions again with tr_arr minus the first element
      // note that the `this` context in the `on` function is a DOM element,
      // so use `d3.select(this)` to turn it into a d3 selection
      if ( tr_arr.length > 1 ) {
        apply_transitions( d3.select(this), tr_arr.slice(1) );
      }
    })
}

Live action example:

    var svg = d3.select('svg').attr('width', 500).attr('height', 500);

    var dataSet = [20, 20];

    var group=svg.append("g");
    var circles = group.selectAll('circle')
    .data(dataSet)
    .enter()
    .append('circle')
    .attr("r",function(d){ return d })
    .attr("cx",function(d, i){ return i * 100 + 50 })
    .attr("cy",50)
    .attr("fill",'black');

    apply_transitions( group.select(":nth-child(1)"), ['a','b','c','d'] );

    apply_transitions( group.select(":nth-child(2)"), ['e','f','g','h'] );

  function apply_transitions( selection, tr_arr ) {

    var transitions = {
      a: function(){ return this.duration(1000).delay(1000).attr('cy', 200) },
      b: function(){ return this.duration(2000).delay(0).attr('r', 40) },
      c: function(){ return this.duration(500).delay(1500).attr('fill', 'red') },
      d: function(){ return this.duration(1500).delay(500).attr('opacity', 0.5) },
      e: function(){ return this.duration(1000).delay(3000).attr('cy', 300) },
      f: function(){ return this.duration(2000).delay(0).attr('r', 60) },
      g: function(){ return this.duration(500).delay(1500).attr('fill', 'magenta') },
      h: function(){ return this.duration(1500).delay(500).attr('opacity', 0.25) }
    };

      transitions[ tr_arr[0] ].call( selection.transition() )
        .on('end', function(){
          if ( tr_arr.length > 1 ) {
            apply_transitions( d3.select(this), tr_arr.slice(1) );
          }
        })
  }
<script src="http://d3js.org/d3.v5.js"></script>
<svg></svg>

Upvotes: 5

Related Questions