d3 / d3-interpolate Goto Github PK
View Code? Open in Web Editor NEWInterpolate numbers, colors, strings, arrays, objects, whatever!
Home Page: https://d3js.org/d3-interpolate
License: ISC License
Interpolate numbers, colors, strings, arrays, objects, whatever!
Home Page: https://d3js.org/d3-interpolate
License: ISC License
In some situations interpolateObject does more work than is desired. For instance, if I have an object like {x: 1, y: 2, label: 'red'}
that interpolates to {x: 3, y: 4, label: 'red'}
. I want it to come out of interpolate as exactly {x: 3, y: 4, label: 'red'}
, however, because interpolateObject tries to interpolate all of the values in the object the output of this interpolation tends to be {x: 3, y: 4, label: 'rgb(255, 0, 0)'}
.
Is there a way to exclude specific keys from the interpolation? Or do I need to do some sort of recombination after the interpolation? Given the design goal of this function to be the fast inner part of a loop for animation, needing to modify the data after interpolation is less than ideal.
I've been noticing this error in Internet Explorer with an application that depends on d3;
Unable to get property 'matrix' of undefined or null reference
I tracked it down to this line. It seems like the problem is almost identical to d3/d3#376. I'm not completely sure what the conditions are that cause consolidate()
to return null in my case. My understanding of the internals of d3 and the SVG API are too limited, and my toolset for debugging issues in IE is too cumbersome for me to quickly find out. However, it seems like the solution is the same as the previous d3 issue. Changing line 21 to the following seems to fix the issue for me;
var m = svgNode.transform.baseVal.consolidate() ? svgNode.transform.baseVal.consolidate().matrix : {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0};
I'm not sure if that's the right fix, but I'll open a PR with that fix for now and await feedback.
A very lightweight piecewise linear scale. Implementation:
d3.piecewise = function(interpolate, values) {
var n = values.length - 1, I = d3.pairs(values, interpolate);
return function(t) {
var i = Math.max(0, Math.min(n - 1, Math.floor(t *= n)));
return I[i](t - i);
};
};
Example usage:
d3.piecewise(d3.interpolateRgb, ["red", "green", "blue"])
In interpolateTransform. Related d3/d3#1472 d3/d3#2541. It seems like it’s required by the spec, even though browsers don’t care.
Currently goes through some extra checks. Might be optimized?
Related d3/d3#2507.
I have a specific case in mind I'm wondering about, related to this plugin, but I'm also wondering how this should be done in general.
It seems like most plugins attach functions to the d3 object itself, so for this plugin my first approach was something like this:
import * as d3 from 'd3';
import 'd3-interpolate';
window.d3 = d3;
The specific issue I'm running into here is that later I have code like
const someFunction = (arr, numSamples) => {
const interpolator = d3.interpolateRgb(arr[0], arr[1]);
return d3.quantize(interpolator, numSamples);
};
which throws the error "d3.quantize is not a function". Placing a debugger to examine the d3 object, it looks like d3.interpolateRgb
exists, along with many of the other d3.interpolate functions, but d3.quantize doesn't.
One quick fix was to do something like
import * as interpolate from 'd3-interpolate';
d3.quantize = interpolate.quantize
but I'm just wondering what the proper approach might be for importing/attaching plugin functions (or if this is just an isolated instance!)
The documentation implies that only properties and elements in b are considered, but in fact, it uses the union of properties and elements in a and b. Related #19.
Are you considering adding it in the future?
Interpolating RGB values without gamma correct produces darker "dirtier" colors than they should be.
The solution would be to move values to linear space, interpolate and bring it back to gamma space.
//pseudocode for R channel
var color_hex = '#FF0000';
var color = hex2bytes(color_hex);
var byte_r = color[0];
var float_r = byte_r/255;
var linear_r = Math.pow(float_r, 2.2);
var inter_r = interpolate(linear_r, linear_r_2, t);
var gamma_r = Math.pow(inter_r, 1/2.2);
var out_r = Math.floor(gamma_r * 255);
Related d3/d3#2878. The old behavior of d3.interpolate was:
Note the “and not coercible to a number” rule in step 4. The new algorithm does not treat objects that are coercible to numbers specially:
A ramification of this is that d3.interpolate no longer supports interpolating dates: dates are treated as generic objects, and since they do not have any enumerable properties, the resulting interpolator returns the empty object.
I see two non-exclusive options:
instanceof Date
, and if so, use a new d3.interpolateDate instead of d3.interpolateObject.!isNaN(b)
, and if so, use d3.interpolateNumber instead of d3.interpolateObject.Note that if we only do the second option, then the resulting interpolator will return numbers rather than Date instances. In practice, this is typically fine because all methods in D3 that accept dates should create a Date instance from a number if they are given a number. However, it might be cleaner to handle dates specially, since the Date is a first-class type in JavaScript.
Detected this circular dependency issue in my team's CI, we use rc-slider
which has d3-interpolate
as dependency.
The circular dependency was reported by CI as follows:
Circular dependency: node_modules/d3-interpolate/src/value.js -> node_modules/d3-interpolate/src/array.js -> node_modules/d3-interpolate/src/value.js
Circular dependency: node_modules/d3-interpolate/src/value.js -> node_modules/d3-interpolate/src/object.js -> node_modules/d3-interpolate/src/value.js
Using [email protected]
when bundling with rollup:
(!) Circular dependency: node_modules\d3-interpolate\src\value.js -> node_modules\d3-interpolate\src\array.js -> node_modules\d3-interpolate\src\value.js
(!) Circular dependency: node_modules\d3-interpolate\src\value.js -> node_modules\d3-interpolate\src\object.js -> node_modules\d3-interpolate\src\value.js
A path element's "d" attribute for an arc (such as those created with d3.arc) contain "A" commands ("elliptical arc curve commands"). They have five parameters, two of which are flags which can only be 0 or 1.
Unfortunately, some transitions can lead to these flags being linearly interpolated, leading to values such as 0.12345. At least in Chrome, this causes error messages. Here is an example:
http://codepen.io/anon/pen/qRrgLe
(You will need to open the console to see the error messages.)
Is there a chance to have these flags only assume the values 0 or 1 during a transition?
I was wondering if I can learn something from your interpolation methods and I found skewY
to be missing. I ask because I want to sharpen my scripting; I want to develop a unified methods to do HTML & SVG animation (if SVG do the transform attribute with the below codes, else do the HTML).
You know regular HTML elements do use skewY
and I don't understand why you decided not to use it for D3 functions as well. D3 is above and beyond me and any other scripting there is for SVG, so I'm here to learn, I hope I can find some help here or in SO website.
As of now, we have in the decompose.js
:
if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
As for skewX
we have
skewX: Math.atan(skewX) * degrees,
similar for skewY
skewY: Math.tan(a) * degrees, // is this correct? it could according to https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
In the index.js
we have skewX
function skewX(a, b, s, q) {
if (a !== b) {
q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: number(a, b)});
} else if (b) {
s.push(pop(s) + "skewX(" + b + degParen);
}
}
Could this mean that skewY
would look like this?
function skewY(a, b, s, q) {
if (a !== b) {
q.push({i: s.push(pop(s) + "skewY(", null, degParen) - 2, x: number(a, b)});
} else if (b) {
s.push(pop(s) + "skewY(" + b + degParen);
}
}
Please, are the decomposition and interpolation functions correct for 'skewY' in the above?
Now that it’s supported by d3-color, it would be nice to do here.
One tricky thing: CSS says that transparent is rgba(0, 0, 0, 0). A more natural representation in d3-color is rgba(NaN, NaN, NaN, 0), so the RGB channels can be inherited. Probably we should fix d3-color to set these channels to NaN if the opacity is zero, so that the existing interpolators will just work automatically.
Can you extract out or expose the transformation decomposition, manipulation, and recompose logic?
Specifically, parseSvg
has been very useful to build the object from the attribute, and it'd be nice if the "build the CSS string from the transform JSON" logic was also exposed (instead of closed over in the interpolation logic).
https://github.com/d3/d3-interpolate/tree/master/src/transform
https://github.com/d3/d3-interpolate/blob/master/src/transform/index.js
Is there a good reason to not make this functionality a dedicated micro library?
d3-transform-matrix
? d3-css-transform
?
In charge of:
Hello
interpolateTransform* interpolate rotation with shortest path. So, looks like it's imposible to make interpolation from 0 to 360 deg. I think this is an issue.
d3-interpolate/src/transform/index.js
Line 21 in 255231b
It’d be nice if there were an interpolator for typed arrays, like Float32Array. The existing d3.interpolateArray is almost what we want—the only change you’d need to make is to say c = new b.constructor(nb)
rather than c = new Array(nb)
.
However, there are two considerations with this approach:
Should d3.interpolate delegate to d3.interpolateArray if b is a typed array? If so, we will need to use a different test than Array.isArray that also returns true for typed arrays.
How should d3.interpolateArray behave if b is not an array and not a typed array? The current implementation is guaranteed to return an Array even if b is not an array (but may be an array-like). If c is constructed using b.constructor, the change to d3.interpolateArray might be considered backwards-incompatible.
It might make more sense to introduce a new d3.interpolateTypedArray, and keep the current behavior of d3.interpolateArray. And we could optionally extend d3.interpolate to delegate to d3.interpolateTypedArray for typed array input. Seems like the following test would work:
function isTypedArray(x) {
return ArrayBuffer.isView(x) && !(x instanceof DataView);
}
export function basis(t1, v0, v1, v2, v3) {
var t2 = t1 * t1, t3 = t2 * t1;
return ((1 - 3 * t1 + 3 * t2 - t3) * v0
+ (4 - 6 * t2 + 3 * t3) * v1
+ (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2
+ t3 * v3) / 6;
}
export default function(values) {
var n = values.length - 1;
return function(t) {
var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n),
v1 = values[i],
v2 = values[i + 1],
v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
return basis((t - i / n) * n, v0, v1, v2, v3);// Why multipy n ???
// how about use the next line ?
return basis(t , v0, v1, v2, v3);
};
}
https://github.com/d3/d3-interpolate/blob/master/src/color.js
It’d be nice for implementation other color spaces.
I’d like a version of d3.interpolateTransform that, instead of taking a transform string as d3.interpolateTransformCss and d3.interpolateTransformSvg do, takes an array of six numbers [a, b, c, d, tx, ty] in the same manner as the matrix transform function.
This would potentially eliminate the need for separate CSS- and SVG-specific transform methods, in conjunction with changing how the transform style and attribute is handling in d3-transition (d3/d3-transition#72).
https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix
I’m quite sure the implementation here doesn’t exactly match the specification for some edge cases. The latest specification is nicely explicit about how to perform 2D transform decomposition and interpolation, so it’d be good to verify that our implementation matches the specification.
Interpolating red
and white
in Lab and HCL:
Due to the particular HCL hue of achromatic colors (~ 158.2° in the current implementation of d3-color), when interpolating a chromatic and achromatic color, the non-zero Chroma of the interpolated color will pick up various hues.
In comparison, chroma.js will not interpolate the hue in this case, with a more predictable (and analogous to Lab) outcome:
Should interpolateHcl behave the same?
The starting value should be treated as an empty object if it is not an object. So, instead of:
> d3_interpolate.value(NaN, {foo: 42})(0.5)
TypeError: Cannot use 'in' operator to search for 'foo' in NaN
at object (/Users/mbostock/Development/d3-interpolate/build/d3-interpolate.js:44:18)
The input NaN
should be treated as {}
:
> d3_interpolate.value({}, {foo: 42})(0.5)
{foo: 42}
Related d3/d3#2688.
In d3-shape, I felt it was too verbose to use the prefixes “curve” for curves and “symbol” for symbol types: d3.shape.cardinal
and d3.shape.circle
seem sufficiently specific. If the functions in this repository are similarly scoped by the module name, then isn’t d3.interpolate.rgb
sufficiently specific, too? Saying d3.interpolate.interpolateRgb
seems redundant.
Of course, this assumes that the symbols aren’t flattened on to the d3
namespace, as in d3.interpolateRgb
as they were in 3.x, but I think it will be nice to scope each module’s symbols so that the way we refer to them is the same whether you are using Rollup for a custom bundles or simply concatenating the pre-built bundles.
The probably means d3.interpolate
would need to be renamed d3.interpolate.auto
, unless we decide to have that function be the default export of the module… which I think probably wouldn’t work well? Which then raises a related issue of what to name d3.ease
in d3/d3-ease#8.
I just realized that in #77, for integer arrays, the implicit rounding is floor
when we would want round
, for symmetry.
I'm not sure where to put that change: in array type detection, and/or in an explicit interpolateNumberArrayRound function.
(Sorry for l'esprit d'escalier)
Scenario: I want to create an interpolator between two objects a = {color: "red"}
and b = {color:"lightblue"}
.
d3.interpolate(a,b)
sees that these properties are colors, and defaults to the hard-coded rgb
(https://github.com/d3/d3-interpolate/blob/master/src/value.js#L15) method. Is there a way to override that default and use a gamma-corrected interpolateRgb (#14) or interpolateLab instead of rgb
?
Unless I'm missing something, I don't see how to override this. (It doesn't even look possible to monkey-patch with d3.interpolateRgb = d3.interpolateLab
, which I would not recommend anyway :))
A possible syntax might be d3.interpolate.color(d3.interpolateLab)(…)
, or maybe more generically d3.interpolate.delegate("color", d3.interpolateLab)(…)
, which could also work for numbers, strings, whatever…
I dislike asking this question without having the proper vocabulary for it, but I was experimenting with reproducing Photoshop's Curves interface using D3 modules.
After reading up on some of the math, experimenting with polynomial interpolation (and experiencing Runge's phenomenon with it), I came back to a cubic spline interpolation — using a package that implements this algorithm — that gave me the correct result.
Pictured below, in combination with a clamped linear scale, and in comparison with some curves available in d3-shape
:
Also, reading through the original implementation of this spline, it also mentions it being a natural spline, and I have found that in some situations, D3's curveNatural
matches this implementation, but in others they diverge, further clouding my understanding of terms :D
I think it would be great to not have to get all the interpolation methods when we just want one (it's linked to d3/d3-color#15)
Lodash does it very well:
// load the modern build
var _ = require('lodash');
// or a method category
var array = require('lodash/array');
// or a method (great for smaller builds with browserify/webpack)
var chunk = require('lodash/array/chunk');
So it would mean that instead of:
var interpolateRound = require('d3-interpolate').interpolateRound
We could do:
var interpolateRound = require('d3-interpolate/interpolateRound')
Which, in this case, is 630 less loc to add to the final build.
I think it might be a problem with all the new d3 repos, I'm happy to add PRs this week if you're keen with the change.
When interpolating from an invalid RGB color, we should assume the (potentially) valid channel values from the target color, rather than always returning black. This should apply to other color spaces, too.
Calling interpolate
with objects without prototype:
var a = Object.create(null);
a.value = 0;
var b = Object.create(null);
b.value = 10;
var interpolator = interpolate(a, b)
results in error:
TypeError: Cannot convert object to primitive value
at isNaN (native)
I've created pull request to fix this: #34
As with #10, the starting value should be treated as an empty array if it’s not an array, rather than potentially throwing an error:
> d3_interpolate.value(undefined, [42])(0.5)
TypeError: Cannot read property 'length' of undefined
I've experimented with monotone cubic interpolation (as per the Steffen method you reference in d3-shape) and with the array values being evenly spaced, the formula is quite simplified, as long as I read the paper correctly:
const monotone = (t1, h, v0, v1, v2, v3) => {
let h2 = h * h;
let t2 = t1 * t1;
let t3 = t2 * t1;
let s20 = (v2 - v0) / (2 * h);
let s31 = (v3 - v1) / (2 * h);
let s21 = (v2 - v1) / h;
return (
(s20 + s31 - 2 * s21) / h2 * t3 +
(3 * s21 - 2 * s20 - s31) / h * t2 +
s20 * t +
v1
);
}
where h = 1 / n
, t1 = t - i / n
and i
, n
, and v0
...v3
being computed the same as with the basis spline.
I tried it for the purpose of color interpolation, although it turned out to be of questionable usefulness, at least in RGB:
Nevertheless, if you think it would be a good addition to d3-interpolate, I'm happy to make a PR. (Could be useful for animations, since it smoothly interpolates the data points?)
Are all the interpolations linear?
Is it possible to do easing or even physics-based (e.g. spring based using stiffness and damping)?
In d3 version 3.5.17 there is no interpolateTransformSvg but there is interpolateTransform. The readme for this repository has it as interpolateTransformSvg. It might be the up and coming api, so it might not really be an issue, but thought that I would let you know anyway.
Thank you for such an awesome library!
Perhaps these could be simple interpolators (taking an argument t in [0,1]) that returns the corresponding color, as is currently implemented in d3-scale.
In many cases in this library we use the interpolated value
a + (b - a) * t
This can return a value not equal to b when t = 1. If we want to guarantee that the exact value b is returned for t = 1, we should instead use the formula
a * (1 - t) + b * t
The parseCss method transforms a statement like translate(23px, 24px)
into a transformation matrix. It constantly modifies the DOM. This is a huge performance issue, when an animation is initiated.
https://github.com/d3/d3-interpolate/blob/master/src/transform/parse.js
What do you think about parsing this with regex instead and mathematically generating the transformation matrices? Should be significantly faster. We can look for inspiration in places like https://github.com/jlmakes/rematrix for matrix generation. An simple example where performance is already a huge drain:
<html>
<head>
<title>Learn D3 in 5 minutes</title>
</head>
<body>
<svg id="canvas"></svg>
<script src="https://d3js.org/d3.v5.js"></script>
<script>
const circles = d3.select('svg')
.selectAll('path')
.data(Array(2).fill())
.enter()
.append('circle')
.attr('r', 20)
let xOffset = 0;
const animate = () => {
xOffset+=0.1
circles.transition()
.duration(100)
.style('transform', (_, index) => `translate(${(index + xOffset) * 30}px, 30px)`)
.on('end', animate);
}
animate();
</script>
</body>
</html>
On my brand new Macbook Pro I can see the framedrops easily with my eyes. As seen in Chrome, this triggers forced reflows. For a lot of simultaneous transitions starting at the same time, the problem is, of course, even bigger.
Is this a known issue? I would be interested in helping out with a pull request
Seems insufficiently useful. Just make the behavior of d3.interpolator fixed. People can create custom interpolators easily enough.
It would be great if the ρ value responsible for the curvature of the 'flight' path in d3.interpolateZoom
function could be modified by users, e.g.:
interpolate.rho(ρ)
var i=d3.interpolateZoom.rho(.25)([ux0, uy0, w0],[ux1, uy1, w1]);
I would imagine the default ρ value would be the current √2.
It would also be good to have a value that produces linear path (e.g. ρ=0), making it a standard non-zoom interpolator.
What I'm not sure about is if the value should only be a constant or also a function, if there should be allowed min & max values, could it accept negative values and if it should be a set function or a get too?
d3.interpolate(BigInt(5), BigInt(15))(0.5)
throws a TypeError: Cannot convert a BigInt value to a number
BigInt64Array and BigUint64Array (typed arrays) could be supported too (see #74 (comment)).
THREE.js and AFrame interchange between strings and Vec3 objects e.g
3 3 3
and {x:3, y:3, z:3}
I've been handling this using my own home-rolled interpolator functions with attrTween, but this seems like a straighforward feature to consider adding to stock d3.
When strings are formatted like: "123, 456, 078" they are can end up dropping the leading zero in the capture group resulting in an incorrect interpolation to: "123,456,78"
Might be a temporary github issue, but I'm not seeing any of the images here. Dev console shows something like this over and over:
Refused to load the image 'https://raw.githubusercontent.com/d3/d3-interpolate/master/img/hsl.png' because it violates the following Content Security Policy directive: "img-src 'self' data: assets-cdn.github.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com collector.githubapp.com avatars0.githubusercontent.com avatars1.githubusercontent.com avatars2.githubusercontent.com avatars3.githubusercontent.com github-cloud.s3.amazonaws.com".
Per #64 (comment). Though, the difference between this as d3.interpolateRgb.gamma(2.4) is pretty minor.
Consider this animation which interpolates using interpolateTransformSvg
:
var p1 = d3.select('svg')
.append('polygon')
.attr('points', '100 90 100 110 10 100')
.attr('transform', 'rotate(20, 100, 100)');
p1.transition()
.duration(2000)
.attr('transform', 'rotate(160, 100, 100)')
.on('start end', function () {
console.log(p1.node().transform.baseVal.consolidate().matrix);
});
It should rotate the polygon about a constant center. Instead, the rotation center moves during the transition. If you look at the logged consolidated transform matrices, this is not that suprising:
{ a: 0.9396926164627075, b: 0.3420201539993286, c: -0.3420201539993286, d: 0.9396926164627075, e: 40.23275375366211, f: -28.171276092529297 }
{ a: -0.9396926164627075, b: 0.3420201539993286, c: -0.3420201539993286, d: -0.9396926164627075, e: 228.17127990722656, f: 159.76724243164062 }
The method of blindly taking the matrix supplied by SVGTransformList.consolidate() as a basis for interpolation is clearly not appropriate in this case.
Rotating without supplying a rotation center works is as expected.
I have a need to linearly deform one (unary, real domain) function onto another.
As a new d3 user I'm still learning what's in the kitchen, so to speak. Before discovering d3-interpolate
I was cobbling this feature together with d3.scaleLinear
. However this was falling down due to needing to construct such a scale on each interpolator call.
Doing the interpolation explicitly fixed the issue and is easy enough, but it felt a little counter to the intended style. After noticing d3-interpolate
I hoped I would find such a function interpolator (homotopy) constructor, but no dice.
Could it be added?
Context: I'm using a transition for changing the "translate"
transform function of some elements. Sometimes the position doesn't change, so the transition goes from foo
to the same foo
.
However, I've notice that when transitioning from "translate(0,0)"
to "translate(0,0)"
the interpolator returns an empty string. The interpolator in question is d3.interpolateTransformSvg
.
For instance:
d3.interpolateTransformSvg("translate(1,1)","translate(0,0)")(1);
//returns "translate(0,0)", as expected
But:
d3.interpolateTransformSvg("translate(0,0)","translate(0,0)")(1);
//returns an empty string ""
Here is a basic demo: https://jsfiddle.net/bvudenyq/
I'm not sure if the issue is in d3.interpolateTransformSvg
itself or in the parse.js file. The solution is quite simple, in my case I'm just using attrTween
with d3.interpolateString
as the interpolator, ditching d3.interpolateTransformSvg
, but I thought I should mention this bug.
Thanks,
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.