| 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 |
24 Setting vs mapping
Should a mark’s color come from your data, or from you? There are two ways to make something red, and gog keeps them apart on purpose.
Mapping lets the data decide a visual property. color(continent) says let the continent decide the color, so gog builds a scale, assigns a color per continent, and draws a legend to decode it.
Setting means you give the property a value yourself. style(color = "tomato") says make them all tomato. No column is read, no scale is built, and no legend appears, because there is nothing to decode.
# MAP: color answers "which continent?"
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
title("Mapped: a legend decodes the color")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
title("Mapped: a legend decodes the color"))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
title("Mapped: a legend decodes the color")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), title("Mapped: a legend decodes the color"))“Given gapminder 2007: points, x is gdp, y is life, color by continent.”
# SET: color answers nothing; note the missing legend
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "tomato") +
title("Set: no legend, because there is nothing to decode")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "tomato") +
title("Set: no legend, because there is nothing to decode"))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "tomato") +
title("Set: no legend, because there is nothing to decode")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "tomato" }),
title("Set: no legend, because there is nothing to decode"))“Given gapminder 2007: points, x is gdp, y is life, colored tomato.”
24.1 Why a constant is not a channel
Every channel in gog maps a data column to a visual property. A constant maps nothing, so it does not meet the definition. The same test settles a question from later in the book. Rotating a 3-D plot is not a channel either, because it is a property of the coordinate space.
A practical test gives the same answer: a mapping to color, size, shape, pattern or opacity draws a legend, and a setting draws none. If the plot needs a legend to be read, it was a mapping. A mapping written with legend = FALSE leaves its legend out, so a plot with no legend can still use a mapping.
24.2 What you can set
You have just seen one setting on a point. Before you write another, you need to know whether the mark can carry it, and the answer differs between a bar and a point.
In the grid below, every setting is a row and every mark is a column. The grid shows what each mark takes and what it refuses, including combinations you would not have guessed were legal. Legal is not the same as useful. Law 8 says the grammar guarantees a plot is legal, and you judge whether it is useful. The grid shows the limit of what can be expressed. Combinations is the companion grid: what a mark lets you map rather than set.
| Setting | point |
line |
area |
bar |
step |
interval |
box |
ribbon |
text |
path |
rule |
zone |
surface |
edge |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
color |
✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ |
opacity |
✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ |
size |
✓︎ | ✓︎ | — | — | ✓︎ | ✓︎ | ✓︎ | — | ✓︎ | ✓︎ | ✓︎ | — | — | ✓︎ |
shape |
✓︎ | — | — | — | — | — | — | — | — | — | — | — | — | — |
pattern |
— | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | ✓︎ | — | ✓︎ | ✓︎ | ✓︎ | — | ✓︎ |
border_color |
✓︎ | — | — | ✓︎ | — | — | ✓︎ | — | ✓︎ | — | — | ✓︎ | ✓︎ | — |
border_size |
✓︎ | — | — | ✓︎ | — | — | ✓︎ | — | ✓︎ | — | — | ✓︎ | ✓︎ | — |
caps |
— | — | — | — | — | ✓︎ | ✓︎ | — | — | — | — | — | — | — |
center |
— | — | — | — | — | ✓︎ | — | — | — | — | — | — | — | — |
nudge |
— | — | — | — | — | — | — | — | ✓︎ | — | — | — | — | — |
arrow |
— | — | — | — | — | — | — | — | — | ✓︎ | — | — | — | ✓︎ |
reach |
— | — | — | — | — | — | — | — | — | — | ✓︎ | — | — | — |
angle |
— | — | — | — | — | — | — | — | ✓︎ | — | — | — | — | — |
gap |
— | — | — | ✓︎ | — | — | ✓︎ | — | — | — | — | — | — | — |
(✓︎ the setting is available · — the mark’s geometry cannot carry it. Generated live from the engine, like the Combinations grid, so it always matches what style() accepts.)
Those rows are every setting there is. x, y, z, group, label and play are absent because they are channels and nothing else, which Two questions, not one explains.
color (CSS name or hex) and opacity (0–1) can be set on every mark. The other rows follow from each mark’s geometry rather than from a list someone chose. A setting is available on every mark whose geometry can carry it, and it behaves the same way on all of them. That is the settable rule: a setting covers its whole geometry class, the group of marks drawn with the same kind of shape.
size is a measurement in pixels: a radius, a stroke width, or a font size. It belongs to point, text, every stroke mark, and the lines of a box. The grid marks it — for bar, area, ribbon, zone and surface. Their positions already decide their size, so there is nothing to set. shape chooses one of seven glyphs. Only point takes it.
pattern is the texture of a mark’s paint. It is one property drawn differently by each geometry, the way color is a fill on a bar and a stroke on a line. On the six stroke marks (line, step, path, interval, rule, edge) it is the dash: "solid"/"dashed"/"dotted"/"dotdash"/"longdash", so an error bar can be dashed too. On the five filled regions (bar, box, area, ribbon, zone) it is a fill texture: "solid"/"hatch"/"crosshatch"/"stripes"/ "grid"/"dots". A surface takes no hatch: the hatch is drawn flat on the screen, so on a tilted sheet it would look denser on some faces than on others. Those hatchings keep a plot readable in grayscale or in black-and-white print. It changes the paint, never the shape: a dashed line and a solid one pass through the same points. A step is a different mark because it holds each value until it changes.
A trend drawn through the data is often dashed, so that it reads as a summary rather than as a measurement:
| 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 * mean + x(year) + y(life) +
style(pattern = "dashed", size = 1.5) + y_label("Mean life expectancy") +
title("A dashed trend line")(data(gapminder_asia) + line * mean + x(col.year) + y(col.life) +
style(pattern = "dashed", size = 1.5) + y_label("Mean life expectancy") +
title("A dashed trend line"))data(gapminder_asia) + line * mean + x(:year) + y(:life) +
style(pattern = "dashed", size = 1.5) +
y_label("Mean life expectancy") + title("A dashed trend line")plot(data(gapminder_asia), layer(line, mean), x(col.year), y(col.life),
style({ pattern: "dashed", size: 1.5 }),
y_label("Mean life expectancy"), title("A dashed trend line"))“Given gapminder Asia: a line derived by mean, x is year, y is life, with pattern dashed and size 1.5.”
On a fill the same setting hatches the region, and the hatch stays visible on a black-and-white photocopy:
data(gapminder_2007) + bar * mean + x(continent) + y(life) +
style(pattern = "crosshatch") +
y_label("Mean life expectancy") +
title("A crosshatched bar")(data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life) +
style(pattern = "crosshatch") +
y_label("Mean life expectancy") +
title("A crosshatched bar"))data(gapminder_2007) + bar * mean + x(:continent) + y(:life) +
style(pattern = "crosshatch") + y_label("Mean life expectancy") +
title("A crosshatched bar")plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life), style({ pattern: "crosshatch" }),
y_label("Mean life expectancy"), title("A crosshatched bar"))“Given gapminder 2007: bars derived by mean, x is continent, y is life, with pattern crosshatch.”
The texture is one per layer: style() gives every bar in the layer the same hatch. It cannot give each category its own hatch. That is a mapping (a category to a texture), and the pattern() channel does it for fills and strokes the way shape() does it for points.
border_color and border_size draw an outline separate from the fill. A border_size of 0 means no border. Both belong to the five closed-shape fills (bar, box, point, zone and surface). On those marks you cannot draw the outline as a second layer. A setting is the only way to get it. color fills the shape; border_color outlines it; the two are independent, so a box can be pale inside and dark-edged:
data(gapminder_2007) + box + x(continent) + y(life) +
style(color = "lightsteelblue", border_color = "#26456e", border_size = 1.6) +
y_label("Life expectancy") + title("A box: light fill, dark outline")(data(gapminder_2007) + box + x(col.continent) + y(col.life) +
style(color = "lightsteelblue", border_color = "#26456e", border_size = 1.6) +
y_label("Life expectancy") + title("A box: light fill, dark outline"))data(gapminder_2007) + box + x(:continent) + y(:life) +
style(color = "lightsteelblue", border_color = "#26456e", border_size = 1.6) +
y_label("Life expectancy") + title("A box: light fill, dark outline")plot(data(gapminder_2007), box, x(col.continent), y(col.life),
style({ color: "lightsteelblue", border_color: "#26456e",
border_size: 1.6 }), y_label("Life expectancy"),
title("A box: light fill, dark outline"))“Given gapminder 2007: boxes, x is continent, y is life, colored lightsteelblue, with border color #26456e and border size 1.6.”
The same two settings outline a point, exactly as on bar and box, so each point stays visible where points overlap:
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "steelblue", opacity = 0.7, size = 5, border_color = "white", border_size = 1) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A point with a white border")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "steelblue", opacity = 0.7, size = 5, border_color = "white", border_size = 1) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A point with a white border"))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "steelblue", opacity = 0.7, size = 5, border_color = "white", border_size = 1) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A point with a white border")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "steelblue", opacity: 0.7, size: 5,
border_color: "white", border_size: 1 }), x_label("GDP per person"),
y_label("Life expectancy"), title("A point with a white border"))“Given gapminder 2007: points, x is gdp, y is life, colored steelblue, with opacity 0.7, size 5, border color white and border size 1.”
opacity fades what color paints, which here is the fill. The white outline stays fully opaque. To make an outline partly transparent, put the transparency in border_color: in "#4682b44d", the last two hex digits make steelblue 30% opaque.
On text, the same two settings draw a halo, a band of color under the letters. The Text chapter shows one.
A curve fill (area, ribbon) takes no border. Its edge is a data curve, so you draw it as a line layer (area + line, line * bounds), and a line can be dashed, which a border cannot. And a cross point has no fill to outline, so a border on a cross-shaped glyph has nothing to draw on, and gog refuses it. When shape is mapped to a column, the categories drawn as crosses go without a border, and gog prints a message that names them.
If you set border_size without border_color, gog chooses the outline’s color in one of three ways. A bar, a box or a surface draws the outline in the color of its fill. In a histogram the outline is drawn in the panel’s background color instead. It separates one bar from the next, as The panel’s background shows. A zone’s outline and a halo on text are drawn in that background color too. A point draws no line around itself, so its outline is a fixed dark gray.
The last seven rows of the grid above each control one part of a mark’s drawing. caps shows or hides the caps at the ends of a whisker, on an interval and a box. center shows or hides an interval’s center dot. nudge moves a text label off its position. arrow draws an arrowhead at one end, or both ends, of a path or an edge. reach says how far a rule reaches across the panel. And angle turns the labels of a text layer by a number of degrees (Turning a label). gap sets how much of the slot of a bar or a box is left empty (The gap beside each bar). A row has a ✓︎ under every mark that has the part its setting controls. For caps that part is a whisker; for arrow, a start and an end that come from the data.
A line shows why arrow does not belong to it. An arrowhead marks the direction the data runs in. A line sorts its vertices by x, so its last point is simply the largest x in the table, not the last row, and an arrowhead there would mark the largest x rather than the end of the data.
data(gapminder_asia) + path + x(gdp) + y(life) + color(country) +
style(arrow = "end") +
x_label("GDP per person") + y_label("Life expectancy") +
title("Each path ends at 2007")(data(gapminder_asia) + path + x(col.gdp) + y(col.life) + color(col.country) +
style(arrow = "end") +
x_label("GDP per person") + y_label("Life expectancy") +
title("Each path ends at 2007"))data(gapminder_asia) + path + x(:gdp) + y(:life) + color(:country) +
style(arrow = "end") + x_label("GDP per person") +
y_label("Life expectancy") + title("Each path ends at 2007")plot(data(gapminder_asia), path, x(col.gdp), y(col.life),
color(col.country), style({ arrow: "end" }), x_label("GDP per person"),
y_label("Life expectancy"), title("Each path ends at 2007"))“Given gapminder Asia: paths, x is gdp, y is life, color by country, with arrow end.”
It takes "end", "start" or "both", not true or false. Arrowheads at both ends are often wanted, and a true-or-false value would need a second setting for them.
24.2.1 One color, any mark
Having set a point’s color once, you should not need a second spelling for a line or a bar.
style(color = ) is the same instruction whichever mark it attaches to. It fills points and bars, and it colors the stroke of a line:
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "red") +
title("point")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "red") +
title("point"))data(gapminder_2007) + point + x(:gdp) + y(:life) + style(color = "red") +
title("point")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "red" }), title("point"))data(gapminder_asia) + line + x(year) + y(life) + group(country) +
style(color = "red") +
title("line")(data(gapminder_asia) + line + x(col.year) + y(col.life) + group(col.country) +
style(color = "red") +
title("line"))data(gapminder_asia) + line + x(:year) + y(:life) + group(:country) +
style(color = "red") + title("line")plot(data(gapminder_asia), line, x(col.year), y(col.life),
group(col.country), style({ color: "red" }), title("line"))“Given gapminder Asia: lines, x is year, y is life, grouped by country, colored red.”
| 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) +
style(color = "red") +
title("bar")(data(medals) + bar + x(col.country) + y(col.gold) +
style(color = "red") +
title("bar"))data(medals) + bar + x(:country) + y(:gold) + style(color = "red") +
title("bar")plot(data(medals), bar, x(col.country), y(col.gold),
style({ color: "red" }), title("bar"))This next sentence looks like the same request, and it is not:
data(gapminder_2007) + point + x(gdp) + y(life) + color("red")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color("red")data(gapminder_2007) + point + x(:gdp) + y(:life) + color("red")plot(data(gapminder_2007), point, x(col.gdp), y(col.life), color("red"))Error:
! gog: `color("red")` binds a *value*, and a channel takes a *column*: `color(red)` maps the column called `red`.
To fix one value for the whole layer instead — no legend, nothing to decode — that is a setting: `style(color = "red")`.
color() takes a column. "red" in quotes is a value, so gog refuses it; color(red) without quotes would map a column called red. Setting a value and mapping a column are different requests, and gog makes you say which one you mean.
24.2.2 Several at once
A figure for publication rarely needs only one setting. Color, opacity, size and shape are decided together, so they are written together:
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "#2a6f97", opacity = 0.45, size = 7, shape = "diamond")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "#2a6f97", opacity = 0.45, size = 7, shape = "diamond"))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "#2a6f97", opacity = 0.45, size = 7, shape = "diamond")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "#2a6f97", opacity: 0.45, size: 7, shape: "diamond" }))“Given gapminder 2007: points, x is gdp, y is life, colored #2a6f97, with opacity 0.45, size 7 and shape diamond.”
One style() call carries as many settings as the mark can take, and every row in the layer is drawn with all of them. The four here are independent: remove any one and the other three are unchanged. Each mark’s chapter lists its settings in a section called What you can set, starting with Point.
24.2.3 CSS colors
A color usually comes as a hex value copied from a design document, or as a name remembered from another tool. Not every name you remember is one gog knows.
A name from CSS, the language web pages are styled with, and a hex value both work anywhere a color is accepted, in style() and in palette():
data(medals) + bar + x(country) + y(gold) +
style(color = "darkslateblue") +
title("A CSS color name")(data(medals) + bar + x(col.country) + y(col.gold) +
style(color = "darkslateblue") +
title("A CSS color name"))data(medals) + bar + x(:country) + y(:gold) +
style(color = "darkslateblue") + title("A CSS color name")plot(data(medals), bar, x(col.country), y(col.gold),
style({ color: "darkslateblue" }), title("A CSS color name"))gog refuses a name it does not recognize and offers the closest valid one, instead of passing it on to be painted black:
data(medals) + bar + x(country) + y(gold) + style(color = "stelblue")data(medals) + bar + x(col.country) + y(col.gold) + style(color = "stelblue")data(medals) + bar + x(:country) + y(:gold) + style(color = "stelblue")plot(data(medals), bar, x(col.country), y(col.gold),
style({ color: "stelblue" }))Error:
! gog: `style(color = "stelblue")` is not a color. Did you mean "steelblue"? Use a CSS color name, or a hex value like "#4e79a7".
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
R users often assume R color names are CSS color names. They are not. R’s colors() includes numbered shades (gray80, gray50, steelblue3) that CSS does not have. gog uses CSS colors because that is what SVG accepts and what the other three bindings use too, so the vocabulary is the same in every language:
data(medals) + bar + x(country) + y(gold) + style(color = "gray80")data(medals) + bar + x(col.country) + y(col.gold) + style(color = "gray80")data(medals) + bar + x(:country) + y(:gold) + style(color = "gray80")plot(data(medals), bar, x(col.country), y(col.gold),
style({ color: "gray80" }))Error:
! gog: `style(color = "gray80")` is not a color. `gray80` is an R color name. gog uses CSS colors, which have no numbered shades — use "lightgray", "darkgray", or "gray", or a hex value like "#cccccc" for an exact shade.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
For an exact shade, give the hex value.
24.3 What settings are for
The grid above says what you can set. Two uses cover most of what you will actually set.
Overplotting. With enough points, the dots cover one another and hide the shape they make together. Lowering opacity changes how the points are drawn, not the data:
| 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) + y(life) +
style(opacity = 0.2, size = 3) +
title("1704 points: low opacity reveals the density")(data(gm_all) + point + x(col.gdp) + y(col.life) +
style(opacity = 0.2, size = 3) +
title("1704 points: low opacity reveals the density"))data(gm_all) + point + x(:gdp) + y(:life) +
style(opacity = 0.2, size = 3) +
title("1704 points: low opacity reveals the density")plot(data(gm_all), point, x(col.gdp), y(col.life),
style({ opacity: 0.2, size: 3 }),
title("1704 points: low opacity reveals the density"))Where many points overlap, the color is darker, so the dense regions show.
A reference layer. When one layer is what the plot is about, the others should be drawn faintly:
data(gapminder_asia) + x(year) + y(life) +
line + group(country) + style(color = "lightgray") +
line * smooth + style(color = "firebrick", size = 3) +
title("Five countries in gray; the trend in red")(data(gapminder_asia) + x(col.year) + y(col.life) +
line + group(col.country) + style(color = "lightgray") +
line * smooth + style(color = "firebrick", size = 3) +
title("Five countries in gray; the trend in red"))data(gapminder_asia) + x(:year) + y(:life) + line + group(:country) +
style(color = "lightgray") + line * smooth +
style(color = "firebrick", size = 3) +
title("Five countries in gray; the trend in red")plot(data(gapminder_asia), x(col.year), y(col.life), line,
group(col.country), style({ color: "lightgray" }), layer(line, smooth),
style({ color: "firebrick", size: 3 }),
title("Five countries in gray; the trend in red"))“Given gapminder Asia: x is year, y is life, lines, grouped by country, colored lightgray, and also a line derived by smooth, colored firebrick, with size 3.”
24.4 Two questions, not one
A mapped opacity works on a point. Try the same binding on a line and gog refuses it.
A line is drawn with one stroke from its first point to its last. The stroke has one width and one opacity, so size and opacity are not channels on line:
data(gapminder_asia) + x(year) + y(life) + line + group(country) + opacity(life)data(gapminder_asia) + x(col.year) + y(col.life) + line + group(col.country) + opacity(col.life)data(gapminder_asia) + x(:year) + y(:life) + line + group(:country) +
opacity(:life)plot(data(gapminder_asia), x(col.year), y(col.life), line,
group(col.country), opacity(col.life))Error:
! gog: `opacity` cannot be bound to `line` — a line takes one opacity for the whole layer, not one per row. Drop the `life` mapping and set it with `style(opacity = )`, or use a mark that maps `opacity`: `point`, `bar`, `text` or `surface`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
But one stroke still has a width and an opacity. Those are settings:
data(gapminder_asia) + line + x(year) + y(life) + group(country) +
style(size = 4, opacity = 0.5) +
title("Set on a line: one width, one opacity")(data(gapminder_asia) + line + x(col.year) + y(col.life) + group(col.country) +
style(size = 4, opacity = 0.5) +
title("Set on a line: one width, one opacity"))data(gapminder_asia) + line + x(:year) + y(:life) + group(:country) +
style(size = 4, opacity = 0.5) +
title("Set on a line: one width, one opacity")plot(data(gapminder_asia), line, x(col.year), y(col.life),
group(col.country), style({ size: 4, opacity: 0.5 }),
title("Set on a line: one width, one opacity"))This is where the distinction matters most. “Can a column be mapped here?” and “Does this mark have this property?” are different questions, and line is where the answers differ.
Every mark answers both. Five properties are a channel and a setting under the same name, and this is what each mark says about them:
| Mark | color |
size |
shape |
pattern |
opacity |
|---|---|---|---|---|---|
point |
map or set | map or set | map or set | — | map or set |
line |
map or set | set only | — | map or set | set only |
area |
map or set | — | — | map or set | set only |
bar |
map or set | — | — | map or set | map or set |
step |
map or set | set only | — | map or set | set only |
interval |
map or set | set only | — | map or set | set only |
box |
map or set | set only | — | map or set | set only |
ribbon |
map or set | — | — | map or set | set only |
text |
map or set | map or set | — | — | map or set |
path |
map or set | set only | — | map or set | set only |
rule |
map or set | set only | — | map or set | set only |
zone |
map or set | — | — | map or set | set only |
surface |
map or set | — | — | — | map or set |
edge |
map or set | set only | — | map or set | map or set |
A set only cell means the mark has the property, but one value for the whole layer, not one per row: a line has one width, and a rule layer one opacity. Bind a column there and the refusal explains that, and names the setting to write instead.
The table above has a column for five of the kernel’s eleven channels. The other six, x, y, z, group, label and play, are only ever mapped, and they have no row on the settings grid either. Positions, groups, labels and the steps of an animation come from data, or they do not exist at all. There is nothing to set. Nine settings are only ever set, and never appear on the channel grid. border_color, border_size, caps, center, nudge, angle, arrow, reach and gap read no column.
24.5 Mapping and setting the same property
It is easy to write both by accident: a color(continent) left from an earlier draft, and a style(color = ) added to try one shade.
Doing both would mean one of the two instructions is silently discarded, which gog never does:
data(gapminder_2007) + point + x(gdp) + y(life) +
color(continent) + style(color = "tomato")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
color(col.continent) + style(color = "tomato"))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
style(color = "tomato")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), style({ color: "tomato" }))Error:
! gog: `color(continent)` maps color and `style(color = "tomato")` sets it — one layer cannot do both. Keep `color(continent)` to show the data, or `style(color = "tomato")` to fix every point at one value.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Write the one you meant.
A mapping can also be one you did not write. A heatmap drawn with zone * bin colors each cell by the number of rows in it. Its sentence has no color(). That color is still a mapping, so the sentence cannot also set a color:
data(gapminder_2007) + zone * bin + x(gdp) + y(life) + style(color = "black")data(gapminder_2007) + zone * bin + x(col.gdp) + y(col.life) + style(color = "black")data(gapminder_2007) + zone * bin + x(:gdp) + y(:life) +
style(color = "black")plot(data(gapminder_2007), layer(zone, bin), x(col.gdp), y(col.life),
style({ color: "black" }))Error:
! gog: `zone * bin` maps color to its `count` by itself, and `style(color = "black")` sets it — one layer cannot do both. Drop `style(color = "black")` to keep the `count` colors, and give `palette()` the colors you want them drawn in.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
To draw the counts in other colors, keep the mapping and choose its colors with palette().
24.6 style and the layer it follows
A trend line is easiest to see when the points under it are faint and the line is not, so the two layers need different settings.
color() written before any mark applies to every layer that can take it. style() attaches to the nearest mark before it, and to no other mark, because a setting written for one layer should not change another:
data(gapminder_2007) + x(gdp) + y(life) +
line * smooth +
point + style(color = "tomato", opacity = 0.4) +
title("Only the points are tomato")(data(gapminder_2007) + x(col.gdp) + y(col.life) +
line * smooth +
point + style(color = "tomato", opacity = 0.4) +
title("Only the points are tomato"))data(gapminder_2007) + x(:gdp) + y(:life) + line * smooth + point +
style(color = "tomato", opacity = 0.4) +
title("Only the points are tomato")plot(data(gapminder_2007), x(col.gdp), y(col.life), layer(line, smooth),
point, style({ color: "tomato", opacity: 0.4 }),
title("Only the points are tomato"))To style both layers, write a style() after each mark.
24.7 palette: choosing the colors a mapping uses
Setting a color and mapping a column are the two choices this chapter has kept apart. Sometimes you want part of each: the continents separated by color, and those five colors chosen by you rather than by gog.
style(color = ) is written instead of a mapping. palette() keeps the mapping and chooses which colors the categories receive:
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
palette(c("tomato", "steelblue", "seagreen", "goldenrod", "orchid"))(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
palette(["tomato", "steelblue", "seagreen", "goldenrod", "orchid"]))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
palette(["tomato", "steelblue", "seagreen", "goldenrod", "orchid"])plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent),
palette(["tomato", "steelblue", "seagreen", "goldenrod", "orchid"]))“Given gapminder 2007: points, x is gdp, y is life, color by continent, with a palette of tomato, steelblue, seagreen, goldenrod and orchid.”
A palette needs a mapping to give its colors to. With no column bound to color it has nothing to color, so it is refused rather than accepted and then ignored:
data(gapminder_2007) + point + x(gdp) + y(life) + palette("okabe")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + palette("okabe")data(gapminder_2007) + point + x(:gdp) + y(:life) + palette("okabe")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
palette("okabe"))Error:
! gog: `palette()` chooses the colors a `color` mapping hands out, and nothing here maps `color`, so it would have no effect. Bind a column with `color(<column>)` to use it — or, to paint every mark one color, that is a setting rather than a palette: `style(color = "…")`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The message names two fixes, because there are two things you may have meant. To give each category its own color, bind the column with color(continent). To paint every mark one color, write style(color = "steelblue"), which is a setting rather than a palette.
The heatmap in Mapping and setting the same property has a color mapping you did not write, and so does a zone * density (Zone). palette() works on both.
One color, though, is not a palette. palette() takes either a palette name ("gog", "okabe") or the colors themselves, and it reads a single word as a palette name. "red" is not a palette name, so gog refuses it rather than quietly using the default palette:
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent) +
palette("red")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent) +
palette("red"))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
palette("red")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), palette("red"))Error:
! gog: `palette("red")` is not a known palette. Named palettes are gog, okabe, soft for categories; blue, viridis, magma, inferno, plasma, cividis, gray for numbers; blue_red, brown_teal for numbers that diverge from a center. `"red"` is a color, not a palette. To paint every mark one color use `style(color = "red")`; to give each category its own color, give `palette()` several colors, "red" among them.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Making everything one color is setting, not choosing a palette, so use style(color = "red").
24.8 theme: settings that belong to no mark
The gridlines distract on a slide. You want the category names along the bottom at 45 degrees.
style() sets a mark’s own appearance. The gridlines, the shape of the panel and the angle of the tick labels belong to no mark, so they have a word of their own: theme().
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
color(continent) + theme(grid = "none")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + theme(grid = "none"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) + theme(grid = "none")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent), theme({ grid: "none" }))“Given gapminder 2007: points, x is gdp on a log scale, y is life, color by continent.”
The gridlines are gone, and nothing about the points changed, because theme() never changes a mark.
grid names each set of gridlines by the axis whose ticks they mark, not by the direction they run. So "x" keeps the lines belonging to the x axis and removes the rest. That naming is the same when the plane is bent into a circle. The next plot keeps the x axis gridlines in polar, a space later in the book. They radiate from the center instead of running from bottom to top:
| direction | bearing | speed | season |
|---|---|---|---|
| N | 19.612260 | 10.1 | Winter |
| N | 357.049499 | 11.0 | Winter |
| N | 349.709737 | 13.8 | Winter |
| N | 2.602499 | 7.4 | Winter |
| N | 338.210715 | 3.7 | Summer |
data(winds) + bar * count + x(direction) + polar() + theme(grid = "x")data(winds) + bar * count + x(col.direction) + polar() + theme(grid = "x")data(winds) + bar * count + x(:direction) + polar() + theme(grid = "x")plot(data(winds), layer(bar, count), x(col.direction), polar(),
theme({ grid: "x" }))“Given the winds: bars derived by count, x is direction, in polar.”
ratio sets the panel’s width divided by its height. The image keeps the size it was given and the panel shrinks inside it, so the plot still takes the same room on a page:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) + theme(ratio = 1)data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) + theme(ratio = 1)data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
theme(ratio = 1)plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), theme({ ratio: 1 }))tick_angle turns the x tick labels to the angle you give, in degrees. You do not need it to keep names apart. When category names do not fit side by side, gog turns them 90 degrees by itself, to read upward. Scales shows what gog does when even turned names do not fit. tick_angle is for choosing the angle yourself. gog keeps the angle you choose, even when fewer names fit at it, and says so. The bottom margin grows to hold the turned labels, and the axis name is below them:
data(gapminder_2007) + bar * mean + x(continent) + y(life) +
theme(tick_angle = 45)(data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life) +
theme(tick_angle = 45))data(gapminder_2007) + bar * mean + x(:continent) + y(:life) +
theme(tick_angle = 45)plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life), theme({ tick_angle: 45 }))axis_label says where an axis’s name is placed. By default each name is centered along its own axis, so the y name is turned through 90 degrees. Ask for "end" and both names move to their axis’s far end and stay horizontal, so nothing on the plot has to be read sideways. It is one property, not one per axis, so the two names never follow different rules:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
x_label("GDP per person") + y_label("Life expectancy (years)") +
theme(axis_label = "end")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
x_label("GDP per person") + y_label("Life expectancy (years)") +
theme(axis_label = "end"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
x_label("GDP per person") + y_label("Life expectancy (years)") +
theme(axis_label = "end")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), x_label("GDP per person"),
y_label("Life expectancy (years)"), theme({ axis_label: "end" }))“Given gapminder 2007: points, x is gdp on a log scale, y is life, with the axis names at their ends.”
A legend on the right makes the panel narrower by the legend’s own width. On a narrow figure, such as one column of a journal page, that leaves little room for the data. legend says which side of the plot the legends are on. The default is "right", and the other sides are "left", "top" and "bottom". Above or below the panel, a legend’s entries are placed side by side, so the panel gets shorter instead of narrower:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
color(continent) + theme(legend = "bottom")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + theme(legend = "bottom"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) + theme(legend = "bottom")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent), theme({ legend: "bottom" }))“Given gapminder 2007: points, x is gdp on a log scale, y is life, color by continent, with the legend at the bottom.”
The panel now takes the width the legend used beside it, and the five continents are listed side by side under the x axis’s name. To leave one legend out instead, write legend = FALSE on its channel (Leaving a legend out).
font_size sets how big the plot’s text is. It is one number, not three. The number itself is the size of a tick label, in pixels. The axis names and the title are a fixed proportion larger. The default of 11 draws tick labels at 11, axis names at 13 and the title at 16. Ask for 16 and those three become 16, 19 and 23:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
title("Bigger text, one number") + theme(font_size = 16)(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
title("Bigger text, one number") + theme(font_size = 16))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
title("Bigger text, one number") + theme(font_size = 16)plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), title("Bigger text, one number"), theme({ font_size: 16 }))Look at the margins. They grew with the text, because the room an axis needs is measured from the labels that go in it. Larger text therefore never overlaps the panel.
A smaller number leaves more room for the data in a small figure:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
title("Smaller text, same plot") + theme(font_size = 8)(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
title("Smaller text, same plot") + theme(font_size = 8))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
title("Smaller text, same plot") + theme(font_size = 8)plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), title("Smaller text, same plot"), theme({ font_size: 8 }))One number sets all the text, so one plot never carries three unrelated text sizes. You cannot ask for a bigger title on its own: the title grows only when the tick labels grow.
The number is a measurement in pixels, not a multiplier. font_size = 1.5 is refused, because a number that small is almost always meant as a multiplier:
data(gapminder_2007) + point + x(gdp) + y(life) + theme(font_size = 1.5)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + theme(font_size = 1.5)data(gapminder_2007) + point + x(:gdp) + y(:life) + theme(font_size = 1.5)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
theme({ font_size: 1.5 }))Error:
! gog: `theme(font_size = )` is how many pixels a tick label is, not a multiplier, so it needs one number of at least 4. The default is 11, and the axis names and the title are derived from it.
font_size does not name a typeface. The engine measures text with its own table of character widths, so it can lay out a plot with no font installed. It asks for the reader’s system font, and the computer that opens the plot supplies the letters.
A preset is a named bundle of these properties, and you ask for one by its name. You can still change any property in it, so one small change never means writing every property yourself:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
theme("minimal", ratio = 1)(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
theme("minimal", ratio = 1))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
theme("minimal", ratio = 1)plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), theme("minimal", { ratio: 1 }))gog has three presets: "gog", the default, "minimal" and "bw". "minimal" removes the gridlines, the same as theme(grid = "none"), and ratio = 1 then makes the panel square.
24.9 Axes, legends, gridlines and the frame
A map, a small figure or a slide often needs less around the data than gog draws by default. The settings that leave each part out are explained in different places, so this section collects them in one table. An axis and a legend are guides: each one decodes a channel, so you leave it out inside that channel’s call, as in color(continent, legend = FALSE). The gridlines and the placement of the legends belong to the whole plot, so theme() sets them. theme() also sets the frame: by default, the two lines along the bottom and the left of the panel.
| To | Write | Explained in |
|---|---|---|
| leave out an axis: its tick marks, its numbers and its name | x(gdp, axis = FALSE) |
Scales |
| leave out only an axis’s name | x_label("") |
this section, below |
| leave out a legend | color(continent, legend = FALSE) |
Channels |
| leave out the gridlines | theme(grid = "none"), or "x" or "y" to keep one set |
theme, above |
| leave out the frame, or draw all four sides | theme(frame = "none"), or "full" for all four sides |
this section, below |
| put the legends on another side | theme(legend = "bottom"), "top" or "left" |
theme, above, and Channels |
The plot below uses four of them at once:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life, axis = FALSE) +
color(continent) + theme(grid = "x", frame = "none", legend = "bottom")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life, axis = False) +
color(col.continent) + theme(grid = "x", frame = "none", legend = "bottom"))data(gapminder_2007) + point + x(:gdp, scale = "log") +
y(:life, axis = false) + color(:continent) +
theme(grid = "x", frame = "none", legend = "bottom")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life, { axis: false }), color(col.continent),
theme({ grid: "x", frame: "none", legend: "bottom" }))“Given gapminder 2007: points, x is gdp on a log scale, y is life without an axis, color by continent, with the legend at the bottom.”
The y axis is gone, with its numbers and its name, and grid = "x" keeps only the gridlines of the x axis. The panel has no frame, and the legend lists the continents side by side under the x axis’s name. Each point keeps its place among the others, because none of these settings changes the data. With the y axis left out, though, a reader can see which points are higher, but not by how many years. So leave out an axis only when its numbers are not needed, as on a map.
To leave out only an axis’s name, give it an empty one: x_label(""). The tick marks and the numbers stay, so the reader can still read values from the axis.
frame says which sides of the panel get a line, and it takes three values. "axes", the default, draws one line along the bottom and one along the left, where the axes are. "full" draws all four sides, and "none" draws none:
data(gapminder_2007) + bar * mean + x(continent) + y(life) +
theme(frame = "full", grid = "y")(data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life) +
theme(frame = "full", grid = "y"))data(gapminder_2007) + bar * mean + x(:continent) + y(:life) +
theme(frame = "full", grid = "y")plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life), theme({ frame: "full", grid: "y" }))The panel now has a line on all four sides. It is called frame and not border because style(border_color = ) already uses that word for something else. A border belongs to a mark, such as a bar or a point. A frame surrounds the panel.
A flow whose two columns name one set of places, flow(a, b, shared = TRUE), has two other defaults. It has no numbers to read values from, so its panel has no frame and is white instead of gray. frame and background change both, as on any other plot.
24.10 A figure for a journal
A journal often asks for a figure on a white background inside a framed panel.
theme("bw") is the preset most often wanted for publication: a white panel inside a full rectangle.
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
color(continent) + theme("bw")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + theme("bw"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) + theme("bw")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent), theme("bw"))"bw" stands for black and white, and the name misleads. What becomes black and white is everything around the data, never the data itself. The continents in that plot keep every color the palette gave them. In a “bw” plot only the data carries color, and everything around it is black, white or gray.
The whole preset is three settings, written out below. This sentence draws the same picture theme("bw") just drew:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
color(continent) + theme(background = "white", frame = "full", strip = "white")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + theme(background = "white", frame = "full", strip = "white"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) +
theme(background = "white", frame = "full", strip = "white")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent),
theme({ background: "white", frame: "full", strip: "white" }))Here background makes the panel white and frame draws a rectangle around it. frame is explained in the section above, strip in the next section, and background in The panel’s background.
Every preset in gog follows the same rule: a preset only names settings you could have written yourself. A preset that did more than that would be a second set of words to learn.
24.11 The strip, and how it prints
The third setting inside theme("bw") is strip. It colors the band above each panel, the band that names the category the panel holds. You only see it on a faceted plot, and its color matters more in print than on screen.
By default the band is a light gray. On a screen that is fine, and it shows which panel each name belongs to:
data(gapminder_2007) + point + x(gdp, tick_count = 3) + y(life) |
facet(continent)(data(gapminder_2007) + point + x(col.gdp, tick_count = 3) + y(col.life) |
facet(col.continent))data(gapminder_2007) + point + x(:gdp, tick_count = 3) + y(:life) |
facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { tick_count: 3 }),
y(col.life), across(col.continent))“Given gapminder 2007: points, x is gdp with 3 ticks, y is life, split into panel columns by continent.”
On paper it is not fine. A light gray is printed as many tiny black dots, and those dots are directly under small text. The label gets harder to read, and a photocopy makes it worse. This is the same reason pattern exists for fills: what is easy to see on a screen can be hard to see on paper.
So theme("bw") makes the band white as well. The framed panel already separates one panel from the next, so the tint is no longer needed:
data(gapminder_2007) + point + x(gdp, tick_count = 3) + y(life) + theme("bw") |
facet(continent)(data(gapminder_2007) + point + x(col.gdp, tick_count = 3) + y(col.life) + theme("bw") |
facet(col.continent))data(gapminder_2007) + point + x(:gdp, tick_count = 3) + y(:life) +
theme("bw") | facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { tick_count: 3 }),
y(col.life), theme("bw"), across(col.continent))You can also name the color yourself, for a poster or a slide where a tint makes the name easier to read rather than harder:
data(gapminder_2007) + point + x(gdp, tick_count = 3) + y(life) +
theme(strip = "#dce8f0", frame = "full") |
facet(continent)(data(gapminder_2007) + point + x(col.gdp, tick_count = 3) + y(col.life) +
theme(strip = "#dce8f0", frame = "full") |
facet(col.continent))data(gapminder_2007) + point + x(:gdp, tick_count = 3) + y(:life) +
theme(strip = "#dce8f0", frame = "full") | facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { tick_count: 3 }),
y(col.life), theme({ strip: "#dce8f0", frame: "full" }),
across(col.continent))An animated plot has the same band (Play, later in the book). It names the moment the animation shows, such as the year, the way a panel band names its category. theme(strip = ) colors that one too. A property that colored one band and not the other would be exactly the kind of exception Law 2 forbids.
24.11.1 The label color on a dark band
Some plots use a dark band on purpose, and that creates one problem: the default label is dark too.
A dark band with light text is often wanted, and you need only one property for it:
data(gapminder_2007) + point + x(gdp, tick_count = 3) + y(life) +
theme(strip = "#22223b", frame = "full") |
facet(continent)(data(gapminder_2007) + point + x(col.gdp, tick_count = 3) + y(col.life) +
theme(strip = "#22223b", frame = "full") |
facet(col.continent))data(gapminder_2007) + point + x(:gdp, tick_count = 3) + y(:life) +
theme(strip = "#22223b", frame = "full") | facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { tick_count: 3 }),
y(col.life), theme({ strip: "#22223b", frame: "full" }),
across(col.continent))You did not name a text color, and the text is drawn white. gog reads the band color you asked for. It has two default label colors, and it picks the one that contrasts more with that band. On a pale band the label stays dark, which is why the faceted plots earlier in this chapter have dark labels.
gog chooses the label color because an unreadable name is a defect, not to save you a line of code. Without that rule, theme(strip = "black") would paint the near-black default label onto a black band. Every panel name would be unreadable while the rest of the plot looked correct. So when you do not name a label color, gog chooses a readable one.
When you want a particular label color, and not only a readable one, name it. gog then uses the color you name as written, without checking it against the band:
data(gapminder_2007) + point + x(gdp, tick_count = 3) + y(life) +
theme(strip = "navy", strip_text = "gold", frame = "full") |
facet(continent)(data(gapminder_2007) + point + x(col.gdp, tick_count = 3) + y(col.life) +
theme(strip = "navy", strip_text = "gold", frame = "full") |
facet(col.continent))data(gapminder_2007) + point + x(:gdp, tick_count = 3) + y(:life) +
theme(strip = "navy", strip_text = "gold", frame = "full") |
facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { tick_count: 3 }),
y(col.life),
theme({ strip: "navy", strip_text: "gold", frame: "full" }),
across(col.continent))Every default in gog works this way: it decides what you did not say, and stops deciding as soon as you say it. The engine guarantees the plot is well-formed and leaves taste to you, which is Law 8.
A "transparent" band gives gog no color to read, because it shows whatever is behind it. So the label stays dark, which suits the white page a band is usually drawn on.
24.12 The panel’s background
A slide or a journal may need a panel in a color other than gog’s light gray. background colors the panel, and it takes any color the rest of the grammar takes, a CSS name such as linen among them:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
theme(background = "linen", frame = "full")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
theme(background = "linen", frame = "full"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
theme(background = "linen", frame = "full")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), theme({ background: "linen", frame: "full" }))A histogram’s bars touch, so gog outlines each bar with a thin line in the panel’s color. The line takes the background’s color, so it separates the bars on a panel of any color:
data(gapminder_2007) + bar * bin + x(life) +
theme(background = "white", frame = "full")(data(gapminder_2007) + bar * bin + x(col.life) +
theme(background = "white", frame = "full"))data(gapminder_2007) + bar * bin + x(:life) +
theme(background = "white", frame = "full")plot(data(gapminder_2007), layer(bar, bin), x(col.life),
theme({ background: "white", frame: "full" }))“Given gapminder 2007: bars derived by bin, x is life, with a white background.”
The same line separates the cells of a treemap. It also separates the parts of a stack that group splits, when they all share one color.
"transparent" is the one value that changes more than the panel. If only the panel were transparent, the margin around it would still be painted. So "transparent" also leaves the margin and the legend’s box unpainted, and the page under the figure shows through. A journal often asks for this, because it prints the figure on a page whose color gog does not know. A transparent panel has no color for the line between touching bars to take, so that line stays gog’s light gray.
24.13 What theme() refuses
theme() checks every name you give it, and refuses one it does not know rather than ignoring it:
data(gapminder_2007) + point + x(gdp) + y(life) + theme("dark")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + theme("dark")data(gapminder_2007) + point + x(:gdp) + y(:life) + theme("dark")plot(data(gapminder_2007), point, x(col.gdp), y(col.life), theme("dark"))Error:
! gog: `theme("dark")` is not a theme. gog has `gog`, `minimal`, or `bw`. A theme is a named preset you can then adjust — `theme("minimal", ratio = 1)`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The gridlines are named by their axis, "x" or "y", and not by the direction they run, so grid = "vertical" is refused:
data(gapminder_2007) + point + x(gdp) + y(life) + theme(grid = "vertical")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + theme(grid = "vertical")data(gapminder_2007) + point + x(:gdp) + y(:life) +
theme(grid = "vertical")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
theme({ grid: "vertical" }))Error:
! gog: `theme(grid = )` is one of "both", "x", "y" or "none".
24.14 style and theme
style() and theme() differ in what they belong to. style() belongs to a mark, and a plot with three layers can carry three different ones. theme() belongs to the whole plot, and there is only one of it: no single layer has gridlines or a background of its own. The two never set the same property, and that is why they are two words rather than one.
Text size shows the split best: both atoms have a size setting, but each sets the size of different text. style(size = 17) on a text layer sets how big those labels are, and they are labels that come from a column. theme(font_size = 16) sets how big the tick labels, the axis names and the title are, and those belong to the whole plot. One is a property of a mark and the other is a property of the plot, so neither can replace the other.
gap is the one name both atoms take, and it means a different thing in each. style(gap = 0.2) leaves a fifth of a bar’s slot empty. theme(gap = 8) on an arrangement puts 8 pixels between its plots.
Grouped bars leave a fifth of each bar’s part of the slot empty unless you set gap. style(gap = 0) makes the bars of a group touch. The gm_eras table holds two years, 1957 and 2007, with one row per country in each:
| country | continent | year | life | population | gdp | era |
|---|---|---|---|---|---|---|
| Afghanistan | Asia | 1957 | 30.332 | 9240934 | 820.8530 | 1957 |
| Afghanistan | Asia | 2007 | 43.828 | 31889923 | 974.5803 | 2007 |
| Albania | Europe | 1957 | 59.280 | 1476505 | 1942.2842 | 1957 |
| Albania | Europe | 2007 | 76.423 | 3600523 | 5937.0295 | 2007 |
| Algeria | Africa | 1957 | 45.685 | 10270856 | 3013.9760 | 1957 |
The plot on the left keeps the default, and the plot on the right sets gap = 0:
(data(gm_eras) + bar * mean * dodge + x(continent) + y(life) +
color(era, legend = FALSE) + title("The default gap")) |
(data(gm_eras) + bar * mean * dodge + x(continent) + y(life) + color(era) +
style(gap = 0) + title("style(gap = 0)"))((data(gm_eras) + bar * mean * dodge + x(col.continent) + y(col.life) +
color(col.era, legend = False) + title("The default gap")) |
(data(gm_eras) + bar * mean * dodge + x(col.continent) + y(col.life) + color(col.era) +
style(gap = 0) + title("style(gap = 0)")))(data(gm_eras) + bar * mean * dodge + x(:continent) + y(:life) +
color(:era, legend = false) + title("The default gap")) |
(data(gm_eras) + bar * mean * dodge + x(:continent) + y(:life) +
color(:era) + style(gap = 0) + title("style(gap = 0)"))beside(plot(data(gm_eras), layer(bar, mean, dodge), x(col.continent),
y(col.life), color(col.era, { legend: false }),
title("The default gap")),
plot(data(gm_eras), layer(bar, mean, dodge), x(col.continent),
y(col.life), color(col.era), style({ gap: 0 }),
title("style(gap = 0)")))“Given the gapminder eras: bars derived by mean and dodge, x is continent, y is life, color by era, without a legend, beside bars derived by mean and dodge, x is continent, y is life, color by era, with gap 0.”
On the right, the two bars of each continent touch. The space between continents is the same in both plots.
theme(gap = ) sets the space between the plots of an arrangement, in pixels. The default is 20, and theme(gap = 40) doubles it:
((data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))) + theme(gap = 40)(((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent))) + theme(gap = 40))((data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))) + theme(gap = 40)beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)),
theme({ gap: 40 }))24.15 In short
A mapping to color, size, shape, pattern or opacity gets a legend, and a setting gets none. That is the quickest test for which one you wrote; a mapping with legend = FALSE also has none. Five names are a channel and a setting at once: color, size, shape, pattern and opacity. Which call you write decides which you get.
24.16 What comes next
A setting changes how a mark looks and computes nothing from the rows. Transforms shows how gog derives values the table does not hold: counts, means, bins, densities, and the low and high values an interval needs.