Skip to content

Commit 06f4452

Browse files
clauswilkehadley
authored andcommitted
Calculate constant descender heights, regardless of label content. (#2471)
* Calculate constant descender heights, regardless of label content. Fixes #2288 * Make themes more visually consistent. Also fix spacing bug for multi-line legend titles. * gracefully handle missing fontsize in theme (e.g., if legend.title has been set to element_blank()) * update vdiffr templates
1 parent b5b8e5b commit 06f4452

File tree

172 files changed

+20839
-20762
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+20839
-20762
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ up correct aspect ratio, and draws a graticule.
308308

309309
* Default themes use `rel()` to set line widths (@baptiste).
310310

311+
* Themes were tweaked for visual consistency and more graceful behavior when changing
312+
the base font size. All absolute heights or widths were replaced with heights or
313+
widths that are proportional to the base font size. One relative font size
314+
was eliminated. (@clauswilke)
315+
311316
### Other
312317

313318
* `fortify()` gains a method for tbls (@karawoo, #2218)
@@ -338,6 +343,8 @@ up correct aspect ratio, and draws a graticule.
338343
summarise the layout, coordinate systems, and layers, of a built ggplot object
339344
(#2034, @wch). This provides a tested API that (e.g.) shiny can depend on.
340345

346+
* The height of descenders is now calculated solely on font metrics and doesn't change with the specific letters in the string. This fixes minor alignment issues with plot titles, subtitles, and legend titles. (#2288, @clauswilke)
347+
341348
* Update startup messages to reflect new resources. (#2410, @mine-cetinkaya-rundel)
342349

343350
# ggplot2 2.2.1

R/guide-colorbar.r

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,14 @@ guide_gengrob.colorbar <- function(guide, theme) {
282282
if (!guide$draw.llim) tic_pos.c <- tic_pos.c[-length(tic_pos.c)]
283283

284284
# title
285+
286+
# obtain the theme for the legend title. We need this both for the title grob
287+
# and to obtain the title fontsize.
288+
title.theme <- guide$title.theme %||% calc_element("legend.title", theme)
289+
285290
grob.title <- ggname("guide.title",
286291
element_grob(
287-
guide$title.theme %||% calc_element("legend.title", theme),
292+
title.theme,
288293
label = guide$title,
289294
hjust = guide$title.hjust %||% theme$legend.title.align %||% 0,
290295
vjust = guide$title.vjust %||% 0.5
@@ -296,10 +301,14 @@ guide_gengrob.colorbar <- function(guide, theme) {
296301
title_width.c <- c(title_width)
297302
title_height <- convertHeight(grobHeight(grob.title), "mm")
298303
title_height.c <- c(title_height)
304+
title_fontsize <- title.theme$size
305+
if (is.null(title_fontsize)) title_fontsize <- 0
299306

300307
# gap between keys etc
301308
hgap <- width_cm(theme$legend.spacing.x %||% unit(0.3, "line"))
302-
vgap <- height_cm(theme$legend.spacing.y %||% (0.5 * unit(title_height, "cm")))
309+
# multiply by 5 instead of 0.5 due to unit error below. this needs to be fixed
310+
# separately (pull request pending).
311+
vgap <- height_cm(theme$legend.spacing.y %||% (5 * unit(title_fontsize, "pt")))
303312

304313
# label
305314
label.theme <- guide$label.theme %||% calc_element("legend.text", theme)

R/guide-legend.r

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,13 @@ guide_gengrob.legend <- function(guide, theme) {
325325

326326
nbreak <- nrow(guide$key)
327327

328+
# obtain the theme for the legend title. We need this both for the title grob
329+
# and to obtain the title fontsize.
330+
title.theme <- guide$title.theme %||% calc_element("legend.title", theme)
331+
328332
grob.title <- ggname("guide.title",
329333
element_grob(
330-
guide$title.theme %||% calc_element("legend.title", theme),
334+
title.theme,
331335
label = guide$title,
332336
hjust = guide$title.hjust %||% theme$legend.title.align %||% 0,
333337
vjust = guide$title.vjust %||% 0.5,
@@ -338,10 +342,12 @@ guide_gengrob.legend <- function(guide, theme) {
338342

339343
title_width <- width_cm(grob.title)
340344
title_height <- height_cm(grob.title)
345+
title_fontsize <- title.theme$size
346+
if (is.null(title_fontsize)) title_fontsize <- 0
341347

342348
# gap between keys etc
343349
hgap <- width_cm(theme$legend.spacing.x %||% unit(0.3, "line"))
344-
vgap <- height_cm(theme$legend.spacing.y %||% (0.5 * unit(title_height, "cm")))
350+
vgap <- height_cm(theme$legend.spacing.y %||% (0.5 * unit(title_fontsize, "pt")))
345351

346352
# Labels
347353
if (!guide$label || is.null(guide$key$.label)) {

R/margins.R

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,20 @@ title_spec <- function(label, x, y, hjust, vjust, angle, gp = gpar(),
6868
gp = gp
6969
)
7070

71-
# The grob dimensions don't include the text descenders, so add on using
72-
# a little trigonometry. This is only exactly correct when vjust = 1.
73-
descent <- descentDetails(text_grob)
74-
text_height <- unit(1, "grobheight", text_grob) + cos(angle / 180 * pi) * descent
75-
text_width <- unit(1, "grobwidth", text_grob) + sin(angle / 180 * pi) * descent
71+
# The grob dimensions don't include the text descenders, so these need to be added
72+
# manually. Because descentDetails calculates the actual descenders of the specific
73+
# text label, which depends on the label content, we replace the label with one that
74+
# has the common letters with descenders. This guarantees that the grob always has
75+
# the same height regardless of whether the text actually contains letters with
76+
# descenders or not. The same happens automatically with ascenders already.
77+
temp <- editGrob(text_grob, label = "gjpqyQ")
78+
descent <- descentDetails(temp)
79+
80+
# Use trigonometry to calculate grobheight and width for rotated grobs. This is only
81+
# exactly correct when vjust = 1. We need to take the absolute value so we don't make
82+
# the grob smaller when it's flipped over.
83+
text_height <- unit(1, "grobheight", text_grob) + abs(cos(angle / 180 * pi)) * descent
84+
text_width <- unit(1, "grobwidth", text_grob) + abs(sin(angle / 180 * pi)) * descent
7685

7786
if (isTRUE(debug)) {
7887
children <- gList(

R/theme-defaults.r

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,19 @@ NULL
6969
theme_grey <- function(base_size = 11, base_family = "",
7070
base_line_size = base_size / 22,
7171
base_rect_size = base_size / 22) {
72+
73+
# The half-line (base-fontsize / 2) sets up the basic vertical
74+
# rhythm of the theme. Most margins will be set to this value.
75+
# However, when we work with relative sizes, we may want to multiply
76+
# `half_line` with the appropriate relative size. This applies in
77+
# particular for axis tick sizes. And also, for axis ticks and
78+
# axis titles, `half_size` is too large a distance, and we use `half_size/2`
79+
# instead.
7280
half_line <- base_size / 2
7381

82+
# Throughout the theme, we use three font sizes, `base_size` (`rel(1)`)
83+
# for normal, `rel(0.8)` for small, and `rel(1.2)` for large.
84+
7485
theme(
7586
# Elements in this first block aren't used directly, but are inherited
7687
# by others
@@ -100,29 +111,29 @@ theme_grey <- function(base_size = 11, base_family = "",
100111
axis.ticks = element_line(colour = "grey20"),
101112
axis.ticks.length = unit(half_line / 2, "pt"),
102113
axis.title.x = element_text(
103-
margin = margin(t = half_line),
114+
margin = margin(t = half_line / 2),
104115
vjust = 1
105116
),
106117
axis.title.x.top = element_text(
107-
margin = margin(b = half_line),
118+
margin = margin(b = half_line / 2),
108119
vjust = 0
109120
),
110121
axis.title.y = element_text(
111122
angle = 90,
112-
margin = margin(r = half_line),
123+
margin = margin(r = half_line / 2),
113124
vjust = 1
114125
),
115126
axis.title.y.right = element_text(
116127
angle = -90,
117-
margin = margin(l = half_line),
128+
margin = margin(l = half_line / 2),
118129
vjust = 0
119130
),
120131

121132
legend.background = element_rect(colour = NA),
122-
legend.spacing = unit(0.4, "cm"),
133+
legend.spacing = unit(2 * half_line, "pt"),
123134
legend.spacing.x = NULL,
124135
legend.spacing.y = NULL,
125-
legend.margin = margin(0.2, 0.2, 0.2, 0.2, "cm"),
136+
legend.margin = margin(half_line, half_line, half_line, half_line),
126137
legend.key = element_rect(fill = "grey95", colour = "white"),
127138
legend.key.size = unit(1.2, "lines"),
128139
legend.key.height = NULL,
@@ -137,7 +148,7 @@ theme_grey <- function(base_size = 11, base_family = "",
137148
legend.box = NULL,
138149
legend.box.margin = margin(0, 0, 0, 0, "cm"),
139150
legend.box.background = element_blank(),
140-
legend.box.spacing = unit(0.4, "cm"),
151+
legend.box.spacing = unit(2 * half_line, "pt"),
141152

142153
panel.background = element_rect(fill = "grey92", colour = NA),
143154
panel.border = element_blank(),
@@ -152,31 +163,30 @@ theme_grey <- function(base_size = 11, base_family = "",
152163
strip.text = element_text(
153164
colour = "grey10",
154165
size = rel(0.8),
155-
margin = margin(half_line, half_line, half_line, half_line)
166+
margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line)
156167
),
157168
strip.text.x = NULL,
158169
strip.text.y = element_text(angle = -90),
159170
strip.placement = "inside",
160171
strip.placement.x = NULL,
161172
strip.placement.y = NULL,
162-
strip.switch.pad.grid = unit(0.1, "cm"),
163-
strip.switch.pad.wrap = unit(0.1, "cm"),
173+
strip.switch.pad.grid = unit(half_line / 2, "pt"),
174+
strip.switch.pad.wrap = unit(half_line / 2, "pt"),
164175

165176
plot.background = element_rect(colour = "white"),
166-
plot.title = element_text(
177+
plot.title = element_text( # font size "large"
167178
size = rel(1.2),
168179
hjust = 0, vjust = 1,
169-
margin = margin(b = half_line * 1.2)
180+
margin = margin(b = half_line)
170181
),
171-
plot.subtitle = element_text(
172-
size = rel(0.9),
182+
plot.subtitle = element_text( # font size "regular"
173183
hjust = 0, vjust = 1,
174-
margin = margin(b = half_line * 0.9)
184+
margin = margin(b = half_line)
175185
),
176-
plot.caption = element_text(
177-
size = rel(0.9),
186+
plot.caption = element_text( # font size "small"
187+
size = rel(0.8),
178188
hjust = 1, vjust = 1,
179-
margin = margin(t = half_line * 0.9)
189+
margin = margin(t = half_line)
180190
),
181191
plot.margin = margin(half_line, half_line, half_line, half_line),
182192

@@ -248,7 +258,7 @@ theme_linedraw <- function(base_size = 11, base_family = "",
248258
strip.text = element_text(
249259
colour = "white",
250260
size = rel(0.8),
251-
margin = margin(half_line, half_line, half_line, half_line)
261+
margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line)
252262
),
253263

254264
complete = TRUE
@@ -290,7 +300,7 @@ theme_light <- function(base_size = 11, base_family = "",
290300
strip.text = element_text(
291301
colour = "white",
292302
size = rel(0.8),
293-
margin = margin(half_line, half_line, half_line, half_line)
303+
margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line)
294304
),
295305

296306
complete = TRUE
@@ -332,7 +342,7 @@ theme_dark <- function(base_size = 11, base_family = "",
332342
strip.text = element_text(
333343
colour = "grey90",
334344
size = rel(0.8),
335-
margin = margin(half_line, half_line, half_line, half_line)
345+
margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line)
336346
),
337347

338348
complete = TRUE
@@ -420,25 +430,24 @@ theme_void <- function(base_size = 11, base_family = "",
420430
legend.text = element_text(size = rel(0.8)),
421431
legend.title = element_text(hjust = 0),
422432
strip.text = element_text(size = rel(0.8)),
423-
strip.switch.pad.grid = unit(0.1, "cm"),
424-
strip.switch.pad.wrap = unit(0.1, "cm"),
433+
strip.switch.pad.grid = unit(half_line / 2, "pt"),
434+
strip.switch.pad.wrap = unit(half_line / 2, "pt"),
425435
panel.ontop = FALSE,
426436
panel.spacing = unit(half_line, "pt"),
427437
plot.margin = unit(c(0, 0, 0, 0), "lines"),
428438
plot.title = element_text(
429439
size = rel(1.2),
430440
hjust = 0, vjust = 1,
431-
margin = margin(t = half_line * 1.2)
441+
margin = margin(t = half_line)
432442
),
433443
plot.subtitle = element_text(
434-
size = rel(0.9),
435444
hjust = 0, vjust = 1,
436-
margin = margin(t = half_line * 0.9)
445+
margin = margin(t = half_line)
437446
),
438447
plot.caption = element_text(
439-
size = rel(0.9),
448+
size = rel(0.8),
440449
hjust = 1, vjust = 1,
441-
margin = margin(t = half_line * 0.9)
450+
margin = margin(t = half_line)
442451
),
443452

444453
complete = TRUE
@@ -480,26 +489,26 @@ theme_test <- function(base_size = 11, base_family = "",
480489
axis.ticks = element_line(colour = "grey20"),
481490
axis.ticks.length = unit(half_line / 2, "pt"),
482491
axis.title.x = element_text(
483-
margin = margin(t = half_line),
492+
margin = margin(t = half_line / 2),
484493
vjust = 1
485494
),
486495
axis.title.x.top = element_text(
487-
margin = margin(b = half_line),
496+
margin = margin(b = half_line / 2),
488497
vjust = 0
489498
),
490499
axis.title.y = element_text(
491500
angle = 90,
492-
margin = margin(r = half_line),
501+
margin = margin(r = half_line / 2),
493502
vjust = 1
494503
),
495504
axis.title.y.right = element_text(
496505
angle = -90,
497-
margin = margin(l = half_line),
506+
margin = margin(l = half_line / 2),
498507
vjust = 0
499508
),
500509

501510
legend.background = element_rect(colour = NA),
502-
legend.spacing = unit(0.4, "cm"),
511+
legend.spacing = unit(2 * half_line, "pt"),
503512
legend.spacing.x = NULL,
504513
legend.spacing.y = NULL,
505514
legend.margin = margin(0, 0, 0, 0, "cm"),
@@ -517,7 +526,7 @@ theme_test <- function(base_size = 11, base_family = "",
517526
legend.box = NULL,
518527
legend.box.margin = margin(0, 0, 0, 0, "cm"),
519528
legend.box.background = element_blank(),
520-
legend.box.spacing = unit(0.4, "cm"),
529+
legend.box.spacing = unit(2 * half_line, "pt"),
521530

522531
panel.background = element_rect(fill = "white", colour = NA),
523532
panel.border = element_rect(fill = NA, colour = "grey20"),
@@ -532,31 +541,30 @@ theme_test <- function(base_size = 11, base_family = "",
532541
strip.text = element_text(
533542
colour = "grey10",
534543
size = rel(0.8),
535-
margin = margin(half_line, half_line, half_line, half_line)
544+
margin = margin(0.8 * half_line, 0.8 * half_line, 0.8 * half_line, 0.8 * half_line)
536545
),
537546
strip.text.x = NULL,
538547
strip.text.y = element_text(angle = -90),
539548
strip.placement = "inside",
540549
strip.placement.x = NULL,
541550
strip.placement.y = NULL,
542-
strip.switch.pad.grid = unit(0.1, "cm"),
543-
strip.switch.pad.wrap = unit(0.1, "cm"),
551+
strip.switch.pad.grid = unit(half_line / 2, "pt"),
552+
strip.switch.pad.wrap = unit(half_line / 2, "pt"),
544553

545554
plot.background = element_rect(colour = "white"),
546555
plot.title = element_text(
547556
size = rel(1.2),
548557
hjust = 0, vjust = 1,
549-
margin = margin(b = half_line * 1.2)
558+
margin = margin(b = half_line)
550559
),
551560
plot.subtitle = element_text(
552-
size = rel(0.9),
553561
hjust = 0, vjust = 1,
554-
margin = margin(b = half_line * 0.9)
562+
margin = margin(b = half_line)
555563
),
556564
plot.caption = element_text(
557-
size = rel(0.9),
565+
size = rel(0.8),
558566
hjust = 1, vjust = 1,
559-
margin = margin(t = half_line * 0.9)
567+
margin = margin(t = half_line)
560568
),
561569
plot.margin = margin(half_line, half_line, half_line, half_line),
562570

0 commit comments

Comments
 (0)