From 062121cb8de76bdecf618285a3172e1de8c42952 Mon Sep 17 00:00:00 2001 From: Danielle Navarro Date: Mon, 20 Mar 2023 09:35:59 +1100 Subject: [PATCH] adds discussion of linewidth aesthetic (#354) * replaces "size" with "linewidth" where needed * adds minimal scale linewidth section * loads grid in its own chunk * adds binned linewidth example and discussion of linear scaling * cleans binned scale * narrower legend key * drops plot, simplifies linewidth discussion --- annotations.Rmd | 2 +- collective-geoms.Rmd | 8 ++++---- ext-springs.Rmd | 23 ++++++++++++---------- layers.Rmd | 2 +- programming.Rmd | 14 +++++++------- scales-colour.Rmd | 2 +- scales-other.Rmd | 37 +++++++++++++++++++++++++++++++++++- statistical-summaries.Rmd | 4 ++-- themes.Rmd | 40 +++++++++++++++++++++++---------------- 9 files changed, 89 insertions(+), 43 deletions(-) diff --git a/annotations.Rmd b/annotations.Rmd index c827872b..2ef463d1 100644 --- a/annotations.Rmd +++ b/annotations.Rmd @@ -401,7 +401,7 @@ mod_coef <- coef(lm(log10(price) ~ log10(carat), data = diamonds)) ggplot(diamonds, aes(log10(carat), log10(price))) + geom_bin2d() + geom_abline(intercept = mod_coef[1], slope = mod_coef[2], - colour = "white", size = 1) + + colour = "white", linewidth = 1) + facet_wrap(vars(cut), nrow = 1) ``` diff --git a/collective-geoms.Rmd b/collective-geoms.Rmd index af73b13e..7301d1c8 100644 --- a/collective-geoms.Rmd +++ b/collective-geoms.Rmd @@ -54,7 +54,7 @@ Instead of setting the grouping aesthetic in `ggplot()`, where it will apply to ```{r layer19} ggplot(Oxboys, aes(age, height)) + geom_line(aes(group = Subject)) + - geom_smooth(method = "lm", size = 2, se = FALSE) + geom_smooth(method = "lm", linewidth = 2, se = FALSE) ``` ## Overriding the default grouping @@ -93,11 +93,11 @@ In ggplot2, this is handled differently for different collective geoms. Lines an df <- data.frame(x = 1:3, y = 1:3, colour = c(1, 3, 5)) ggplot(df, aes(x, y, colour = factor(colour))) + - geom_line(aes(group = 1), size = 2) + + geom_line(aes(group = 1), linewidth = 2) + geom_point(size = 5) ggplot(df, aes(x, y, colour = colour)) + - geom_line(aes(group = 1), size = 2) + + geom_line(aes(group = 1), linewidth = 2) + geom_point(size = 5) ``` @@ -112,7 +112,7 @@ interp <- data.frame( colour = approx(df$x, df$colour, xout = xgrid)$y ) ggplot(interp, aes(x, y, colour = colour)) + - geom_line(size = 2) + + geom_line(linewidth = 2) + geom_point(data = df, size = 5) ``` diff --git a/ext-springs.Rmd b/ext-springs.Rmd index 881e6d5b..556ebf84 100644 --- a/ext-springs.Rmd +++ b/ext-springs.Rmd @@ -283,7 +283,7 @@ Because we've written a new stat, we get a number of features, like scaling and ggplot(some_data) + geom_spring( aes(x, y, xend = xend, yend = yend, colour = class), - size = 1 + linewidth = 1 ) + facet_wrap(~ class) ``` @@ -514,7 +514,7 @@ GeomSpring <- ggproto("GeomSpring", Geom, required_aes = c("x", "y", "xend", "yend"), default_aes = aes( colour = "black", - size = 0.5, + linewidth = 0.5, linetype = 1L, alpha = NA, diameter = 1, @@ -637,12 +637,15 @@ For example `unit(0.5, "npc") + unit(1, "cm")` defines a point one centimeter to #### Example -Given this very cursory introduction, let's now look at an example grob. -The code below will create a grob that appears as a square if bigger than 5 cm and a circle if smaller:: +Given this very cursory introduction, let's now look at an example grob. First, let's load the grid package: ```{r} library(grid) +``` + +The code below will create a grob that appears as a square if bigger than 5 cm and a circle if smaller: +```{r} surpriseGrob <- function(x, y, size, default.units = "npc", name = NULL, @@ -802,7 +805,7 @@ GeomSpring <- ggproto("GeomSpring", Geom, draw_panel = function(data, panel_params, coord, n = 50, lineend = "butt", na.rm = FALSE) { data <- remove_missing(data, na.rm = na.rm, - c("x", "y", "xend", "yend", "linetype", "size"), + c("x", "y", "xend", "yend", "linetype", "linewidth"), name = "geom_spring") if (is.null(data) || nrow(data) == 0) return(zeroGrob()) if (!coord$is_linear()) { @@ -814,7 +817,7 @@ GeomSpring <- ggproto("GeomSpring", Geom, tension = coord$tension, n = n, gp = gpar( col = alpha(coord$colour, coord$alpha), - lwd = coord$size * .pt, + lwd = coord$linewidth * .pt, lty = coord$linetype, lineend = lineend ) @@ -823,7 +826,7 @@ GeomSpring <- ggproto("GeomSpring", Geom, required_aes = c("x", "y", "xend", "yend"), default_aes = aes( colour = "black", - size = 0.5, + linewidth = 0.5, linetype = 1L, alpha = NA, diameter = 0.35, @@ -960,7 +963,7 @@ With our scales defined let us have a look: ggplot(some_data) + geom_spring(aes(x = x, y = y, xend = xend, yend = yend, tension = tension, diameter = diameter)) + - scale_tension(range = c(0.1, 5)) + scale_tension(range = c(0.1, 5)) ``` The code above shows us that both the default scale (we didn't add an explicit scale for diameter) and the custom scales (`scale_tension()`) work. @@ -994,7 +997,7 @@ draw_key_spring <- function(data, params, size) { ), vp = viewport(clip = "on") ) -} +} ``` We add a little flourish here that is not necessary for the point key constructor, which is that we define a clipping viewport for our grob. @@ -1022,7 +1025,7 @@ The default key size is a bit cramped for our key, but that has to be modified b ggplot(some_data) + geom_spring(aes(x = x, y = y, xend = xend, yend = yend, tension = tension, diameter = diameter)) + - scale_tension(range = c(0.1, 5)) + + scale_tension(range = c(0.1, 5)) + theme(legend.key.size = unit(1, "cm")) ``` diff --git a/layers.Rmd b/layers.Rmd index f0639dd1..9978dae3 100644 --- a/layers.Rmd +++ b/layers.Rmd @@ -119,7 +119,7 @@ We've generated these datasets because it's common to enhance the display of raw ```{r} ggplot(mpg, aes(displ, hwy)) + geom_point() + - geom_line(data = grid, colour = "blue", size = 1.5) + + geom_line(data = grid, colour = "blue", linewidth = 1.5) + geom_text(data = outlier, aes(label = model)) ``` diff --git a/programming.Rmd b/programming.Rmd index 523a11fe..17afce42 100644 --- a/programming.Rmd +++ b/programming.Rmd @@ -29,8 +29,8 @@ Each component of a ggplot plot is an object. Most of the time you create the co bestfit <- geom_smooth( method = "lm", se = FALSE, - colour = alpha("steelblue", 0.5), - size = 2 + colour = alpha("steelblue", 0.5), + linewidth = 2 ) ggplot(mpg, aes(cty, hwy)) + geom_point() + @@ -40,20 +40,20 @@ ggplot(mpg, aes(displ, hwy)) + bestfit ``` -That's a great way to reduce simple types of duplication (it's much better than copying-and-pasting!), but requires that the component be exactly the same each time. If you need more flexibility, you can wrap these reusable snippets in a function. For example, we could extend our `bestfit` object to a more general function for adding lines of best fit to a plot. The following code creates a `geom_lm()` with three parameters: the model `formula`, the line `colour` and the line `size`: +That's a great way to reduce simple types of duplication (it's much better than copying-and-pasting!), but requires that the component be exactly the same each time. If you need more flexibility, you can wrap these reusable snippets in a function. For example, we could extend our `bestfit` object to a more general function for adding lines of best fit to a plot. The following code creates a `geom_lm()` with three parameters: the model `formula`, the line `colour` and the `linewidth`: ```{r geom-lm} geom_lm <- function(formula = y ~ x, colour = alpha("steelblue", 0.5), - size = 2, ...) { + linewidth = 2, ...) { geom_smooth(formula = formula, se = FALSE, method = "lm", colour = colour, - size = size, ...) + linewidth = linewidth, ...) } ggplot(mpg, aes(displ, 1 / hwy)) + geom_point() + geom_lm() ggplot(mpg, aes(displ, 1 / hwy)) + geom_point() + - geom_lm(y ~ poly(x, 2), size = 1, colour = "red") + geom_lm(y ~ poly(x, 2), linewidth = 1, colour = "red") ``` Pay close attention to the use of "`...`". When included in the function definition "`...`" allows a function to accept arbitrary additional arguments. Inside the function, you can then use "`...`" to pass those arguments on to another function. Here we pass "`...`" onto `geom_smooth()` so the user can still modify all the other arguments we haven't explicitly overridden. When you write your own component functions, it's a good idea to always use "`...`" in this way. \indexc{...} @@ -175,7 +175,7 @@ geom_mean <- function(..., bar.params = list(), errorbar.params = list()) { ggplot(mpg, aes(class, cty)) + geom_mean( colour = "steelblue", - errorbar.params = list(width = 0.5, size = 1) + errorbar.params = list(width = 0.5, linewidth = 1) ) ggplot(mpg, aes(class, cty)) + geom_mean( diff --git a/scales-colour.Rmd b/scales-colour.Rmd index 415c7925..2c0c7a3b 100644 --- a/scales-colour.Rmd +++ b/scales-colour.Rmd @@ -174,7 +174,7 @@ All continuous colour scales have an `na.value` parameter that controls what col ```{r} df <- data.frame(x = 1, y = 1:5, z = c(1, 3, 2, NA, 5)) base <- ggplot(df, aes(x, y)) + - geom_tile(aes(fill = z), size = 5) + + geom_tile(aes(fill = z), linewidth = 5) + labs(x = NULL, y = NULL) + scale_x_continuous(labels = NULL) diff --git a/scales-other.Rmd b/scales-other.Rmd index 125e749f..2a85ede0 100644 --- a/scales-other.Rmd +++ b/scales-other.Rmd @@ -5,7 +5,7 @@ columns(1, 2 / 3) # Other aesthetics {#scale-other} -In addition to position and colour, there are several other aesthetics that ggplot2 can use to represent data. In this chapter we'll look at size scales (Section \@ref(scale-size)), shape scales (Section \@ref(scale-shape)), and line type scales (Section \@ref(scale-linetype)), which use visual features other than location and colour to represent data values. Additionally, we'll talk about manual scales (Section \@ref(scale-manual)) and identity scales (Section \@ref(scale-identity)): these don't necessarily use different visual features, but they construct data mappings in an unusual way. +In addition to position and colour, there are several other aesthetics that ggplot2 can use to represent data. In this chapter we'll look at size scales (Section \@ref(scale-size)), shape scales (Section \@ref(scale-shape)), line width scales (Section \@ref(scale-linewidth)), and line type scales (Section \@ref(scale-linetype)), which use visual features other than location and colour to represent data values. Additionally, we'll talk about manual scales (Section \@ref(scale-manual)) and identity scales (Section \@ref(scale-identity)): these don't necessarily use different visual features, but they construct data mappings in an unusual way. ## Size {#scale-size} \index{Size} @@ -169,6 +169,41 @@ base + For more information about manual scales see Section \@ref(scale-manual). +## Line width {#scale-linewidth} + +The linewidth aesthetic, introduced in ggplot2 3.4.0, is used to control the width of lines. In earlier versions of ggplot2 the size aesthetic was used for this purpose, which caused some difficulty for complex geoms such as `geom_pointrange()` that contain both points and lines. For these geoms it's often important to be able to separately control the size of the points and the width of the lines. This is illustrated in the plots below. In the leftmost plot both the size and linewidth aesthetics are set at their default values. The middle plot increases the size of the points while leaving the linewidth unchanged, whereas the plot on the right increases the linewidth while leaving the point size unchanged. + +`r columns(3, 2)` +```{r} +base <- ggplot(airquality, aes(x = factor(Month), y = Temp)) + +base + geom_pointrange(stat = "summary", fun.data = "median_hilow") +base + geom_pointrange( + stat = "summary", + fun.data = "median_hilow", + size = 2 +) +base + geom_pointrange( + stat = "summary", + fun.data = "median_hilow", + linewidth = 2 +) +``` + +In practice you're most likely to set linewidth as a fixed parameter, as shown in the previous example, but it is a true aesthetic and can be mapped onto data values: + +`r columns(1, 1/2, 1)` +```{r} +ggplot(airquality, aes(Day, Temp, group = Month)) + + geom_line(aes(linewidth = Month)) + + scale_linewidth(range = c(0.5, 3)) +``` + +Linewidth scales behave like size scales in most ways, but there are differences. As discussed earlier the default behaviour of a size scale is to increase linearly with the area of the plot marker (e.g., the diameter of a circular plot marker increases with the square root of the data value). In contrast, the linewidth increases linearly with the data value. + +Binned linewidth scales can be added using `scale_linewidth_binned()`. + + ## Line type {#scale-linetype} It is possible to map a variable onto the linetype aesthetic in ggplot2. This works best for discrete variables with a small number of categories, and `scale_linetype()` is an alias for `scale_linetype_discrete()`. Continuous variables cannot be mapped to line types unless `scale_linetype_binned()` is used: although there is a `scale_linetype_continuous()` function, all it does is produce an error. To see why the linetype aesthetic is suited only to cases with a few categories, consider this plot: diff --git a/statistical-summaries.Rmd b/statistical-summaries.Rmd index f28ad6db..5c5280ad 100644 --- a/statistical-summaries.Rmd +++ b/statistical-summaries.Rmd @@ -67,12 +67,12 @@ For more complicated geoms which involve some statistical transformation, we spe # Unweighted ggplot(midwest, aes(percwhite, percbelowpoverty)) + geom_point() + - geom_smooth(method = lm, size = 1) + geom_smooth(method = lm, linewidth = 1) # Weighted by population ggplot(midwest, aes(percwhite, percbelowpoverty)) + geom_point(aes(size = poptotal / 1e6)) + - geom_smooth(aes(weight = poptotal), method = lm, size = 1) + + geom_smooth(aes(weight = poptotal), method = lm, linewidth = 1) + scale_size_area(guide = "none") ``` diff --git a/themes.Rmd b/themes.Rmd index a4e5719a..6e577ace 100644 --- a/themes.Rmd +++ b/themes.Rmd @@ -72,11 +72,15 @@ styled <- labelled + theme_bw() + theme( plot.title = element_text(face = "bold", size = 12), - legend.background = element_rect(fill = "white", size = 4, colour = "white"), + legend.background = element_rect( + fill = "white", + linewidth = 4, + colour = "white" + ), legend.justification = c(0, 1), legend.position = c(0, 1), - axis.ticks = element_line(colour = "grey70", size = 0.2), - panel.grid.major = element_line(colour = "grey70", size = 0.2), + axis.ticks = element_line(colour = "grey70", linewidth = 0.2), + panel.grid.major = element_line(colour = "grey70", linewidth = 0.2), panel.grid.minor = element_blank() ) styled @@ -189,22 +193,22 @@ There are four basic types of built-in element functions: text, lines, rectangle base_t + theme(axis.title.y = element_text(margin = margin(r = 10))) ``` -* `element_line()` draws lines parameterised by `colour`, `size` and +* `element_line()` draws lines parameterised by `colour`, `linewidth` and `linetype`: \indexf{element\_line} \index{Themes!lines} ```{r element_line} base + theme(panel.grid.major = element_line(colour = "black")) - base + theme(panel.grid.major = element_line(size = 2)) + base + theme(panel.grid.major = element_line(linewidth = 2)) base + theme(panel.grid.major = element_line(linetype = "dotted")) ``` * `element_rect()` draws rectangles, mostly used for backgrounds, parameterised - by `fill` colour and border `colour`, `size` and `linetype`. + by `fill` colour and border `colour`, `linewidth` and `linetype`. \index{Background} \index{Themes!background} \indexf{theme\_rect} ```{r element_rect} base + theme(plot.background = element_rect(fill = "grey80", colour = NA)) - base + theme(plot.background = element_rect(colour = "red", size = 2)) + base + theme(plot.background = element_rect(colour = "red", linewidth = 2)) base + theme(panel.background = element_rect(fill = "linen")) ``` @@ -268,9 +272,9 @@ plot.margin | `margin()` | margins around plot `r columns(3, 3/4)` ```{r plot} -base + theme(plot.background = element_rect(colour = "grey50", size = 2)) +base + theme(plot.background = element_rect(colour = "grey50", linewidth = 2)) base + theme( - plot.background = element_rect(colour = "grey50", size = 2), + plot.background = element_rect(colour = "grey50", linewidth = 2), plot.margin = margin(2, 2, 2, 2) ) base + theme(plot.background = element_rect(fill = "lightblue")) @@ -300,7 +304,7 @@ df <- data.frame(x = 1:3, y = 1:3) base <- ggplot(df, aes(x, y)) + geom_point() # Accentuate the axes -base + theme(axis.line = element_line(colour = "grey50", size = 1)) +base + theme(axis.line = element_line(colour = "grey50", linewidth = 1)) # Style both x and y axis labels base + theme(axis.text = element_text(color = "blue", size = 12)) # Useful for long labels @@ -352,7 +356,7 @@ base + theme( legend.background = element_rect( fill = "lemonchiffon", colour = "grey50", - size = 1 + linewidth = 1 ) ) base + theme( @@ -394,11 +398,11 @@ base + theme(panel.background = element_rect(fill = "lightblue")) # Tweak major grid lines base + theme( - panel.grid.major = element_line(color = "gray60", size = 0.8) + panel.grid.major = element_line(color = "gray60", linewidth = 0.8) ) # Just in one direction base + theme( - panel.grid.major.x = element_line(color = "gray60", size = 0.8) + panel.grid.major.x = element_line(color = "gray60", linewidth = 0.8) ) ``` @@ -408,7 +412,7 @@ Note that aspect ratio controls the aspect ratio of the _panel_, not the overall base2 <- base + theme(plot.background = element_rect(colour = "grey50")) # Wide screen base2 + theme(aspect.ratio = 9 / 16) -# Long and skiny +# Long and skinny base2 + theme(aspect.ratio = 2 / 1) # Square base2 + theme(aspect.ratio = 1) @@ -438,8 +442,12 @@ base_f <- ggplot(df, aes(x, y)) + geom_point() + facet_wrap(~z) base_f base_f + theme(panel.spacing = unit(0.5, "in")) base_f + theme( - strip.background = element_rect(fill = "grey20", color = "grey80", size = 1), - strip.text = element_text(colour = "white") + strip.text = element_text(colour = "white"), + strip.background = element_rect( + fill = "grey20", + color = "grey80", + linewidth = 1 + ) ) ```