Greedy evaluation of transition.{style,attr,text}.

Rather than computing the ending value when the transition starts, the ending
value is computed when the transition is scheduled. This gives more predictable
behavior and makes it easier to debug evaluation errors since they occur
immediately (during user code) rather than inside a d3_timer callback.

The behavior of attrTween and styleTween are unchanged, since the interpolator
can only be constructed once the starting value is known. This commit also
removes d3.tween; I may add this back in a future commit, but I think there is
probably a better way to specify an interpolator for transitions.
This commit is contained in:
Mike Bostock 2012-10-16 16:40:46 -07:00
Родитель 0794607c39
Коммит 3af91c35e4
10 изменённых файлов: 141 добавлений и 108 удалений

Просмотреть файл

@ -123,7 +123,6 @@ d3.core.js: \
src/core/transition-each.js \ src/core/transition-each.js \
src/core/transition-transition.js \ src/core/transition-transition.js \
src/core/transition-tween.js \ src/core/transition-tween.js \
src/core/tween.js \
src/core/timer.js \ src/core/timer.js \
src/core/mouse.js \ src/core/mouse.js \
src/core/touches.js \ src/core/touches.js \

92
d3.js поставляемый
Просмотреть файл

@ -591,11 +591,47 @@
return transition; return transition;
} }
} }
function d3_tweenNull(d, i, a) { function d3_transition_attr(name, b) {
return a != "" && d3_tweenRemove; function attrNull() {
this.removeAttribute(name);
}
function attrNullNS() {
this.removeAttributeNS(name.space, name.local);
}
function attrString() {
var a = this.getAttribute(name), i;
return a !== b && (i = interpolate(a, b), function(t) {
this.setAttribute(name, i(t));
});
}
function attrStringNS() {
var a = this.getAttributeNS(name.space, name.local), i;
return a !== b && (i = interpolate(a, b), function(t) {
this.setAttributeNS(name.space, name.local, i(t));
});
}
var interpolate;
name = d3.ns.qualify(name);
return b == null ? name.local ? attrNullNS : attrNull : (b += "", interpolate = d3_interpolateByName(name), name.local ? attrStringNS : attrString);
} }
function d3_tweenByName(b, name) { function d3_transition_style(name, b, priority) {
return d3.tween(b, d3_interpolateByName(name)); function styleNull() {
this.style.removeProperty(name);
}
function styleString() {
var a = getComputedStyle(this, null).getPropertyValue(name), i;
return a !== b && (i = interpolate(a, b), function(t) {
this.style.setProperty(name, i(t), priority);
});
}
var interpolate;
return b == null ? styleNull : (b += "", interpolate = d3_interpolateByName(name), styleString);
}
function d3_transition_text(value) {
if (value == null) value = "";
return function() {
this.textContent = value;
};
} }
function d3_timer_step() { function d3_timer_step() {
var elapsed, now = Date.now(), t1 = d3_timer_queue; var elapsed, now = Date.now(), t1 = d3_timer_queue;
@ -4052,7 +4088,7 @@
for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
return this; return this;
} }
if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name); if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
priority = ""; priority = "";
} }
return this.each(d3_selection_style(name, value, priority)); return this.each(d3_selection_style(name, value, priority));
@ -4371,21 +4407,26 @@
}; };
d3_transitionPrototype.attr = function(name, value) { d3_transitionPrototype.attr = function(name, value) {
if (arguments.length < 2) { if (arguments.length < 2) {
for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); for (value in name) this.attr(value, name[value]);
return this; return this;
} }
return this.attrTween(name, d3_tweenByName(value, name)); var id = this.id;
return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
node.__transition__[id].tween.set("attr." + name, d3_transition_attr(name, value.call(node, node.__data__, i, j)));
} : (value = d3_transition_attr(name, value), function(node) {
node.__transition__[id].tween.set("attr." + name, value);
}));
}; };
d3_transitionPrototype.attrTween = function(nameNS, tween) { d3_transitionPrototype.attrTween = function(nameNS, tween) {
function attrTween(d, i) { function attrTween(d, i) {
var f = tween.call(this, d, i, this.getAttribute(name)); var f = tween.call(this, d, i, this.getAttribute(name));
return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) { return f && function(t) {
this.setAttribute(name, f(t)); this.setAttribute(name, f(t));
}; };
} }
function attrTweenNS(d, i) { function attrTweenNS(d, i) {
var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) { return f && function(t) {
this.setAttributeNS(name.space, name.local, f(t)); this.setAttributeNS(name.space, name.local, f(t));
}; };
} }
@ -4397,26 +4438,34 @@
if (n < 3) { if (n < 3) {
if (typeof name !== "string") { if (typeof name !== "string") {
if (n < 2) value = ""; if (n < 2) value = "";
for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); for (priority in name) this.style(priority, name[priority], value);
return this; return this;
} }
priority = ""; priority = "";
} }
return this.styleTween(name, d3_tweenByName(value, name), priority); var id = this.id;
return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
node.__transition__[id].tween.set("style." + name, d3_transition_style(name, value.call(node, node.__data__, i, j), priority));
} : (value = d3_transition_style(name, value, priority), function(node) {
node.__transition__[id].tween.set("style." + name, value);
}));
}; };
d3_transitionPrototype.styleTween = function(name, tween, priority) { d3_transitionPrototype.styleTween = function(name, tween, priority) {
if (arguments.length < 3) priority = ""; if (arguments.length < 3) priority = "";
return this.tween("style." + name, function(d, i) { return this.tween("style." + name, function(d, i) {
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) { return f && function(t) {
this.style.setProperty(name, f(t), priority); this.style.setProperty(name, f(t), priority);
}; };
}); });
}; };
d3_transitionPrototype.text = function(value) { d3_transitionPrototype.text = function(value) {
return this.tween("text", function(d, i) { var id = this.id;
this.textContent = typeof value === "function" ? value.call(this, d, i) : value; return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
}); node.__transition__[id].tween.set("text", d3_transition_text(value.call(node, node.__data__, i, j)));
} : (value = d3_transition_text(value), function(node) {
node.__transition__[id].tween.set("text", value);
}));
}; };
d3_transitionPrototype.remove = function() { d3_transitionPrototype.remove = function() {
return this.each("end.transition", function() { return this.each("end.transition", function() {
@ -4490,17 +4539,6 @@
node.__transition__[id].tween.set(name, tween); node.__transition__[id].tween.set(name, tween);
}); });
}; };
d3.tween = function(b, interpolate) {
function tweenFunction(d, i, a) {
var v = b.call(this, d, i);
return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v + "");
}
function tweenString(d, i, a) {
return a != b && interpolate(a, b);
}
return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString);
};
var d3_tweenRemove = {};
var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout;
d3.timer = function(callback, delay, then) { d3.timer = function(callback, delay, then) {
if (arguments.length < 3) { if (arguments.length < 3) {

8
d3.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -13,9 +13,7 @@ d3_selectionPrototype.style = function(name, value, priority) {
} }
// For style(string), return the computed style value for the first node. // For style(string), return the computed style value for the first node.
if (n < 2) return window if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
.getComputedStyle(this.node(), null)
.getPropertyValue(name);
// For style(string, string) or style(string, function), use the default // For style(string, string) or style(string, function), use the default
// priority. The priority is ignored for style(string, null). // priority. The priority is ignored for style(string, null).

Просмотреть файл

@ -4,11 +4,14 @@ d3_transitionPrototype.attr = function(name, value) {
// For attr(object), the object specifies the names and values of the // For attr(object), the object specifies the names and values of the
// attributes to transition. The values may be functions that are // attributes to transition. The values may be functions that are
// evaluated for each element. // evaluated for each element.
for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); for (value in name) this.attr(value, name[value]);
return this; return this;
} }
return this.attrTween(name, d3_tweenByName(value, name)); var id = this.id;
return d3_selection_each(this, typeof value === "function"
? function(node, i, j) { node.__transition__[id].tween.set("attr." + name, d3_transition_attr(name, value.call(node, node.__data__, i, j))); }
: (value = d3_transition_attr(name, value), function(node) { node.__transition__[id].tween.set("attr." + name, value); }));
}; };
d3_transitionPrototype.attrTween = function(nameNS, tween) { d3_transitionPrototype.attrTween = function(nameNS, tween) {
@ -16,17 +19,41 @@ d3_transitionPrototype.attrTween = function(nameNS, tween) {
function attrTween(d, i) { function attrTween(d, i) {
var f = tween.call(this, d, i, this.getAttribute(name)); var f = tween.call(this, d, i, this.getAttribute(name));
return f === d3_tweenRemove return f && function(t) { this.setAttribute(name, f(t)); };
? (this.removeAttribute(name), null)
: f && function(t) { this.setAttribute(name, f(t)); };
} }
function attrTweenNS(d, i) { function attrTweenNS(d, i) {
var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
return f === d3_tweenRemove return f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
? (this.removeAttributeNS(name.space, name.local), null)
: f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
} }
return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
}; };
function d3_transition_attr(name, b) {
var interpolate;
name = d3.ns.qualify(name);
// For attr(string, null), remove the attribute with the specified name.
function attrNull() {
this.removeAttribute(name);
}
function attrNullNS() {
this.removeAttributeNS(name.space, name.local);
}
// For attr(string, string), set the attribute with the specified name.
function attrString() {
var a = this.getAttribute(name), i;
return a !== b && (i = interpolate(a, b), function(t) { this.setAttribute(name, i(t)); });
}
function attrStringNS() {
var a = this.getAttributeNS(name.space, name.local), i;
return a !== b && (i = interpolate(a, b), function(t) { this.setAttributeNS(name.space, name.local, i(t)); });
}
return b == null
? (name.local ? attrNullNS : attrNull)
: (b += "", interpolate = d3_interpolateByName(name), name.local ? attrStringNS : attrString);
}

Просмотреть файл

@ -8,7 +8,7 @@ d3_transitionPrototype.style = function(name, value, priority) {
// specifies the priority. // specifies the priority.
if (typeof name !== "string") { if (typeof name !== "string") {
if (n < 2) value = ""; if (n < 2) value = "";
for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); for (priority in name) this.style(priority, name[priority], value);
return this; return this;
} }
@ -18,15 +18,35 @@ d3_transitionPrototype.style = function(name, value, priority) {
} }
// Otherwise, a name, value and priority are specified, and handled as below. // Otherwise, a name, value and priority are specified, and handled as below.
return this.styleTween(name, d3_tweenByName(value, name), priority); var id = this.id;
return d3_selection_each(this, typeof value === "function"
? function(node, i, j) { node.__transition__[id].tween.set("style." + name, d3_transition_style(name, value.call(node, node.__data__, i, j), priority)); }
: (value = d3_transition_style(name, value, priority), function(node) { node.__transition__[id].tween.set("style." + name, value); }));
}; };
d3_transitionPrototype.styleTween = function(name, tween, priority) { d3_transitionPrototype.styleTween = function(name, tween, priority) {
if (arguments.length < 3) priority = ""; if (arguments.length < 3) priority = "";
return this.tween("style." + name, function(d, i) { return this.tween("style." + name, function(d, i) {
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
return f === d3_tweenRemove return f && function(t) { this.style.setProperty(name, f(t), priority); };
? (this.style.removeProperty(name), null)
: f && function(t) { this.style.setProperty(name, f(t), priority); };
}); });
}; };
function d3_transition_style(name, b, priority) {
var interpolate;
// For style(name, null) or style(name, null, priority), remove the style
// property with the specified name. The priority is ignored.
function styleNull() {
this.style.removeProperty(name);
}
// For style(name, string) or style(name, string, priority), set the style
// property with the specified name, using the specified priority.
function styleString() {
var a = getComputedStyle(this, null).getPropertyValue(name), i;
return a !== b && (i = interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
}
return b == null ? styleNull : (b += "", interpolate = d3_interpolateByName(name), styleString);
}

Просмотреть файл

@ -1,7 +1,11 @@
d3_transitionPrototype.text = function(value) { d3_transitionPrototype.text = function(value) {
return this.tween("text", function(d, i) { var id = this.id;
this.textContent = typeof value === "function" return d3_selection_each(this, typeof value === "function"
? value.call(this, d, i) ? function(node, i, j) { node.__transition__[id].tween.set("text", d3_transition_text(value.call(node, node.__data__, i, j))); }
: value; : (value = d3_transition_text(value), function(node) { node.__transition__[id].tween.set("text", value); }));
});
}; };
function d3_transition_text(value) {
if (value == null) value = "";
return function() { this.textContent = value; };
}

Просмотреть файл

@ -1,27 +0,0 @@
d3.tween = function(b, interpolate) {
function tweenFunction(d, i, a) {
var v = b.call(this, d, i);
return v == null
? a != "" && d3_tweenRemove
: a != v && interpolate(a, v + "");
}
function tweenString(d, i, a) {
return a != b && interpolate(a, b);
}
return typeof b === "function" ? tweenFunction
: b == null ? d3_tweenNull
: (b += "", tweenString);
};
var d3_tweenRemove = {};
function d3_tweenNull(d, i, a) {
return a != "" && d3_tweenRemove;
}
function d3_tweenByName(b, name) {
return d3.tween(b, d3_interpolateByName(name));
}

Просмотреть файл

@ -1,27 +0,0 @@
require("../env");
var vows = require("vows"),
assert = require("assert");
var suite = vows.describe("d3.tween");
suite.addBatch({
"tween": {
"coerces constants to strings before interpolating": function() {
var t = d3.tween({toString: function() { return "#fff"; }}, d3.interpolate),
i = t(null, 0, "red");
assert.strictEqual(i(0), "#ff0000");
assert.strictEqual(i(.5), "#ff8080");
assert.strictEqual(i(1), "#ffffff");
},
"coerces function return values to strings before interpolating": function() {
var t = d3.tween(function(d) { return {toString: function() { return d; }}; }, d3.interpolate),
i = t("#fff", 0, "red");
assert.strictEqual(i(0), "#ff0000");
assert.strictEqual(i(.5), "#ff8080");
assert.strictEqual(i(1), "#ffffff");
}
}
});
suite.export(module);

Просмотреть файл

@ -5,6 +5,7 @@ CSSStyleDeclaration = window.CSSStyleDeclaration;
require("../lib/sizzle/sizzle"); require("../lib/sizzle/sizzle");
Sizzle = window.Sizzle; Sizzle = window.Sizzle;
getComputedStyle = window.getComputedStyle;
process.env.TZ = "America/Los_Angeles"; process.env.TZ = "America/Los_Angeles";