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-transition.js \
src/core/transition-tween.js \
src/core/tween.js \
src/core/timer.js \
src/core/mouse.js \
src/core/touches.js \

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

@ -591,11 +591,47 @@
return transition;
}
}
function d3_tweenNull(d, i, a) {
return a != "" && d3_tweenRemove;
function d3_transition_attr(name, b) {
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) {
return d3.tween(b, d3_interpolateByName(name));
function d3_transition_style(name, b, priority) {
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() {
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));
return this;
}
if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name);
if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
priority = "";
}
return this.each(d3_selection_style(name, value, priority));
@ -4371,21 +4407,26 @@
};
d3_transitionPrototype.attr = function(name, value) {
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.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) {
function attrTween(d, i) {
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));
};
}
function attrTweenNS(d, i) {
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));
};
}
@ -4397,26 +4438,34 @@
if (n < 3) {
if (typeof name !== "string") {
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;
}
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) {
if (arguments.length < 3) priority = "";
return this.tween("style." + name, function(d, i) {
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) {
var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
return f && function(t) {
this.style.setProperty(name, f(t), priority);
};
});
};
d3_transitionPrototype.text = function(value) {
return this.tween("text", function(d, i) {
this.textContent = typeof value === "function" ? value.call(this, d, i) : value;
});
var id = this.id;
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() {
return this.each("end.transition", function() {
@ -4490,17 +4539,6 @@
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;
d3.timer = function(callback, delay, then) {
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.
if (n < 2) return window
.getComputedStyle(this.node(), null)
.getPropertyValue(name);
if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
// For style(string, string) or style(string, function), use the default
// 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
// attributes to transition. The values may be functions that are
// 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.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) {
@ -16,17 +19,41 @@ d3_transitionPrototype.attrTween = function(nameNS, tween) {
function attrTween(d, i) {
var f = tween.call(this, d, i, this.getAttribute(name));
return f === d3_tweenRemove
? (this.removeAttribute(name), null)
: f && function(t) { this.setAttribute(name, f(t)); };
return f && function(t) { this.setAttribute(name, f(t)); };
}
function attrTweenNS(d, i) {
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) { this.setAttributeNS(name.space, name.local, f(t)); };
return f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
}
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.
if (typeof name !== "string") {
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;
}
@ -18,15 +18,35 @@ d3_transitionPrototype.style = function(name, value, priority) {
}
// 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) {
if (arguments.length < 3) priority = "";
return this.tween("style." + name, function(d, i) {
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
return f === d3_tweenRemove
? (this.style.removeProperty(name), null)
: f && function(t) { this.style.setProperty(name, f(t), priority); };
var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
return 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) {
return this.tween("text", function(d, i) {
this.textContent = typeof value === "function"
? value.call(this, d, i)
: value;
});
var id = this.id;
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); }));
};
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");
Sizzle = window.Sizzle;
getComputedStyle = window.getComputedStyle;
process.env.TZ = "America/Los_Angeles";