3

Some ways to add labels on contour plots

# load packages
library('mgcv')
library('gratia') # draw(); smooth_estimates()
library('metR') # geom_contour2(); geom_text_contour()
library('ggplot2')

Simulate data using the example from Gavin Simpson's website: https://fromthebottomoftheheap.net/2018/10/23/introducing-gratia/

set.seed(1)
dat <- gamSim(2, n = 4000, dist = "normal", scale = 1, verbose = FALSE)
mod <- gam(y ~ s(x, z, k = 30), data = dat$data, method = "REML")
sm <- smooth_estimates(mod); sm

Plot using gratia with the number of contour lines automatically adjusted:

draw(mod) +
  geom_text_contour( 
    aes(z = est), # 'est' from smooth_estimates(mod)
    colour = "black", size = 4.5, fontface = "bold",
    stroke = 0.3, stroke.colour = "white", # 'stroke' controls the width of stroke relative to the size of the text 
    skip = 0, # number of contours to skip
    rotate = FALSE, # horizontal labeling; if TRUE, rotate text following the contour
    label.placer = label_placer_fraction(frac = 0.5)) # 'frac = 0.5' places the label at equal distance from extremities. Try 'label.placer = label_placer_n(2)' to display two labels per contour line

enter image description here

However, contour lines and labeling do no longer match if we use e.g. 'n_contour = 10' within draw(). To allow this matching, use 'n_contour = 0' within draw(), define 'binwidth' within geom_contour2() and 'breaks' within geom_text_contour(), as follows.

Plot using gratia::draw with 'binwidth'-adjusted contour lines:

min(sm$est); max(sm$est) # find min() and max() for adjusting the 'est' z-scale
draw(mod, n_contour = 0) +
  geom_contour2(aes(z = est), binwidth = 0.2) +
  geom_text_contour(
    aes(z = est), # 'est' from smooth_estimates(mod)
    breaks = seq(-0.4, 0.4, by = 0.2), # 'breaks' must match with 'binwidth' above
    colour = "black", size = 4.5, fontface = "bold",
    stroke = 0.3, stroke.colour = "white", # 'stroke' controls the width of stroke relative to the size of the text
    skip = 0, # number of contours to skip
    rotate = FALSE, # horizontal labelling; if TRUE, rotate text following the contour
    label.placer = label_placer_fraction(frac = 0.5)) # 'frac = 0.5' places the label at equal distance from contour lines' extremities. Try 'label.placer = label_placer_n(2)' to display two labels per contour line

enter image description here

Also possible to customize the graph directly with ggplot2:

ggplot(data = sm, aes(x = x, y = z, z = est)) +
  geom_contour2(aes(z = est), binwidth = 0.1) +
  geom_text_contour(
    aes(z = est), # 'est' from smooth_estimates(mod)
    breaks = seq(-0.4, 0.4, by = 0.1), # 'breaks' instead of 'bins' to not have too many decimals
    colour = "black", size = 4.5, fontface = "bold",
    stroke = 0.3, stroke.colour = "white", # 'stroke' controls the width of stroke relative to the size of the text
    skip = 0, # number of contours to skip
    rotate = FALSE, # horizontal labelling; if TRUE, rotate text following the contour
    label.placer = label_placer_fraction(frac = 0.5)) # 'frac = 0.5' places the label at equal distance from contour lines' extremities. Try 'label.placer = label_placer_n(2)' to display two labels per contour line

enter image description here

denis
  • 199
  • 1
  • 8

1 Answers1

3

You can use geom_textcontour from geomtextpath to obtain nicely placed labels without having to tweak lots of different parameters:

library(geomtextpath)

ggplot(sm, aes(x, z, z = est)) + geom_textcontour()

enter image description here

To use it within the gratia::draw framework, you can remove the existing contour from the plot first:

p <- draw(mod)
p$layers[[2]] <- NULL
p + geom_textcontour(aes(z = est), fontface = 'bold')

enter image description here


EDIT

To get a similar effect to the stroke parameter we can do:

library(ggfx)

p + with_outer_glow(geom_textcontour(aes(z = est), fontface = 'bold', 
                                     linetype = NA),
                    colour = 'white', expand = 3, sigma = 1) +
  geom_textcontour(aes(z = est), fontface = 'bold', textcolour = NA)

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • thanks, an interesting option, although it does not allow 'stroke', 'rotate', 'skip' and other metR::geom_text_contour options. – denis Feb 20 '23 at 10:12
  • @demon "stroke" isn't needed because of the break in the lines, but if necessary a white margin _can_ be added to the text using ggfx. The angle is controllable using `angle`, and what's more, there are multiple options for adjusting position, spacing, smoothing etc. You can easily skip lines with custom labeling. IMHO it is easier to customize the results than it is with `geom_text_contour`. But then, I might be a bit biased... – Allan Cameron Feb 20 '23 at 11:40
  • Allan, it's great! Thank you, much simpler. – denis Feb 20 '23 at 12:57
  • in your last edited code, how and where do you place the 'angle' argument? I can't get the contours text to horizontal (as with 'rotate=FALSE'). – denis Mar 11 '23 at 18:22