Skip to main content
A mapping binds your data columns to aesthetics — the visual properties a chart draws with, like horizontal position, vertical position, and color. It’s the bridge between a row of data and what’s drawn on screen.
This reads: put month on the x-axis, revenue on the y-axis, and give each product its own color. The strings are column keys.

Built-in aesthetics

These are the built-in aesthetics you can map: Not every geom reads every aesthetic — a size mapping means something to geom.point() but nothing to geom.line(). Each geom documents the aesthetics it uses. A custom geom can declare positional channels of its own — a candlestick’s open / high / low / close, say — and the engine trains and scales them exactly like the built-in ones.

What splits data into series

Grouping follows one rule:
  • If group is mapped, it is the only thing that groups. Nothing else splits the data.
  • Otherwise, a series is formed per unique combination of every categorical visual aesthetic that is mapped — color, size, alpha, strokeWidth and lineType. A lone lineType: 'scenario' mapping splits one line into several, with no colour involved.
Positional aesthetics never split series, and a visual aesthetic pointing at the same column as x or y is left out of the grouping — position has already partitioned the data by it. color and group differ in what else they do: color assigns a visual (a hue per category, with a legend) as well as splitting; group only separates the observations. Use group when you want, say, one line per category but a single color for all of them.

Variables and constants

An aesthetic can be bound to a variable (a column, read per row) or a constant (one literal value applied to every observation):
  • 'revenue' — string shorthand for a variable mapping.
  • { variable: 'revenue' } — the explicit variable form.
  • { value: 'red' } — a constant. The value still flows through the aesthetic’s scale, so { value: 'Pro' } on color resolves to whatever color the scale assigns the Pro category.

Where a mapping lives

A mapping can sit at two levels: Spec-level — shared by every layer. Set it with createSpec({ ... }):
Layer-level — merged over the spec-level mapping for one geom only, via a geom’s aes option. This is how combos give each layer a different y:
Layer mappings win where they overlap; anything they don’t set falls through to the spec-level mapping. Two metrics on one chart need more than a second mapping: set yScaleType: 'secondary' on the layer that belongs to the right-hand axis. Without it the layer is positioned against the primary domain, which draws a plausible-looking but wrong chart. The scale.ySecondary() above is optional — the engine adds an inferred secondary scale as soon as a layer asks for one — but declaring it lets you give that axis its own options.
A layer’s aes option is named mapping in the serialized spec: geom.line({ aes: { y: 'profit' } }) becomes { "type": "layer", "geom": "line", "mapping": { "y": "profit" } }. Worth knowing if you hand-write the JSON.

Every mapped position needs a scale

Mapping a column to x or y is only half the story — you must also declare the scale that turns those values into positions. scale.x() and scale.y() are never created automatically.
A mapped position aesthetic with no matching scale.*() produces NaN positions. If a chart renders blank, a missing scale is the first thing to check.

Next

  • Scales — turn mapped values into positions and colors
  • Geoms & layers — which aesthetics each geom reads