43

I'd like to know if there is a way to draw "outlined text" with ggplot2, for example black text with a small white border, in order to make it easily readable on backgrounds such as maps.

Ideally I'd like to achieve the same type of labels you can see on Google Maps :

enter image description here

Thanks in advance for any hints !

juba
  • 47,631
  • 14
  • 113
  • 118
  • [related question](http://stackoverflow.com/questions/7734535/control-font-thickness-without-changing-font-size) – baptiste May 21 '12 at 23:45
  • There is now a pkg for this: https://cran.r-project.org/web/packages/shadowtext/vignettes/shadowtext.html – Ben Apr 26 '19 at 20:16

4 Answers4

28

Much simplier solution is to use shadowtext library and use geom_shadowtext instead of geom_text

Karol Daniluk
  • 506
  • 4
  • 10
  • Is there any way to combine `geom_shadowtext` with `geom_text_repel`? – jlp Aug 27 '20 at 23:31
  • EDIT: my previous question has been also asked here: https://stackoverflow.com/questions/56318012/using-ggrepel-and-shadowtext-on-the-same-geom-text – jlp Aug 27 '20 at 23:39
16

Here is an approach that implements the general idea from the shadowtext function in the TeachingDemos package. The code for the middle part could be wrapped into a function to simplify some things. The example is blatantly stolen from Richie Cotton's answer:

d <- diamonds[sample(nrow(diamonds), 10), ]  


p <- ggplot(d, aes(carat, price) ) 
theta <- seq(pi/8, 2*pi, length.out=16)
xo <- diff(range(d$carat))/200
yo <- diff(range(d$price))/200
for(i in theta) {
    p <- p + geom_text( 
        bquote(aes(x=carat+.(cos(i)*xo),y=price+.(sin(i)*yo),label=cut)), 
                    size=12, colour='black' )
}
p <- p + geom_text( aes(label=cut), size=12, colour='white' )
p <- p + opts( panel.background=theme_rect(fill='green' ) )
print(p)

enter image description here

Greg Snow
  • 48,497
  • 6
  • 83
  • 110
  • adding many new layers is probably a bit overkill, you could instead define a [grob](http://stackoverflow.com/questions/7734535/control-font-thickness-without-changing-font-size)+geom that replaces `textGrob+geom_text` as one layer. – baptiste May 21 '12 at 23:47
  • 1
    That is very cunning. I like it. – Richie Cotton May 22 '12 at 17:41
  • Another small problem is that all the black labels are written at once, and then all the white labels. That's not ideal where labels overlap such as in the bottom left corner of your example. But that's certainly the best answer so far, thanks ! – juba May 23 '12 at 07:58
  • @baptiste, that is a good idea, do you have any references or pointers on how to define a grob+geom object? – Greg Snow May 23 '12 at 15:36
  • 1
    @GregSnow there's no real reference apart from the source code. The only guide [that I know of](https://github.com/hadley/ggplot2/wiki/Creating-a-new-geom) is a bit outdated and unclear. The easiest thing would be to copy the code of `geom-text.r`, give it a new name, replace the `textGrob` used in its draw method with a vectorised version of `gridExtra::stextGrob`. – baptiste May 23 '12 at 23:50
  • 1
    Very nice solution! If you add `alpha` you get a nice shade gradient; e.g. `bquote(aes(x=carat+.(cos(i)*xo),y=price+.(sin(i)*yo),label=cut)), size=12, colour='black', alpha=1/8)` – Stingery Sep 16 '15 at 15:51
7

Not ideal or very flexible but you can get the effect by drawing bold mono text, then standard mono text on top.

I've used a green panel background to simulate the map.

d <- diamonds[sample(nrow(diamonds), 10), ]

(p <- ggplot(d, aes(carat, price)) +
  geom_text(
    aes(label = cut, family = "mono", fontface = "bold"), 
    size = 12, 
    colour = "black"
  ) +
  geom_text(
    aes(label = cut, family = "mono"), 
    size = 12, 
    colour = "white"
  ) +
  opts(panel.background = theme_rect(fill = "green"))
)

text-on-bold-text with the diamonds dataset

Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
2

The accepted answer by Greg Snow doesn't work anymore with ggplot2@2.2.1 because of the call of aes instead of aes_q.

Use

for(i in theta) {
  p <- p + geom_text( 
    aes_q(x = bquote(carat+.(cos(i)*xo)),
          y = bquote(price+.(sin(i)*yo)),
          label = ~cut), 
    size=12, colour='black' )
}

instead.

grssnbchr
  • 2,877
  • 7
  • 37
  • 71