39  Selection

Which countries in a scatter are the rich ones, and where are they? Finding them among 142 dots takes a long time. Add one word, brush, to the sentence and the reader can draw a box around any group. The countries inside the box keep their color, and the rest are dimmed:

gapminder_2007: first 5 of 142 rows
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
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
  theme(width = 620) + brush
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
  theme(width = 620) + brush)
data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
  theme(width = 620) + brush
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  color(col.continent), theme({ width: 620 }), brush)
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: points, x is gdp, y is life, color by continent, and let the reader select.”

In the web edition, drag across that plot. In the PDF it is the plain scatter, because a bare brush selects nothing until a reader drags.

Bare brush means the positions this plot binds, so here it is a region over gdp and life together. The sentence offers the reader a selection without choosing in advance which part of the plot to select.

Every country is still drawn while you drag, so the group is read against what it was taken from. Remove the other rows and the plot no longer shows that the rich countries are at the top right of the whole world.

39.1 Saying where the selection starts

In the PDF nothing can be dragged, so a reader of the first plot must still find the rich countries without help. Name a column and the brush states a bound on it: the low and high values the selection starts at. That is how you point at something rather than let a reader look for it:

data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
  theme(width = 620) + brush(gdp, at = c(20000, 60000))
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
  theme(width = 620) + brush(col.gdp, at = [20000, 60000]))
data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
  theme(width = 620) + brush(:gdp, at = [20000, 60000])
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  color(col.continent), theme({ width: 620 }),
  brush(col.gdp, { at: [20000, 60000] }))
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: points, x is gdp, y is life, color by continent, and let the reader select gdp, starting from 20000 to 60000.”

The thirty or so countries above 20,000 dollars keep their color, and almost all of them are above 75 years. A bound written into the sentence points the reader at a group, as a note on the plot would. The sentence has done the selecting, and a reader can still drag across the panel to replace the bound.

The two forms differ in who chooses the group. brush leaves the choice to the reader. brush(gdp, at = ...) makes the choice in the sentence, so the selection also appears in the PDF.

39.2 Highlighting, never removing rows

A brush highlights rows and never removes them. That rule separates brush from an atom you already know. limits also takes a range in a column’s own units, and it puts that range to a different use.

limits on a column of the table runs before the statistics. It removes rows, and the transform then reads the rows that are left as if they were the whole table. brush runs after the axes and statistics are computed. It removes nothing and recomputes nothing.

One range, one mark, two outcomes:

data(gapminder_2007) + point + x(gdp, limits = c(20000, 60000)) + y(life) +
  theme(width = 620, height = 300) + title("limits: 109 countries gone")
(data(gapminder_2007) + point + x(col.gdp, limits = [20000, 60000]) + y(col.life) +
  theme(width = 620, height = 300) + title("limits: 109 countries gone"))
data(gapminder_2007) + point + x(:gdp, limits = [20000, 60000]) +
  y(:life) + theme(width = 620, height = 300) +
  title("limits: 109 countries gone")
plot(data(gapminder_2007), point, x(col.gdp, { limits: [20000, 60000] }),
  y(col.life), theme({ width: 620, height: 300 }),
  title("limits: 109 countries gone"))
gog: `x(gdp, limits = …)` excludes 109 of 142 rows, which fall outside [20000, 60000]. Stating a domain is what removes them — widen the limits if they should be drawn.
20K 30K 40K 50K 60K 74 76 78 80 82 limits: 109 countries gone Life Gdp

“Given gapminder 2007: points, x is gdp from 20000 to 60000, y is life.”

data(gapminder_2007) + point + x(gdp) + y(life) +
  theme(width = 620, height = 300) + brush(gdp, at = c(20000, 60000)) +
  title("brush: all 142 still here")
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
  theme(width = 620, height = 300) + brush(col.gdp, at = [20000, 60000]) +
  title("brush: all 142 still here"))
data(gapminder_2007) + point + x(:gdp) + y(:life) +
  theme(width = 620, height = 300) + brush(:gdp, at = [20000, 60000]) +
  title("brush: all 142 still here")
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  theme({ width: 620, height: 300 }),
  brush(col.gdp, { at: [20000, 60000] }),
  title("brush: all 142 still here"))
0 10K 20K 30K 40K 50K 40 50 60 70 80 brush: all 142 still here Life Gdp

The limits plot is drawn from fewer rows. Its axis runs from 20,000 to 60,000, only the countries inside that range are drawn, and both axes were fitted to the rows that were left. Look at its y axis: the lowest number on it is 74, because the poorer countries were removed, and they included every country with a low life expectancy. gog prints how many it removed rather than removing them in silence.

The brush plot is the whole world, with one group keeping its color. Its axes are the axes of all 142 countries, so the selected points are exactly where an unbrushed plot would have put them.

The two plots do not share an axis, even though one is drawn above the other. A country at 30,000 dollars is a quarter of the way along the top plot’s axis and more than halfway along the bottom one’s. Read each plot against its own axes.

Which of the two you want depends on your question, and the grammar lets you write either one. Ask how does this subgroup look on its own and you want limits. Ask where is this subgroup inside the whole and you want brush.

39.3 Two bounds make a rectangle

Which of the rich countries also live longest? That is two conditions on one row. One brush tests one column. Write two, and a row has to satisfy both:

data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
  theme(width = 620) + brush(gdp, at = c(20000, 60000)) + brush(life, at = c(78, 85))
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
  theme(width = 620) + brush(col.gdp, at = [20000, 60000]) + brush(col.life, at = [78, 85]))
data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
  theme(width = 620) + brush(:gdp, at = [20000, 60000]) +
  brush(:life, at = [78, 85])
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  color(col.continent), theme({ width: 620 }),
  brush(col.gdp, { at: [20000, 60000] }),
  brush(col.life, { at: [78, 85] }))
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: points, x is gdp, y is life, color by continent, and let the reader select gdp, starting from 20000 to 60000, and life, starting from 78 to 85.”

These are the rich countries that also live longest. Six countries inside the gdp bound fall outside the life bound, and you can see which six, because they are still drawn, dimmed.

Two bounds needed brush written twice, and that follows the rest of the grammar. A position is one column at a time, and so is a bound.

39.4 A categorical column selects slots

Both bounds so far have been ranges of numbers. What happens when the column holds names instead? at is still where the selection starts, and the column decides which reading you get, in the same way it decides whether color uses a ramp or a palette. Numbers are a range. Names are a set of slots:

data(gapminder_2007) + point + x(continent) + y(life) + color(continent) +
  theme(width = 620) + brush(continent, at = c("Europe", "Oceania"))
(data(gapminder_2007) + point + x(col.continent) + y(col.life) + color(col.continent) +
  theme(width = 620) + brush(col.continent, at = ["Europe", "Oceania"]))
data(gapminder_2007) + point + x(:continent) + y(:life) +
  color(:continent) + theme(width = 620) +
  brush(:continent, at = ["Europe", "Oceania"])
plot(data(gapminder_2007), point, x(col.continent), y(col.life),
  color(col.continent), theme({ width: 620 }),
  brush(col.continent, { at: ["Europe", "Oceania"] }))
Asia Europe Africa Americas Oceania 40 50 60 70 80 Life Continent Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: points, x is continent, y is life, color by continent, and let the reader select continent, starting with Europe and Oceania.”

Drag across the slots and the selection follows them, whole slots at a time, because a slot cannot be split.

The column is on the x axis here, and that matters. A drag can only change a bound on a column that one of the panel’s axes shows. Add the same brush(continent, at = ...) to the first scatter in this chapter, where continent is only a color. The sentence is still legal and still dims the right countries. But no axis shows continents, so a drag cannot change the selection the sentence chose.

Without at, brush(continent) on that scatter could never select a row, because no drag could ever set its bound. So gog refuses the sentence, and its message names two ways to fix it:

data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
  brush(continent)
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
  brush(col.continent))
data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
  brush(:continent)
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  color(col.continent), brush(col.continent))
Error:
! gog: `brush(continent)` with no `at` selects only what a reader drags. No axis of this plot shows `continent`, so a drag on it cannot set the bound. This brush would never select a row. Say where the selection is: `brush(continent, at = ...)`. Or compose this plot with `|` or `/` beside a plot of the same table whose brush can select rows, such as a flat plot that names a brush: it then shows that plot's selection.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

The second fix, a plot beside it that can select, is drawn in Two views, one selection.

It does not matter which axis places the column. Here is the first plot of this section with x and y exchanged, in a shorter panel:

data(gapminder_2007) + point + x(life) + y(continent) + color(continent) +
  theme(width = 620, height = 320) + brush(continent, at = c("Europe", "Oceania"))
(data(gapminder_2007) + point + x(col.life) + y(col.continent) + color(col.continent) +
  theme(width = 620, height = 320) + brush(col.continent, at = ["Europe", "Oceania"]))
data(gapminder_2007) + point + x(:life) + y(:continent) +
  color(:continent) + theme(width = 620, height = 320) +
  brush(:continent, at = ["Europe", "Oceania"])
plot(data(gapminder_2007), point, x(col.life), y(col.continent),
  color(col.continent), theme({ width: 620, height: 320 }),
  brush(col.continent, { at: ["Europe", "Oceania"] }))
40 50 60 70 80 Oceania Americas Africa Europe Asia Continent Life Continent Asia Europe Africa Americas Oceania

The slots now run up the left side, and the same two continents keep their color. Drag on the panel and the selected band is horizontal instead of vertical, still one whole slot at a time. The bound never mentions an axis. It names a column, so moving the column to the other axis turns the drag from vertical to horizontal, and the brush stays exactly as it was written.

39.5 Two views, one selection

A brush is most useful across two plots. A selection inside one plot marks a group you can already see there. The useful question is where is this group in a different picture, and that needs a second picture:

(data(gapminder_2007) + point + x(gdp) + y(life) +
   brush(gdp, at = c(20000, 60000)) + theme(height = 230)) /
  (data(gapminder_2007) + point + x(gdp) + y(population, scale = "log") +
     brush(gdp, at = c(20000, 60000)) + theme(height = 200))
((data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
   brush(col.gdp, at = [20000, 60000]) + theme(height = 230)) /
  (data(gapminder_2007) + point + x(col.gdp) + y(col.population, scale = "log") +
     brush(col.gdp, at = [20000, 60000]) + theme(height = 200)))
(data(gapminder_2007) + point + x(:gdp) + y(:life) +
  brush(:gdp, at = [20000, 60000]) + theme(height = 230)) /
  (data(gapminder_2007) + point + x(:gdp) +
  y(:population, scale = "log") + brush(:gdp, at = [20000, 60000]) +
  theme(height = 200))
below(plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  brush(col.gdp, { at: [20000, 60000] }), theme({ height: 230 })),
  plot(data(gapminder_2007), point, x(col.gdp),
  y(col.population, { scale: "log" }),
  brush(col.gdp, { at: [20000, 60000] }), theme({ height: 200 })))
40 50 60 70 80 Life 0 10K 20K 30K 40K 50K 1M 10M 100M 1B Population Gdp

“Given gapminder 2007: points, x is gdp, y is life, and let the reader select gdp, starting from 20000 to 60000, above points, x is gdp, y is population on a log scale, and let the reader select gdp, starting from 20000 to 60000.”

Two plots are in one arrangement. In both plots, the same countries keep their color. Drag in either and the other follows. The top plot says the rich countries live long. The bottom plot says their populations run from under a million to over three hundred million. Neither plot could have told you both.

Nothing was added to the grammar to link them. Look at what the two sentences share: each one says brush(gdp, at = c(20000, 60000)). A bound is a fact about a column’s values rather than about a panel, so two plots that brush the same column answer the same question. There is no name to declare, no identifier to match, and no third atom joining them.

Composition has a matching rule for axes. Two composed plots that name the same column on the same axis, and read it the same way, share that axis, which is why these two are the same width and the gdp numbers are written once. The selection is shared for a related reason: both sentences brush the same column.

Two plots of one table can also brush different columns, and they still share one selection. A row is selected only if it satisfies both bounds, as with two bounds in one plot:

((data(gapminder_2007) + point + x(gdp) + y(life) +
    brush(gdp, at = c(0, 10000))) |
   (data(gapminder_2007) + point + x(life) + y(population, scale = "log") +
      brush(life, at = c(60, 80)))) + theme(height = 320)
(((data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
    brush(col.gdp, at = [0, 10000])) |
   (data(gapminder_2007) + point + x(col.life) + y(col.population, scale = "log") +
      brush(col.life, at = [60, 80]))) + theme(height = 320))
((data(gapminder_2007) + point + x(:gdp) + y(:life) +
  brush(:gdp, at = [0, 10000])) |
  (data(gapminder_2007) + point + x(:life) +
  y(:population, scale = "log") + brush(:life, at = [60, 80]))) +
  theme(height = 320)
beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  brush(col.gdp, { at: [0, 10000] })),
  plot(data(gapminder_2007), point, x(col.life),
  y(col.population, { scale: "log" }), brush(col.life, { at: [60, 80] })),
  theme({ height: 320 }))
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp 40 50 60 70 80 1M 10M 100M 1B Population Life

“Given gapminder 2007: points, x is gdp, y is life, and let the reader select gdp, starting from 0 to 10000, beside points, x is life, y is population on a log scale, and let the reader select life, starting from 60 to 80.”

The left plot brushes gdp, and the right plot brushes life. A country is selected only if its GDP per person is 10,000 dollars or less and its life expectancy is from 60 to 80 years. In both plots, those 48 countries keep their color and the other 94 are dimmed. So each plot dims some countries inside its own bound. On the left, 40 countries below 10,000 dollars are dimmed, because they live less than 60 years. A third plot of the same table, composed with these two but with no brush, dims nothing. In the web edition, the count of selected rows reads 48 of 142 selected.

A plot can also brush a column that none of its axes shows. Here the left plot from above is beside a plot of population against life with brush(gdp):

(data(gapminder_2007) + point + x(gdp) + y(life) +
   brush(gdp, at = c(0, 10000))) |
  (data(gapminder_2007) + point + x(population, scale = "log") + y(life) +
     brush(gdp))
((data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
   brush(col.gdp, at = [0, 10000])) |
  (data(gapminder_2007) + point + x(col.population, scale = "log") + y(col.life) +
     brush(col.gdp)))
(data(gapminder_2007) + point + x(:gdp) + y(:life) +
  brush(:gdp, at = [0, 10000])) |
  (data(gapminder_2007) + point + x(:population, scale = "log") +
  y(:life) + brush(:gdp))
beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  brush(col.gdp, { at: [0, 10000] })),
  plot(data(gapminder_2007), point, x(col.population, { scale: "log" }),
  y(col.life), brush(col.gdp)))
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp 1M 10M 100M 1B Population

The same countries keep their color in both plots. No axis of the right plot shows gdp, so no drag there can change the bound. In the web edition the pointer stays a plain arrow over it, as Looking closer explains. A drag on the left plot moves the selection in both.

39.6 One bound reaches every panel

Faceting is the other way to get more than one panel, and a brush treats it the same way. The bound is a fact about a column, and every panel reads that column, so every panel answers:

data(gapminder_2007) + point + x(gdp) + y(life) +
  brush(gdp, at = c(20000, 60000)) | facet(continent)
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
  brush(col.gdp, at = [20000, 60000]) | facet(col.continent))
data(gapminder_2007) + point + x(:gdp) + y(:life) +
  brush(:gdp, at = [20000, 60000]) | facet(:continent)
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  brush(col.gdp, { at: [20000, 60000] }), across(col.continent))
0 20K 40K 40 50 60 70 80 0 20K 40K 0 20K 40K 0 20K 40K 0 20K 40K Asia Europe Africa Americas Oceania Life Gdp

“Given gapminder 2007: points, x is gdp, y is life, and let the reader select gdp, starting from 20000 to 60000, split into panel columns by continent.”

Five panels, one bound, and the rich countries keeping their color. Europe has many and Africa has none, which is the comparison the split was made for. facet splits the rows into panels, brush dims the rows outside the bound, and neither atom refers to the other.

In the web edition, resting the pointer on a mark shows that row’s values. A bound is shared across panels, because it names a column. The pointer is not: its values come from the panel you are pointing at, never from a row another panel drew.

39.7 Which one is that?

A selection shows you where a group is. It does not say which country each dot is, and that is usually the next question. Layer text over the points, and the bound decides which labels stay legible:

data(gapminder_2007) + point + x(gdp) + y(life) +
  text + label(country) + style(size = 7) +
  brush(gdp, at = c(30000, 60000)) + theme(width = 620)
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
  text + label(col.country) + style(size = 7) +
  brush(col.gdp, at = [30000, 60000]) + theme(width = 620))
data(gapminder_2007) + point + x(:gdp) + y(:life) + text +
  label(:country) + style(size = 7) + brush(:gdp, at = [30000, 60000]) +
  theme(width = 620)
plot(data(gapminder_2007), point, x(col.gdp), y(col.life), text,
  label(col.country), style({ size: 7 }),
  brush(col.gdp, { at: [30000, 60000] }), theme({ width: 620 }))
Afghanistan Albania Algeria Angola Argentina Bahrain Bangladesh Benin Bolivia Bosnia and Herzegovina Botswana Brazil Bulgaria Burkina Faso Burundi Cambodia Cameroon Central African Republic Chad Chile China Colombia Comoros Congo, Dem. Rep. Congo, Rep. Costa Rica Cote d'Ivoire Croatia Cuba Czech Republic Djibouti Dominican Republic Ecuador Egypt El Salvador Equatorial Guinea Eritrea Ethiopia Gabon Gambia Ghana Greece Guatemala Guinea Guinea-Bissau Haiti Honduras Hungary India Indonesia Iran Iraq Israel Italy Jamaica Jordan Kenya Korea, Dem. Rep. Korea, Rep. Lebanon Lesotho Liberia Libya Madagascar Malawi Malaysia Mali Mauritania Mauritius Mexico Mongolia Montenegro Morocco Mozambique Myanmar Namibia Nepal New Zealand Nicaragua Niger Nigeria Oman Pakistan Panama Paraguay Peru Philippines Poland Portugal Puerto Rico Reunion Romania Rwanda Sao Tome and Principe Saudi Arabia Senegal Serbia Sierra Leone Slovak Republic Slovenia Somalia South Africa Spain Sri Lanka Sudan Swaziland Syria Taiwan Tanzania Thailand Togo Trinidad and Tobago Tunisia Turkey Uganda Uruguay Venezuela Vietnam West Bank and Gaza Yemen, Rep. Zambia Zimbabwe Australia Austria Belgium Canada Denmark Finland France Germany Hong Kong, China Iceland Ireland Japan Kuwait Netherlands Norway Singapore Sweden Switzerland United Kingdom United States 0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

“Given gapminder 2007: points, x is gdp, y is life, and also text, label by country, and let the reader select gdp, starting from 30000 to 60000.”

A hundred and forty-two labels on one scatter cannot be read, and a selection solves that here. The labels you did not select are dimmed with their points, and twenty names keep their color. Drag across the panel for a new bound, and a different group of names becomes readable.

This works because text, like point, draws one shape for each row, so a brush can dim each label on its own. No special rule joins brush to text: each atom follows its own rule, and this plot uses both.

39.8 What can be brushed

A selection marks rows, so a shape can be brushed only when all of its rows are selected, or none are. The simplest case is one row is one shape. Five marks meet that test and draw a selection: point, bar, text, rule and zone. An interval with the bounds transform meets the test too, because each whisker’s two ends come from one row.

A brush draws the selected bars and the others as two groups. A bar’s width depends on how closely the bars are spaced, so gog measures the spacing over all of them first. Each bar keeps its width, and the brush only dims the bars outside the bound:

medals: all 5 rows
country gold silver bronze
USA 46 37 38
China 38 31 22
Great Britain 29 17 19
Russia 19 18 9
Germany 17 10 15
data(medals) + bar + x(country) + y(gold) + brush(gold, at = c(20, 40))
data(medals) + bar + x(col.country) + y(col.gold) + brush(col.gold, at = [20, 40])
data(medals) + bar + x(:country) + y(:gold) + brush(:gold, at = [20, 40])
plot(data(medals), bar, x(col.country), y(col.gold),
  brush(col.gold, { at: [20, 40] }))
USA China Great Britain Russia Germany 0 10 20 30 40 Gold Country

“Given the medals: bars, x is country, y is gold, and let the reader select gold, starting from 20 to 40.”

China’s and Great Britain’s bars, with 38 and 29 gold medals, keep their color. The other three are dimmed, and all five keep their width.

The bound names a column, not an axis, so the same bound works on horizontal bars:

data(medals) + bar + x(gold) + y(country) + brush(gold, at = c(20, 40))
data(medals) + bar + x(col.gold) + y(col.country) + brush(col.gold, at = [20, 40])
data(medals) + bar + x(:gold) + y(:country) + brush(:gold, at = [20, 40])
plot(data(medals), bar, x(col.gold), y(col.country),
  brush(col.gold, { at: [20, 40] }))
0 10 20 30 40 Germany Russia Great Britain China USA Country Gold

A brush works the same way on grouped bars, which bar * dodge sets side by side. The channel_sales table holds each quarter’s revenue from two ways of selling. Its channel column names them, Retail and Online:

channel_sales: first 5 of 6 rows
quarter channel revenue
Q1 Retail 4
Q1 Online 3
Q2 Retail 5
Q2 Online 6
Q3 Retail 4
data(channel_sales) + bar * dodge + x(quarter) + y(revenue) + color(channel) +
  brush(revenue, at = c(4.5, 8))
(data(channel_sales) + bar * dodge + x(col.quarter) + y(col.revenue) + color(col.channel) +
  brush(col.revenue, at = [4.5, 8]))
data(channel_sales) + bar * dodge + x(:quarter) + y(:revenue) +
  color(:channel) + brush(:revenue, at = [4.5, 8])
plot(data(channel_sales), layer(bar, dodge), x(col.quarter),
  y(col.revenue), color(col.channel),
  brush(col.revenue, { at: [4.5, 8] }))
Q1 Q2 Q3 0 2 4 6 8 Revenue Quarter Channel Retail Online

“Given the channel sales table: bars derived by dodge, x is quarter, y is revenue, color by channel, and let the reader select revenue, starting from 4.5 to 8.”

Three bars have a revenue from 4.5 to 8: both bars of the second quarter and the online bar of the third. That bar sold exactly 8, and a bound includes both its ends. The three keep their color, and each bar stays beside the other bar of its quarter.

A brush on quarter selects whole slots, as A categorical column selects slots showed for points. Both bars of a selected quarter keep their color:

data(channel_sales) + bar * dodge + x(quarter) + y(revenue) + color(channel) +
  brush(quarter, at = c("Q2", "Q3"))
(data(channel_sales) + bar * dodge + x(col.quarter) + y(col.revenue) + color(col.channel) +
  brush(col.quarter, at = ["Q2", "Q3"]))
data(channel_sales) + bar * dodge + x(:quarter) + y(:revenue) +
  color(:channel) + brush(:quarter, at = ["Q2", "Q3"])
plot(data(channel_sales), layer(bar, dodge), x(col.quarter),
  y(col.revenue), color(col.channel),
  brush(col.quarter, { at: ["Q2", "Q3"] }))
Q1 Q2 Q3 0 2 4 6 8 Revenue Quarter Channel Retail Online

Split into panels, the bars keep their width too, because gog measures the spacing over all the bars in a panel, selected or not:

data(channel_sales) + bar + x(quarter) + y(revenue) +
  brush(revenue, at = c(4.5, 8)) | facet(channel)
(data(channel_sales) + bar + x(col.quarter) + y(col.revenue) +
  brush(col.revenue, at = [4.5, 8]) | facet(col.channel))
data(channel_sales) + bar + x(:quarter) + y(:revenue) +
  brush(:revenue, at = [4.5, 8]) | facet(:channel)
plot(data(channel_sales), bar, x(col.quarter), y(col.revenue),
  brush(col.revenue, { at: [4.5, 8] }), across(col.channel))
Q1 Q2 Q3 0 2 4 6 8 Q1 Q2 Q3 Retail Online Revenue Quarter

The retail panel keeps one bar, the second quarter’s, and the online panel keeps two.

gog accepts only some brushes on stacked bars. stack sets each quarter’s bars one on top of the other. The top of each bar is a running total: its own revenue plus the revenue below it. stack writes those totals into revenue, so a brush on revenue would dim by the totals while the count tests each row’s own value. Q3’s online bar sold 8, but its top is at 12. The two would disagree, so gog refuses it:

data(channel_sales) + bar * stack + x(quarter) + y(revenue) + color(channel) +
  brush(revenue, at = c(4.5, 8))
(data(channel_sales) + bar * stack + x(col.quarter) + y(col.revenue) + color(col.channel) +
  brush(col.revenue, at = [4.5, 8]))
data(channel_sales) + bar * stack + x(:quarter) + y(:revenue) +
  color(:channel) + brush(:revenue, at = [4.5, 8])
plot(data(channel_sales), layer(bar, stack), x(col.quarter),
  y(col.revenue), color(col.channel),
  brush(col.revenue, { at: [4.5, 8] }))
Error:
! gog: a brush on `revenue` over a stacked `bar` is valid grammar, but this engine does not draw it, because `stack` turns each bar's `revenue` into the running total of the bars below it. The selection would follow the pile rather than each row's own value. Brush the category instead, as in `brush(quarter, at = ...)`, or set the bars side by side with `dodge`, where each bar stands at its own value.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A bare brush is refused too, because it selects on the plot’s positions, and revenue is one of them. A brush on quarter or on channel is accepted, because stack does not change those columns:

data(channel_sales) + bar * stack + x(quarter) + y(revenue) + color(channel) +
  brush(channel, at = "Online")
(data(channel_sales) + bar * stack + x(col.quarter) + y(col.revenue) + color(col.channel) +
  brush(col.channel, at = "Online"))
data(channel_sales) + bar * stack + x(:quarter) + y(:revenue) +
  color(:channel) + brush(:channel, at = "Online")
plot(data(channel_sales), layer(bar, stack), x(col.quarter),
  y(col.revenue), color(col.channel),
  brush(col.channel, { at: "Online" }))
Q1 Q2 Q3 0 5 10 Revenue Quarter Channel Retail Online

“Given the channel sales table: bars derived by stack, x is quarter, y is revenue, color by channel, and let the reader select channel, starting with Online.”

The online part of each bar keeps its color, and the retail part is dimmed.

A jittered point, a swarmed point and a repelled text refuse, because each one’s place depends on the rows drawn with it. A jittered point’s offset is derived from its place in the table. A swarmed point, point * dodge, is placed by where the other points in its slot are. A repelled label is placed by what the other labels around it are doing. Drawn as two groups, every one of these marks would move when the selection changed. An edge also draws one shape per row, and it refuses as well. The Network chapter gives the reason: a network has no axes, so there is nothing for a range of values to be measured on.

A line does not meet the test at all. It draws one shape through many rows, so a row is a corner of the line rather than a thing of its own. Selecting half of a line would mean cutting it in two. group() cuts a line into pieces, but each piece still runs through many rows, so the engine refuses even this sentence:

gapminder_asia: first 5 of 60 rows
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) + line + x(year) + y(life) + group(country) +
  brush(life, at = c(60, 80))
(data(gapminder_asia) + line + x(col.year) + y(col.life) + group(col.country) +
  brush(col.life, at = [60, 80]))
data(gapminder_asia) + line + x(:year) + y(:life) + group(:country) +
  brush(:life, at = [60, 80])
plot(data(gapminder_asia), line, x(col.year), y(col.life),
  group(col.country), brush(col.life, { at: [60, 80] }))
Error:
! gog: a `line` draws one shape through many rows, so there is no single row to select. Brush a layer that draws a selection: `point`, `bar`, `text`, `rule`, `zone`, `interval * bounds` and `ribbon * flow`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

The refusal lists the layers that draw a selection. A brushed plot needs at least one of them.

A summarized layer refuses for a different reason. A bar of bar * count stands for as many as fifty-two countries at once. There is no way to draw “twelve of these are selected”: dimming the whole bar would be wrong, and dimming part of it would invent a mark nobody asked for. gog says so instead of guessing:

data(gapminder_2007) + bar * count + x(continent) +
  brush(gdp, at = c(20000, 60000))
(data(gapminder_2007) + bar * count + x(col.continent) +
  brush(col.gdp, at = [20000, 60000]))
data(gapminder_2007) + bar * count + x(:continent) +
  brush(:gdp, at = [20000, 60000])
plot(data(gapminder_2007), layer(bar, count), x(col.continent),
  brush(col.gdp, { at: [20000, 60000] }))
Error:
! gog: a brush on `bar * count` is valid grammar, but this engine does not draw it: `count` summarizes many rows into one, and the engine cannot say which of them you selected. Brush the layer that draws the rows themselves, or drop the brush.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A flow is the only summary that answers a brush. Each band stands for the rows that share a place at every stage, so a brush on a stage keeps or dims a whole band, and never part of one.

Apart from the edge’s, none of the refusals above applies when the plot also carries a layer that can answer. The plot draws, and gog reports which layer it left whole:

data(gapminder_2007) + point + x(gdp) + y(life) +
  line * smooth +
  brush(gdp, at = c(20000, 60000))
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
  line * smooth +
  brush(col.gdp, at = [20000, 60000]))
data(gapminder_2007) + point + x(:gdp) + y(:life) + line * smooth +
  brush(:gdp, at = [20000, 60000])
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  layer(line, smooth), brush(col.gdp, { at: [20000, 60000] }))
gog: the `line` layer is drawn whole, because it draws one shape through many rows and there is no single row to select. The selection still reads on the rest of the plot.
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

“Given gapminder 2007: points, x is gdp, y is life, and also a line derived by smooth, and let the reader select gdp, starting from 20000 to 60000.”

The points answer the selection and the trend line does not, so the line is drawn once, undimmed, through all 142 countries. That is the right answer: a trend fitted to the selection alone would be a different statistic, and the sentence did not ask for one.

A range written from high to low selects nothing, so it is a mistake rather than an empty selection:

data(gapminder_2007) + point + x(gdp) + y(life) +
  brush(gdp, at = c(60000, 20000))
(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
  brush(col.gdp, at = [60000, 20000]))
data(gapminder_2007) + point + x(:gdp) + y(:life) +
  brush(:gdp, at = [60000, 20000])
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  brush(col.gdp, { at: [60000, 20000] }))
Error:
! gog: `brush(gdp)` at 60000 to 20000 selects nothing, because the range does not run upward. Write the smaller number first.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A map cannot be brushed at all. It projects longitude and latitude to new positions before it draws anything, and a brush on it would count one set of places while it dims another. So gog refuses any sentence with both brush and map(). quakes_fiji records earthquakes near Fiji, with longitude in east and latitude in north:

quakes_fiji: first 5 of 1000 rows
east north elevation magnitude slab
181.62 -20.42 -562 4.8 540-630 km
181.03 -20.62 -650 4.2 630-720 km
184.10 -26.00 -42 5.4 0-90 km
181.66 -17.97 -626 4.1 540-630 km
181.96 -20.42 -649 4.0 630-720 km

The sentence below adds brush to a map of them:

data(quakes_fiji) + point + x(east) + y(north) + map() +
  brush(east, at = c(178, 182))
(data(quakes_fiji) + point + x(col.east) + y(col.north) + map() +
  brush(col.east, at = [178, 182]))
data(quakes_fiji) + point + x(:east) + y(:north) + map() +
  brush(:east, at = [178, 182])
plot(data(quakes_fiji), point, x(col.east), y(col.north), map(),
  brush(col.east, { at: [178, 182] }))
Error:
! gog: `brush` cannot select on a `map()`. A map projects longitude and latitude before it draws, and a brush on it would count one set of rows while it dims another. Drop `brush()`, or brush the same columns without `map()`, where the axes are the degrees themselves: `point + x(east) + y(north) + brush(east)`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

To select places by longitude and latitude, brush the same columns without map(), as the message says. Without a projection, the axes are in degrees, so the band from 178 to 182 has straight sides:

data(quakes_fiji) + point + x(east) + y(north) +
  brush(east, at = c(178, 182))
(data(quakes_fiji) + point + x(col.east) + y(col.north) +
  brush(col.east, at = [178, 182]))
data(quakes_fiji) + point + x(:east) + y(:north) +
  brush(:east, at = [178, 182])
plot(data(quakes_fiji), point, x(col.east), y(col.north),
  brush(col.east, { at: [178, 182] }))
165 170 175 180 185 -30 -20 -10 North East

The earthquakes inside the band keep their color.

Apart from the backward range, the edge and the map, every refusal in this section comes from two questions. Can the engine say which rows your selection holds? And can it dim them while every mark keeps its place and its size? A played plot raises the first question again, because it draws only some of its rows at a time. The next section answers it.

39.9 Selecting while the plot plays

A sequence draws one year at a time, and you still want one group marked in every year. A frame is a subset of the rows, and a bound is a test on a column’s values, so the two combine without either one changing. The bound is tested again inside every frame:

gm_all: first 5 of 1704 rows
country continent year life population gdp
Afghanistan Asia 1952 28.801 8425333 779.4453
Afghanistan Asia 1957 30.332 9240934 820.8530
Afghanistan Asia 1962 31.997 10267083 853.1007
Afghanistan Asia 1967 34.020 11537966 836.1971
Afghanistan Asia 1972 36.088 13079460 739.9811
data(gm_all) + point + x(gdp, scale = "log") + y(life) + color(continent) +
  theme(width = 620) + play(year) + brush(gdp, at = c(3000, 20000))
(data(gm_all) + point + x(col.gdp, scale = "log") + y(col.life) + color(col.continent) +
  theme(width = 620) + play(col.year) + brush(col.gdp, at = [3000, 20000]))
data(gm_all) + point + x(:gdp, scale = "log") + y(:life) +
  color(:continent) + theme(width = 620) + play(:year) +
  brush(:gdp, at = [3000, 20000])
plot(data(gm_all), point, x(col.gdp, { scale: "log" }), y(col.life),
  color(col.continent), theme({ width: 620 }), play(col.year),
  brush(col.gdp, { at: [3000, 20000] }))
1K 10K 100K 40 60 80 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007 Life Gdp Continent Asia Europe Africa Americas Oceania

“Given all the gapminder years: points, x is gdp on a log scale, y is life, color by continent, played by year, and let the reader select gdp, starting from 3000 to 20000.”

In the web edition, watch countries cross into the selection, frame by frame. In the PDF, the plot shows the first frame, 1952. The bound stays at 3,000 to 20,000 dollars while the countries move. In 1952 the selected group is that year’s middle-income countries. By 2007 many have grown past 20,000 and poorer ones have grown into the range. A brush does not pause the sequence or skip a frame.

39.9.1 What the count includes

Stop the clock, draw a small box, and the count under the plot (the line reading 17 of 1704 selected) looks far too large. The number is right. A bound is a test on a column’s values, so it is true or false for every row in the table. It does not stop at the frame you can see. The count and show rows, a button under the plot, report the whole selection across every frame. The picture shows you one frame of it.

The played plot in this section makes that concrete. gm_all is 1,704 rows, which is 142 countries in each of twelve years, and one frame draws 142 of them. Stop on 1982 and drag across the four countries furthest to the left, and the count reads far more than four. This brush tests only gdp, so the box covers the whole height of the panel, and it tests every year’s rows, not only 1982’s. In other years, other countries had a gdp in that range too. The four you can see are the 1982 rows of a selection that spans all twelve years.

The table that show rows opens is where you check it. It lists the year of every selected row, beside the other columns the sentence maps. You can see which frame each row belongs to, and pick out the four that belong to the frame on screen.

A faceted plot never raises this question. There every selected row is drawn at once, so a count of the whole selection is a count of what you see. A sequence shows one frame of twelve at a time, so most of the selected rows are not on the screen. They are in other frames. The rule is the same one; only the picture differs.

39.9.2 Choosing a bound the data crosses

A bound chosen for the late frames can mislead a reader in the early ones. Count what a bound selects in each frame before you write it down. From 3,000 to 20,000 dollars selects between 52 and 74 countries in every frame here. From 20,000 to 60,000 dollars selects none at all in 1952 and 1957, one in 1962, and four by 1972. The second bound draws a true picture, and it still reads as a broken plot, because a reader cannot tell nothing is selected here from this is not working. To see it, drag across the panel from 20,000 to 60,000 dollars and step back to 1952: every country in that frame is dimmed.

One column cannot both cut the frames and carry a bound. Brushing the column the frames are cut on would select frames rather than rows inside one, and that is a control for moving through time rather than a selection:

data(gm_all) + point + x(gdp) + y(life) + play(year) + brush(year, at = c(1980, 2007))
data(gm_all) + point + x(col.gdp) + y(col.life) + play(col.year) + brush(col.year, at = [1980, 2007])
data(gm_all) + point + x(:gdp) + y(:life) + play(:year) +
  brush(:year, at = [1980, 2007])
plot(data(gm_all), point, x(col.gdp), y(col.life), play(col.year),
  brush(col.year, { at: [1980, 2007] }))
Error:
! gog: `year` is already the column `play()` cuts the frames on, so `brush(year)` would select frames rather than rows inside one. That is a scrub bar, which belongs to the viewer and not to the sentence. Brush a column the frames are not cut on.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

Moving the clock belongs to the page a plot is shown on, not to the sentence that describes the plot. That is what the buttons under a played plot do, and Play says why they have no word here.

39.10 All three at once

A bound and a sequence combined without a new rule, and a third position joins them the same way. Play drew the cube of countries through the years, and nothing in it marks the middle-income countries. The third position, the frames and the selection are three separate ideas, and none of them was designed around the others. So all three can be written in one sentence, and none of them changes what the others do:

data(gm_all) + point + x(gdp, scale = "log") + y(life) + z(population) +
  color(continent) + play(year) + brush(gdp, at = c(3000, 20000))
(data(gm_all) + point + x(col.gdp, scale = "log") + y(col.life) + z(col.population) +
  color(col.continent) + play(col.year) + brush(col.gdp, at = [3000, 20000]))
data(gm_all) + point + x(:gdp, scale = "log") + y(:life) +
  z(:population) + color(:continent) + play(:year) +
  brush(:gdp, at = [3000, 20000])
plot(data(gm_all), point, x(col.gdp, { scale: "log" }), y(col.life),
  z(col.population), color(col.continent), play(col.year),
  brush(col.gdp, { at: [3000, 20000] }))
100K 10K 1K 80 60 40 1000M 500M 0 Gdp Life Population 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007 Continent Asia Europe Africa Americas Oceania

“Given all the gapminder years: points, x is gdp on a log scale, y is life, z is population, color by continent, played by year, and let the reader select gdp, starting from 3000 to 20000.”

A cube of 142 countries, twelve frames, and one bound tested inside every one of them. Drag the plot to turn it and the selection turns with the points. The bound picks rows and the drag picks the viewing angle, and neither one depends on the other.

One thing is different in the cube. There the drag turns the plot, so it cannot also draw a selection, and you name the bound in the sentence. Every plot in this chapter follows one rule: a drag does one thing at a time. In the cube, that one thing is turning. A brush with no at could never select a row here, as brush(continent) could not on the scatter in A categorical column selects slots. So gog refuses it and asks for the bound, or for a flat plot beside the cube:

data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) + z(population) +
  brush(gdp)
(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) + z(col.population) +
  brush(col.gdp))
data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  z(:population) + brush(:gdp)
plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), z(col.population), brush(col.gdp))
Error:
! gog: `brush(gdp)` with no `at` selects only what a reader drags. In the cube a drag turns the plot. This brush would never select a row. Say where the selection is: `brush(gdp, at = ...)`. Or compose this plot with `|` or `/` beside a plot of the same table whose brush can select rows, such as a flat plot that names a brush: it then shows that plot's selection.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A drag on the cube cannot change the selection, but you can still read it. The count and show rows are on their own line under the plot, and you can read the selected rows while you turn the cube.

39.11 A cube beside a flat plot

Alone, a cube takes its selection only from the bound in its sentence. To choose the selection with the pointer, drag across a flat plot beside the cube. Give the flat plot a brush, join the two plots with | (or /), and give the cube a bare brush. Both plots must read the same table. The rows you select keep their color in both plots:

(data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
   brush(gdp, at = c(3000, 20000))) |
  (data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
     z(population) + brush)
((data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
   brush(col.gdp, at = [3000, 20000])) |
  (data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
     z(col.population) + brush))
(data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  brush(:gdp, at = [3000, 20000])) |
  (data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  z(:population) + brush)
beside(plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), brush(col.gdp, { at: [3000, 20000] })),
  plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), z(col.population), brush))
1K 10K 40 50 60 70 80 Life Gdp 10K 1K 80 70 60 50 40 1000M 500M 0 Gdp Life Population

“Given gapminder 2007: points, x is gdp on a log scale, y is life, and let the reader select gdp, starting from 3000 to 20000, beside points, x is gdp on a log scale, y is life, z is population, and let the reader select.”

In the web edition, drag across the flat plot. While you drag, the countries you select keep their color in the cube too. A drag on the cube still turns it, and the selection does not change. What a drag does depends on where it starts. Inside the flat plot’s panel it selects, and anywhere else it turns the cube. The pointer shows which: a crosshair, for drawing a box, over the flat plot’s panel, and an open hand, for turning, everywhere else.

On a flat plot, a bare brush means the positions the plot binds. On the cube it names no bound, because no drag on the cube can select. It says only that the cube shows the selection of the plot beside it, the way two flat plots of one table share one selection (Two views, one selection). Leave it out, and the cube dims nothing. In the PDF, both plots show the bound the flat plot’s sentence names.

With / in place of |, the two plots are drawn one above the other. Here the flat plot is above the cube:

(data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
   brush(gdp, at = c(3000, 20000))) /
  (data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
     z(population) + brush)
((data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
   brush(col.gdp, at = [3000, 20000])) /
  (data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
     z(col.population) + brush))
(data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  brush(:gdp, at = [3000, 20000])) /
  (data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  z(:population) + brush)
below(plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), brush(col.gdp, { at: [3000, 20000] })),
  plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), z(col.population), brush))
1K 10K 40 50 60 70 80 Life Gdp 10K 1K 80 70 60 50 40 1000M 500M 0 Gdp Life Population

The flat plot does not have to be written first. Here the cube is above it:

(data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
   z(population) + brush) /
  (data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
     brush(gdp, at = c(3000, 20000)))
((data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
   z(col.population) + brush) /
  (data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
     brush(col.gdp, at = [3000, 20000])))
(data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  z(:population) + brush) /
  (data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
  brush(:gdp, at = [3000, 20000]))
below(plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), z(col.population), brush),
  plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
  y(col.life), brush(col.gdp, { at: [3000, 20000] })))
10K 1K 80 70 60 50 40 1000M 500M 0 Gdp Life Population 1K 10K 40 50 60 70 80 Life Gdp

Wherever the flat plot is drawn, a drag selects only when it starts in its panel.

Alone, the cube has no other plot’s selection to show, so gog refuses its bare brush, as it refused brush(gdp) above. The message suggests two fixes: name the bound, or put the cube beside a brushed flat plot. A plot on a globe and a polar plot work the same way. Beside such a plot, a bare brush shows its selection, and alone it is refused.

39.12 Changing the bound yourself

After the first one, every brushed plot in this chapter states where its selection starts. In the web edition, drag across a flat panel and your range replaces the sentence’s bound. The plot redraws as you drag. On a flow, a click on a slot moves the bound instead, as Selecting one place shows.

This changes nothing about the sentence, and the cube works the same way. space(turn =, tilt =) chooses the angle a plot starts at, and the pointer moves it from there. brush(gdp, at = ) chooses the bound a plot starts with, and the pointer replaces it from there. Drag to find a selection, read its two ends off the axis, and write them into the sentence.

In the PDF, each plot shows the bound its sentence asked for. That is why naming it matters even though a reader of the web edition can replace it: that reader starts from it too.

Like a bare brush, a brush that names a column but no at selects nothing until a reader drags. On a polar plot a drag never changes the selection. So there, as in the cube, a brush must name its bound, or show the selection of a flat plot beside it. Where the page cannot find a row explains why. A flow drawn in polar is the one exception, because a click on a slot selects.

39.13 What the page adds

A flat brushed plot on a web page carries two lines of controls under it. None of them are in the sentence, and that is the rule rather than an omission. They report a selection, or they change how you look at one. Not one of them changes what the plot claims about the data, so none of them needs a word in the grammar. A plot in the cube follows the same rule: space() names the angle the plot starts at, and turning it needs no word. It carries two lines of controls, or three when it is brushed.

The first line is the one every plot carries:

Control What it does
the magnifiers, − and + Makes the picture smaller or larger
the frame button (four corners around a small square) Returns to the whole picture
the camera Saves what you are looking at as a picture file

It is first, directly under the picture, on a plain plot and a brushed one alike. This matters. A reader who has found the magnifiers once finds them in the same place under every plot in this book, rather than in a place that moves with whatever else the plot needed.

A plot that also plays carries three more buttons at the end of that same line, which pause the sequence and step through its frames. Play sets them out.

The second line is what a brush adds:

Control What it does
drag: and its icons Says what dragging the plot will do, and switches it: a rectangle, a free shape, or moving the picture. Absent on a flow, where a click on a slot selects. Absent also where no axis shows any column a brush names, because a drag there has no bound to change
40 of 142 selected How many rows the current bound selects. On a played plot this counts every frame, not the one on screen. Plots composed with | or / share this count. When they read the same table, each row is counted once, and only if all their bounds select it
show rows Opens a table of those rows; while the table is open the button reads hide rows. A long selection arrives ten at a time, with ‹ and › under the table
clear Puts the selection back to what the sentence asked for
unstamp Removes every stamp at once (Stamping a point explains stamps). With two or more, the button shows how many. Absent until you have made one

In the cube and on a polar plot, the line a brush adds holds only the count and show rows. In the cube it comes third, under the line that shows the angle. When a cube or a polar plot is beside a brushed flat plot, the arrangement carries the whole line, because a drag on the flat plot selects. With a cube, it is still the third line.

A control drawn as an icon names itself when the pointer rests on it. So do clear and unstamp, which say what they undo.

39.13.1 Dragging a box

A bound written in the sentence is a starting point, and a drag replaces it. Drag across the panel. A dotted box follows the pointer, and it shows exactly what is bound. On a plot with one brush the box spans the whole panel in the other direction, because that direction was not selected. On a plot with two, it is a rectangle. A box that narrowed an axis you never bound would claim a range you did not choose. The box disappears when you release the pointer, and from then on the dimming shows the bound.

Dragging in either direction gives the same bound, so you can start from any corner. A click on empty space empties the selection, so nothing is dimmed. A click that lands on a mark does something else, and Stamping a point is about that.

39.13.2 Drawing around a group

A rectangle is what a sentence can say. Some groups are not rectangles. The points of a scatter often form a shape: a narrow tail to one side, a curve, a group far from the rest. A box drawn around such a group includes points you did not mean.

The second icon beside drag: is the free shape. Choose it, then draw around the group with the pointer held down. The outline closes itself when you release the pointer, and everything inside it is selected. The count and the row table read exactly as they do for a box, because a shape and a bound ask the same question: which rows do you mean.

Nothing about the sentence changes. You do not type a shape, and the PDF still shows the bound the sentence named. Drawing one is a way of looking, like turning a cube, rather than a claim about the data.

That is also why the grammar has no word for it. Other tools call the gesture a lasso, and gog has no such word. Written down, a drawn shape is a long list of corner points, too long to type or read as part of a sentence. So you can draw the shape, but you cannot write it.

Two restrictions apply. A free shape needs two continuous axes, so where one axis is categorical the drag stays a rectangle. And clear removes a free shape and returns to the bound the sentence named. It cannot restore the shape, because no sentence can state a drawn shape.

39.13.3 Reading what you selected

The dimming shows where a group is, but not which rows are in it. The count says how many rows are selected, and show rows opens the row table. It lists the columns the sentence maps, not every column of your data, because the columns you did not plot are not what you are looking at. On a flow it also lists the columns named in flow(), so each row shows its place in each of them.

A long selection arrives ten rows at a time. The line under the table says which rows you are reading, as 11–20 of 40, and the two arrows beside it move you through the rest. Every row you selected is reachable, and none of them pushes the plot off the screen. A selection short enough to fit shows no arrows at all.

The row table lists every row you selected, with those columns, in one place, away from the picture. It shows country names only when the sentence maps country, as the labeled plot in Which one is that? does. Draw a bound tight around one point and the table lists that point alone.

39.13.4 Pointing at a mark

The row table shows which rows a group holds. Sometimes you want only the values behind one point. Rest the pointer on a mark, and a small card beside it shows the values of that mark’s row. The card lists the columns the sentence names, and nothing else from your table.

This needs no brush. The card appears on a point, a text label, a bar or a zone tile drawn with no transform. On a bar or a tile, it appears anywhere on the rectangle. Where the page cannot find a row lists the plots that show no card, and says why. Here is a scatter with no brush in its sentence. It uses the gapminder_2007 table, shown at the top of this chapter:

data(gapminder_2007) + point + x(gdp) + y(life) + color(continent)
data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent)
data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent)
plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
  color(col.continent))
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: points, x is gdp, y is life, color by continent.”

In the web edition, rest the pointer on a point. The card shows that point’s continent, gdp and life. It does not name the country, because the sentence never names country. If the sentence sets how a number is written, as x(gdp, short = TRUE) does, the card writes it that way too (Writing the numbers). A date is written as a date, such as 2024-03-01, not as a number.

The card goes away when the pointer leaves the mark, and nothing stays on the picture. On a brushed plot, a click keeps the card on the picture, and the next section is about that. On a plot without brush, a click does nothing.

39.13.5 Stamping a point

The pointer shows one row’s values at a time, and they disappear as soon as you move away from the mark. That is right for a quick look and wrong for a comparison. Suppose three points on a scatter interest you. Reading them one after another means remembering the first two while you look at the third.

On a brushed plot, click a mark and its values stay. That is a stamp: a dot on the mark, a short line, and a small card saying what the pointer said. Click as many as you want to compare. The card is on the picture beside the mark it belongs to, which is the difference between a stamp and the row table. The row table names rows. A stamp labels the plot.

A card needs space on the picture. On a crowded scatter a card can cover the points you want to read. So drag a card and it goes where you put it. The line stretches to follow, and a long line ends in an arrowhead at the stamped point. Move four cards out to the edges and the picture underneath stays whole.

A card keeps the distance you gave it from its point. Zoom in, pan across, or drag a new bound, and the card moves with its point. Zoom far enough that the point leaves the picture on screen, and the whole stamp goes out of sight with it. It has nothing left to point at, and a card left over an unrelated corner would claim a point that is not there. Bring the point back and the card is where you left it.

A played plot works the same way, for the same reason. A row there is one country in one year, so a stamp made in 1972 names something the 1987 frame does not draw. The stamp is hidden while the other frames run, and it returns to its point when the sequence comes back to 1972. The card names the year it holds. Stamp the same country in three frames and each card appears in its own frame.

There are three ways to take a stamp off. The × in the corner of a card removes that card. Clicking the card itself does the same, as long as you did not move it: if the pointer moved between press and release, the click counted as a drag instead. unstamp removes every stamp at once.

clear does not remove the stamps, because a stamp is not a selection. A click on empty space does not remove them either. It empties the selection, so every point returns to full strength, and every stamp is still there. clear, unstamp and the frame button each undo one thing, which Three controls, three things sets out.

The camera saves the stamps too. Arrange the cards, press the camera, and the picture file carries the plot with your annotations on it, each card where you put it and each line pointing at its row. The camera saves what you are looking at, the same way it saves how far you have zoomed in and the angle a cube is turned to.

You do not type a stamp, and the PDF carries none.

A stamp is for deciding which points are worth naming. A plot with a name on every point is unreadable, and a plot with a name on the few that matter is the one you wanted. Stamping lets you try a few before you choose. Then put those rows in a small table of their own, give it to a text layer, and the picture carries the names itself, in the web edition and the PDF alike. A note is a layer shows how.

Arranging the cards by hand has a written form too. text * repel does the same job inside the sentence. Every label is placed outside its own dot, and one that moved far keeps a thin line back to the point it names. That is the stamp’s line again, written down rather than dragged. Stamp to decide what is worth naming, then write the labels down and let repel place them.

A stamp is for looking at the data. text is for telling a reader what it says.

Both can go into a presentation, and they are not the same there. A saved picture is fixed: it keeps one moment of one session and cannot be drawn again. A sentence with text in it draws again for anyone who has the data, in the PDF and on the page and in all four languages. Stamp and save to keep this one picture. Write the labels down to keep the plot itself.

39.13.6 Where the page cannot find a row

On a polar plot, the pointer shows no values, with a brush or without one. The reason is how the page finds the row under the pointer. From each row’s own values and the values at the ends of each axis, it computes where that row was drawn. That is exact when both axes are straight lines. A polar plot has no straight axes: it draws x as an angle and y as a distance from the center.

So the pointer shows nothing, rather than the values of the wrong row. Nothing can be stamped either. The page never reads positions from the picture itself. It only computes them.

The same reason applies to a drag. The page would read the box you drag as a range of values on a straight axis. On a polar plot, the rows in that range are drawn somewhere else, not under the box. So on a brushed polar plot, a drag never changes the selection.

The bound the sentence names still works: gog tests it against each row’s values, so the count and the dimmed marks agree. That is why gog accepts a brush with a bound on a polar plot, and no brush on a map. As in the cube, you choose the selection by naming the bound in the sentence, or by dragging across a flat plot beside the polar plot (A cube beside a flat plot). On a polar plot alone, a brush with no at would never select a row, so gog refuses it.

Here the medal bars from What can be brushed are bent into a circle. The bound in the sentence still selects China and Great Britain, as it did on straight axes:

data(medals) + bar + x(country) + y(gold) + polar() +
  brush(gold, at = c(20, 40))
(data(medals) + bar + x(col.country) + y(col.gold) + polar() +
  brush(col.gold, at = [20, 40]))
data(medals) + bar + x(:country) + y(:gold) + polar() +
  brush(:gold, at = [20, 40])
plot(data(medals), bar, x(col.country), y(col.gold), polar(),
  brush(col.gold, { at: [20, 40] }))
USA China Great Britain Russia Germany 10 20 30 40 Gold Country

“Given the medals: bars, x is country, y is gold, in polar, and let the reader select gold, starting from 20 to 40.”

In the web edition, drag across it: the selection does not change. A flow drawn in polar is the exception: a click on a slot selects, as Selecting a place in either column shows.

In the cube, as on a polar plot, the pointer shows no values, and nothing can be stamped. There the viewing angle moves every mark, so the page cannot compute where each row was drawn.

The pointer still shows values on a plot with a layer whose marks are not drawn at their rows’ values. jitter sets each point beside its value, repel moves each label away from the others, and a summary such as bin or mean draws one shape for many rows. As What can be brushed showed, a layer like that either refuses the brush or is drawn whole beside a layer that answers it. The pointer then reads that other layer, whose marks are drawn at their values. A flow is the exception. Its layers answer the brush, but none of its marks is drawn at a row’s value, so the pointer shows no values there.

Without a brush, the card passes the same test: it appears only on marks drawn at their own row’s values. A plot drawn only by a summary, such as bar * count or bar * mean, shows none, because each bar summarizes many rows. A line, a step, a path, an area or a box draws one shape through many rows, so it shows none either. A rule has one position, and the page needs two to find a row, so it shows none. Neither does a layer under jitter, dodge, stack or repel, because each of those moves a mark away from its own value. Add a layer that draws each mark at its row’s values, such as point, and the card appears on that layer’s marks. In polar, in the cube, on a map and on a globe, no layer shows it.

One more limit is size. A plot without brush whose table has more than 20,000 rows shows no card, because carrying all those rows would make the page much larger. Your data on the page says what the page carries.

39.13.7 Looking closer

A crowded corner is easier to read when it is larger. + and − magnify the picture, and the frame button returns to the whole of it. Zooming in switches the drag to panning, because a reader who has just looked closer usually wants to move around inside the picture. The frame button sets the drag back to selecting. You can switch by hand at any time with the icons beside drag:. The pointer also tells you which mode you are in: a crosshair to draw a box, an open hand to move the picture. A brushed polar plot has no drag: icons, because a drag there never selects. There, once you zoom in, a drag moves the picture.

A plot whose axes show no column a brush names has a third shape: a plain arrow. A drag there has no bound to change, so it draws no box. Resting the pointer on a point still shows its card, and a click still stamps it. Once you zoom in, a drag moves the picture, as on a polar plot.

This is a view, so it recomputes nothing. The bars are the same bars and the ticks are the same ticks, drawn larger. Zoom far enough into a histogram and you will see the same bins, not finer ones. If you want the statistics re-run over a narrower range, that is limits, the atom this chapter compared with brush.

39.13.8 Three controls, three things

Three of these controls undo something, and pressing the wrong one removes work you meant to keep. clear acts on the selection, unstamp acts on the stamps, and the frame button acts on the view. Each one undoes only its own thing, so pressing one never costs you the other two. The frame button is on the line above the other two, beside the magnifiers. It puts back the view they changed.

The × on a card is a fourth undo, and it is not under the plot at all. It belongs to the card rather than to the plot. A control under the picture acts on the whole plot: all the stamps, the whole selection or the whole view. A control on a card acts on that card alone.

The PDF has none of these controls, and it shows the bound the sentence named.

39.13.9 Your data on the page

Before you publish a plot on a web page, know what data it carries. Two kinds of plot in this chapter need the table in the page. The browser redraws a brushed plot as the reader drags, and it reads the rows of a plot that shows the card. In both, the page carries every row of the table, but only the columns the sentence names.

The rows are all there, though, each with its exact values in the columns the page carries. So check those values before you publish, the way you would check any file before sharing it. A plot without brush whose table has more than 20,000 rows carries none of its rows, and shows no card. Plots that carry the table lists every kind of plot that carries its table, and what a saved SVG file carries instead.

39.14 In short

A selection runs after the axes and statistics are computed: it dims rows, removes none, and leaves the axes where they were. The column decides the reading, a range for numbers and a set for categories. A bound tests a column’s values rather than a panel, so the brushed plots of one table share one selection. Everything a brush needs is already inside the web page: every row of the table, but only the columns the sentence names.

39.15 What comes next

Selection is the last atom this book teaches. The chapters so far started from the grammar; the cookbook starts from your data instead: one variable first, then two, then more.