19

I want to plot a label over a line in a monochrome graphic. So I need small white border on each letter of the label.

The border or background of rectangle of the text label is not useful because it hides a lot of the plotted data.

Is there a way to put a border, shadow or buffer around text labels in R plots?

shadowtext <- function(x, y=NULL, labels, col='white', bg='black',
                   theta= seq(pi/4, 2*pi, length.out=8), r=0.1, ... ) {

  xy <- xy.coords(x,y)
  xo <- r*strwidth('x')
  yo <- r*strheight('x')

  for (i in theta) {
    text( xy$x + cos(i)*xo, xy$y + sin(i)*yo, labels, col=bg, ... )
  }
  text(xy$x, xy$y, labels, col=col, ... )
}

pdf(file="test.pdf", width=2, height=2); par(mar=c(0,0,0,0)+.1)
  plot(c(0,1), c(0,1), type="l", lwd=20, axes=FALSE, xlab="", ylab="")
  text(1/6, 1/6, "Test 1")
  text(2/6, 2/6, "Test 2", col="white")
  shadowtext(3/6, 3/6, "Test 3")
  shadowtext(4/6, 4/6, "Test 4", col="black", bg="white")
  shadowtext(5/6, 5/6, "Test 5", col="black", bg="white", theta = seq(pi/4, 2*pi, length.out=24))
dev.off()

The code above use the solution from koekenbakker. This is fine for PNG graphic, but I need a different approach for high resolution PDF.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
fnd
  • 335
  • 2
  • 9
  • 1
    There are many different plotting functions and graphics systems in R. Please be much more specific about exactly what plotting commands you are currently using. Even better, include a [reproducible example](http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example). – MrFlick Sep 02 '14 at 20:26
  • What's the problem with the pdf? You mean the resolution? You can adapt the theta parameter for higher resolution. I'll change this in my answer. – koekenbakker Sep 03 '14 at 12:22
  • you're right. In my example I tried a theta of 24 items and was not enough. But in your example works perfect with 50. Thanks – fnd Sep 03 '14 at 14:41

5 Answers5

22

You can try this 'shadowtext' function that draws a halo or border around the text by printing it several times with a slight offset in a different colour. All credits to Greg Snow here.

shadowtext <- function(x, y=NULL, labels, col='white', bg='black', 
                       theta= seq(0, 2*pi, length.out=50), r=0.1, ... ) {

    xy <- xy.coords(x,y)
    xo <- r*strwidth('A')
    yo <- r*strheight('A')

    # draw background text with small shift in x and y in background colour
    for (i in theta) {
        text( xy$x + cos(i)*xo, xy$y + sin(i)*yo, labels, col=bg, ... )
    }
    # draw actual text in exact xy position in foreground colour
    text(xy$x, xy$y, labels, col=col, ... )
}

# And here is an example of use:
# pdf(file="test2.pdf", width=2, height=2); par(mar=c(0,0,0,0)+.1)
plot(c(0,1), c(0,1), type="n", lwd=20, axes=FALSE, xlab="", ylab="")

rect(xleft = 0.5, xright = 1, ybottom = 0, ytop = 1, col=1)
text(1/6, 1/6, "Test 1")
shadowtext(2/6, 2/6, "Test 2", col='red', bg="blue")
shadowtext(3/6, 3/6, "Test 3", cex=2)

# `r` controls the width of the border
shadowtext(5/6, 5/6, "Test 4", col="black", bg="white", cex=4, r=0.2)
# dev.off()

enter image description here

koekenbakker
  • 3,524
  • 2
  • 21
  • 30
  • that's awesome! wonder if something similar could be adapted for mtext. On can for sure adapt this using xpd=T, but it would be nice to easily use this to write titles and legends in the margins! – Simon C. Feb 11 '21 at 00:06
13

I needed to do this for a map in R and ended up using the "raster" package to draw halos around text labels.

http://rpackages.ianhowson.com/cran/raster/man/text.html

For example,

library(raster)

text(Points, labels = Points$Labels, halo = TRUE, hw = 0.08, hc = "white", cex = 0.8)
# hw = halo width
# hc = halo color

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nova
  • 5,423
  • 2
  • 42
  • 62
11

The shadowtext package can be used to draw outline or shadow around text for ggplot2 plots.

library(ggplot2)
library(shadowtext)

jet.colors <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", 
                                 "yellow", "#FF7F00", "red", "#7F0000"))

### Note: jet (rainbow) is not color-blind friendly, not perceptually uniform, and can be misleading 
# so please don't use it for your plots
# https://blogs.egu.eu/divisions/gd/2017/08/23/the-rainbow-colour-map/
# https://www.nature.com/articles/519291d
# Choose viridis instead https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html
# I only use jet here for the demonstration of the `shadowtext` package.

ggplot(faithfuld, aes(waiting, eruptions)) +
  geom_raster(aes(fill = density)) +
  scale_fill_gradientn(colors = jet.colors(7)) +
  geom_shadowtext(aes(x = 75, y = 4.5),
    label = "White text with black outline\nwill be visible on any background",
    check_overlap = TRUE,
    size = 8) +
  theme_minimal()

Created on 2018-10-14 by the reprex package (v0.2.1.9000)

Tung
  • 26,371
  • 7
  • 91
  • 115
2

I wrote a similar function for text fields, which also works on logarithmic scales.

install.packages("berryFunctions")
library("berryFunctions")
?textField

This may be considered nicer for vector graphics. Here are some examples:

enter image description here

PS: If you want to contribute: https://github.com/brry/berryFunctions

Berry Boessenkool
  • 1,506
  • 11
  • 15
1

Here is my attempt to add a text buffer when labelling contours (or circles in this example). It's definitely not as effective or pretty as koekenbakker's solution, but it served its purpose for me to isolate the text label from the line.

require(shape)
plot(250,250, xlim=c(0,500), ylim=c(0,500), axes=F, ylab="", xlab="", col="black") ## Set up the plotting area

circle_radius<-c(200, 150, 50) ## We want to draw a few circles which we will label after using our buffer

for (i in 1:length(circle_radius)){
    plotcircle(mid=c(250,250), r=circle_radius[i], lwd=0.5,lcol="grey40") ## Iteratively plot the circles

text_buffer<-seq(0.1, 0.7, by=0.01) ## This is the key to the text buffer. Create a vector of values to set the character size (cex) during the following loop

for(j in text_buffer){
    text(x=250+circle_radius[i], 250, print(paste(circle_radius[i],"m")), cex=j, srt=270, col="white") ## Write the text buffer in white starting from the smallest value of cex to the largest
}  ## End of loop for writing buffer
    text(x=250+circle_radius[i], 250, print(paste(circle_radius[i],"m")), cex=0.5, srt=270) ## Write over the buffer with black text

}  ## End of loop for drawing circles

Plot of circles with text buffers

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131