GithubHelp home page GithubHelp logo

d3-interpolate's Introduction

d3-interpolate

This module provides a variety of interpolation methods for blending between two values. Values may be numbers, colors, strings, arrays, or even deeply-nested objects. For example:

const i = d3.interpolateNumber(10, 20);
i(0.0); // 10
i(0.2); // 12
i(0.5); // 15
i(1.0); // 20

The returned function i is called an interpolator. Given a starting value a and an ending value b, it takes a parameter t in the domain [0, 1] and returns the corresponding interpolated value between a and b. An interpolator typically returns a value equivalent to a at t = 0 and a value equivalent to b at t = 1.

You can interpolate more than just numbers. To find the perceptual midpoint between steelblue and brown:

d3.interpolateLab("steelblue", "brown")(0.5); // "rgb(142, 92, 109)"

Here’s a more elaborate example demonstrating type inference used by interpolate:

const i = d3.interpolate({colors: ["red", "blue"]}, {colors: ["white", "black"]});
i(0.0); // {colors: ["rgb(255, 0, 0)", "rgb(0, 0, 255)"]}
i(0.5); // {colors: ["rgb(255, 128, 128)", "rgb(0, 0, 128)"]}
i(1.0); // {colors: ["rgb(255, 255, 255)", "rgb(0, 0, 0)"]}

Note that the generic value interpolator detects not only nested objects and arrays, but also color strings and numbers embedded in strings!

Installing

If you use npm, npm install d3-interpolate. You can also download the latest release on GitHub. For vanilla HTML in modern browsers, import d3-interpolate from Skypack:

<script type="module">

import {interpolateRgb} from "https://cdn.skypack.dev/d3-interpolate@3";

const interpolate = interpolateRgb("steelblue", "brown");

</script>

For legacy environments, you can load d3-interpolate’s UMD bundle from an npm-based CDN such as jsDelivr; a d3 global is exported. (If using color interpolation, also load d3-color.)

<script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-interpolate@3"></script>
<script>

const interpolate = d3.interpolateRgb("steelblue", "brown");

</script>

API Reference

# d3.interpolate(a, b) · Source, Examples

Returns an interpolator between the two arbitrary values a and b. The interpolator implementation is based on the type of the end value b, using the following algorithm:

  1. If b is null, undefined or a boolean, use the constant b.
  2. If b is a number, use interpolateNumber.
  3. If b is a color or a string coercible to a color, use interpolateRgb.
  4. If b is a date, use interpolateDate.
  5. If b is a string, use interpolateString.
  6. If b is a typed array of numbers, use interpolateNumberArray.
  7. If b is a generic array, use interpolateArray.
  8. If b is coercible to a number, use interpolateNumber.
  9. Use interpolateObject.

Based on the chosen interpolator, a is coerced to the suitable corresponding type.

# d3.interpolateNumber(a, b) · Source, Examples

Returns an interpolator between the two numbers a and b. The returned interpolator is equivalent to:

function interpolator(t) {
  return a * (1 - t) + b * t;
}

Caution: avoid interpolating to or from the number zero when the interpolator is used to generate a string. When very small values are stringified, they may be converted to scientific notation, which is an invalid attribute or style property value in older browsers. For example, the number 0.0000001 is converted to the string "1e-7". This is particularly noticeable with interpolating opacity. To avoid scientific notation, start or end the transition at 1e-6: the smallest value that is not stringified in scientific notation.

# d3.interpolateRound(a, b) · Source, Examples

Returns an interpolator between the two numbers a and b; the interpolator is similar to interpolateNumber, except it will round the resulting value to the nearest integer.

# d3.interpolateString(a, b) · Source, Examples

Returns an interpolator between the two strings a and b. The string interpolator finds numbers embedded in a and b, where each number is of the form understood by JavaScript. A few examples of numbers that will be detected within a string: -1, 42, 3.14159, and 6.0221413e+23.

For each number embedded in b, the interpolator will attempt to find a corresponding number in a. If a corresponding number is found, a numeric interpolator is created using interpolateNumber. The remaining parts of the string b are used as a template: the static parts of the string b remain constant for the interpolation, with the interpolated numeric values embedded in the template.

For example, if a is "300 12px sans-serif", and b is "500 36px Comic-Sans", two embedded numbers are found. The remaining static parts (of string b) are a space between the two numbers (" "), and the suffix ("px Comic-Sans"). The result of the interpolator at t = 0.5 is "400 24px Comic-Sans".

# d3.interpolateDate(a, b) · Source, Examples

Returns an interpolator between the two dates a and b.

Note: no defensive copy of the returned date is created; the same Date instance is returned for every evaluation of the interpolator. No copy is made for performance reasons; interpolators are often part of the inner loop of animated transitions.

# d3.interpolateArray(a, b) · Source, Examples

Returns an interpolator between the two arrays a and b. If b is a typed array (e.g., Float64Array), interpolateNumberArray is called instead.

Internally, an array template is created that is the same length as b. For each element in b, if there exists a corresponding element in a, a generic interpolator is created for the two elements using interpolate. If there is no such element, the static value from b is used in the template. Then, for the given parameter t, the template’s embedded interpolators are evaluated. The updated array template is then returned.

For example, if a is the array [0, 1] and b is the array [1, 10, 100], then the result of the interpolator for t = 0.5 is the array [0.5, 5.5, 100].

Note: no defensive copy of the template array is created; modifications of the returned array may adversely affect subsequent evaluation of the interpolator. No copy is made for performance reasons; interpolators are often part of the inner loop of animated transitions.

# d3.interpolateNumberArray(a, b) · Source, Examples

Returns an interpolator between the two arrays of numbers a and b. Internally, an array template is created that is the same type and length as b. For each element in b, if there exists a corresponding element in a, the values are directly interpolated in the array template. If there is no such element, the static value from b is copied. The updated array template is then returned.

Note: For performance reasons, no defensive copy is made of the template array and the arguments a and b; modifications of these arrays may affect subsequent evaluation of the interpolator.

# d3.interpolateObject(a, b) · Source, Examples

Returns an interpolator between the two objects a and b. Internally, an object template is created that has the same properties as b. For each property in b, if there exists a corresponding property in a, a generic interpolator is created for the two elements using interpolate. If there is no such property, the static value from b is used in the template. Then, for the given parameter t, the template's embedded interpolators are evaluated and the updated object template is then returned.

For example, if a is the object {x: 0, y: 1} and b is the object {x: 1, y: 10, z: 100}, the result of the interpolator for t = 0.5 is the object {x: 0.5, y: 5.5, z: 100}.

Object interpolation is particularly useful for dataspace interpolation, where data is interpolated rather than attribute values. For example, you can interpolate an object which describes an arc in a pie chart, and then use d3.arc to compute the new SVG path data.

Note: no defensive copy of the template object is created; modifications of the returned object may adversely affect subsequent evaluation of the interpolator. No copy is made for performance reasons; interpolators are often part of the inner loop of animated transitions.

# d3.interpolateTransformCss(a, b) · Source, Examples

Returns an interpolator between the two 2D CSS transforms represented by a and b. Each transform is decomposed to a standard representation of translate, rotate, x-skew and scale; these component transformations are then interpolated. This behavior is standardized by CSS: see matrix decomposition for animation.

# d3.interpolateTransformSvg(a, b) · Source, Examples

Returns an interpolator between the two 2D SVG transforms represented by a and b. Each transform is decomposed to a standard representation of translate, rotate, x-skew and scale; these component transformations are then interpolated. This behavior is standardized by CSS: see matrix decomposition for animation.

# d3.interpolateZoom(a, b) · Source, Examples

Returns an interpolator between the two views a and b of a two-dimensional plane, based on “Smooth and efficient zooming and panning” by Jarke J. van Wijk and Wim A.A. Nuij. Each view is defined as an array of three numbers: cx, cy and width. The first two coordinates cx, cy represent the center of the viewport; the last coordinate width represents the size of the viewport.

The returned interpolator exposes a duration property which encodes the recommended transition duration in milliseconds. This duration is based on the path length of the curved trajectory through x,y space. If you want a slower or faster transition, multiply this by an arbitrary scale factor (V as described in the original paper).

# interpolateZoom.rho(rho) · Source

Given a zoom interpolator, returns a new zoom interpolator using the specified curvature rho. When rho is close to 0, the interpolator is almost linear. The default curvature is sqrt(2).

# d3.interpolateDiscrete(values) · Source, Examples

Returns a discrete interpolator for the given array of values. The returned interpolator maps t in [0, 1 / n) to values[0], t in [1 / n, 2 / n) to values[1], and so on, where n = values.length. In effect, this is a lightweight quantize scale with a fixed domain of [0, 1].

Sampling

# d3.quantize(interpolator, n) · Source, Examples

Returns n uniformly-spaced samples from the specified interpolator, where n is an integer greater than one. The first sample is always at t = 0, and the last sample is always at t = 1. This can be useful in generating a fixed number of samples from a given interpolator, such as to derive the range of a quantize scale from a continuous interpolator.

Caution: this method will not work with interpolators that do not return defensive copies of their output, such as d3.interpolateArray, d3.interpolateDate and d3.interpolateObject. For those interpolators, you must wrap the interpolator and create a copy for each returned value.

Color Spaces

# d3.interpolateRgb(a, b) · Source, Examples

rgb

Or, with a corrected gamma of 2.2:

rgbGamma

Returns an RGB color space interpolator between the two colors a and b with a configurable gamma. If the gamma is not specified, it defaults to 1.0. The colors a and b need not be in RGB; they will be converted to RGB using d3.rgb. The return value of the interpolator is an RGB string.

# d3.interpolateRgbBasis(colors) · Source, Examples

Returns a uniform nonrational B-spline interpolator through the specified array of colors, which are converted to RGB color space. Implicit control points are generated such that the interpolator returns colors[0] at t = 0 and colors[colors.length - 1] at t = 1. Opacity interpolation is not currently supported. See also d3.interpolateBasis, and see d3-scale-chromatic for examples.

# d3.interpolateRgbBasisClosed(colors) · Source, Examples

Returns a uniform nonrational B-spline interpolator through the specified array of colors, which are converted to RGB color space. The control points are implicitly repeated such that the resulting spline has cyclical C² continuity when repeated around t in [0,1]; this is useful, for example, to create cyclical color scales. Opacity interpolation is not currently supported. See also d3.interpolateBasisClosed, and see d3-scale-chromatic for examples.

# d3.interpolateHsl(a, b) · Source, Examples

hsl

Returns an HSL color space interpolator between the two colors a and b. The colors a and b need not be in HSL; they will be converted to HSL using d3.hsl. If either color’s hue or saturation is NaN, the opposing color’s channel value is used. The shortest path between hues is used. The return value of the interpolator is an RGB string.

# d3.interpolateHslLong(a, b) · Source, Examples

hslLong

Like interpolateHsl, but does not use the shortest path between hues.

# d3.interpolateLab(a, b) · Source, Examples

lab

Returns a CIELAB color space interpolator between the two colors a and b. The colors a and b need not be in CIELAB; they will be converted to CIELAB using d3.lab. The return value of the interpolator is an RGB string.

# d3.interpolateHcl(a, b) · Source, Examples

hcl

Returns a CIELChab color space interpolator between the two colors a and b. The colors a and b need not be in CIELChab; they will be converted to CIELChab using d3.hcl. If either color’s hue or chroma is NaN, the opposing color’s channel value is used. The shortest path between hues is used. The return value of the interpolator is an RGB string.

# d3.interpolateHclLong(a, b) · Source, Examples

hclLong

Like interpolateHcl, but does not use the shortest path between hues.

# d3.interpolateCubehelix(a, b) · Source, Examples

cubehelix

Or, with a gamma of 3.0 to emphasize high-intensity values:

cubehelixGamma

Returns a Cubehelix color space interpolator between the two colors a and b using a configurable gamma. If the gamma is not specified, it defaults to 1.0. The colors a and b need not be in Cubehelix; they will be converted to Cubehelix using d3.cubehelix. If either color’s hue or saturation is NaN, the opposing color’s channel value is used. The shortest path between hues is used. The return value of the interpolator is an RGB string.

# d3.interpolateCubehelixLong(a, b) · Source, Examples

cubehelixLong

Or, with a gamma of 3.0 to emphasize high-intensity values:

cubehelixGammaLong

Like interpolateCubehelix, but does not use the shortest path between hues.

# interpolate.gamma(gamma)

Given that interpolate is one of interpolateRgb, interpolateCubehelix or interpolateCubehelixLong, returns a new interpolator factory of the same type using the specified gamma. For example, to interpolate from purple to orange with a gamma of 2.2 in RGB space:

const interpolator = d3.interpolateRgb.gamma(2.2)("purple", "orange");

See Eric Brasseur’s article, Gamma error in picture scaling, for more on gamma correction.

# d3.interpolateHue(a, b) · Source, Examples

Returns an interpolator between the two hue angles a and b. If either hue is NaN, the opposing value is used. The shortest path between hues is used. The return value of the interpolator is a number in [0, 360).

Splines

Whereas standard interpolators blend from a starting value a at t = 0 to an ending value b at t = 1, spline interpolators smoothly blend multiple input values for t in [0,1] using piecewise polynomial functions. Only cubic uniform nonrational B-splines are currently supported, also known as basis splines.

# d3.interpolateBasis(values) · Source, Examples

Returns a uniform nonrational B-spline interpolator through the specified array of values, which must be numbers. Implicit control points are generated such that the interpolator returns values[0] at t = 0 and values[values.length - 1] at t = 1. See also d3.curveBasis.

# d3.interpolateBasisClosed(values) · Source, Examples

Returns a uniform nonrational B-spline interpolator through the specified array of values, which must be numbers. The control points are implicitly repeated such that the resulting one-dimensional spline has cyclical C² continuity when repeated around t in [0,1]. See also d3.curveBasisClosed.

Piecewise

# d3.piecewise([interpolate, ]values) · Source, Examples

Returns a piecewise interpolator, composing interpolators for each adjacent pair of values. The returned interpolator maps t in [0, 1 / (n - 1)] to interpolate(values[0], values[1]), t in [1 / (n - 1), 2 / (n - 1)] to interpolate(values[1], values[2]), and so on, where n = values.length. In effect, this is a lightweight linear scale. For example, to blend through red, green and blue:

const interpolate = d3.piecewise(d3.interpolateRgb.gamma(2.2), ["red", "green", "blue"]);

If interpolate is not specified, defaults to d3.interpolate.

d3-interpolate's People

Contributors

dependabot[bot] avatar devgru avatar fil avatar gerardofurtado avatar jetzhliu avatar joallard avatar krukon avatar mbostock avatar stefanullinger avatar stof avatar waldyrious avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-interpolate's Issues

Exclude keys from interpolation?

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.

Performance issue: parseCss

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.

pastedgraphic-3

Is this a known issue? I would be interested in helping out with a pull request

d3.interpolateFunction?

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?

Update readme for interpolateTransformSvg

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!

Support BigInt?

  • 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)).

Gamma correct RGB interpolation

Are you considering adding it in the future?

Interpolating RGB values without gamma correct produces darker "dirtier" colors than they should be.

screen shot 2016-01-07 at 11 14 52

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);

Interpolation questions

Are all the interpolations linear?
Is it possible to do easing or even physics-based (e.g. spring based using stiffness and damping)?

parseSvg function crashes in IE

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.

Cubic spline

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:

screen shot 2018-03-03 at 19 31 09

  • Would such a spline interpolator be a good addition to d3-interpolate?
  • Is it called something specific? (I'm a bit lost among the various types of splines...)

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

Interpolation of objects without prototype results in error

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

Recommended way to use D3+plugins with ES6-style imports?

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!)

d3.piecewise?

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"])

Add option to modify ρ value in d3.interpolateZoom

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.interpolateTransform that takes a matrix.

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).

interpolateMonotone

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:

screen shot 2018-04-10 at 13 11 08

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?)

interpolateRgb("invalid", "red")(1) returns black

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.

  • rgb.r
  • rgb.g
  • rgb.b
  • hsl.h
  • hsl.s
  • hsl.l
  • hcl.h
  • hcl.c
  • hcl.l
  • lab.l
  • lab.a
  • lab.b
  • cubehelix.h
  • cubehelix.c
  • cubehelix.l

support for Vec3

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.

Images in readme not loading?

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".

Interpolate transform function lists, not just matrices.

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);
  });

Codepen

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.

interpolateHcl and achromatic colors

Interpolating red and white in Lab and HCL:

screen shot 2018-04-04 at 15 29 10

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:

screen shot 2018-04-04 at 15 35 12

Should interpolateHcl behave the same?

Remove d3.interpolators.

Seems insufficiently useful. Just make the behavior of d3.interpolator fixed. People can create custom interpolators easily enough.

Interpolate typed arrays?

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);
}

Circular dependencies between array.js <-> value.js and object.js <-> value.js

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

Feature request: changing the default color interpolation method

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…

https://observablehq.com/d/7f6b33e509e0e698

object(NaN, {foo: 42})(0.5) throws TypeError

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.

Interpolating opacity.

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.

d3.interpolate should special-case number-like objects?

Related d3/d3#2878. The old behavior of d3.interpolate was:

  1. If b is a color, interpolateRgb is used.
  2. If b is a string, interpolateString is used.
  3. If b is an array, interpolateArray is used.
  4. If b is an object and not coercible to a number, interpolateObject is used.
  5. Otherwise, interpolateNumber is used.

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:

  1. If b is null, undefined or a boolean, use the constant b.
  2. If b is a number, use interpolateNumber.
  3. If b is a color or a string coercible to a color, use interpolateRgb.
  4. If b is a string, use interpolateString.
  5. If b is an array, use interpolateArray.
  6. Use interpolateObject.

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:

  1. After step 5, check if b is a date using instanceof Date, and if so, use a new d3.interpolateDate instead of d3.interpolateObject.
  2. After step 5, check if b is coercible to a number using !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.

Circular dependencies when bundling D3 interpolate 1.3.2:

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

Expose `/transform` helpers/methods

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:

  • Parse the style string into a JSON representation
  • Write a style string from JSON
  • Apply multiple JSON transformations down into 1 representation

Should functions have the prefix “interpolate”?

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.

d3.interpolateTransformSvg returning an empty string

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,

Interpolating arcs can lead to error messages

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?

interpolateNumberArrayRound ?

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)

Add each interpolate as their own file in build

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.

cubehelixDefault and rainbow?

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.

Why b-splines t value need ((t - i / n) * n?

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);
  };
}

Why is skewY missing?

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?

array(undefined, [42]) throws TypeError

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

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.