Skip to content

Commit e219102

Browse files
teunbrandthomasp85
authored andcommitted
add section about guide extensions (#5693)
1 parent 6a4f14c commit e219102

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

vignettes/extending-ggplot2.Rmd

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,3 +1095,161 @@ What we are doing above is to intercept the `compute_layout` and `map_data` meth
10951095
2. Based on the FacetWrap implementation rewrite FacetTrans to take the strip.placement theme setting into account.
10961096
3. Think about which caveats there are in FacetBootstrap specifically related to adding multiple layers with the same data.
10971097

1098+
## Creating new guides
1099+
1100+
Guides are closely related to scales and aesthetics, so an important part of guides is taking information from the scale and translating it to a graphic.
1101+
This information is passed around inside guides as a `key` dataframe.
1102+
For existing guides, you can glance at what a key contains by using the `get_guide_data()` function.
1103+
Typical variables you may see in guides are the aesthetic mapped by the scale, such as the hexadecimal colours in the example below, what those aesthetic represent in the `.value` column and how they should be labelled in the `.label` column.
1104+
Sometimes, the aesthetic is used in computations.
1105+
To avoid interpreting the values and labels as aesthetics, it is customary to prefix these with `.`.
1106+
1107+
```{r}
1108+
p <- ggplot(mpg, aes(displ, hwy, colour = drv)) +
1109+
geom_point() +
1110+
scale_colour_discrete(
1111+
labels = c("4-wheel drive", "front wheel drive", "rear wheel drive")
1112+
)
1113+
1114+
get_guide_data(p, "colour")
1115+
```
1116+
1117+
### Overriding scale extraction
1118+
1119+
Let's now make a first guide extension by adjusting the guide's key.
1120+
Axes are most straightforward to extend, because they are the least complicated.
1121+
We'll build an axis that accepts custom values for the guide's `key`.
1122+
We can begin by making a custom ggproto class that inherits from the axis guide.
1123+
An important extension point is the `extract_key()` method, which determines how break information is transferred from the scale to the guide.
1124+
In our class, we reject the scale's reality and substitute our own.
1125+
1126+
```{r}
1127+
GuideKey <- ggproto(
1128+
"Guide", GuideAxis,
1129+
1130+
# Some parameters are required, so it is easiest to copy the base Guide's
1131+
# parameters into our new parameters.
1132+
# We add a new 'key' parameter for our own guide.
1133+
params = c(GuideAxis$params, list(key = NULL)),
1134+
1135+
# It is important for guides to have a mapped aesthetic with the correct name
1136+
extract_key = function(scale, aesthetic, key, ...) {
1137+
key$aesthetic <- scale$map(key$aesthetic)
1138+
names(key)[names(key) == "aesthetic"] <- aesthetic
1139+
key
1140+
}
1141+
)
1142+
```
1143+
1144+
### Guide constructors
1145+
1146+
Now we can make a guide constructor that creates a custom key to pass along on.
1147+
The `new_guide()` function instantiates a new guide with the given parameters.
1148+
This function automatically rejects any parameters that are not in the class' `params` field, so it is important to declare these.
1149+
1150+
```{r}
1151+
guide_key <- function(
1152+
aesthetic, value = aesthetic, label = as.character(aesthetic),
1153+
...,
1154+
# Standard guide arguments
1155+
theme = NULL, title = waiver(), order = 0, position = waiver()
1156+
) {
1157+
1158+
key <- data.frame(aesthetic, .value = value, .label = label, ...)
1159+
1160+
new_guide(
1161+
# Arguments passed on to the GuideKey$params field
1162+
key = key, theme = theme, title = title, order = order, position = position,
1163+
# Declare which aesthetics are supported
1164+
available_aes = c("x", "y"),
1165+
# Set the guide class
1166+
super = GuideKey
1167+
)
1168+
}
1169+
```
1170+
1171+
Our new guide can now be used inside the `guides()` function or as the `guide` argument in a position scale.
1172+
1173+
```{r key_example}
1174+
#| fig.alt: >
1175+
#| Scatterplot of engine displacement versus highway miles per
1176+
#| gallon. The x-axis axis ticks are at 2.5, 3.5, 4.5, 5.5 and 6.5.
1177+
1178+
ggplot(mpg, aes(displ, hwy)) +
1179+
geom_point() +
1180+
scale_x_continuous(
1181+
guide = guide_key(aesthetic = 2:6 + 0.5)
1182+
)
1183+
```
1184+
1185+
### Custom drawings
1186+
1187+
If we are feeling more adventurous, we can also alter they way guides are drawn.
1188+
The majority of drawing code is in the `Guide$build_*()` methods, which is all orchestrated by the `Guide$draw()` method.
1189+
For derived guides, such as the custom key guide we're extending here, overriding a `Guide$build_*()` method should be sufficient.
1190+
If you are writing a completely novel guide that does not resemble the structure of any existing guide, overriding the `Guide$draw()` method might be wise.
1191+
1192+
In this example, we are changing the way the labels are drawn, so we should edit the `Guide$build_labels()` method.
1193+
We'll edit the method so that the labels are drawn with a `colour` set in the key.
1194+
In addition to the `key` and `params` variable we've seen before, we now also have an `elements` variable, which is a list of precomputed theme elements. We can use the `elements$text` element to draw a graphical object (grob) in the style of axis text.
1195+
Perhaps the most finicky thing about drawing guides is that a lot of settings depend on the guide's `position` parameter.
1196+
1197+
```{r key_ggproto_edit}
1198+
# Same as before
1199+
GuideKey <- ggproto(
1200+
"Guide", GuideAxis,
1201+
params = c(GuideAxis$params, list(key = NULL)),
1202+
extract_key = function(scale, aesthetic, key, ...) {
1203+
key$aesthetic <- scale$map(key$aesthetic)
1204+
names(key)[names(key) == "aesthetic"] <- aesthetic
1205+
key
1206+
},
1207+
1208+
# New method to draw labels
1209+
build_labels = function(key, elements, params) {
1210+
position <- params$position
1211+
# Downstream code expects a list of labels
1212+
list(element_grob(
1213+
elements$text,
1214+
label = key$.label,
1215+
x = switch(position, left = 1, right = 0, key$x),
1216+
y = switch(position, top = 0, bottom = 1, key$y),
1217+
margin_x = position %in% c("left", "right"),
1218+
margin_y = position %in% c("top", "bottom"),
1219+
colour = key$colour
1220+
))
1221+
}
1222+
)
1223+
```
1224+
1225+
Because we are incorporating the `...` argument to `guide_key()` in the key, adding a `colour` column to the key is straightforward.
1226+
We can check that are guide looks correct in the different positions around the panel.
1227+
1228+
```{r key_example_2}
1229+
#| fig.alt: >
1230+
#| Scatterplot of engine displacement versus highway miles per
1231+
#| gallon. There are two x-axes at the bottom and top of the plot. The bottom
1232+
#| has labels alternating in red and gray, and the top has red, green and blue
1233+
#| labels.
1234+
1235+
ggplot(mpg, aes(displ, hwy)) +
1236+
geom_point() +
1237+
guides(
1238+
x = guide_key(
1239+
aesthetic = 2:6 + 0.5,
1240+
colour = c("red", "grey", "red", "grey", "red")
1241+
),
1242+
x.sec = guide_key(
1243+
aesthetic = c(2, 4, 6),
1244+
colour = c("tomato", "limegreen", "dodgerblue")
1245+
)
1246+
)
1247+
```
1248+
1249+
### Exercises
1250+
1251+
* Extend `guide_key()` to also pass on `family`, `face` and `size` aesthetics from the key to the labels.
1252+
* Override the `GuideKey$build_ticks()` method to also pass on `colour` and `linewidth` settings to the tick marks.
1253+
Looking at `Guide$build_ticks()` is a good starting point.
1254+
* Compare `GuideKey$extract_key()` to `Guide$extract_key()`.
1255+
What steps have been skimmed over in the example?

0 commit comments

Comments
 (0)