Functions for generating ranges, tick values, and other numeric sequences commonly used in scales and axes. These utilities are essential for creating evenly-spaced values and "nice" numbers for data visualization.
Returns an array containing an arithmetic progression, similar to Python's built-in range.
/**
* Returns an array containing an arithmetic progression
* @param start - Starting value (defaults to 0 if omitted)
* @param stop - Ending value (exclusive)
* @param step - Step size (defaults to 1)
* @returns Array of numbers in arithmetic progression
*/
function range(start?: number, stop: number, step?: number): number[];
function range(stop: number): number[];Usage Examples:
import { range } from "d3-array";
// Basic usage
range(5); // [0, 1, 2, 3, 4]
range(2, 8); // [2, 3, 4, 5, 6, 7]
range(0, 10, 2); // [0, 2, 4, 6, 8]
// Negative step
range(10, 0, -1); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// Fractional step
range(0, 1, 0.2); // [0, 0.2, 0.4, 0.6000000000000001, 0.8]
// Empty range (invalid parameters)
range(5, 2); // []
range(0, 0); // []Practical Examples:
import { range } from "d3-array";
// Generate array indices
const data = ['a', 'b', 'c', 'd'];
const indices = range(data.length); // [0, 1, 2, 3]
// Create evenly spaced values for interpolation
const steps = range(0, 1, 1/10); // [0, 0.1, 0.2, ..., 0.9]
// Generate coordinates for a grid
const gridX = range(0, 100, 10); // [0, 10, 20, ..., 90]
const gridY = range(0, 50, 5); // [0, 5, 10, ..., 45]Functions for generating "nice" tick values for axes and scales.
Returns an array of approximately count + 1 uniformly-spaced, nicely-rounded values.
/**
* Returns nicely-rounded, uniformly-spaced tick values
* @param start - Starting value (inclusive)
* @param stop - Ending value (inclusive)
* @param count - Approximate number of intervals (actual ticks = count + 1)
* @returns Array of nice tick values
*/
function ticks(start: number, stop: number, count: number): number[];Usage Examples:
import { ticks } from "d3-array";
ticks(0, 10, 5); // [0, 2, 4, 6, 8, 10] (6 ticks, 5 intervals)
ticks(0, 1, 10); // [0.0, 0.1, 0.2, ..., 1.0] (11 ticks)
ticks(-5, 5, 5); // [-4, -2, 0, 2, 4] (5 ticks)
// Handles edge cases gracefully
ticks(0, 0, 5); // [0] (single point)
ticks(10, 0, 5); // [] (invalid range)Returns the difference between adjacent tick values if the same arguments were passed to ticks.
/**
* Returns the step size between adjacent tick values
* @param start - Starting value
* @param stop - Ending value
* @param count - Approximate number of intervals
* @returns The step size between ticks
*/
function tickStep(start: number, stop: number, count: number): number;Usage Examples:
import { tickStep } from "d3-array";
tickStep(0, 10, 5); // 2 (ticks at 0, 2, 4, 6, 8, 10)
tickStep(0, 1, 10); // 0.1 (ticks at 0.0, 0.1, 0.2, etc.)
tickStep(0, 100, 4); // 25 (ticks at 0, 25, 50, 75, 100)Like tickStep, but guarantees that the result is an integer when the step would be less than one.
/**
* Like tickStep, but returns negative inverse if step would be < 1
* @param start - Starting value (must be <= stop)
* @param stop - Ending value (must be >= start)
* @param count - Approximate number of intervals
* @returns Integer increment (positive or negative)
*/
function tickIncrement(start: number, stop: number, count: number): number;Usage Examples:
import { tickIncrement, tickStep } from "d3-array";
// When step >= 1, same as tickStep
tickIncrement(0, 10, 5); // 2
tickStep(0, 10, 5); // 2
// When step < 1, returns negative inverse
tickIncrement(0, 1, 10); // -10 (instead of 0.1)
tickStep(0, 1, 10); // 0.1
// This ensures integer arithmetic for tick generation
tickIncrement(0, 0.5, 10); // -20 (instead of 0.05)Returns a new interval [niceStart, niceStop] that covers the given interval with nice round numbers.
/**
* Returns a nice interval covering the given range
* @param start - Starting value (must be <= stop)
* @param stop - Ending value (must be >= start)
* @param count - Approximate number of intervals for computing nice values
* @returns [niceStart, niceStop] array with rounded endpoints
*/
function nice(start: number, stop: number, count: number): [number, number];Usage Examples:
import { nice } from "d3-array";
nice(1.3, 8.7, 5); // [0, 10] (extends range to nice round numbers)
nice(0.17, 0.91, 4); // [0, 1] (rounds to unit interval)
nice(23, 87, 3); // [0, 100] (extends to century boundaries)
// Useful for chart axis bounds
const dataRange = [12.3, 97.8];
const niceRange = nice(dataRange[0], dataRange[1], 8);
console.log(niceRange); // [10, 100] or similar nice boundariesimport { extent, ticks, nice } from "d3-array";
function createAxisTicks(data, accessor, tickCount = 8) {
// Get data range
const [min, max] = extent(data, accessor);
// Extend to nice boundaries
const [niceMin, niceMax] = nice(min, max, tickCount);
// Generate nice tick values
const tickValues = ticks(niceMin, niceMax, tickCount);
return {
domain: [niceMin, niceMax],
ticks: tickValues,
step: tickValues[1] - tickValues[0]
};
}
// Usage with data
const salesData = [
{month: 'Jan', sales: 12300},
{month: 'Feb', sales: 15600},
{month: 'Mar', sales: 18900},
// ... more data
];
const yAxis = createAxisTicks(salesData, d => d.sales);
console.log(yAxis);
// {
// domain: [10000, 20000],
// ticks: [10000, 12000, 14000, 16000, 18000, 20000],
// step: 2000
// }import { range } from "d3-array";
function createAnimationFrames(duration, fps = 60) {
const totalFrames = Math.ceil(duration * fps / 1000);
return range(totalFrames).map(frame => ({
frame,
time: (frame / totalFrames) * duration,
progress: frame / (totalFrames - 1)
}));
}
// 2-second animation at 60fps
const frames = createAnimationFrames(2000);
frames.forEach(({frame, time, progress}) => {
console.log(`Frame ${frame}: ${time}ms (${(progress * 100).toFixed(1)}%)`);
});import { range, ticks } from "d3-array";
function generateGrid(width, height, gridSize) {
// Create grid lines at regular intervals
const verticalLines = range(0, width + 1, gridSize);
const horizontalLines = range(0, height + 1, gridSize);
return {
vertical: verticalLines.map(x => ({x1: x, y1: 0, x2: x, y2: height})),
horizontal: horizontalLines.map(y => ({x1: 0, y1: y, x2: width, y2: y}))
};
}
// Alternative with nice grid spacing
function generateNiceGrid(width, height, approximateGridCount = 10) {
const xTicks = ticks(0, width, approximateGridCount);
const yTicks = ticks(0, height, approximateGridCount);
return {
vertical: xTicks.map(x => ({x1: x, y1: 0, x2: x, y2: height})),
horizontal: yTicks.map(y => ({x1: 0, y1: y, x2: width, y2: y}))
};
}import { extent, nice, ticks } from "d3-array";
class LinearScale {
constructor(domain, range) {
this.domain(domain);
this.range(range);
}
domain(domain) {
if (!domain) return this._domain;
this._domain = [...domain];
return this;
}
range(range) {
if (!range) return this._range;
this._range = [...range];
return this;
}
// Create nice domain
nice(count = 10) {
const [min, max] = this._domain;
this._domain = nice(min, max, count);
return this;
}
// Generate tick values
ticks(count = 10) {
const [min, max] = this._domain;
return ticks(min, max, count);
}
// Map value from domain to range
scale(value) {
const [d0, d1] = this._domain;
const [r0, r1] = this._range;
return r0 + (value - d0) * (r1 - r0) / (d1 - d0);
}
}
// Usage
const data = [2.1, 15.7, 8.3, 12.9, 6.4];
const [min, max] = extent(data);
const scale = new LinearScale([min, max], [0, 100])
.nice(5); // Make domain boundaries nice
console.log('Domain:', scale.domain()); // [0, 20] (nice boundaries)
console.log('Ticks:', scale.ticks(5)); // [0, 5, 10, 15, 20]
// Map data values to screen coordinates
data.forEach(value => {
console.log(`${value} -> ${scale.scale(value)}`);
});import { range } from "d3-array";
function generateColorSteps(startColor, endColor, steps) {
return range(steps).map(i => {
const t = i / (steps - 1);
return interpolateColor(startColor, endColor, t);
});
}
function interpolateColor(color1, color2, t) {
// Simple RGB interpolation
const r1 = parseInt(color1.slice(1, 3), 16);
const g1 = parseInt(color1.slice(3, 5), 16);
const b1 = parseInt(color1.slice(5, 7), 16);
const r2 = parseInt(color2.slice(1, 3), 16);
const g2 = parseInt(color2.slice(3, 5), 16);
const b2 = parseInt(color2.slice(5, 7), 16);
const r = Math.round(r1 + (r2 - r1) * t);
const g = Math.round(g1 + (g2 - g1) * t);
const b = Math.round(b1 + (b2 - b1) * t);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
// Generate gradient colors
const gradient = generateColorSteps('#ff0000', '#0000ff', 10);
console.log(gradient); // ['#ff0000', '#e6001c', ..., '#0000ff']import { range } from "d3-array";
function logRange(start, stop, base = 10) {
const logStart = Math.log(start) / Math.log(base);
const logStop = Math.log(stop) / Math.log(base);
const steps = Math.floor(logStop - logStart) + 1;
return range(steps).map(i => Math.pow(base, logStart + i));
}
// Generate powers of 10
const powersOf10 = logRange(1, 1000);
console.log(powersOf10); // [1, 10, 100, 1000]
// Generate powers of 2
const powersOf2 = logRange(1, 64, 2);
console.log(powersOf2); // [1, 2, 4, 8, 16, 32, 64]