Comments (14)
It might be worth thinking about d3-interpolate in this context as well. The easeBind function would probably go away if we went with the above proposal, and it would make d3-ease function differently from d3-interpolate’s optional parameters (such as interpolateCubehelix). I was quite happy with that design so it’s just a small bummer that we have the function ambiguity here.
from d3-ease.
Use cases with hypothetical examples, some of which are probably impossible to disambiguate.
- Specify a non-parameterizable easing function for all selected nodes.
transition.ease(d3.easeCubicIn);
- Specify a default parameterizable easing function for all selected nodes.
transition.ease(d3.easePolyIn);
- Specify a custom parameterizable easing function for all selected nodes.
transition.ease(d3.easePolyIn, 2);
transition.ease(d3.easePolyIn(2)); // Or this maybe?
- Specify a non-parameterizable easing function for certain selected nodes.
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; });
- Specify a default parameterizable easing function for certain selected nodes.
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; });
- Specify a custom parameterizable easing function for certain selected nodes.
transition.ease(function(d, i) { return d3.easePolyIn(d.exponent); }); // Maybe this?
transition.ease(function(d, i) { return d3.easeBind(d3.easePolyIn, d.exponent); }); // Or this maybe?
- Invoke a non-parameterizable easing function directly.
var te = d3.easeCubicIn(t);
- Invoke a default parameterizable easing function directly.
var te = d3.easePolyIn(t);
- Invoke a custom parameterizable easing function directly.
var te = d3.easePolyIn(t, 2);
- Specify a custom ease implementation to all selected nodes:
transition.ease({ease: Math.sqrt});
- Specify a custom ease implementation to some selected nodes:
transition.ease(function() { return {ease: Math.sqrt}; });
from d3-ease.
Approach A. ease.ease(t[, arguments…])
d3.easeLinearIn = {
ease: function(t) {
return +t;
}
};
d3.easePolyIn = {
ease: function(t, e) {
if (e == null) e = 3;
return Math.pow(t, e);
}
};
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn); // 2
transition.ease(d3.easePolyIn, 2); // 3
transition.ease(d3.easeBind(d3.easePolyIn, 2)); // 3, equivalent
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easeBind(d3.easePolyIn, d.exponent); }); // 6
var te = d3.easeCubicIn.ease(t); // 7
var te = d3.easePolyIn.ease(t); // 8
var te = d3.easePolyIn.ease(t, 2); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: works well for 1-5; 10-11 seem reasonable.
Cons: requires implicit easeBind for 3; requires explicit easeBind for 6; requires ease.ease for 7-9. I worry that d3.easeBind is slower than using a closure since it requires derived variables to be recomputed each time the easing function is evaluated (as opposed to d3.interpolateBind, where the interpolator is first constructed, and then called repeatedly).
from d3-ease.
Approach B. ease.ease([arguments…])(t)
d3.easeLinearIn = {
ease: function() {
return function(t) {
return +t;
};
}
};
d3.easePolyIn = {
ease: function(e) {
if (e == null) e = 3;
return function(t) {
return Math.pow(t, e);
};
}
};
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn); // 2
transition.ease(d3.easePolyIn, 2); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easeBind(d3.easePolyIn, d.exponent); }); // 6
var te = d3.easeCubicIn.ease()(t); // 7
var te = d3.easePolyIn.ease()(t); // 8
var te = d3.easePolyIn.ease(2)(t); // 9
transition.ease({ease: function() { return Math.sqrt; }}); // 10
transition.ease(function() { return {ease: function() { return Math.sqrt; }}; }); // 11
Pros: 1-5 are fine.
Cons: 6 requires the use of d3.easeBind, although the bound function is executed only once to create the easing function. 7-9 are awkward. 10-11 are super awkward. Somewhat inefficient for non-parameterizable and default easing methods unless you implement caching, as in:
function linearIn(t) {
return +t;
}
d3.easeLinearIn = {
ease: function() {
return linearIn;
}
};
Note that this optimization can be implemented as a wrapper, though.
The implementation of easeBind might look like this:
d3.easeBind = function(ease) {
var args = [].slice.call(arguments, 1);
return {
ease: function() {
return ease.ease.apply(ease, args);
}
};
};
from d3-ease.
Approach C. ease(t[, arguments…]), ease.ease = ease. (Approach A + convenience for direct usage.)
d3.easeLinearIn = function(t) {
return +t;
};
d3.easePolyIn = function(t, e) {
if (e == null) e = 3;
return Math.pow(t, e);
};
d3.easeLinearIn.ease = d3.easeLinearIn;
d3.easePolyIn.ease = d3.easePolyIn;
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn); // 2
transition.ease(d3.easePolyIn, 2); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easeBind(d3.easePolyIn, d.exponent); }); // 6
var te = d3.easeCubicIn(t); // 7
var te = d3.easePolyIn(t); // 8
var te = d3.easePolyIn(t, 2); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: Usage is clean (and backwards-compatible!).
Cons: Requires implicit easeBind for 3; requires explicit easeBind for 6. The definition is a little awkward and potentially misleading. It looks like the ease function is being used directly, but ease.ease is used instead.
from d3-ease.
Approach D. ease([arguments…]).ease(t)
d3.easeLinearIn = function() {
return {
ease: function(t) {
return +t;
}
};
};
d3.easePolyIn = function(e) {
if (e == null) e = 3;
return {
ease: function(t) {
return Math.pow(t, e);
}
};
};
Examples:
transition.ease(d3.easeCubicIn()); // 1
transition.ease(d3.easePolyIn()); // 2
transition.ease(d3.easePolyIn(2)); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn() : d3.easeLinearIn(); }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn() : d3.easeLinearIn(); }); // 5
transition.ease(function(d, i) { return d3.easePolyIn(d.exponent); }); // 6
var te = d3.easeCubicIn().ease(t); // 7
var te = d3.easePolyIn().ease(t); // 8
var te = d3.easePolyIn(2).ease(t); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: No need for d3.easeBind; a clean interface. Symmetry between non-parameterizable and parameterizable easing methods.
Cons: Even non-parameterizable and default easing methods require parens, and if you forget them, bound data is passed to the easing factory which could result in surprising behavior and inefficiency. Somewhat inefficient for non-parameterizable and default easing methods unless you implement caching, as in:
var linearIn = {
ease: function(t) {
return +t;
}
};
d3.easeLinearIn = function() {
return linearIn;
};
Note that this optimization can be implemented as a wrapper, though.
from d3-ease.
Approach E. ease.ease(t) and ease([arguments…]).ease(t)
d3.easeLinearIn = {
ease: function(t) {
return +t;
}
};
d3.easePolyIn = function(e) {
if (e == null) e = 3;
return {
ease: function(t) {
return Math.pow(t, e);
}
};
};
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn()); // 2
transition.ease(d3.easePolyIn(2)); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn() : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easePolyIn(d.exponent); }); // 6
var te = d3.easeCubicIn.ease(t); // 7
var te = d3.easePolyIn().ease(t); // 8
var te = d3.easePolyIn(2).ease(t); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: No need for d3.easeBind.
Cons: Asymmetry between non-parameterizable and parameterizable easing methods. If you forget to evaluate a parameterizable easing function (even with default parameters), bound data is passed to the easing factory which could result in surprising behavior and inefficiency.
from d3-ease.
Approach F. ease.ease(t) and ease.of([arguments…]).ease(t)
d3.easeLinearIn = {
ease: function(t) {
return +t;
}
};
d3.easePolyIn = {
of: function(e) {
if (e == null) e = 3;
return {
ease: function(t) {
return Math.pow(t, e);
}
};
}
};
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn); // 2
transition.ease(d3.easePolyIn, 2); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easePolyIn.of(d.exponent); }); // 6
var te = d3.easeCubicIn.ease(t); // 7
var te = d3.easePolyIn.of().ease(t); // 8
var te = d3.easePolyIn.of(2).ease(t); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: No need for d3.easeBind. Symmetry between non-parameterizable and parameterizable easing methods, at least for transition.ease.
Cons: Now transition.ease has to test for two interfaces: an easing function, and an easing function factory. The latter is only useful in cases 2-3 to provide symmetry. The name “of” is extremely generic.
from d3-ease.
Approach G. ease.ease(t) and ease.argument(value).ease(t)
d3.easeLinearIn = {
ease: function(t) {
return +t;
}
};
function PolyIn(exponent) {
this._exponent = exponent;
}
PolyIn.prototype = {
exponent: function(e) {
return new PolyIn(e);
},
ease: function(t) {
return Math.pow(t, this._exponent);
}
};
d3.easePolyIn = new PolyIn(1);
Alternative implementation with closures:
d3.easePolyIn = (function polyIn(e) {
return {
exponent: polyIn,
ease: function(t) {
return Math.pow(t, e);
}
};
})(1);
Examples:
transition.ease(d3.easeCubicIn); // 1
transition.ease(d3.easePolyIn); // 2
transition.ease(d3.easePolyIn.exponent(2)); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubicIn : d3.easeLinearIn; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePolyIn : d3.easeLinearIn; }); // 5
transition.ease(function(d, i) { return d3.easePolyIn.exponent(d.exponent); }); // 6
var te = d3.easeCubicIn.ease(t); // 7
var te = d3.easePolyIn.ease(t); // 8
var te = d3.easePolyIn.exponent(2).ease(t); // 9
transition.ease({ease: Math.sqrt}); // 10
transition.ease(function() { return {ease: Math.sqrt}; }); // 11
Pros: Clean interface, arguments are explicitly named and self-describing; symmetry between non-parameterizable and default parameterizable easing methods; explicit differentiation for non-default parameterized easing methods.
Cons: None? Well, it requires specifying whether ease.argument returns a new easing function or mutates the behavior of ease. Most D3 setter methods modify in-place, but obviously mutating the behavior of the global d3.easePolyIn would be bad. Possibly the first call creates a new instance, and then subsequent calls modify the instance in-place, but that sounds kind of icky, too.
This pattern also extends nicely to optional parameters for interpolation, such as interpolate.gamma.
from d3-ease.
Fixed in 0.6.
from d3-ease.
I like the new named parameters.
I don’t like the new easing interface. It bothers me that I can’t just pass a function to transition.ease, and that easing requires an interface whereas interpolators and scales do not. And I don’t think the answer is to require an interface for interpolators and scales! (Imagine the hassle!)
The only reason this interface is required is for transition.ease to accept both a function that returns an easing method and an easing method directly. Wouldn’t it be a lot simpler to avoid that ambiguity by having a separate method, say transition.easeEach, if you wanted to specify per-element easing functions? That name isn’t ideal, but it seems less bad than requiring an easing interface.
The function vs. constant overloading works with the other methods in D3 because the constant values aren’t functions. Once the “constant” is itself a function, it seems reasonable to make the distinction explicit rather than inferred. Plus, there are already cases where functions are required for per-element evaluation, such as transition.tween.
from d3-ease.
I think there’s a way to do with without requiring you to call ease.ease if you want to use an easing function directly: the built-in easing methods could be functions that you can call directly while also exposing an ease.ease method that points back to the function. So:
d3.easeLinear = function(t) { return +t; };
d3.easeLinear.ease = d3.easeLinear; // Have it both ways!
When setting transition.ease, it could check with the specified ease has an ease.ease method, and use it like an object; otherwise it would assume that the ease is a function that should be evaluated for each node and return the corresponding ease instance.
If you want to pass a custom easing function and use that for all nodes, we could have a d3.ease method to wrap your function:
d3.ease = function(ease) {
return {ease: ease};
};
Then you could say, for example:
transition.ease(d3.ease(function(t) { return Math.sqrt(t); }));
transition.ease(d3.ease(Math.sqrt)); // Equivalently.
Sure, that’s a little more work than specifying a bare function, but I don’t think custom easing functions will be very common, and it seems fine to require a few more characters to make it happen, as compared to:
transition.ease(function(t) { return Math.sqrt(t); });
transition.ease(Math.sqrt); // Equivalently.
Also, I do think it could be useful to have an interpolator interface, too. For example, if you want to reuse an interpolator for your style tweens, you currently have to do this, which is awkward:
var redblue = d3.interpolateRgb("red", "blue");
transition.styleTween("color", function() { return redblue; });
You can also say this, but it’s less efficient because it’s constructing a separate interpolator (with the same start and end values) for each node:
transition.styleTween("color", d3.interpolateRgb.bind(null, "red", "blue"));
I suppose we could optimize d3.interpolateRgb to reuse interpolators, but that’d be a fair amount of work. If there were an interpolator interface, you could say:
transition.styleTween("color", d3.interpolateRgb("red", "blue"));
If you wanted to write a custom interpolator, you could use (a new) d3.interpolator wrapper that works like the proposed d3.ease above:
transition.styleTween("color", d3.interpolator(function(t) { return "rgb(" + Math.round(t * 255) + ",0,0)"; }));
And of course this means that d3.interpolateRgb and the like would return functions with interpolator.interpolate methods that point back to the function. Like this:
var i = d3.interpolateRgb("red", "blue");
i.interpolate = i; // Have it both ways!
I also have a related concern about whether D3’s extensive use of closures makes it harder for JavaScript runtimes to optimize; I see the “Not optimized: optimized too many times” error in some of my initial testing of 4.0. It’s possible that by only supporting the ease.ease and interpolator.interpolate interface (and using private state rather than captured variables) could be easier for runtimes to optimize.
That would necessitate using the interfaces, rather than the closures, even when using easing or interpolation directly. Which means you’d be saying this:
transition.ease(d3.easeCubic); // 1
transition.ease(d3.easePoly); // 2
transition.ease(d3.easePoly.exponent(2)); // 3
transition.ease(function(d, i) { return i & 1 ? d3.easeCubic : d3.easeLinear; }); // 4
transition.ease(function(d, i) { return i & 1 ? d3.easePoly : d3.easeLinear; }); // 5
transition.ease(function(d, i) { return d3.easePoly.exponent(d.exponent); }); // 6
var te = d3.easeCubic.ease(t); // 7
var te = d3.easePoly.ease(t); // 8
var te = d3.easePoly.exponent(2).ease(t); // 9
transition.ease(d3.ease(Math.sqrt)); // 10
transition.ease(function() { return d3.ease(Math.sqrt); }); // 11
But… that seems pretty reasonable.
from d3-ease.
Also, tweak to approach G:
d3.easeLinear = d3.ease(function(t) {
return +t;
});
from d3-ease.
Punting on this for now.
from d3-ease.
Related Issues (16)
- Consider using a more generic easing library HOT 5
- Accelerate, then coast?
- Rename ease(type[, parameters…]) to bind?
- Remove non-in aliases.
- Easing/Transitioning in Chrome HOT 4
- update docs with npm version HOT 1
- README needs API reference.
- README references obsolete APIs. HOT 3
- Return values of 0 and 1 are not always as expected HOT 2
- Cubic Bézier easing? HOT 1
- broken Map polyfill HOT 1
- Register with bower. HOT 1
- Don’t ignore NaN parameters? HOT 2
- Symbols, not strings? HOT 1
- bounce optional params HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from d3-ease.