Advanced mathematical interpolation including B-splines, smooth zoom transitions, and piecewise interpolation.
Uniform nonrational B-spline interpolation through arrays of numbers.
/**
* Returns a uniform nonrational B-spline interpolator through an array of numbers.
* Returns values[0] at t=0 and values[values.length-1] at t=1.
* @param values - Array of numbers to interpolate through smoothly
* @returns Interpolator function returning smoothly interpolated number
*/
function interpolateBasis(values: number[]): (t: number) => number;Usage Examples:
import { interpolateBasis } from "d3-interpolate";
// Smooth curve through control points
const spline = interpolateBasis([0, 10, 5, 15, 8]);
console.log(spline(0.0)); // 0 (start value)
console.log(spline(0.25)); // ~6.5 (smooth interpolation)
console.log(spline(0.5)); // ~8.1 (smooth interpolation)
console.log(spline(0.75)); // ~10.8 (smooth interpolation)
console.log(spline(1.0)); // 8 (end value)
// Animation easing curve
const easing = interpolateBasis([0, 0.1, 0.9, 1]);
console.log(easing(0.5)); // Smooth S-curve transition
// Smooth data interpolation
const temperatures = interpolateBasis([20, 25, 30, 28, 22]);
for (let i = 0; i <= 10; i++) {
console.log(`t=${i/10}: ${temperatures(i/10).toFixed(1)}°C`);
}Cyclical B-spline with continuous derivatives at boundaries.
/**
* Returns a uniform nonrational B-spline interpolator with cyclical continuity.
* Control points are repeated such that the spline has cyclical C² continuity.
* @param values - Array of numbers to interpolate through cyclically
* @returns Interpolator function returning smoothly interpolated number
*/
function interpolateBasisClosed(values: number[]): (t: number) => number;Usage Examples:
import { interpolateBasisClosed } from "d3-interpolate";
// Smooth cyclical animation
const cyclical = interpolateBasisClosed([0, 10, 5]);
console.log(cyclical(0.0)); // 0
console.log(cyclical(0.33)); // ~10 (smooth transition)
console.log(cyclical(0.66)); // ~5 (smooth transition)
console.log(cyclical(1.0)); // 0 (back to start, continuous)
// Periodic motion (like sine wave approximation)
const wave = interpolateBasisClosed([0, 1, 0, -1]);
for (let i = 0; i < 8; i++) {
console.log(`Phase ${i}: ${wave(i/8).toFixed(2)}`);
}
// Smooth color channel cycling
const redChannel = interpolateBasisClosed([128, 255, 200, 100]);Smooth zooming and panning transitions using van Wijk and Nuij's algorithm.
/**
* Returns an interpolator for smooth zoom/pan transitions between two views.
* Each view is [cx, cy, width] where cx,cy is center and width is viewport size.
* @param a - Starting view [centerX, centerY, width]
* @param b - Ending view [centerX, centerY, width]
* @returns Zoom interpolator with duration property
*/
function interpolateZoom(a: [number, number, number], b: [number, number, number]): ZoomInterpolator;
interface ZoomInterpolator extends ((t: number) => [number, number, number]) {
/** Recommended transition duration in milliseconds based on path length */
duration: number;
/** Configure curvature parameter rho (default: sqrt(2)) */
rho(rho: number): ZoomInterpolator;
}Features:
Usage Examples:
import { interpolateZoom } from "d3-interpolate";
// Basic zoom transition
const zoomInterp = interpolateZoom(
[0, 0, 10], // Start: center at origin, width 10
[100, 50, 2] // End: center at (100,50), width 2 (zoomed in)
);
console.log(zoomInterp(0.0)); // [0, 0, 10]
console.log(zoomInterp(0.5)); // [~50, ~25, ~4.5] (smooth curve)
console.log(zoomInterp(1.0)); // [100, 50, 2]
// Use recommended duration for animation
console.log(`Recommended duration: ${zoomInterp.duration}ms`);
// Configure curvature (rho close to 0 = more linear)
const linearZoom = interpolateZoom([0, 0, 100], [200, 100, 1]).rho(0.1);
const curvedZoom = interpolateZoom([0, 0, 100], [200, 100, 1]).rho(5);
// Map-style zoom transition
const mapZoom = interpolateZoom(
[0, 0, 1000], // Zoomed out view
[500, 300, 10] // Zoomed in to specific location
);
console.log(`Map transition duration: ${mapZoom.duration}ms`);
// Zoom out then zoom in (complex path)
const complexZoom = interpolateZoom(
[100, 100, 1], // Very zoomed in
[110, 110, 1] // Slightly different position, same zoom
);
// Creates smooth arc rather than direct lineCompose multiple interpolators for adjacent value pairs.
/**
* Returns a piecewise interpolator composing interpolators for adjacent pairs.
* Maps t in [0, 1/(n-1)] to interpolate(values[0], values[1]), etc.
* @param interpolate - Interpolator function to use (defaults to d3.interpolate)
* @param values - Array of values to interpolate between sequentially
* @returns Interpolator function that transitions through all values
*/
function piecewise(interpolate?: Function, values?: any[]): (t: number) => any;
// Alternative single-argument form
function piecewise(values: any[]): (t: number) => any;Usage Examples:
import { piecewise, interpolateRgb, interpolateNumber } from "d3-interpolate";
// Color progression using RGB interpolation
const colorProg = piecewise(interpolateRgb.gamma(2.2), ["red", "green", "blue"]);
console.log(colorProg(0.0)); // "rgb(255, 0, 0)" (red)
console.log(colorProg(0.25)); // red to green transition
console.log(colorProg(0.5)); // "rgb(0, 255, 0)" (green)
console.log(colorProg(0.75)); // green to blue transition
console.log(colorProg(1.0)); // "rgb(0, 0, 255)" (blue)
// Multi-stage animation values
const animation = piecewise([0, 10, 5, 20]);
console.log(animation(0.0)); // 0
console.log(animation(0.167)); // ~3.3 (0 to 10)
console.log(animation(0.333)); // 10
console.log(animation(0.5)); // ~7.5 (10 to 5)
console.log(animation(0.667)); // 5
console.log(animation(0.833)); // ~12.5 (5 to 20)
console.log(animation(1.0)); // 20
// Custom interpolator for specific types
const stringPiecewise = piecewise(
(a, b) => (t) => t < 0.5 ? a : b, // Snap interpolation
["start", "middle", "end"]
);
console.log(stringPiecewise(0.25)); // "start"
console.log(stringPiecewise(0.5)); // "middle"
console.log(stringPiecewise(0.75)); // "middle"
// Temperature progression through day
const tempCurve = piecewise([
15, // 6am
18, // 9am
25, // 12pm
28, // 3pm
24, // 6pm
18 // 9pm
]);
for (let hour = 0; hour < 24; hour += 3) {
const t = (hour - 6) / 18; // Map 6am-9pm to [0,1]
if (t >= 0 && t <= 1) {
console.log(`${hour}:00 - ${tempCurve(t).toFixed(1)}°C`);
}
}Generate uniformly-spaced samples from any interpolator.
/**
* Returns n uniformly-spaced samples from the specified interpolator.
* First sample at t=0, last sample at t=1.
* @param interpolator - Interpolator function to sample
* @param n - Number of samples (must be >= 2)
* @returns Array of n sampled values
*/
function quantize(interpolator: (t: number) => any, n: number): any[];Caution: Does not work with interpolators that don't return defensive copies (like interpolateArray, interpolateDate, interpolateObject). For those, wrap the interpolator to create copies.
Usage Examples:
import { quantize, interpolateNumber, interpolateRgb, interpolateBasis } from "d3-interpolate";
// Sample numeric interpolation
const numSamples = quantize(interpolateNumber(0, 100), 5);
console.log(numSamples); // [0, 25, 50, 75, 100]
// Color palette generation
const colorPalette = quantize(interpolateRgb("red", "blue"), 7);
console.log(colorPalette);
// ["rgb(255, 0, 0)", "rgb(213, 0, 42)", ..., "rgb(0, 0, 255)"]
// Sample B-spline curve
const curveSamples = quantize(interpolateBasis([0, 10, 2, 8]), 9);
console.log(curveSamples); // [0, ~2.4, ~5.7, ..., 8]
// Generate gradient stops for CSS
const gradient = quantize(interpolateRgb("#ff0000", "#0000ff"), 5);
const css = gradient.map((color, i) => `${color} ${i * 25}%`).join(", ");
console.log(`background: linear-gradient(to right, ${css})`);
// Safe wrapper for object interpolation
const objInterp = (a, b) => (t) => {
const result = interpolateObject(a, b)(t);
return JSON.parse(JSON.stringify(result)); // Deep copy
};
const objSamples = quantize(objInterp({x: 0}, {x: 10}), 3);
console.log(objSamples); // [{x: 0}, {x: 5}, {x: 10}]Based on "Smooth and efficient zooming and panning" by van Wijk and Nuij:
rho parameter controls path shape (default: √2)For n values, maps parameter t:
[0, 1/(n-1)) → interpolate(values[0], values[1])[1/(n-1), 2/(n-1)) → interpolate(values[1], values[2])This creates a lightweight linear scale for sequential value transitions.