GithubHelp home page GithubHelp logo

d3.interpolateFunction? about d3-interpolate HOT 9 OPEN

d3 avatar d3 commented on April 19, 2024
d3.interpolateFunction?

from d3-interpolate.

Comments (9)

mbostock avatar mbostock commented on April 19, 2024

Are you imagining something like d3.interpolateMeta(a, b) that takes two interpolators and returns an interpolator that linearly interpolates the output of a and b?

from d3-interpolate.

phs avatar phs commented on April 19, 2024

Maybe. I was looking at my hand-crafted function and thinking about how I would generalize it to allow the functions themselves to be interpolators (and so, to allow things to gel with non-number output types.)

I think it works cleanly? (i.e. without excessive object creation) But if not, I would be happy with a function that only to interpolated between unary functions from reals to reals.

from d3-interpolate.

phs avatar phs commented on April 19, 2024

Right, so here's a general, but possibly unperformant implementation:

function interpolateMeta(a, b) {
  return (alpha) => interpolate(a(1 - alpha), b(alpha));
}

Here we do the interpolation in the domain, since we don't want to assume the codomain is numeric. This leads directly calling the (possibly expensive?) factory on each step.

If we know the codomain is numeric, we can do the interpolation there instead. Note this won't in general result in the same interpolator as above, but that's ok: I just want a continuous, linearish deformation.

function interpolateFunction(a, b) {
  return (alpha) => (d) => (1 - alpha) * a(d) + alpha * b(d);
}

from d3-interpolate.

phs avatar phs commented on April 19, 2024

Err, excuse me. My interpolateMeta doesn't look that bad on performance, but it does assume the domains of a and b are in the unit interval. The thing I was afraid of was this:

function interpolateAnyDomainAndCodomain(a, b) {
  return (alpha) => (d) => interpolate(a(d), b(d))(alpha);
}

Here a and b are general functions, but the interpolate call is stuck under the (d) =>.

from d3-interpolate.

phs avatar phs commented on April 19, 2024

Sorry, last one. Upon third reading, my interpolateMeta is simply wrong (for example, interpolate(a, b)(0) != a.) interpolateAnyDomainAndCodomain does what was intended.

from d3-interpolate.

mbostock avatar mbostock commented on April 19, 2024

I was thinking this:

function interpolateMeta(a, b) {
  return (t) => interpolate(a(t), b(t))(t);
}

Which, as you point out, isn’t ideal from a performance perspective because it creates as new closure for each invocation of the meta-interpolator. This is an unfortunate consequence of the design of interpolators in this library. But, since the behavior of d3.interpolate is explicitly defined, it could use private internal methods to implement the above behavior more efficiently, after restructuring the implementation of the other interpolators, e.g.,

// A private function that interpolates directly rather than returning an interpolator.
function interpolateNumber(a, b, t) {
  return (a = +a) + (b - a) * t;
}

A more extreme option would be to have separate methods, i.e., to make the above reusable implementation public:

  • d3.interpolateNumber(a, b, t) - returns the interpolated value
  • d3.interpolatorNumber(a, b) - returns an interpolator that takes a t
  • d3.interpolateString(a, b, t) -
  • d3.interpolatorString(a, b) -
  • etc.

That might be a good long-term strategy although it’s highly inconvenient in terms of backwards-compatibility.

Technically, it doesn’t require that the domains of the two interpolators a and b are the unit interval—that’s just the convention. But it does require that they have the same domain, and that this domain is also the domain that will be used with the returned “meta” interpolator.

from d3-interpolate.

mbostock avatar mbostock commented on April 19, 2024

Oh also, as far as the dynamically-typed nature of d3.interpolate goes, the behavior of d3.interpolateMeta could be defined such that the first time the returned interpolator is invoked, it determines the type of interpolator to use subsequently based on the return value of b(t). So if b(t) is a string, then it henceforth uses d3.interpolateString every time (even if subsequently the behavior of b changes—though in practice it’s hard to imagine a good reason for an interpolator to return inconsistent types).

from d3-interpolate.

mbostock avatar mbostock commented on April 19, 2024

Lastly if you could elaborate on some practical use cases for this feature it would be helpful in establishing motivation. Thanks!

from d3-interpolate.

phs avatar phs commented on April 19, 2024

The motivation is visually intuitive non-linear transforms of unit square patches (linear transforms are already easy directly with SVG.)

The unit square restriction may look odd, but it makes what comes next easier. Such patches are also easy to make from arbitrary rectangles using the existing d3.scale* functions.

So as an example, imagine transforming a time-series line graph (given by function d) by mapping its base and top lines onto a pair of arbitrary paths (a and b), such as those made by d3.line or d3.radialLine. Assuming I've already scaled d by sending both its domain and range to unit intervals, what should I do to transform it onto the patch defined by a and b?

I think one answer is:

function transformedD(u) {
  return interpolate(a(u), b(u))(d(u));
}

For our given u, we see where both a and b land, and choosing a point on the resulting line segment that is d(u) of the way towards the b end (which is where we sent the top line.)

Let's see if we can tease the bits apart a little.

function interpolateMeta(a, b) {
  return (u) => (t) => interpolate(a(u), b(u))(t);
}

var transform = interpolateMeta(a, b);
var transformedDPrime = (v) => transform(v)(d(v));

Make sure I'm not nuts:

transformedDPrime
(v) => transform(v)(d(v))                                    // subst transformedDPrime
(v) => interpolateMeta(a, b)(v)(d(v))                        // subst transform
(v) => ( (u) => (t) => interpolate(a(u), b(u))(t) )(v)(d(v)) // eval interpolateMeta(a, b)
(v) => ( (t) => interpolate(a(v), b(v))(t) )(d(v))           // beta
(v) => interpolate(a(v), b(v))(d(v))                         // beta
(u) => interpolate(a(u), b(u))(d(u))                         // alpha
transformedD                                                 // unsubst transformedD

I get the feeling we can play with binder order to avoid making closures, but I need to run for the moment.

from d3-interpolate.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.