d3 / d3-ease

Easing functions for smooth animation.

Home Page:https://d3js.org/d3-ease

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Return values of 0 and 1 are not always as expected

danmarshall opened this issue · comments

I ran into an issue animating with easeExpInOut where the end result was not exactly 1. The readme states "A good easing type should return 0 if t = 0 and 1 if t = 1", but a few of these do not.

https://observablehq.com/@danmarshall/d3-easing-at-0-1

easeBack 0: 0, 1: 1
easeBackIn 0: 0, 1: 0.9999999999999998
easeBackInOut 0: 0, 1: 1
easeBackOut 0: 2.220446049250313e-16, 1: 1
easeBounce 0: 0, 1: 1
easeBounceIn 0: 0, 1: 1
easeBounceInOut 0: 0, 1: 1
easeBounceOut 0: 0, 1: 1
easeCircle 0: 0, 1: 1
easeCircleIn 0: 0, 1: 1
easeCircleInOut 0: 0, 1: 1
easeCircleOut 0: 0, 1: 1
easeCubic 0: 0, 1: 1
easeCubicIn 0: 0, 1: 1
easeCubicInOut 0: 0, 1: 1
easeCubicOut 0: 0, 1: 1
easeElastic 0: 0, 1: 1.00048828125
easeElasticIn 0: -0.00048828124999999875, 1: 1
easeElasticInOut 0: -0.00024414062499999938, 1: 1.000244140625
easeElasticOut 0: 0, 1: 1.00048828125
easeExp 0: 0.00048828125, 1: 0.99951171875
easeExpIn 0: 0.0009765625, 1: 1
easeExpInOut 0: 0.00048828125, 1: 0.99951171875
easeExpOut 0: 0, 1: 0.9990234375
easeLinear 0: 0, 1: 1
easePoly 0: 0, 1: 1
easePolyIn 0: 0, 1: 1
easePolyInOut 0: 0, 1: 1
easePolyOut 0: 0, 1: 1
easeQuad 0: 0, 1: 1
easeQuadIn 0: 0, 1: 1
easeQuadInOut 0: 0, 1: 1
easeQuadOut 0: 0, 1: 1
easeSin 0: 0, 1: 1
easeSinIn 0: 0, 1: 0.9999999999999999
easeSinInOut 0: 0, 1: 1
easeSinOut 0: 0, 1: 1

Not sure if this is a bug or not, but hopefully this helps anyone else with this issue.

I think it's a bug.

For back it's just a matter of reordering the operations to avoid bad rounding.

backIn(t) is computed as t * t * ((s + 1) * t - s) where s is the "overshoot" parameter. If computed as t * t * (s * (t - 1) + t) it will return 1 for t=1, with no additional cost (same number of multiplications and additions).

For elastic, and exp the formula is not really expected to give 0 in 0 or 1 in 1 as it uses Math.pow(2,-10) instead of 0.

A solution for these would be to introduce easePow(x) = (Math.pow(2, 10 * (x-1)) - 0.0009765625) * 1.0009775171065494 (the two constants being 2^-10 and 1/(1-2^-10)) and use it internally in lieu of Math.pow(2, 10 * (x-1)). This would have an added cost of 1 multiplication and 1 addition. It also means we have to change all the tests.

Finally for sinIn there doesn't seem to be a solution other than an equality test (t === 1 ? 1 : Math.cos(…))

I've pushed a rather simple solution for back and for sin, and a second commit solves it for elastic and exp (but breaks all the tests).