Position scales must be declared
Position scales are never created automatically. If you mapx or y, add scale.x() / scale.y(). Miss one and the observations get no position on that axis: the compile succeeds and the chart is blank.
color, size, …) are more forgiving, but declaring them keeps a chart predictable.
Two scales appear on their own when the spec implies them:
- A color scale. With no
colorscale in the spec, the engine appendsscale.color.discrete(), so mapped groups still get the default palette. Under a geom that draws its value as color (geom.tile()), the appended scale is inferred from the mapped column instead, so a numeric column lands on a continuous ramp. - A secondary y scale. As soon as any layer sets
yScaleType: 'secondary', an inferredySecondaryscale is appended if you didn’t declare one.
Inference vs explicit types
Call a position scale bare to infer its type from the mapped column’s value format. Onx, y and ySecondary:
- text → discrete (band) scale
- number / percentage / currency → continuous scale
- date →
datetimescale
A geom’s needs come before the column’s format. Under a bar layer,
scale.x()
resolves to a band and scale.y() to a continuous scale; under a
tile layer, both resolve to bands. A value
axis that bars or areas stand on always holds zero, declared explicitly or
not. A domainMin above zero gives way to it, with an
INCOMPATIBLE_SCALE_DOMAIN warning.INCOMPATIBLE_TYPE. When the column’s type is wrong, declare its format.
UNSUPPORTED_SCALE_TYPE warning. Only its reverse carries over.
A bar’s band applies to the whole plot’s shared x scale, sibling line layers included. With numeric x values, geom.line() plus geom.bar() and scale.x.continuous() resolves to a band scale, with UNSUPPORTED_SCALE_TYPE, and the line uses those bands. A line alone keeps its continuous x scale.
Scales by aesthetic
Categorical color
scale.color.discrete() takes the theme’s palettes.categorical list when a mounted theme has one, else the built-in palette: the multicolor set, or brick tones spread by group count when the geoms touch (stacked bars and areas, pies, tiles). scale.color.discrete({ range }) sets explicit colors in group order and cycles when groups outnumber colors.
Continuous color
Map a numeric column tocolor and the values run through a ramp instead of a set of hues:
range when it names one. Otherwise it is the mounted theme’s sequential list, or its diverging list when domainMid is set and falls inside the domain. With no theme list the scale takes the built-in ramp of that class: light brick to dark brick, or blue through neutral gray to brick with the gray at the midpoint.
ReadonlyArray<number | string>
default:"null"
An explicit ramp of two or more stops. A
range takes precedence over the
theme’s lists.'rgb' | 'lab' | 'hcl' | 'hsl'
default:"lab"
The space the ramp’s stops are interpolated in.
'lab' is perceptually
near-uniform; 'rgb' matches d3’s raw output.boolean
default:"false"
Flips the ramp so the first stop lands on the high end.
number
default:"null"
Maps this data value, usually
0, to the ramp’s midpoint. The scale becomes
diverging only when the value lies strictly inside the domain. Use an odd
number of stops with a neutral color in the middle. Even-length diverging
ramps still render, blending the two central colors and emitting an
EVEN_DIVERGING_RAMP warning.boolean
Symmetrises the domain about
domainMid so equal magnitudes get equal color
intensity. Defaults to true once domainMid is set, and has no effect
without it.Identity scales
An identity scale passes data values straight through:{ size: 10 } becomes 10px. Use it when the data already holds pixel sizes, opacities or stroke widths.
scale.size.continuous() is not identity: it defaults to a sqrt transform, so a value maps to the point’s area, not its radius. Doubling a value doubles the area, which is what the eye reads.
Continuous scale options
The continuous methods (and.log() / .sqrt(), which are continuous with a transform) accept:
number
Pin the lower bound of the domain; the maximum still follows the data.
domainMin: 0 keeps zero on the axis, from the top when the data is all
negative.number
Pin the upper bound of the domain; the minimum still follows the data.
boolean
default:"true"
Extend the domain to round values —
[3, 97] becomes [0, 100].boolean
default:"false"
Reverse the direction of the scale.
'linear' | 'log' | 'sqrt'
default:"linear"
A mathematical transform applied to the scale.
scale.y.log() is shorthand
for transform: 'log'. On size, the default is sqrt. A log scale whose
domain includes zero falls back to linear, with an INCOMPATIBLE_SCALE_DOMAIN
warning.boolean
Pin out-of-domain values to the ends of the range instead of extrapolating
past them. Defaults to
false on x / y / ySecondary and true on every
other aesthetic.[number, number]
The output range for the magnitude aesthetics. Ignored on position aesthetics,
where the range is the panel. Defaults per aesthetic:
size [4, 20],
alpha [0.1, 1], strokeWidth [1, 4]. A continuous color scale takes a
color ramp here instead — see Continuous color.Datetime scale options
scale.x.datetime() and its siblings take epoch-millisecond bounds:
number
Lower bound, in milliseconds since the epoch —
Date.parse('2020-01-01').number
Upper bound, in milliseconds since the epoch.
boolean
default:"false"
Extend the domain to round temporal values. Off by default: rounding to whole
months can stretch or clip the data in ways that read as a bug.
boolean
default:"false"
Reverse the direction of the scale.
boolean
default:"false"
Pin out-of-domain instants to the ends of the range.
Discrete scale options
Array<string | number>
The band values, in display order. Omit to take them from the data.
Array<string | number>
Explicit output values — colors, sizes, or dash patterns — aligned to
domain.number
default:"0.1"
The gap between bands as a fraction of the band step,
0–1. Inner padding is
padding; outer padding is padding / 2. Under
coord.polar every discrete scale’s
padding is forced to 0, so pie wedges meet with no gap. geom.tile()
defaults its position scales to 0 for the same reason; an explicit padding
still applies.boolean
default:"false"
Reverse the band order — the first domain entry maps to the end of the range.
Defaults to
true on the cross axis of a geom banded on both axes, so a
heatmap reads first value at the top, like a
matrix.Next
- Coordinate systems — the plane scales place values into
- Chart types — scales at work in each recipe

