Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New interpolateFromCurve, interpolateCardinal, interpolateCatmull-Rom & interpolateMonotoneX #67

Closed
wants to merge 2 commits into from
Closed

Conversation

jamesleesaunders
Copy link

@jamesleesaunders jamesleesaunders commented Jul 13, 2019

Although D3 has a wide selection of curve functions; curveMonotoneX, curveCardinal etc. for generating SVG curves, there is not as many interpolate functions for situations where, rather than SVG, we want to interpolate values for other use, for example in my case X3DOM IndexedFaceSet.

This PR adds 4 new interpolation functions which compliment the existing intrpolateBasis function:

  • interpolateFromCurve which can be passed values and a d3.curve function and returns an interpolator, for example: d3.interpolateFromCurve(values, d3.curveMonotoneX);
  • interpolateCardinal, interpolateCatmullRom & interpolateMonotoneX are all basically shortcut functions to the interpolateFromCurve above.

These have been developed with the help from a fellow X3DOM developer @andreasplesch who came up with the the original draft version of interpolateFromCurve.

To see a demo of these in action see:
https://observablehq.com/@jamesleesaunders/why-are-there-no-d3-interpolatecardinal-and-d3-interpolate

The problem with this function is considerably inefficient because, under the hood, it has to create a detached SVG element, generate the curve then reverse engineer the SVG curve back to values. The process of converting values -> curve -> svg -> values is a little bit long winded and could be a little heavy.

@andreasplesch
Copy link

Here is the original function on observable: https://observablehq.com/@andreasplesch/svg-path-sampler

It can be imported into other notebooks, for example: https://observablehq.com/@andreasplesch/line-chart

Here is a discussion: https://talk.observablehq.com/t/obtain-interpolated-y-values-without-drawing-a-line/1796

Copy link

@andreasplesch andreasplesch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it is not a the lightest solution but it is often requested since d3 users see the curves and think there must an interpolator as well. It feels, it should go in a helper/addon repo but I do not know if there is such a thing.

src/fromCurve.js Show resolved Hide resolved
src/fromCurve.js Outdated
l = (mn + l) / 2;
}
nextDelta = svgpath.getPointAtLength(l).x - targetX;
if (Math.abs(Math.abs(delta) - Math.abs(nextDelta)) < epsilon) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an attempt to detect lack of improvement during iteration and then bailing. But it turned out to be a flawed idea since there may be no improvement in the current bisecting but there could be still improvement in the next one. I think it is better to remove that check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @andreasplesch I will make these tweaks.
And thanks for coming up with this ideas in the first place! All credit goes to you for this!

@mbostock
Copy link
Member

Thanks for the pull request. I recommend you release this as a standalone module. I tend to keep the core modules small.

@jamesleesaunders
Copy link
Author

jamesleesaunders commented Jul 14, 2019

Thanks for reviewing Mike, I will indeed consider releasing this as a standalone module. But before I do I’d be interested to know you have seen much appetite for a potential d3.interpolateCardinal etc. type functions? Other than myself (and the people on this Observable thread). Has this been requested before?

I am happy to close this PR and go for separate module, but would like to make one last bid for consideration for it to go into core as I think this would benefit other non-SVG based applications.

Thanks for all you great work Mike!

@mbostock
Copy link
Member

I have not heard any other requests for this feature.

Also, I would suggest parametric evaluation of curves rather than using SVG’s getPointAtLength, as shown here for cubic Bézier segments:

https://observablehq.com/@mbostock/fourier-series-path-sampling

@andreasplesch
Copy link

Requests for interpolators corresponding to the curves do come up regularly, Here is one: #52 . It is a natural expectation.
Parametric sampling would help a bit with convergence during bisection which then would use t instead of length but requires a lot of code (until svg path normalization becomes implemented). For high curvature, very winding paths, it might actually slow down convergence a bit since the search may have to go through many densely sample portions. Then again, these portions would also have a lot of the length of the total. Interrupted paths presumably would just work.

@jamesleesaunders
Copy link
Author

Thanks @mbostock I have opted to create this as a standalone module https://github.com/jamesleesaunders/d3-interpolate-curve should you ever want to include this in core I would be more than happy to help.

@andreasplesch I hope you don't mind me taking the reins on this one. I have added you as a collaborator to d3-interpolate-curve please feel free to contribute as you see fit.

@mbostock
Copy link
Member

I don’t think you would need any bisection or search at all—just direct evaluation. See interpolateBasis for example: the total curve is divided into n segments, which are then evaluated using the basis function.

https://github.com/d3/d3-interpolate/blob/master/src/basis.js

@andreasplesch
Copy link

Yes, but only if you want the x and y of a given t, no ? Often what is needed is the y of an x.

@mbostock
Copy link
Member

Correct, yes. In the case of interpolateBasis it’s simply f(t) outputs a number in [0, n] that is then passed to a piecewise linear interpolator. In the general case of a two-dimensional curve in xy there could be more than one y-value for a given x.

@andreasplesch
Copy link

True, the general case needs the parametric form. The case of a single-valued function (http://mathworld.wolfram.com/Function.html) is the common case and is often expected to be evaluated as y(x). And people then just use the curves to fit something and want to access it as y(x).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants