You have more columns than positions. Where do the rest go? Two positions are spoken for. Every further variable rides a refining channel (color, shape, size, opacity) or the third position, z. Each channel answers one kind of question, and each mapping earns the legend that decodes it.
37.1 Which group is each mark?
A category rides color, and the palette gives each category one hue:
data(gapminder_2007) + point +x(gdp, scale ="log") +y(life) +color(continent) +size(population) +title("The health and wealth of nations, 2007")
(data(gapminder_2007) + point + x(col.gdp, scale ="log") + y(col.life) + color(col.continent) + size(col.population) + title("The health and wealth of nations, 2007"))
data(gapminder_2007) + point +x(:gdp, scale ="log") +y(:life) +color(:continent) +size(:population) +title("The health and wealth of nations, 2007")
plot(data(gapminder_2007), point,x(col.gdp, { scale:"log" }),y(col.life),color(col.continent),size(col.population),title("The health and wealth of nations, 2007"))
Every channel keeps its own rule and its own legend; none of them changed meaning by being combined. Four variables is a sensible ceiling in practice; beyond it, consider panels instead of more channels.
37.5 What if the third variable is a number?
color accepts either kind of column. A numeric column gets a sequential ramp instead of a palette, and a skewed one reads better on a log ramp, exactly like a skewed axis:
The scatter above is a cloud: each flower is its own dot and the table’s order means nothing. Sometimes the order is the whole story. Two gliders circling one thermal are measured every few seconds, and each row is where that glider was next, so the marks should be joined, in the order they were recorded, and the mark that joins rows in table order is path:
plot(data(thermals), path,x(col.east),y(col.north),z(col.altitude),color(col.glider),title("Two gliders, one thermal"))
Each thread rises because the gliders do. Nothing in the sentence draws a spiral: path was told to connect the rows in order, the rows happen to go round while they go up, and the coil is what that is. Watch where the two threads cross, too. Whichever is nearer covers the other, and which one that is changes as they climb, so neither glider is simply drawn on top.
Swing the view and the same route answers a different question, without a single binding changing:
data(thermals) + path +x(east) +y(north) +z(altitude) +color(glider) +space(turn =20, tilt =80) +title("The same two routes, seen from overhead")
(data(thermals) + path + x(col.east) + y(col.north) + z(col.altitude) + color(col.glider) + space(turn =20, tilt =80) + title("The same two routes, seen from overhead"))
data(thermals) + path +x(:east) +y(:north) +z(:altitude) +color(:glider) +space(turn =20, tilt =80) +title("The same two routes, seen from overhead")
plot(data(thermals), path,x(col.east),y(col.north),z(col.altitude),color(col.glider),space({ turn:20,tilt:80 }),title("The same two routes, seen from overhead"))
From nearly overhead the climb collapses and you are looking down at two circles. The plot has become a picture of the turn radius, and the reading it hands you is one the first view buried: Bravo is flying a far tighter circle than Alpha, which is why Bravo is also the one climbing faster.
One atom away.path is the only stroke that takes z, and the reason is the one that separates it from line in the plane. A path joins rows in the table’s order, and an order belongs to no axis, so it survives the third dimension untouched. A line sorts by x, and the cube has no left to right: at some angles x runs straight into the page and becomes depth rather than position, which would leave a line sorted by an axis you cannot see. So a rising route is path; line says so:
data(thermals) + line +x(east) +y(north) +z(altitude) +color(glider)
Error:
! gog: `line` reads a *domain* left to right — it sorts by `x` and draws one value for each — and a cube has no left to right: `x` is one of three equal positions, and at some viewing angles it runs into the page and becomes depth. A `line` in space would be sorted by an axis the reader cannot see, so this is refused rather than drawn. For a route through three dimensions use `path`, which is `line` with that sort removed: `path + x(<a>) + y(<b>) + z(altitude)`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
37.8 What if the shape needs three dimensions?
quakes_fiji is a thousand earthquakes recorded near Fiji, each with a place on the globe and a depth below it. The reason it is here is that no pair of its columns shows what all three do:
data(quakes_fiji) + point +x(east) +y(north) +z(elevation) +color(magnitude) +palette("viridis") +space(turn =120, tilt =20) +title("A thousand earthquakes, and the plate they sit on")
(data(quakes_fiji) + point + x(col.east) + y(col.north) + z(col.elevation) + color(col.magnitude) + palette("viridis") + space(turn =120, tilt =20) + title("A thousand earthquakes, and the plate they sit on"))
data(quakes_fiji) + point +x(:east) +y(:north) +z(:elevation) +color(:magnitude) +palette("viridis") +space(turn =120, tilt =20) +title("A thousand earthquakes, and the plate they sit on")
plot(data(quakes_fiji), point,x(col.east),y(col.north),z(col.elevation),color(col.magnitude),palette("viridis"),space({ turn:120,tilt:20 }),title("A thousand earthquakes, and the plate they sit on"))
They are not scattered through the volume. They lie on a sheet, tilted, running from shallow near the trench down past 600 km to the west, which is a slab of ocean floor sinking under the plate beside it. That is a shape, and a shape needs three positions to be a shape.
Now cut it into 90 km slices of depth and run them:
The cluster walks down and west as the sequence runs, which is the tilt of that sheet read one slice at a time. The frames are not time here. play advances through the levels of an ordered column, and whether that column measures years or kilometers is the column’s business, not the channel’s. Depth happens to be the one variable a cross-section is usually cut on, so this is the ordinary tomographic slice, spelled with the atom that was designed for animation.
The same slices laid out across the page instead of along the clock, which is the other half of the same split:
Eight cubes, on one shared scale, so the march is a comparison rather than a memory. Which of the two you want is a question about the reader: panels let them look back, frames let them see the movement. Play is where that choice is argued.
37.9 What this section refuses
Glyphs cannot order themselves, so shape refuses a numeric column, and names the channels that do carry magnitude:
data(gapminder_2007) + point +x(gdp) +y(life) +shape(population)
Error:
! gog: `shape(population)` maps a continuous (numeric) column, but `shape` on `point` needs a categorical (text) column. Use `size` or `color` to show a numeric column.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.