function d3_transition(groups, id) {
  d3_arraySubclass(groups, d3_transitionPrototype);

  var tweens = {},
      event = d3.dispatch("start", "end"),
      ease = d3_transitionEase,
      then = Date.now();

  groups.id = id;

  groups.tween = function(name, tween) {
    if (arguments.length < 2) return tweens[name];
    if (tween == null) delete tweens[name];
    else tweens[name] = tween;
    return groups;
  };

  groups.ease = function(value) {
    if (!arguments.length) return ease;
    ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments);
    return groups;
  };

  groups.each = function(type, listener) {
    if (arguments.length < 2) return d3_transition_each.call(groups, type);
    event[type].add(listener);
    return groups;
  };

  d3.timer(function(elapsed) {
    groups.each(function(d, i, j) {
      var tweened = [],
          node = this,
          delay = groups[j][i].delay,
          duration = groups[j][i].duration,
          lock = node.__transition__ || (node.__transition__ = {active: 0, count: 0});

      ++lock.count;

      delay <= elapsed ? start(elapsed) : d3.timer(start, delay, then);

      function start(elapsed) {
        if (lock.active > id) return stop();
        lock.active = id;

        for (var tween in tweens) {
          if (tween = tweens[tween].call(node, d, i)) {
            tweened.push(tween);
          }
        }

        event.start.dispatch.call(node, d, i);
        if (!tick(elapsed)) d3.timer(tick, 0, then);
        return 1;
      }

      function tick(elapsed) {
        if (lock.active !== id) return stop();

        var t = (elapsed - delay) / duration,
            e = ease(t),
            n = tweened.length;

        while (n > 0) {
          tweened[--n].call(node, e);
        }

        if (t >= 1) {
          stop();
          d3_transitionInheritId = id;
          event.end.dispatch.call(node, d, i);
          d3_transitionInheritId = 0;
          return 1;
        }
      }

      function stop() {
        if (!--lock.count) delete node.__transition__;
        return 1;
      }
    });
    return 1;
  }, 0, then);

  return groups;
}

var d3_transitionRemove = {};

function d3_transitionNull(d, i, a) {
  return a != "" && d3_transitionRemove;
}

function d3_transitionTween(b) {

  function transitionFunction(d, i, a) {
    var v = b.call(this, d, i);
    return v == null
        ? a != "" && d3_transitionRemove
        : a != v && d3.interpolate(a, v);
  }

  function transitionString(d, i, a) {
    return a != b && d3.interpolate(a, b);
  }

  return typeof b === "function" ? transitionFunction
      : b == null ? d3_transitionNull
      : (b += "", transitionString);
}

var d3_transitionPrototype = [],
    d3_transitionId = 0,
    d3_transitionInheritId = 0,
    d3_transitionEase = d3.ease("cubic-in-out");

d3_transitionPrototype.call = d3_selectionPrototype.call;

d3.transition = function() {
  return d3_selectionRoot.transition();
};

d3.transition.prototype = d3_transitionPrototype;
