or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bins.mdindex.mditerables.mdsearch.mdsequences.mdsets.mdstatistics.mdtransformations.md
tile.json

sequences.mddocs/

Sequences

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.

Capabilities

Range Generation

range

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]

Tick Generation

Functions for generating "nice" tick values for axes and scales.

ticks

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)

tickStep

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)

tickIncrement

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)

Nice Intervals

nice

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 boundaries

Practical Examples

Chart Axis Generation

import { 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
// }

Animation Frames

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

Grid Generation

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

Scale Domain Calculation

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

Color Palette Generation

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']

Logarithmic Sequences

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]

Performance Considerations

  • range: Creates arrays in memory; for large ranges, consider generators or iterative approaches
  • ticks: Optimized for creating nice values; prefer over manual calculation
  • Floating Point: Be aware of IEEE 754 precision issues with fractional steps
  • Memory Usage: Large ranges consume memory; consider lazy evaluation for very large sequences