# Interpolators

## Non-numeric range

When the range of a continuous scale is not an interval between real numbers, but an array of values of a non-numeric type (such as color codes), the scale must use a function to compute intermediate values between two given values. Such a function is called an interpolator.

The default interpolator is `d3.interpolate(a,b)`. Actually, `d3.interpolate(a,b)` automatically detects the type of the values, by looking at the type of value `b`, and picks the appropriate interpolator for that type. It recognizes color codes, numbers, strings containing numbers, JavaScript date objects, arrays and objects.

Interval `[a,b]` represents the range of the scale. When `d3.interpolate(a,b)` is used on a d3 scale (like in the example below), arguments `a` and `b` are not explicitly given. The range of the scale is automatically passed to arguments `a` and `b` of the interpolator. The arguments `a` and `b` are only explicitly used when the interpolator is used as a "stand-alone" function (see later).

The default interpolator can be changed by using `myScale.interpolate(interpolator)`. The next example calls the default interpolator in three different ways. Scale `d3.scaleLinear` automatically picks `.interpolate(d3.interpolate)` which automatically picks `.interpolate(d3.interpolateRgb)` for color types.

``````
let colorScale;
colorScale = d3.scaleLinear([0, 20], ["Tomato", "Turquoise"]);
// is equivalent to:
colorScale = d3.scaleLinear([0, 20], ["Tomato", "Turquoise"])
.interpolate(d3.interpolate);
// is equivalent to:
colorScale = d3.scaleLinear([0, 20], ["Tomato", "Turquoise"])
.interpolate(d3.interpolateRgb);
``````

Result:

Next example changes the default interpolator to `d3.interpolateHclLong`:

``````
const colorScale = d3.scaleLinear([0, 20], ["Tomato", "Turquoise"])
.interpolate(d3.interpolateHclLong);
d3.select("#container")
.selectAll("span")
.data([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])
.join("span")
.style("background", d => colorScale(d))
``````

Result:

PS. Compare the scale above with the visual representation of the previous color scale that uses default `d3.interpolateRgb`.

In the next example the range is an interval of JavaScript date objects. By default, `d3.interpolate` picks `d3.interpolateDate` for JavaScript date types.

``````
const days = d3.scaleLinear().range([new Date("2009-01-01"), new Date("2009-12-31")]);
console.log(days(0.5)); // logs: Thu Jul 02 2009
``````

Note that if the domain of a scale is not specified, it defaults to `[0, 1]`. The same thing holds for the range.

Note that interpolators maintain the linearity of the linear scale. Interpolators on other kinds of continuous scales also maintain the mapping function of the scale.

## Round ranges

In fact, not only continuous scales with non-numeric ranges, but all scales, including the ones with numeric ranges, use an interpolator to compute intermediate values between two given values. When `d3.interpolate` recognizes a numeric range, it picks the default `d3.interpolateNumber` interpolator for number types:

``````
let numbers;
numbers = d3.scaleLinear().range([20, 60]);
console.log(numbers(0.5)); // logs: 40
// is equivalent to:
numbers = d3.scaleLinear().range([20, 60]).interpolate(d3.interpolate);
console.log(numbers(0.5)); // logs: 40
// is equivalent to:
numbers = d3.scaleLinear().range([20, 60]).interpolate(d3.interpolateNumber);
console.log(numbers(0.5)); // logs: 40
``````

For continuous scales with ranges with number values, the default interpolator `d3.interpolateNumber` can be changed to `d3.interpolateRound`. This rounds the returned values to integers.

``````
let numbers;

numbers = d3.scaleLinear().range([0, 3]).interpolate(d3.interpolateRound);
console.log(numbers(0.6)); // logs: 2
console.log(numbers(0.7)); // logs: 2

// or a shorter version:

numbers = d3.scaleLinear().rangeRound([0, 3]);
console.log(numbers(0.6)); // logs: 2
console.log(numbers(0.7)); // logs: 2
``````

`d3.interpolateRound` maps from a continuous input domain to a discrete output range (integers are discrete). An example with continuous input and discrete output (integers connected to "grades"):

``````
const students = [
{ name: "Joe", score: 35},
{ name: "Abby", score: 86},
{ name: "Casey", score: 44},
{ name: "Max", score: 62}
];

.rangeRound([0, 5]);

d3.select("#container")
.selectAll("span")
.data(students)
.join("span")
.text(d => ` \${d.name} scored a \${["F", "E", "D", "C", "B", "A"][grades(d.score / 100)]}.`); // ["F", "E", "D", "C", "B", "A"][2] === "D"
``````

Result:

BTW: The same thing could have been achieved by using `d3.scaleQuantize` instead of `d3.scaleLinear` (later more about quantize scales).

## String and object ranges

By default, `d3.interpolate` uses `d3.interpolateString` on string valued ranges. The string interpolator finds numbers embedded in the strings and interpolate them. See "Stand alone" interpolators for an example.

For array or object valued ranges, `d3.interpolate` uses `d3.interpolateArray` and `d3.interpolateObject` respectively. If the array or object contains string values, `d3.interpolateString` will be used to interpolate, and if the array or object contains nested arrays of objects, `d3.interpolateArray` or `d3.interpolateObject` will be used recursively. Eventually `d3.interpolateNumber` will be used to interpolate all embedded numbers or embedded values that can be coerced to numbers.

``````
const myScale = d3.scaleLinear()
.range([[0, "0.5 mile", [12]], [10, "28 miles", [36]]]);
console.log(myScale(0.5)); // logs: [ 5, "14.25 miles", [24] ]
``````
``````
const myScale = d3.scaleLinear()
.range([
{ time: 0, distance: "0.5 mile", details: { cost: 12 } },
{ time: 10, distance: "28 miles", details: { cost: 36 } }
]);
console.log(myScale(0.5)); // logs: { time: 5, distance: "14.25 miles", details: { cost: 24 } }
``````

If object `b` in `range([a, b])` contains enumerable properties that are not present in object `a`, then these properties are copied to the resulting interpolated object, without being changed. This provides a way to only interpolate some properties and leave the others unchanged:

``````
const myScale = d3.scaleLinear()
.range([
{ a: 0, c: 12 },
{ a: "10", b: "5 do not change", c: 36 }
]);
console.log(myScale(0.5)); // logs: { b: "5 do not change", a: "5", c: 24 }
``````

The returned interpolated object is not a deep clone and is for every interpolation the same object:

``````
const myScale = d3.scaleLinear()
.range([
{ prop1: 0,  prop2: "0 cats",  prop3: { k: 0 } },
{ prop1: 10, prop2: "10 dogs", prop3: { k: 10 } }
]);

const a = myScale(0.5);
const b = myScale(0.7);
console.log(a.prop3 === b.prop3); // logs: true // (not a deep clone)
console.log(a.prop3); // logs: { k: 7 }
console.log(a === b); // logs: true
console.log(a); // logs: { prop1: 7, prop2: "7 dogs", prop3: { k: 7 } }
``````