| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| Afghanistan | Asia | 2007 | 43.828 | 31889923 | 974.5803 |
| Albania | Europe | 2007 | 76.423 | 3600523 | 5937.0295 |
| Algeria | Africa | 2007 | 72.301 | 33333216 | 6223.3675 |
| Angola | Africa | 2007 | 42.731 | 12420476 | 4797.2313 |
| Argentina | Americas | 2007 | 75.320 | 40301927 | 12779.3796 |
29 Composition
How do you put two different plots in one figure? Faceting cannot do it: every panel it draws comes from one plot and one sentence. Composition takes plots that were never one plot and places them in one figure. Wilkinson separates the two, noting that “some tabled graphics are really two or more graphics glued together” (Wilkinson, 2005). gog separates them too, with the operators you have already learned. | places two things side by side, and / places one above the other, whether those things are panels or whole plots:
plot | facet(continent): one plot, split by a column, panels side by sideplot / facet(continent): one plot, split by a column, panels one above the otherplot_a | plot_b: two plots, arranged side by sideplot_a / plot_b: two plots, one above the other
A facet splits one plot by a column of its data. An arrangement places finished plots side by side or one above the other, and says nothing about any plot’s data. | and / place things the same way in both cases. What you write after the operator decides what is placed: a facet() splits the plot into panels, and a second plot makes an arrangement.
29.1 Two plots, one arrangement
Two views of one table often belong side by side, even when they share no axis. Describe each plot in full, inside its own parentheses, then put | between them:
(data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent)))(data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)))“Given gapminder 2007: points, x is gdp, y is life, beside bars derived by count, x is continent.”
The two plots measure entirely different things: a country’s income against its life expectancy, and how many countries each continent holds. They share nothing, not a scale, not an axis, not a legend, because there is nothing to share. Each plot is drawn in a cell, its own part of the arrangement. The two cells are equal, and a narrow gap between them leaves each a little under half the width. Inside each cell, a panel holds the marks, and the axes and their labels take the rest. The arrangement does align the two panels at the top and the bottom.
/ places the same two plots one above the other. Each one now has the full width of the arrangement:
(data(gapminder_2007) + point + x(gdp) + y(life)) /
(data(gapminder_2007) + bar * count + x(continent))((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) /
(data(gapminder_2007) + bar * count + x(col.continent)))(data(gapminder_2007) + point + x(:gdp) + y(:life)) /
(data(gapminder_2007) + bar * count + x(:continent))below(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)))“Given gapminder 2007: points, x is gdp, y is life, above bars derived by count, x is continent.”
One above the other, the two panels align at the left and the right, even when one plot’s tick labels are wider.
Here the parentheses are required. / groups before +, so without them / would join only y(life) and the second data(), and gog would refuse that sentence. With |, gog draws the arrangement without them in R and Python, but refuses it in Julia. So whenever this book composes plots, each plot goes inside its own parentheses, whichever operator joins them. Parentheses in an arrangement shows what each language’s order does to an arrangement.
Both operators take arrangements as well as plots, so one arrangement can hold another. (a | b) / c is a row of two plots above a third. The marginal plot later in this chapter is built the other way: one plot above a row of two.
In R, Python and Julia, / groups before |, so a | b / c reads as a | (b / c). Write the parentheses whenever you mix the two operators. JavaScript spells the operators as words, so there is no order to remember. The JavaScript chapter shows those words.
29.3 The marginal plot
A scatter shows how two columns vary together, but not how the rows spread along each one alone. Go back to the histogram that shared the scatter’s gdp axis. That histogram is a marginal: one column’s distribution, drawn along the edge of the plot that uses the column. Put a second marginal on the right and the picture is the marginal plot, which most tools offer as a named chart type:
(data(gapminder_2007) + bar * bin + x(gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * bin + y(life) + theme(width = 130)))((data(gapminder_2007) + bar * bin + x(col.gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * bin + y(col.life) + theme(width = 130))))(data(gapminder_2007) + bar * bin + x(:gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * bin + y(:life) + theme(width = 130)))below(plot(data(gapminder_2007), layer(bar, bin), x(col.gdp),
theme({ height: 130 })),
beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, bin), y(col.life),
theme({ width: 130 }))))“Given gapminder 2007: bars derived by bin, x is gdp; above points, x is gdp, y is life, beside bars derived by bin, y is life.”
That figure is built from three ordinary plots and two operators. The top histogram shares gdp with the scatter, so its panel is drawn exactly as wide as the scatter’s. The right-hand one shares life, so its panel is exactly as tall as the scatter’s, and the life axis is drawn once, on the left. Nothing in the sentence draws the empty top-right corner. Both marginals stop where the scatter’s panel stops, and the corner is the room left over.
The marginals need no special rule either. bar * bin + y(life) is the histogram you already know. Its bars run left to right, because y now carries the bins and the count goes on x. A rule from A column two plots share also applies: a plot that does not draw an axis reserves no room for it. The scatter draws both shared axes, so only the gap between the plots separates each marginal from it.
A density curve can be drawn above the scatter instead of a histogram. The curve continues past the smallest and the largest gdp until it is nearly zero at both ends. Drawn alone, the curve’s gdp axis therefore starts below zero, where no country is. If those ends set the shared axis, the scatter’s axis would also start near −12,000, with an empty band on its left. Above the scatter, the shared axis covers only the rows of both plots, not the curve’s tails. The scatter’s axis is then the same as when it is drawn alone. The curve is cut to fit it, from the smallest to the largest gdp in the table:
(data(gapminder_2007) + line * density + x(gdp) + theme(height = 130)) /
(data(gapminder_2007) + point + x(gdp) + y(life))((data(gapminder_2007) + line * density + x(col.gdp) + theme(height = 130)) /
(data(gapminder_2007) + point + x(col.gdp) + y(col.life)))(data(gapminder_2007) + line * density + x(:gdp) + theme(height = 130)) /
(data(gapminder_2007) + point + x(:gdp) + y(:life))below(plot(data(gapminder_2007), layer(line, density), x(col.gdp),
theme({ height: 130 })),
plot(data(gapminder_2007), point, x(col.gdp), y(col.life)))“Given gapminder 2007: a line derived by density, x is gdp, above points, x is gdp, y is life.”
The curve starts high, at the poorest country, because it is cut there rather than falling to zero.
29.4 The scatterplot matrix
How do three columns vary together, when you want to see every pair in one figure? A scatterplot matrix answers with a grid of scatters, one for each pair of columns. Each pair appears twice, once on each side of the diagonal, with x and y swapped. The grid takes life, gdp and population in that order, from left to right and from top to bottom. Each plot puts its grid column’s name on x and its grid row’s name on y. On the diagonal, a plot would put one column on both x and y, which draws only a straight line. So the diagonal holds each column’s histogram, the picture the marginal plot drew along its edges. The figure is nine ordinary plots, three in each grid row, joined by |, with the three grid rows joined by /:
((data(gapminder_2007) + bar * bin + x(life)) |
(data(gapminder_2007) + point + x(gdp, scale = "log") + y(life)) |
(data(gapminder_2007) + point + x(population, scale = "log") + y(life))) /
((data(gapminder_2007) + point + x(life) + y(gdp, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(gdp, scale = "log")) |
(data(gapminder_2007) + point + x(population, scale = "log") +
y(gdp, scale = "log"))) /
((data(gapminder_2007) + point + x(life) + y(population, scale = "log")) |
(data(gapminder_2007) + point + x(gdp, scale = "log") +
y(population, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(population, scale = "log")))(((data(gapminder_2007) + bar * bin + x(col.life)) |
(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life)) |
(data(gapminder_2007) + point + x(col.population, scale = "log") + y(col.life))) /
((data(gapminder_2007) + point + x(col.life) + y(col.gdp, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(col.gdp, scale = "log")) |
(data(gapminder_2007) + point + x(col.population, scale = "log") +
y(col.gdp, scale = "log"))) /
((data(gapminder_2007) + point + x(col.life) + y(col.population, scale = "log")) |
(data(gapminder_2007) + point + x(col.gdp, scale = "log") +
y(col.population, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(col.population, scale = "log"))))((data(gapminder_2007) + bar * bin + x(:life)) |
(data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life)) |
(data(gapminder_2007) + point + x(:population, scale = "log") +
y(:life))) /
((data(gapminder_2007) + point + x(:life) + y(:gdp, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(:gdp, scale = "log")) |
(data(gapminder_2007) + point + x(:population, scale = "log") +
y(:gdp, scale = "log"))) /
((data(gapminder_2007) + point + x(:life) +
y(:population, scale = "log")) |
(data(gapminder_2007) + point + x(:gdp, scale = "log") +
y(:population, scale = "log")) |
(data(gapminder_2007) + bar * bin + x(:population, scale = "log")))below(below(beside(beside(plot(data(gapminder_2007), layer(bar, bin),
x(col.life)),
plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life))),
plot(data(gapminder_2007), point, x(col.population, { scale: "log" }),
y(col.life))),
beside(beside(plot(data(gapminder_2007), point, x(col.life),
y(col.gdp, { scale: "log" })),
plot(data(gapminder_2007), layer(bar, bin),
x(col.gdp, { scale: "log" }))),
plot(data(gapminder_2007), point, x(col.population, { scale: "log" }),
y(col.gdp, { scale: "log" })))),
beside(beside(plot(data(gapminder_2007), point, x(col.life),
y(col.population, { scale: "log" })),
plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.population, { scale: "log" }))),
plot(data(gapminder_2007), layer(bar, bin),
x(col.population, { scale: "log" }))))The shared-axis rule decides which axes are drawn. Every plot in a grid column names the same x, so that axis is drawn once, along the bottom. In each grid row, the two scatters name the same y, so they share one scale, and one value is at the same height in both. population runs from about 200,000 people to 1.3 billion, and gdp from about 280 dollars to 49,000, so both are read on a log scale. Every plot that names one of them writes scale = "log", because a column read two ways gets two axes.
Each histogram puts the count on y, which no other plot in its grid row does, so it draws its own Count axis. In the top row the histogram is the leftmost plot, and its y is the count, not life. So the life axis is drawn on the left of the middle plot, the first plot in that row with life on y.
The two plots of gdp and life show the clearest pattern: people in richer countries live longer. population shows almost no pattern with either of the other two, so a country’s size tells you little about its income or its life expectancy.
Many tools offer this figure as a function of its own. In gog it is nine ordinary sentences, written with words you already know. Wilkinson counts this figure as a kind of facet (Faceting). gog’s facet() cannot draw it: a facet draws one sentence in every panel, and here the plots read different columns.
29.5 A clustered panel decides the order
A categorical axis needs an order, and most plots take the one the data arrived in. A panel with cluster computes a better one. cluster builds a tree that joins the most similar categories first, and reading its leaves along the axis gives an order that puts similar categories next to each other. When a clustered panel is composed with another, the axis they share takes the order the tree computed.
A shared categorical axis takes the order a clustered panel computed for it.
The other panels need no cluster of their own, and their categories take the tree’s order. gog refuses two panels that derive different orders for one shared axis, because one axis cannot hold two orders. Give both the same columns to cluster(), or cluster in one panel alone. The Cluster chapter uses this rule: a tile plot with a tree on two of its sides, each tree deciding one axis.
29.6 How thin is the marginal histogram? theme(width =, height =)
In the marginal plot, both histograms are thin because each one asks for a small size. The top histogram asks for 130 pixels of height, and the right-hand one for 130 pixels of width. A plot asks for room in pixels with theme(width = ) and theme(height = ). The plots that ask for nothing divide what is left, so the scatter takes the rest.
On a plot drawn alone, the same two arguments set the size of the whole image:
data(gapminder_2007) + point + x(gdp) + y(life) + theme(width = 420, height = 320)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + theme(width = 420, height = 320)data(gapminder_2007) + point + x(:gdp) + y(:life) +
theme(width = 420, height = 320)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
theme({ width: 420, height: 320 }))Alone or in an arrangement, the two arguments mean the same thing, which is Compositional Invariance, the sixth of the nine laws. A plot that asks for 130 pixels of height is 130 pixels tall, whether it is drawn alone or above another plot. Beside another plot it is also 130 pixels tall, and the plot beside it takes the same height.
Do not confuse theme(width =, height =) with theme(ratio = ). The second one shapes the panel inside the room the plot was given, and it never resizes the image. When a plot states a ratio, the arrangement does not align its panel with the other panels.
29.7 How big is the arrangement itself?
An arrangement that states no size is 800 by 600 pixels, the same as a plot that states none. Its plots can decide a different size. Plots one above the other that all ask for a height make the arrangement as tall as their heights plus the gaps. Plots side by side that all ask for a width make it as wide as their widths plus the gaps. Each gap is 20 pixels unless the arrangement sets its own with theme(gap = ), shown at the end of this section.
Plots side by side divide the arrangement’s width and share one height. A height written into one of them is the height of all of them, and of the arrangement. Plots side by side that ask for different heights are refused. Plots one above the other share one width, by the same rule. The marginal plot stays 800 by 600 pixels as a whole, because the scatter asks for nothing.
A plot that asks for nothing still needs room, so the other plots must leave some of the arrangement’s height for it:
(data(gapminder_2007) + point + x(gdp) + y(life) + theme(height = 700)) /
(data(gapminder_2007) + bar * count + x(continent))((data(gapminder_2007) + point + x(col.gdp) + y(col.life) + theme(height = 700)) /
(data(gapminder_2007) + bar * count + x(col.continent)))(data(gapminder_2007) + point + x(:gdp) + y(:life) +
theme(height = 700)) /
(data(gapminder_2007) + bar * count + x(:continent))below(plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
theme({ height: 700 })),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)))Error:
! gog: the plots below each other ask for 700px of height, and the 20px between them makes 720px; the arrangement has 600. A `theme(height = )` on a composed plot is how much of the arrangement that plot takes, so the ones that state it must leave room for the ones that do not.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The bottom plot asks for nothing, so the arrangement keeps its 600 pixels. gog added the top plot’s 700 pixels and the 20-pixel gap, and compared 720 with those 600.
When the plots do not decide the arrangement’s size, you can choose it: give the arrangement its own theme(). Put parentheses around the whole arrangement before you add theme() to it. In R and Python, + groups before |, so without them the theme() would go to the second plot instead of the arrangement. Julia would give it to the arrangement, so the parentheses also make the sentence mean the same thing in every language:
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))) + theme(height = 260)(((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent))) + theme(height = 260))((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))) +
theme(height = 260)beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)),
theme({ height: 260 }))The arrangement is now 260 pixels tall instead of 600, and both plots take that full height. An arrangement takes the same two size arguments as a plot.
In this sentence the outer parentheses happen to change nothing. Without them, R and Python give theme(height = 260) to the second plot, but plots side by side always share one height, so the arrangement is still 260 pixels tall. With a width, the parentheses matter. (plot_a | plot_b) + theme(width = 500) makes the whole arrangement 500 pixels wide. Without the parentheses, the arrangement stays 800 pixels wide: the second plot takes 500 of them, and the first plot is squeezed into the other 280.
Sizing the arrangement matters most when a plot cannot be stretched to fill its cell. A cube is drawn with one scale in both screen directions, so its shape is never stretched. In a tall, narrow cell the cube is therefore drawn small, with an empty band above it and another below it. The figure is then the wrong shape, and you correct it by sizing the arrangement.
An arrangement can also set the space between its plots. theme(gap = ) takes a number of pixels, 0 or more. Written on the outermost arrangement, it also sets the gaps inside each arrangement nested in it, unless that arrangement sets its own. Here the marginal plot’s histograms are drawn closer to the scatter than the default 20 pixels:
((data(gapminder_2007) + bar * bin + x(gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * bin + y(life) + theme(width = 130)))) +
theme(gap = 4)(((data(gapminder_2007) + bar * bin + x(col.gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * bin + y(col.life) + theme(width = 130)))) +
theme(gap = 4))((data(gapminder_2007) + bar * bin + x(:gdp) + theme(height = 130)) /
((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * bin + y(:life) + theme(width = 130)))) +
theme(gap = 4)below(plot(data(gapminder_2007), layer(bar, bin), x(col.gdp),
theme({ height: 130 })),
beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, bin), y(col.life),
theme({ width: 130 }))), theme({ gap: 4 }))An arrangement takes three settings and no others: width, height and gap. Every other property theme() sets belongs to one plot: its gridlines, its frame, its background, the angle of its tick labels. An arrangement is not a plot, so each of those properties is refused, and the refusal says to write it into the plot it describes:
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))) + theme(grid = "none")(((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent))) + theme(grid = "none"))((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))) +
theme(grid = "none")beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)),
theme({ grid: "none" }))Error:
! gog: `theme(grid = )` describes a panel, and an arrangement is plots placed together rather than a panel of its own. On an arrangement, `theme()` states how big the figure is and how far apart its plots are — `theme(width = )`, `theme(height = )` and `theme(gap = )` — and nothing else. Write this into the plot it describes, before composing: `(plot + theme(grid = )) | other_plot`.
29.8 What an arrangement is not
A title over two plots seems to belong to the arrangement rather than to either plot, so you might try writing it there. An arrangement only places plots, so anything belonging to one plot is written into that plot before it is composed. An arrangement accepts theme(width = ), theme(height = ) and theme(gap = ). Every other atom added to an arrangement is refused, and the message names where it belongs:
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))) + title("Two views")(((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent))) + title("Two views"))((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))) +
title("Two views")beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)),
title("Two views"))Error:
! gog: `title()` belongs to a plot, and the left side is an arrangement of them. Write it into the plot it describes, before composing: `(plot + title("...")) | other_plot`.
gog draws no title over a whole arrangement. In an arrangement of plots one above the other, the top plot’s title is above everything else, and the bottom plot’s caption is below everything else. When the plots are side by side, write the title in your document, as a line of text above the figure.
And an arrangement cannot be faceted, because a facet splits one plot by a column and an arrangement is not one plot:
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))) | facet(continent)(((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent))) | facet(col.continent))((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))) |
facet(:continent)Error:
! gog: `|` faceted an arrangement of plots, and a facet splits *one* plot by a column. Facet the plots before composing them: `(plot | facet(g)) | other_plot`.
Facet first, then compose: (plot | facet(g)) | other_plot is a faceted plot beside another plot, and it is legal.
29.9 Where these operators come from
You have now seen | mean two things. In plot | facet(continent) it splits one plot into panels, and in plot_a | plot_b it puts two plots side by side. Neither meaning is this grammar’s invention, and the two were borrowed from different places.
Splitting a plot into panels is the older use of |. Trellis display wrote a conditioned plot as y ~ x | g (Becker et al., 1996), and lattice writes it that way still. There | means “given”, the same meaning it has in conditional probability. That is exactly what plot | facet(continent) says.
Putting plots side by side with | comes from patchwork (Pedersen, 2019). That package writes p1 | p2 for side by side and p1 / p2 for one above the other. gog spells composition the same way on purpose. A reader who already uses that package already knows both operators. A different spelling for an idea that already has one would only be one more thing to learn.
Composing pictures with operators is older than both of them. Henderson’s Functional Geometry (Henderson, 1982) describes an algebra of pictures, and it places pictures with beside and above. The textbook Structure and Interpretation of Computer Programs teaches the same picture language. Haskell’s diagrams library spells the two operations ||| and ===. The JavaScript binding has no operators to overload, so it spells the two operations as words: beside(), and below() for what Henderson called above. His above(a, b) and gog’s below(a, b) draw the same picture, with a on top and b under it.
gog follows Wilkinson in most of its decisions, and this is one place where it does not. His graph algebra also has a /, and there it means nesting (a/b, “a within b”), not one plot above another. That nesting is not gog’s nest() atom, which divides one panel into areas sized by a measure. gog’s facet operators always cross and never nest, as the Faceting chapter explains, so gog can use / to place one plot above another.
29.10 Layering, faceting, or composition?
Layering, faceting and composition answer different questions, so your data decides which one you want:
- Layering. The layers draw different tables that measure the same thing. One coordinate space, one pair of axes, and the second table annotates the first: a threshold, a band, a note, a forecast. Data shows how to add a second table.
- Faceting. The panels show the same measurement for different subsets. One scale, one legend, and you can compare the panels directly.
- Composition. The plots show different measurements that a reader should see in one figure. Each keeps its own space, and the only thing they may share is a column.
In layering, the engine cannot check that a second table measures what the axes measure, so you have to check it yourself. A second table that measures something else still draws when you layer it: gog finds every column, draws every mark, and the picture is wrong. A sentence that puts sales figures on an income axis is legal, and gog draws it. So if the second table does not measure what the axes measure, compose two plots instead of layering one onto the other.
A marginal plot is composition because the histogram and the scatter measure different things: one counts countries, the other puts each country at its own position. They share gdp, and that one shared column is what the shared-axis rule turns into a single axis.
29.11 In short
| and / place the panels of one plot after a facet(), and whole plots in an arrangement. Composed plots that read the same column the same way, on the same axis, share one scale. On the axis the arrangement aligns, that scale is one axis, drawn once. A column read two ways gets two axes. An arrangement draws no mark of its own, and takes only its size and the gap between its plots.
29.12 What comes next
Composition places finished plots side by side or one above the other, and none of those plots shows where each part of a total went. Flow draws that: it carries each amount through a series of stages, read from the table’s own columns.