0

I have ranking data for the top n employees that I'd like to use to drive a visualization. I know that because the number of employees, n, that are displayed will never change, if I update the data join using a composite (employee + rank) key function, the enter and exit selections should always contain the same number of elements and I should receive an exiting/entering employee pair everytime an employee's rank changes.

Now I'm trying to animate the exiting and entering of elements. Using the technique described by Mr. Bostock in d3: How to properly chain transitions on different selections I can chain two transitions and apply the first to the exiting selection and the second to the entering selection to sequence transitions on different selections.

The problem comes when I try to embellish the exit transition with sub-transitions (e.g. turning the exiting transition into a two-part, chained transition composed of a text tween and then background transition). As the enter transition doesn't know about the number of sub-transitions (nor the delay/duration) in the exit transition, I have to manually compute the enter transition delay/duration. Furthermore, I either have to calculate from the data how long the delay/duration of the sub-transitions are or have to assume the longest delay/duration possible for every transition on every element in the exiting selection (e.g. I have to assume the sub-transition to tween the text for every employee is tweening the longest possible employee name).

Is there some way, since I have the same number of exiting and entering elements, to chain transitions like they were on the same elements? I.e. with the same selection I can chain transitions such that subsequent transitions inherit delay/duration, can I do this with disparate selections of the same size? Basically, how can I write the following without having to estimate delay/duration?

function chart() {
  var scale = d3.scale.ordinal().rangeBands([0, 150], .5);
  return function(selection) {
    selection.each(function(data) {
      scale.domain(data);
      var svg = d3.select(this).selectAll('svg').data([data]);
      var svgEnter = svg.enter().append('svg');
        var g = svg.selectAll('g').data(function(d) {
          return d;
        }, function(d, i) {
          return d + i;
        }).attr('class', 'update');
       var gExit = g.exit();
        var gEnter = g.enter().append('g').attr('class', 'enter').attr('transform', function(d) {
          return 'translate(150 ' + scale(d) + ')';
        });
        g.order();
        var gExitTransition = d3.transition().each(function() {
          var t0 = gExit.transition().delay(function(_, i) {
            return i * 100;
          }).duration(function(d) {
            return d.length * 250 / 5;
          });
          var t1 = t0.transition();
          var t2 = t1.transition();
          t0.select('text').tween('text', function(d) {
            var i = d3.interpolate(this.textContent.length, 0);
            return function(t) {
              this.textContent = d.slice(0, i(t));
            };
          });
          t1.select('rect').attr('y', scale.rangeBand() / 2).attr('height', 1e-6);
          t2.remove();
        });
        var gEnterTransition = gExitTransition.transition().each(function() {
          var t0 = gEnter.transition().delay(function(_, i) {
            return i * 100 + 500;
          });
          var t1 = t0.transition().duration(function(d) {
            return d.length * 250 / 5;
          });
            var rectEnter = gEnter.append('rect').attr('y', scale.rangeBand() / 2).attr('width', 50).attr('height', 1e-6).style('fill', 'rgba(0, 0, 0, .1)');
            t0.select('rect').attr('y', 0).attr('height', scale.rangeBand());
            var textEnter = gEnter.append('text').attr('y', scale.rangeBand() / 2).style('alignment-baseline', 'middle');
            t1.select('text').tween('text', function(d) {
              var i = d3.interpolate(this.textContent.length, d.length);
              return function(t) {
                this.textContent = d.slice(0, i(t));
              };
            });
        });
    });
  };
}
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('').map(function(c) {
  return d3.range(Math.floor(5 * Math.random()) + 1).map(function() {
    return c;
  }).join('');
});
var myChart = chart();
var selection = d3.select('body');
(function(f) {
  f();
  setInterval(f, 3000);
})(function() {
  selection.datum(d3.shuffle(alphabet.slice(0)).slice(0, 10).sort()).call(myChart);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
lwiseman
  • 750
  • 7
  • 29
  • 1
    Consider upgrading your code to D3 v4.x. It will make this task here easier. – Gerardo Furtado Jun 06 '17 at 00:05
  • Interesting! I didn't realize v4 included any significant changes to how transitions work. I'll give that a shot and post back if I can figure it out before anyone chimes in with v3 advice. – lwiseman Jun 06 '17 at 00:14
  • The changes are significant! Have [a look here](https://github.com/d3/d3-transition#selection_transition). – Gerardo Furtado Jun 06 '17 at 00:17

1 Answers1

2

I got a partial solution working by switching to v4 on Gerardo's advice; it works and avoids the need to calculate timing/duration across differing selections, but I basically wait for a start event from the transition on the first selection and then filter a transition on the second selection until the element indices are equal which feels really dumb.

function chart() {
  var scale = d3.scaleBand().range([0, 150]).padding(.5);
  return function(selection) {
    selection.each(function(data) {
      scale.domain(data);
      var svg = d3.select(this).selectAll('svg').data([data]);
      var svgEnter = svg.enter().append('svg');
      svg = svg.merge(svgEnter);
        var g = svg.selectAll('g').data(function(d) {
          return d;
        }, function(d, i) {
          return d + i;
        }).attr('class', 'update');
       var gExit = g.exit().attr('class', 'exit');
        var gEnter = g.enter().append('g').attr('class', 'enter').attr('transform', function(d) {
          return 'translate(150 ' + scale(d) + ')';
        });
        g = g.merge(gEnter).order();
          var rectEnter = gEnter.append('rect').attr('y', scale.bandwidth() / 2).attr('width', 50).attr('height', 1e-6).style('fill', 'rgba(0, 0, 0, .1)');
          var textEnter = gEnter.append('text').attr('y', scale.bandwidth() / 2).style('alignment-baseline', 'middle');
          var t0 = gExit.transition().delay(function(_, i) {
            return i * 100;
          });
          t0.select('text').duration(function(d) {
            return d.length * 250 / 5;
          }).tween('text', function(d) {
            var i = d3.interpolate(this.textContent.length, 0);
            return function(t) {
              this.textContent = d.slice(0, i(t));
            }.bind(this);
          });
          var t1 = t0.transition();
          t1.select('rect').attr('y', scale.bandwidth() / 2).attr('height', 1e-6);
          var t2 = t1.transition().on('start', function(d, i) {
            var t3 = gEnter.transition(d3.active(this)).filter(function(_, j) {
              return i === j;
            });
            t3.select('rect').attr('y', 0).attr('height', scale.bandwidth());
            var t4 = t3.transition();
            t4.select('text').duration(function(d) {
              return d.length * 250 / 5;
            }).tween('text', function(d) {
              var i = d3.interpolate(this.textContent.length, d.length);
              return function(t) {
                this.textContent = d.slice(0, i(t));
              }.bind(this);
            });
          });
          t2.remove();
    });
  };
}
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('').map(function(c) {
  return d3.range(Math.floor(5 * Math.random()) + 1).map(function() {
    return c;
  }).join('');
});
var myChart = chart();
var selection = d3.select('body');
(function(f) {
  f();
  d3.interval(f, 3000);
})(function() {
  selection.datum(d3.shuffle(alphabet.slice(0)).slice(0, 10).sort()).call(myChart);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
lwiseman
  • 750
  • 7
  • 29