UNPKG

d3

Version:

A small, free JavaScript library for manipulating documents based on data.

241 lines (211 loc) 6.95 kB
d3.transition = d3_root.transition; var d3_transitionId = 0, d3_transitionInheritId = 0; function d3_transition(groups) { var transition = {}, transitionId = d3_transitionInheritId || ++d3_transitionId, tweens = {}, interpolators = [], remove = false, event = d3.dispatch("start", "end"), stage = [], delay = [], duration = [], durationMax, ease = d3.ease("cubic-in-out"); // // Be careful with concurrent transitions! // // Say transition A causes an exit. Before A finishes, a transition B is // created, and believes it only needs to do an update, because the elements // haven't been removed yet (which happens at the very end of the exit // transition). // // Even worse, what if either transition A or B has a staggered delay? Then, // some elements may be removed, while others remain. Transition B does not // know to enter the elements because they were still present at the time // the transition B was created (but not yet started). // // To prevent such confusion, we only trigger end events for transitions if // the transition ending is the only one scheduled for the given element. // Similarly, we only allow one transition to be active for any given // element, so that concurrent transitions do not overwrite each other's // properties. // // TODO Support transition namespaces, so that transitions can proceed // concurrently on the same element if needed. Hopefully, this is rare! // groups.each(function() { (this.__transition__ || (this.__transition__ = {})).owner = transitionId; }); function step(elapsed) { var clear = true, k = -1; groups.each(function() { if (stage[++k] == 2) return; // ended var t = (elapsed - delay[k]) / duration[k], tx = this.__transition__, te, // ease(t) tk, // tween key ik = interpolators[k]; // Check if the (un-eased) time is outside the range [0,1]. if (t < 1) { clear = false; if (t < 0) return; } else { t = 1; } // Determine the stage of this transition. // 0 - Not yet started. // 1 - In progress. // 2 - Ended. if (stage[k]) { if (!tx || tx.active != transitionId) { stage[k] = 2; return; } } else if (!tx || tx.active > transitionId) { stage[k] = 2; return; } else { stage[k] = 1; event.start.dispatch.apply(this, arguments); ik = interpolators[k] = {}; tx.active = transitionId; for (tk in tweens) { if (te = tweens[tk].apply(this, arguments)) { ik[tk] = te; } } } // Apply the interpolators! te = ease(t); for (tk in ik) ik[tk].call(this, te); // Handle ending transitions. if (t == 1) { stage[k] = 2; if (tx.active == transitionId) { var owner = tx.owner; if (owner == transitionId) { delete this.__transition__; if (remove) this.parentNode.removeChild(this); } d3_transitionInheritId = transitionId; event.end.dispatch.apply(this, arguments); d3_transitionInheritId = 0; tx.owner = owner; } } }); return clear; } transition.delay = function(value) { var delayMin = Infinity, k = -1; if (typeof value == "function") { groups.each(function(d, i) { var x = delay[++k] = +value.apply(this, arguments); if (x < delayMin) delayMin = x; }); } else { delayMin = +value; groups.each(function(d, i) { delay[++k] = delayMin; }); } d3_timer(step, delayMin); return transition; }; transition.duration = function(value) { var k = -1; if (typeof value == "function") { durationMax = 0; groups.each(function(d, i) { var x = duration[++k] = +value.apply(this, arguments); if (x > durationMax) durationMax = x; }); } else { durationMax = +value; groups.each(function(d, i) { duration[++k] = durationMax; }); } return transition; }; transition.ease = function(value) { ease = typeof value == "function" ? value : d3.ease.apply(d3, arguments); return transition; }; transition.attrTween = function(name, tween) { /** @this {Element} */ function attrTween(d, i) { var f = tween.call(this, d, i, this.getAttribute(name)); return function(t) { this.setAttribute(name, f(t)); }; } /** @this {Element} */ function attrTweenNS(d, i) { var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); return function(t) { this.setAttributeNS(name.space, name.local, f(t)); }; } tweens["attr." + name] = name.local ? attrTweenNS : attrTween; return transition; }; transition.attr = function(name, value) { return transition.attrTween(name, d3_transitionTween(value)); }; transition.styleTween = function(name, tween, priority) { if (arguments.length < 3) priority = null; /** @this {Element} */ function styleTween(d, i) { var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); return function(t) { this.style.setProperty(name, f(t), priority); }; } tweens["style." + name] = styleTween; return transition; }; transition.style = function(name, value, priority) { if (arguments.length < 3) priority = null; return transition.styleTween(name, d3_transitionTween(value), priority); }; transition.text = function(value) { tweens.text = function(d, i) { this.textContent = typeof value == "function" ? value.call(this, d, i) : value; }; return transition; }; transition.select = function(query) { var k, t = d3_transition(groups.select(query)).ease(ease); k = -1; t.delay(function(d, i) { return delay[++k]; }); k = -1; t.duration(function(d, i) { return duration[++k]; }); return t; }; transition.selectAll = function(query) { var k, t = d3_transition(groups.selectAll(query)).ease(ease); k = -1; t.delay(function(d, i) { return delay[i ? k : ++k]; }) k = -1; t.duration(function(d, i) { return duration[i ? k : ++k]; }); return t; }; transition.remove = function() { remove = true; return transition; }; transition.each = function(type, listener) { event[type].add(listener); return transition; }; transition.call = d3_call; return transition.delay(0).duration(250); } function d3_transitionTween(b) { return typeof b == "function" ? function(d, i, a) { return d3.interpolate(a, String(b.call(this, d, i))); } : (b = String(b), function(d, i, a) { return d3.interpolate(a, b); }); }