Multi-color text in ggplot2

Occasionally when producing charts, it’s helpful to plot a single text element in multiple colors. Here’s an example of labels from the SDG Atlas where we used multiple colors to good effect to make labels for “Bangladesh” and “United States” clearer (I’ve darkened some parts of the chart to make the relevant labels clearer):


I say “we”. Actually, this is the kind of thing that was handled by the graphic designers we worked with, presumably using some Illustrator magic. I like it, and think it is a good labelling technique to have in your toolkit, when used in moderation as it is here.

The question is: can we do the same in pure R/ggplot2, in order to script more steps in our dataviz production? (Of course pleasing labelling in ggplot2 is itself a massive problem, but I’m working on that.)

You might think you could just paint the two halves of the label (e.g. “Bangl” & “adesh”) separately as different labels, but that won’t work: ggplot2 doesn’t give you pixel-level control over where things end up, so it’ll be near-impossible to align the two halves.

Instead what we can do is print the label twice, as follows:

 annotate("text", x = firebird$wt, y=firebird$mpg, hjust = 0, label="Pontiac Firebird", color="black") +
 annotate("text", x = firebird$wt, y=firebird$mpg, hjust = 0, label="Pon", color="white")

Of course this may not always work. One could imagine in some output formats overprinting will change the appearance of the text (though svg() looked ok). Or what if you have hjust = 0.5 for centered labels?

For a more generalisable technique, we can adapt an idea described here for base graphics. The key is that geom_text & co take a ‘parse’ argument, which parses plotmath expressions. So rather than splitting the label in two, we

annotate("text", x = firebird$wt, y=firebird$mpg, hjust = 0, parse=T, label='"Pon" * phantom("tiac Firebird")', color="white") +
annotate("text", x = firebird$wt, y=firebird$mpg, hjust = 0, parse=T, label='phantom("Pon") * "tiac Firebird"', color="black")

It’s still quite fiddly, and obviously fragile (in the sense that if things move around at the pixel level, the different parts of the label will likely end up in the wrong place). But it is possible!


Full gist:


  1. How would you write this as a function for strings of any length? I’m in the process of trying to write one where I specify the color of each word, but I’m finding it quite difficult to do. It seems like it would be quite valuable to have a function that allows easy coloring of text, rather than having a different annotate() function for every color you need.

    1. Already figured out how to do it. Will make a post on Reddit / wordpress with the code after making it more robust.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s