Skip to content

Commit

Permalink
Clamp easing functions to [0,1].
Browse files Browse the repository at this point in the history
Rather than have a treshold in transitions, we now clamp the easing functions.
This guarantees that when the transition ends, the tweens will be called with
t=1, and produce clean output values. Previously, that was not the case for
certain easing functions, such as exp-out and elastic.
  • Loading branch information
mbostock committed Aug 30, 2011
1 parent 69bf26e commit e7ac548
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 36 deletions.
12 changes: 9 additions & 3 deletions d3.js
Expand Up @@ -591,9 +591,15 @@ d3.ease = function(name) {
var i = name.indexOf("-"),
t = i >= 0 ? name.substring(0, i) : name,
m = i >= 0 ? name.substring(i + 1) : "in";
return d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1)));
return d3_ease_clamp(d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1))));
};

function d3_ease_clamp(f) {
return function(t) {
return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
};
}

function d3_ease_reverse(f) {
return function(t) {
return 1 - f(1 - t);
Expand Down Expand Up @@ -1802,15 +1808,15 @@ function d3_transition(groups, id) {
function tick(elapsed) {
if (lock.active !== id) return stop();

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

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

if (t === 1) {
if (t >= 1) {
stop();
d3_transitionInheritId = id;
event.end.dispatch.call(node, d, i);
Expand Down
4 changes: 2 additions & 2 deletions d3.min.js

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion src/core/ease.js
Expand Up @@ -60,9 +60,15 @@ d3.ease = function(name) {
var i = name.indexOf("-"),
t = i >= 0 ? name.substring(0, i) : name,
m = i >= 0 ? name.substring(i + 1) : "in";
return d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1)));
return d3_ease_clamp(d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1))));
};

function d3_ease_clamp(f) {
return function(t) {
return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
};
}

function d3_ease_reverse(f) {
return function(t) {
return 1 - f(1 - t);
Expand Down
4 changes: 2 additions & 2 deletions src/core/transition.js
Expand Up @@ -57,15 +57,15 @@ function d3_transition(groups, id) {
function tick(elapsed) {
if (lock.active !== id) return stop();

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

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

if (t === 1) {
if (t >= 1) {
stop();
d3_transitionInheritId = id;
event.end.dispatch.call(node, d, i);
Expand Down
56 changes: 28 additions & 28 deletions test/core/ease-test.js
Expand Up @@ -51,33 +51,33 @@ suite.addBatch({
var e = ease("bounce");
assert.inDelta(e(.5), 0.765625, 1e-6);
},
"all easing functions return approximately 0 for t = 0": function(ease) {
assert.inDelta(ease("linear")(0), 0, 1e-9);
assert.inDelta(ease("poly", 2)(0), 0, 1e-9);
assert.inDelta(ease("quad")(0), 0, 1e-9);
assert.inDelta(ease("cubic")(0), 0, 1e-9);
assert.inDelta(ease("sin")(0), 0, 1e-9);
assert.inDelta(ease("exp")(0), 0, 1e-3); // XXX
assert.inDelta(ease("circle")(0), 0, 1e-9);
assert.inDelta(ease("elastic")(0), 0, 1e-9);
assert.inDelta(ease("back")(0), 0, 1e-9);
assert.inDelta(ease("bounce")(0), 0, 1e-9);
},
"all easing functions return approximately 1 for t = 1": function(ease) {
assert.inDelta(ease("linear")(1), 1, 1e-9);
assert.inDelta(ease("poly", 2)(1), 1, 1e-9);
assert.inDelta(ease("quad")(1), 1, 1e-9);
assert.inDelta(ease("cubic")(1), 1, 1e-9);
assert.inDelta(ease("sin")(1), 1, 1e-9);
assert.inDelta(ease("exp")(1), 1, 1e-9);
assert.inDelta(ease("circle")(1), 1, 1e-9);
assert.inDelta(ease("elastic")(1), 1, 1e-3); // XXX
assert.inDelta(ease("back")(1), 1, 1e-9);
assert.inDelta(ease("bounce")(1), 1, 1e-9);
"all easing functions return exactly 0 for t = 0": function(ease) {
assert.equal(ease("linear")(0), 0);
assert.equal(ease("poly", 2)(0), 0);
assert.equal(ease("quad")(0), 0);
assert.equal(ease("cubic")(0), 0);
assert.equal(ease("sin")(0), 0);
assert.equal(ease("exp")(0), 0);
assert.equal(ease("circle")(0), 0);
assert.equal(ease("elastic")(0), 0);
assert.equal(ease("back")(0), 0);
assert.equal(ease("bounce")(0), 0);
},
"all easing functions return exactly 1 for t = 1": function(ease) {
assert.equal(ease("linear")(1), 1);
assert.equal(ease("poly", 2)(1), 1);
assert.equal(ease("quad")(1), 1);
assert.equal(ease("cubic")(1), 1);
assert.equal(ease("sin")(1), 1);
assert.equal(ease("exp")(1), 1);
assert.equal(ease("circle")(1), 1);
assert.equal(ease("elastic")(1), 1);
assert.equal(ease("back")(1), 1);
assert.equal(ease("bounce")(1), 1);
},
"the -in suffix returns the identity": function(ease) {
assert.equal(ease("linear-in"), ease("linear"));
assert.equal(ease("quad-in"), ease("quad"));
assert.inDelta(ease("linear-in")(.25), ease("linear")(.25), 1e-6);
assert.inDelta(ease("quad-in")(.75), ease("quad")(.75), 1e-6);
},
"the -out suffix returns the reverse": function(ease) {
assert.inDelta(ease("sin-out")(.25), 1 - ease("sin-in")(.75), 1e-6);
Expand All @@ -94,10 +94,10 @@ suite.addBatch({
assert.inDelta(ease("bounce-out-in")(.25), .5 * ease("bounce-out")(.5), 1e-6);
assert.inDelta(ease("elastic-out-in")(.25), .5 * ease("elastic-out")(.5), 1e-6);
},
"does not clamp input time": function(ease) {
"clamps input time": function(ease) {
var e = ease("linear");
assert.inDelta(e(-1), -1, 1e-6);
assert.inDelta(e(2), 2, 1e-6);
assert.inDelta(e(-1), 0, 1e-6);
assert.inDelta(e(2), 1, 1e-6);
},
"poly": {
"supports an optional polynomial": function(ease) {
Expand Down

0 comments on commit e7ac548

Please sign in to comment.