Skip to content

Commit 3af91c3

Browse files
committed
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.
1 parent 0794607 commit 3af91c3

10 files changed

Lines changed: 141 additions & 108 deletions

File tree

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ d3.core.js: \
123123
src/core/transition-each.js \
124124
src/core/transition-transition.js \
125125
src/core/transition-tween.js \
126-
src/core/tween.js \
127126
src/core/timer.js \
128127
src/core/mouse.js \
129128
src/core/touches.js \

d3.js

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -591,11 +591,47 @@
591591
return transition;
592592
}
593593
}
594-
function d3_tweenNull(d, i, a) {
595-
return a != "" && d3_tweenRemove;
594+
function d3_transition_attr(name, b) {
595+
function attrNull() {
596+
this.removeAttribute(name);
597+
}
598+
function attrNullNS() {
599+
this.removeAttributeNS(name.space, name.local);
600+
}
601+
function attrString() {
602+
var a = this.getAttribute(name), i;
603+
return a !== b && (i = interpolate(a, b), function(t) {
604+
this.setAttribute(name, i(t));
605+
});
606+
}
607+
function attrStringNS() {
608+
var a = this.getAttributeNS(name.space, name.local), i;
609+
return a !== b && (i = interpolate(a, b), function(t) {
610+
this.setAttributeNS(name.space, name.local, i(t));
611+
});
612+
}
613+
var interpolate;
614+
name = d3.ns.qualify(name);
615+
return b == null ? name.local ? attrNullNS : attrNull : (b += "", interpolate = d3_interpolateByName(name), name.local ? attrStringNS : attrString);
596616
}
597-
function d3_tweenByName(b, name) {
598-
return d3.tween(b, d3_interpolateByName(name));
617+
function d3_transition_style(name, b, priority) {
618+
function styleNull() {
619+
this.style.removeProperty(name);
620+
}
621+
function styleString() {
622+
var a = getComputedStyle(this, null).getPropertyValue(name), i;
623+
return a !== b && (i = interpolate(a, b), function(t) {
624+
this.style.setProperty(name, i(t), priority);
625+
});
626+
}
627+
var interpolate;
628+
return b == null ? styleNull : (b += "", interpolate = d3_interpolateByName(name), styleString);
629+
}
630+
function d3_transition_text(value) {
631+
if (value == null) value = "";
632+
return function() {
633+
this.textContent = value;
634+
};
599635
}
600636
function d3_timer_step() {
601637
var elapsed, now = Date.now(), t1 = d3_timer_queue;
@@ -4052,7 +4088,7 @@
40524088
for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
40534089
return this;
40544090
}
4055-
if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name);
4091+
if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
40564092
priority = "";
40574093
}
40584094
return this.each(d3_selection_style(name, value, priority));
@@ -4371,21 +4407,26 @@
43714407
};
43724408
d3_transitionPrototype.attr = function(name, value) {
43734409
if (arguments.length < 2) {
4374-
for (value in name) this.attrTween(value, d3_tweenByName(name[value], value));
4410+
for (value in name) this.attr(value, name[value]);
43754411
return this;
43764412
}
4377-
return this.attrTween(name, d3_tweenByName(value, name));
4413+
var id = this.id;
4414+
return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
4415+
node.__transition__[id].tween.set("attr." + name, d3_transition_attr(name, value.call(node, node.__data__, i, j)));
4416+
} : (value = d3_transition_attr(name, value), function(node) {
4417+
node.__transition__[id].tween.set("attr." + name, value);
4418+
}));
43784419
};
43794420
d3_transitionPrototype.attrTween = function(nameNS, tween) {
43804421
function attrTween(d, i) {
43814422
var f = tween.call(this, d, i, this.getAttribute(name));
4382-
return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) {
4423+
return f && function(t) {
43834424
this.setAttribute(name, f(t));
43844425
};
43854426
}
43864427
function attrTweenNS(d, i) {
43874428
var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
4388-
return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) {
4429+
return f && function(t) {
43894430
this.setAttributeNS(name.space, name.local, f(t));
43904431
};
43914432
}
@@ -4397,26 +4438,34 @@
43974438
if (n < 3) {
43984439
if (typeof name !== "string") {
43994440
if (n < 2) value = "";
4400-
for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value);
4441+
for (priority in name) this.style(priority, name[priority], value);
44014442
return this;
44024443
}
44034444
priority = "";
44044445
}
4405-
return this.styleTween(name, d3_tweenByName(value, name), priority);
4446+
var id = this.id;
4447+
return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
4448+
node.__transition__[id].tween.set("style." + name, d3_transition_style(name, value.call(node, node.__data__, i, j), priority));
4449+
} : (value = d3_transition_style(name, value, priority), function(node) {
4450+
node.__transition__[id].tween.set("style." + name, value);
4451+
}));
44064452
};
44074453
d3_transitionPrototype.styleTween = function(name, tween, priority) {
44084454
if (arguments.length < 3) priority = "";
44094455
return this.tween("style." + name, function(d, i) {
4410-
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
4411-
return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) {
4456+
var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
4457+
return f && function(t) {
44124458
this.style.setProperty(name, f(t), priority);
44134459
};
44144460
});
44154461
};
44164462
d3_transitionPrototype.text = function(value) {
4417-
return this.tween("text", function(d, i) {
4418-
this.textContent = typeof value === "function" ? value.call(this, d, i) : value;
4419-
});
4463+
var id = this.id;
4464+
return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
4465+
node.__transition__[id].tween.set("text", d3_transition_text(value.call(node, node.__data__, i, j)));
4466+
} : (value = d3_transition_text(value), function(node) {
4467+
node.__transition__[id].tween.set("text", value);
4468+
}));
44204469
};
44214470
d3_transitionPrototype.remove = function() {
44224471
return this.each("end.transition", function() {
@@ -4490,17 +4539,6 @@
44904539
node.__transition__[id].tween.set(name, tween);
44914540
});
44924541
};
4493-
d3.tween = function(b, interpolate) {
4494-
function tweenFunction(d, i, a) {
4495-
var v = b.call(this, d, i);
4496-
return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v + "");
4497-
}
4498-
function tweenString(d, i, a) {
4499-
return a != b && interpolate(a, b);
4500-
}
4501-
return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString);
4502-
};
4503-
var d3_tweenRemove = {};
45044542
var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout;
45054543
d3.timer = function(callback, delay, then) {
45064544
if (arguments.length < 3) {

d3.min.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/selection-style.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ d3_selectionPrototype.style = function(name, value, priority) {
1313
}
1414

1515
// For style(string), return the computed style value for the first node.
16-
if (n < 2) return window
17-
.getComputedStyle(this.node(), null)
18-
.getPropertyValue(name);
16+
if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name);
1917

2018
// For style(string, string) or style(string, function), use the default
2119
// priority. The priority is ignored for style(string, null).

src/core/transition-attr.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,56 @@ d3_transitionPrototype.attr = function(name, value) {
44
// For attr(object), the object specifies the names and values of the
55
// attributes to transition. The values may be functions that are
66
// evaluated for each element.
7-
for (value in name) this.attrTween(value, d3_tweenByName(name[value], value));
7+
for (value in name) this.attr(value, name[value]);
88
return this;
99
}
1010

11-
return this.attrTween(name, d3_tweenByName(value, name));
11+
var id = this.id;
12+
return d3_selection_each(this, typeof value === "function"
13+
? function(node, i, j) { node.__transition__[id].tween.set("attr." + name, d3_transition_attr(name, value.call(node, node.__data__, i, j))); }
14+
: (value = d3_transition_attr(name, value), function(node) { node.__transition__[id].tween.set("attr." + name, value); }));
1215
};
1316

1417
d3_transitionPrototype.attrTween = function(nameNS, tween) {
1518
var name = d3.ns.qualify(nameNS);
1619

1720
function attrTween(d, i) {
1821
var f = tween.call(this, d, i, this.getAttribute(name));
19-
return f === d3_tweenRemove
20-
? (this.removeAttribute(name), null)
21-
: f && function(t) { this.setAttribute(name, f(t)); };
22+
return f && function(t) { this.setAttribute(name, f(t)); };
2223
}
2324

2425
function attrTweenNS(d, i) {
2526
var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
26-
return f === d3_tweenRemove
27-
? (this.removeAttributeNS(name.space, name.local), null)
28-
: f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
27+
return f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
2928
}
3029

3130
return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
3231
};
32+
33+
function d3_transition_attr(name, b) {
34+
var interpolate;
35+
36+
name = d3.ns.qualify(name);
37+
38+
// For attr(string, null), remove the attribute with the specified name.
39+
function attrNull() {
40+
this.removeAttribute(name);
41+
}
42+
function attrNullNS() {
43+
this.removeAttributeNS(name.space, name.local);
44+
}
45+
46+
// For attr(string, string), set the attribute with the specified name.
47+
function attrString() {
48+
var a = this.getAttribute(name), i;
49+
return a !== b && (i = interpolate(a, b), function(t) { this.setAttribute(name, i(t)); });
50+
}
51+
function attrStringNS() {
52+
var a = this.getAttributeNS(name.space, name.local), i;
53+
return a !== b && (i = interpolate(a, b), function(t) { this.setAttributeNS(name.space, name.local, i(t)); });
54+
}
55+
56+
return b == null
57+
? (name.local ? attrNullNS : attrNull)
58+
: (b += "", interpolate = d3_interpolateByName(name), name.local ? attrStringNS : attrString);
59+
}

src/core/transition-style.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ d3_transitionPrototype.style = function(name, value, priority) {
88
// specifies the priority.
99
if (typeof name !== "string") {
1010
if (n < 2) value = "";
11-
for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value);
11+
for (priority in name) this.style(priority, name[priority], value);
1212
return this;
1313
}
1414

@@ -18,15 +18,35 @@ d3_transitionPrototype.style = function(name, value, priority) {
1818
}
1919

2020
// Otherwise, a name, value and priority are specified, and handled as below.
21-
return this.styleTween(name, d3_tweenByName(value, name), priority);
21+
var id = this.id;
22+
return d3_selection_each(this, typeof value === "function"
23+
? function(node, i, j) { node.__transition__[id].tween.set("style." + name, d3_transition_style(name, value.call(node, node.__data__, i, j), priority)); }
24+
: (value = d3_transition_style(name, value, priority), function(node) { node.__transition__[id].tween.set("style." + name, value); }));
2225
};
2326

2427
d3_transitionPrototype.styleTween = function(name, tween, priority) {
2528
if (arguments.length < 3) priority = "";
2629
return this.tween("style." + name, function(d, i) {
27-
var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
28-
return f === d3_tweenRemove
29-
? (this.style.removeProperty(name), null)
30-
: f && function(t) { this.style.setProperty(name, f(t), priority); };
30+
var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name));
31+
return f && function(t) { this.style.setProperty(name, f(t), priority); };
3132
});
3233
};
34+
35+
function d3_transition_style(name, b, priority) {
36+
var interpolate;
37+
38+
// For style(name, null) or style(name, null, priority), remove the style
39+
// property with the specified name. The priority is ignored.
40+
function styleNull() {
41+
this.style.removeProperty(name);
42+
}
43+
44+
// For style(name, string) or style(name, string, priority), set the style
45+
// property with the specified name, using the specified priority.
46+
function styleString() {
47+
var a = getComputedStyle(this, null).getPropertyValue(name), i;
48+
return a !== b && (i = interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
49+
}
50+
51+
return b == null ? styleNull : (b += "", interpolate = d3_interpolateByName(name), styleString);
52+
}

src/core/transition-text.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
d3_transitionPrototype.text = function(value) {
2-
return this.tween("text", function(d, i) {
3-
this.textContent = typeof value === "function"
4-
? value.call(this, d, i)
5-
: value;
6-
});
2+
var id = this.id;
3+
return d3_selection_each(this, typeof value === "function"
4+
? function(node, i, j) { node.__transition__[id].tween.set("text", d3_transition_text(value.call(node, node.__data__, i, j))); }
5+
: (value = d3_transition_text(value), function(node) { node.__transition__[id].tween.set("text", value); }));
76
};
7+
8+
function d3_transition_text(value) {
9+
if (value == null) value = "";
10+
return function() { this.textContent = value; };
11+
}

src/core/tween.js

Lines changed: 0 additions & 27 deletions
This file was deleted.

test/core/tween-test.js

Lines changed: 0 additions & 27 deletions
This file was deleted.

test/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CSSStyleDeclaration = window.CSSStyleDeclaration;
55

66
require("../lib/sizzle/sizzle");
77
Sizzle = window.Sizzle;
8+
getComputedStyle = window.getComputedStyle;
89

910
process.env.TZ = "America/Los_Angeles";
1011

0 commit comments

Comments
 (0)