| year | sales |
|---|---|
| 2019 | 120 |
| 2020 | 135 |
| 2021 | 128 |
| 2022 | 152 |
| 2023 | 168 |
27 Encoding scope
If you write one x() before two marks, which marks use it? Both do. Each word you write in a sentence, such as point, x() or style(), is an atom. Each atom binds to a scope: the whole plot, or one layer. Each mark you write starts a new layer. A layer holds that one mark, plus the atoms bound to the mark. For example, color() and style() are bound to a mark when you write them after it. In point + color(continent) + style(size = 3), both are bound to the point. The x() in the question is written before any mark, so it binds to the whole plot. This chapter gives the rule that decides whether an atom binds to the plot or to one layer.
27.1 The rule
The rule for every atom you write with +, and for facet(), fits in one table. Each row names an atom, the scope it binds to, and whether it matters where you write it.
| Atom | Scope | Depends on where you write it? |
|---|---|---|
x(), y(), z() before any mark |
Plot, every layer | Yes |
x(), y(), z() after a mark |
Layer, the column that mark reads. A layer with no x() of its own reads the first x() in the sentence, and the same holds for y() and z() |
Yes |
the first data() or query() |
Plot, default table | Yes |
each later data() or query() |
Next layer only | Yes |
color(), size(), shape(), pattern(), opacity(), group(), label(), play() before any mark |
Plot, passed to every layer whose mark can take it. A layer that cannot take it is left unchanged, and gog says so (below) |
Yes |
color(), size(), shape(), pattern(), opacity(), group(), label(), play() after a mark |
Layer, that mark alone | Yes |
style() |
Layer, nearest preceding mark; refused when no mark precedes it | Yes |
title(), subtitle(), caption(), x_label(), y_label(), z_label(), palette(), theme(), order(), brush(), and a space: space(), polar(), nest(), map(), globe() or network() |
Plot | No |
facet(), written after | or / |
Plot, the whole plot | No |
In every row marked Yes, where you write the atom decides its scope. Write a channel before any mark and it binds to the plot. Write a channel or style() after a mark and it binds to that mark. data() and query() work differently. The first data() or query() opens the sentence and is the plot’s table. Each later one binds to the mark written directly after it, not to the mark before it. The atoms in the last two rows, marked No, belong to the plot wherever you write them. They are the title, the subtitle, the caption and the axis labels, palette(), theme(), order(), brush(), the spaces, and facet(), which always splits the whole plot.
The positions, x(), y() and z(), need a closer look when they are written after a mark, because this book writes them there in almost every sentence, as in data(gapminder_2007) + point + x(gdp) + y(life). These positions belong to point. They still reach every other layer that names no column of its own for that axis. So in practice they serve the whole plot, until a layer names a column of its own. The next section shows that case with an example.
Where two bindings could apply, the nearest one wins. That is the same rule that lets a data() written between two marks give the next layer its own table in place of the plot’s.
27.2 One axis, and which column each layer reads
The rule above never gives a layer axes of its own. Every layer of a plot shares the same axes. That is what makes it one plot rather than two drawn on top of each other. gog allows no second axis. Each of x, y and z has one scale, one set of ticks and one label. So a second table’s values must be in the same units as the first table’s, such as dollars in both. Values in different units need a plot of their own. Composition covers the choice between layering, faceting, and an arrangement of separate plots.
Sharing an axis is not the same as sharing a column name, and the two are easy to confuse. Almost always you want to bind the positions once, before the marks, so that every layer reads them:
# x and y written once, before the marks: all three marks use them
data(actuals) + x(year) + y(sales) +
bar +
line +
point(data(actuals) + x(col.year) + y(col.sales) +
bar +
line +
point)data(actuals) + x(:year) + y(:sales) + bar + line + pointplot(data(actuals), x(col.year), y(col.sales), bar, line, point)“Given the actuals: x is year, y is sales, bars, and also a line, and also points.”
That sentence has three layers, one for each mark. The plot is not a fourth layer. It holds the table and the positions that all three read, and it draws the frame around them: the axes with their ticks and labels, and a title or a legend when the sentence has one. Only the layers draw the data.
Write a position after a mark, and that mark reads the column you name. No second axis appears. The layer only chooses which of its columns supplies the values. That rule is why the milestones table from Data can keep its own column names:
| 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 |
| at | value | note |
|---|---|---|
| 10000 | 72 | less steep from here |
| 40000 | 80 | almost flat here |
# `milestones` calls its columns `at` and `value`, not `gdp` and `life`
data(gapminder_2007) + point + style(color = "#9e9e9e") + x(gdp) + y(life) +
data(milestones) + text + x(at) + y(value) + label(note) +
style(size = 15, border_size = 8)(data(gapminder_2007) + point + style(color = "#9e9e9e") + x(col.gdp) + y(col.life) +
data(milestones) + text + x(col.at) + y(col.value) + label(col.note) +
style(size = 15, border_size = 8))data(gapminder_2007) + point + style(color = "#9e9e9e") + x(:gdp) +
y(:life) + data(milestones) + text + x(:at) + y(:value) + label(:note) +
style(size = 15, border_size = 8)plot(data(gapminder_2007), point, style({ color: "#9e9e9e" }), x(col.gdp),
y(col.life), data(milestones), text, x(col.at), y(col.value),
label(col.note), style({ size: 15, border_size: 8 }))“Given gapminder 2007: points, x is gdp, y is life; then given the milestones: text, x is at, y is value, label by note, with size 15 and border size 8.”
The two style() settings only make the notes easier to read: the points are gray, and each note has a halo, a band of the background color under its letters (A halo around each label).
Look at the axis labels: they are still Gdp and Life. Each axis is named after the first column written for it, unless you name the axis with x_label() or y_label(). Here those first columns are gdp and life, written after point. The axis belongs to the plot, and only the choice of column belongs to the layer. Without this rule, you would have to rename at and value in milestones to gdp and life. Renaming is easy when you build the table yourself. When the table comes from somewhere else, renaming means changing data you did not write.
A layer that names no column of its own reads the first column written for each axis, wherever the layer is written. So point + x(gdp) + y(life) + line draws a line through the points. point + line + x(gdp) + y(life) draws the same plot: the positions are written after line, and point, which names none, reads them too. color() does not work this way. Written after one mark, it never reaches another. Which orders matter showed that difference with a trend line, and Channels: plot-scoped or layer-scoped below gives the rule.
Naming a column per layer also lets you put two columns of one table on one axis:
| session | open | close | high | low |
|---|---|---|---|---|
| 01 | 100.39 | 100.45 | 100.66 | 99.98 |
| 02 | 99.41 | 99.72 | 99.93 | 98.66 |
| 03 | 101.02 | 103.11 | 104.19 | 100.53 |
| 04 | 102.09 | 102.01 | 102.98 | 101.60 |
| 05 | 105.03 | 104.85 | 105.71 | 103.86 |
data(sessions) + x(session) +
line + y(open) + style(color = "steelblue") +
line + y(close) + style(color = "tomato", pattern = "dashed") +
y_label("Price")(data(sessions) + x(col.session) +
line + y(col.open) + style(color = "steelblue") +
line + y(col.close) + style(color = "tomato", pattern = "dashed") +
y_label("Price"))data(sessions) + x(:session) + line + y(:open) +
style(color = "steelblue") + line + y(:close) +
style(color = "tomato", pattern = "dashed") + y_label("Price")plot(data(sessions), x(col.session), line, y(col.open),
style({ color: "steelblue" }), line, y(col.close),
style({ color: "tomato", pattern: "dashed" }), y_label("Price"))“Given the sessions: x is session, a line, y is open, colored steelblue, and also a line, y is close, colored tomato, with pattern dashed.”
The solid steelblue line is open and the dashed tomato line is close, both on one y axis. y_label("Price") names that axis. Without it, the axis would be named Open, after the first column written for it.
That plot works, but there is usually a better way to draw two series. Reshape the table to tidy data: one value column and a type column, mapped with color(type). That form draws a legend, and it works for any number of series. A y() in each layer, as above, is the better choice only in one of these situations:
- A setting. The lines need a setting that no channel can map. A line’s width and its opacity can only be set, and a setting applies to a whole layer, so two widths need two layers. A color or a dash can be mapped instead, with
color(type)orpattern(type). - Another table. The values are in another table, as the notes in
milestonesdid.
A layer may name its own column, as the text layer did with milestones. It may not name its own scale:
data(gapminder_2007) + point + x(gdp) + y(life) +
data(milestones) + text + x(at, scale = "log") + y(value) + label(note)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
data(milestones) + text + x(col.at, scale = "log") + y(col.value) + label(col.note))data(gapminder_2007) + point + x(:gdp) + y(:life) + data(milestones) +
text + x(:at, scale = "log") + y(:value) + label(:note)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
data(milestones), text, x(col.at, { scale: "log" }), y(col.value),
label(col.note))Error:
! gog: this layer's `x(at, …)` gives it its own scale, and the layers of one plot share one x axis — two scales on it would be two coordinate spaces in one panel, where every mark reads against an axis the expression never named. Set the scale once for the plot, before the marks: `x(<column>, scale = "log")`. A layer may name its own *column* for the shared axis — `x(at)` on its own is fine — but not its own scale. If the two series are genuinely in different units, facet on the measure instead.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A second scale on one axis is a second axis. The points would be placed on a linear x axis, and the notes on a log x axis. A panel draws only one x axis, so nothing would show which axis places the notes. The milestones plot wrote x(at). This one writes x(at, scale = "log"), and that is the only difference. A layer names its own column, never its own scale.
27.3 Channels: plot-scoped or layer-scoped
The section above was about positions alone, because every layer shares the same x, y and z axes, and that sharing gives positions their extra rules. The other channels, such as color() and size(), have no axis. They follow only the rule in the table at the start of this chapter: where you write a channel decides its scope.
A channel is often meant for one layer alone. You may want the points colored by country, and one trend line in a single color, fitted to all of them together. Written after a mark, a channel binds to that mark alone. The table gapminder_asia holds five Asian countries, one row per country and year:
| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| China | Asia | 1952 | 44.00000 | 556263527 | 400.4486 |
| China | Asia | 1957 | 50.54896 | 637408000 | 575.9870 |
| China | Asia | 1962 | 44.50136 | 665770000 | 487.6740 |
| China | Asia | 1967 | 58.38112 | 754550000 | 612.7057 |
| China | Asia | 1972 | 63.11888 | 862030000 | 676.9001 |
data(gapminder_asia) + x(year) + y(life) +
point + color(country) + # color applies to point only
line * smooth # the trend line uses a single default color(data(gapminder_asia) + x(col.year) + y(col.life) +
point + color(col.country) +
line * smooth)data(gapminder_asia) + x(:year) + y(:life) + point + color(:country) +
line * smoothplot(data(gapminder_asia), x(col.year), y(col.life), point,
color(col.country), layer(line, smooth))“Given gapminder Asia: x is year, y is life, points colored by country, and also a line derived by smooth.”
Each point is drawn in its country’s color. The line takes no color, so it is one dark gray trend line, fitted to all five countries together. Whenever a plot has a color legend, a layer that neither maps nor sets color is drawn in this gray. No country in the legend is gray, so the line cannot be mistaken for one.
Written before any mark, a channel has no mark to bind to. It binds to the plot instead, so every layer that can accept it gets it:
data(gapminder_asia) + x(year) + y(life) + color(country) +
point +
line * smooth(data(gapminder_asia) + x(col.year) + y(col.life) + color(col.country) +
point +
line * smooth)data(gapminder_asia) + x(:year) + y(:life) + color(:country) + point +
line * smoothplot(data(gapminder_asia), x(col.year), y(col.life), color(col.country),
point, layer(line, smooth))“Given gapminder Asia: x is year, y is life, color by country, points and also lines derived by smooth.”
Now the line takes color as well. A line colored by a categorical column splits into one line per category, so the plot draws five trends, one per country.
That is the same rule that x and y follow. Written before the marks, they apply to every mark. Written after one mark, they belong to that mark. Positions add the one rule from the previous section: a layer that names no column of its own reads the first column written for that axis.
27.3.1 Why a channel binds to one mark only
Suppose you want the lines colored by country and only the points sized by population. A channel written after a mark binds to that mark alone, never to an earlier one:
# `size` is written after `point`, so it belongs to the points;
# the line written above it does not change, and keeps its own color
data(gapminder_asia) + x(year) + y(life) +
line + color(country) +
point + size(population)(data(gapminder_asia) + x(col.year) + y(col.life) +
line + color(col.country) +
point + size(col.population))data(gapminder_asia) + x(:year) + y(:life) + line + color(:country) +
point + size(:population)plot(data(gapminder_asia), x(col.year), y(col.life), line,
color(col.country), point, size(col.population))“Given gapminder Asia: x is year, y is life, lines colored by country, and also points sized by population.”
This plot is possible only because a channel binds to one mark. If size also bound to the earlier line, gog would refuse the whole plot: a line takes one size for the whole layer, not one per row. There would then be no way to describe the plot you wanted.
27.3.2 One column per legend
Binding a channel to one mark has one limit. A plot draws one legend for color, and that legend names one column. So when two layers both map color, they must name the same column. The same is true of size, shape, pattern and opacity, the other channels that draw a legend.
Suppose the lines above stay colored by country, and the points are colored by population instead of sized by it. The one legend would name country, the first column written, and nothing would explain the colors of the points. So gog refuses the plot:
data(gapminder_asia) + x(year) + y(life) +
line + color(country) +
point + color(population)(data(gapminder_asia) + x(col.year) + y(col.life) +
line + color(col.country) +
point + color(col.population))data(gapminder_asia) + x(:year) + y(:life) + line + color(:country) +
point + color(:population)plot(data(gapminder_asia), x(col.year), y(col.life), line,
color(col.country), point, color(col.population))Error:
! gog: `color` maps 2 columns in one plot: `country` (on `line`), and `population` (on `point`). A plot draws one legend for `color`, and it names `country`, so `population` would be drawn with no legend to read it by. Keep one column on `color` and show `population` another way: `size(population)` on the `point`, or `opacity(population)` on the `point`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The message names two other channels for population, each with its own legend: size or opacity on the points. The plot above uses size. Writing legend = FALSE on either color lets the plot draw, with no color legend at all.
27.3.3 A plot-scoped channel and the marks left unchanged
A channel bound to one mark must be one that mark can accept, or gog refuses the plot. A channel written for the whole plot works differently. What happens when some marks can accept it and others cannot? size written for the whole plot applies to the points, and not to the line. A line has one width for the whole layer rather than one width per row, so it has no size to map. gog applies size to every mark that can map it, and prints a message naming the marks it left unchanged. That message is an Assumption: gog says what it decided instead of refusing the sentence, so the plot still draws:
data(gapminder_asia) + x(year) + y(life) + size(population) +
line + group(country) +
point(data(gapminder_asia) + x(col.year) + y(col.life) + size(col.population) +
line + group(col.country) +
point)data(gapminder_asia) + x(:year) + y(:life) + size(:population) + line +
group(:country) + pointplot(data(gapminder_asia), x(col.year), y(col.life), size(col.population),
line, group(col.country), point)gog: `size(population)` is written for the whole plot, so it applies to `point` — `line` takes one size for the whole layer, not one per row, so it is left unchanged. Move `size(population)` after a mark to bind it to that mark alone.
“Given gapminder Asia: x is year, y is life, size by population, lines grouped by country, and also points.”
If no mark in the plot can accept the channel, the binding does nothing at all, so gog refuses it:
data(gapminder_asia) + x(year) + y(life) + size(population) +
line + group(country)(data(gapminder_asia) + x(col.year) + y(col.life) + size(col.population) +
line + group(col.country))data(gapminder_asia) + x(:year) + y(:life) + size(:population) + line +
group(:country)plot(data(gapminder_asia), x(col.year), y(col.life), size(col.population),
line, group(col.country))Error:
! gog: `size` maps `population` for the whole plot, written before the marks, and no mark here takes it: `line` takes one size for the whole layer, not one per row. Drop the `population` mapping and set it with `style(size = )` after `line`, or add a mark that maps `size`: `point` or `text`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The message names the mark that could not accept the binding, and why: a line has one width for the whole layer. It says what to do: drop size(population) and set that width with style(size = ) after line, or add a mark that maps size, such as point. gog never drops such a channel without a message.
27.4 data(): a second table in one sentence
A forecast, a threshold or a set of labels usually comes in a table of its own, so one plot often reads two tables. The Data chapter already gave the rule for two tables, and that rule is a scope rule. The first data() is the plot’s table. Each later data() binds to the mark written directly after it.
| year | sales |
|---|---|
| 2024 | 180 |
| 2025 | 195 |
| 2026 | 210 |
data(actuals) + x(year) + y(sales) +
line +
data(forecast) + point # forecast applies to point, not to line(data(actuals) + x(col.year) + y(col.sales) +
line +
data(forecast) + point)data(actuals) + x(:year) + y(:sales) + line + data(forecast) + pointplot(data(actuals), x(col.year), y(col.sales), line, data(forecast),
point)“Given the actuals: x is year, y is sales, a line; then given the forecast: points.”
Where you write data() matters here. A later data() gives its table to the next mark only. A mark written after that one reads the plot’s table again.
27.5 What comes next
Scope decides what an atom binds to inside one panel: the plot, or one layer. Faceting makes more panels, one per category of a column. Every panel is drawn by the same sentence, and by default every panel is read against the same scales.