Skip to content

Scales

Scales map data values to visual values, turning a number like 42 into a pixel position, a color, or a band width. They are the bridge between your data domain and the visual range on screen — the piece of a data visualization that sits between the numbers and the geometry.

Fourteen scale factories ship with @ripl/core: scaleContinuous, scaleBand, scalePoint, scaleDiscrete, scaleOrdinal, scaleDiverging, scaleLogarithmic (aliased scaleLog), scaleSymlog, scalePower (with a scaleSqrt shorthand), scaleRadial, scaleQuantile, scaleQuantize, scaleThreshold and scaleTime.

Every scale is a callable function: pass a domain value in, get a range value out. Scales also expose inverse (reverse mapping), ticks (nice axis values), includes (domain membership), and the original domain/range arrays.

NOTE

For the full API, see the Scales API Reference.

Demo

Use the controls below to explore different scale types. The scale maps a domain value (bottom axis) to a range value (left axis).

Continuous

The most common scale. Maps a numeric domain linearly to a numeric range.

ts
import {
    scaleContinuous,
} from '@ripl/web';

const x = scaleContinuous([0, 100], [0, 800]);

x(0); // 0
x(50); // 400
x(100); // 800

x.inverse(400); // 50
x.ticks(5); // [0, 25, 50, 75, 100]

Options: clamp (constrain output to range), padToTicks (extend domain to nice tick boundaries), and nice (expand the domain to round, tick-aligned boundaries at construction; true targets ~10 ticks, or pass a number). nice is a construction-time option by design, so scales stay plain callable objects with no chained .nice() method:

ts
const y = scaleContinuous([3, 97], [400, 0], { nice: true });

y.domain; // [0, 100], snapped to round boundaries

Band

Divides a continuous range into evenly spaced bands for categorical data. Exposes bandwidth and step.

ts
import {
    scaleBand,
} from '@ripl/web';

const x = scaleBand(['Jan', 'Feb', 'Mar'], [0, 300], {
    innerPadding: 0.1,
    outerPadding: 0.05,
});

x('Jan'); // band start position
x.bandwidth; // width of each band
x.step; // distance between band starts

Options: innerPadding, outerPadding, alignment, round.

Point

The categorical analogue of a continuous axis: it positions discrete values at evenly spaced points rather than bands (no bandwidth). With zero padding the first and last values sit exactly on the range endpoints. Exposes step, and inverse returns the nearest domain value.

ts
import {
    scalePoint,
} from '@ripl/web';

const x = scalePoint(['Mon', 'Tue', 'Wed', 'Thu'], [0, 300], {
    padding: 0.5,
});

x('Mon'); // first point position
x.step; // distance between adjacent points
x.inverse(105); // nearest domain value, e.g. 'Tue'

Options: padding (space before the first and after the last point, as a fraction of the step) and alignment (0–1).

Discrete

Maps discrete domain values to evenly spaced positions in a numeric range.

ts
import {
    scaleDiscrete,
} from '@ripl/web';

const color = scaleDiscrete(['low', 'mid', 'high'], [0, 100]);

color('low'); // 0
color('mid'); // 50
color('high'); // 100

Ordinal

Maps each distinct domain value to a value from range of any type, cycling when there are more categories than range values. Unknown values encountered later are assigned the next range slot, so a chart can color series without pre-declaring every category. Its most common use is categorical color.

ts
import {
    scaleOrdinal,
} from '@ripl/web';

const color = scaleOrdinal(
    ['apples', 'oranges', 'pears'],
    ['#3a86ff', '#ff006e', '#ffbe0b']
);

color('apples'); // '#3a86ff'
color('pears'); // '#ffbe0b'
color('grapes'); // '#3a86ff', cycling back to the first range value

Diverging

Like continuous, but splits at a midpoint. This is useful for scales that diverge around zero (e.g. temperature anomalies, profit/loss).

ts
import {
    scaleDiverging,
} from '@ripl/web';

const x = scaleDiverging([-100, 100], [0, 800], {
    midpoint: 0,
});

x(-100); // 0
x(0); // 400
x(100); // 800

Logarithmic

Maps values using a log transformation. Useful for data spanning several orders of magnitude.

ts
import {
    scaleLog,
    scaleLogarithmic,
} from '@ripl/web';

const x = scaleLogarithmic([1, 1000], [0, 600], { base: 10 });

x(1); // 0
x(10); // 200
x(100); // 400
x(1000); // 600

// scaleLog is a shortcut for base-10
const y = scaleLog([1, 1000], [0, 600]);

Symmetric Log

A log scale that also handles zero and negative values. It stays approximately linear within a threshold constant (default 1) of zero and compresses logarithmically beyond it, so, unlike a plain log scale, its domain can cross zero. A domain symmetric about zero places zero at the range midpoint.

ts
import {
    scaleSymlog,
} from '@ripl/web';

const x = scaleSymlog([-100, 100], [0, 400]);

x(-100); // 0
x(0); // 200, zero sits at the midpoint
x(100); // 400

x.inverse(200); // 0

// A larger constant widens the near-zero linear region
const y = scaleSymlog([-1000, 1000], [0, 400], { constant: 10 });

Power

Maps values using an exponential transformation. exponent: 2 gives a quadratic curve, exponent: 0.5 gives a square root curve.

ts
import {
    scalePower,
    scaleSqrt,
} from '@ripl/web';

const x = scalePower([0, 100], [0, 400], { exponent: 2 });

// scaleSqrt is a shortcut for exponent 0.5
const y = scaleSqrt([0, 100], [0, 400]);

Radial

Maps a numeric magnitude onto a ring radius (typically [innerRadius, outerRadius]) for radial and polar charts. It clamps by default, so a value beyond the domain lands exactly on the outer ring instead of overshooting it, and a single-value domain [max] is treated as [0, max].

ts
import {
    scaleRadial,
} from '@ripl/web';

const radius = scaleRadial([0, 100], [0, 240]);

radius(0); // 0
radius(50); // 120
radius(100); // 240
radius(200); // 240, clamped to the outer ring

radius.inverse(120); // 50

Quantile

Divides a sorted numeric domain into quantiles, each mapped to a discrete range value.

ts
import {
    scaleQuantile,
} from '@ripl/web';

const color = scaleQuantile(
    [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
    ['low', 'medium', 'high']
);

color(15); // 'low'
color(55); // 'medium'
color(95); // 'high'

Quantize

Divides a continuous domain into uniform segments mapped to discrete range values.

ts
import {
    scaleQuantize,
} from '@ripl/web';

const rating = scaleQuantize([0, 100], ['poor', 'fair', 'good', 'excellent']);

rating(20); // 'poor'
rating(40); // 'fair'
rating(60); // 'good'
rating(90); // 'excellent'

Threshold

Maps values to range values based on threshold breakpoints.

ts
import {
    scaleThreshold,
} from '@ripl/web';

const grade = scaleThreshold([60, 70, 80, 90], ['F', 'D', 'C', 'B', 'A']);

grade(55); // 'F'
grade(65); // 'D'
grade(75); // 'C'
grade(85); // 'B'
grade(95); // 'A'

Time

Maps Date objects to a numeric range using linear interpolation of timestamps.

ts
import {
    scaleTime,
} from '@ripl/web';

const x = scaleTime(
    [new Date('2024-01-01'), new Date('2024-12-31')],
    [0, 800]
);

x(new Date('2024-07-01')); // ~400
x.inverse(400); // ≈ Date('2024-07-01')
x.ticks(6); // 6 calendar-aligned dates

ticks is calendar-aware: it picks the interval (seconds up to years) whose spacing is closest to the requested count, and month/year steps use calendar arithmetic so ticks land on real month and year boundaries rather than fixed millisecond offsets.

Common Scale Properties

Every scale function exposes:

PropertyTypeDescription
domainTDomain[]The input domain
rangeTRange[]The output range
inverse(value)(TRange) → TDomainReverse mapping
ticks(count?)(number?) → TDomain[]Generate nice tick values
includes(value)(TDomain) → booleanTest domain membership