10

Here's a plot:

library(ggplot2)
library(tibble)

ggplot(head(mtcars) %>% rownames_to_column("cars"),
       aes(x = reorder(cars, - drat), 
           y = drat)) +
  geom_col() +
  coord_flip()

How can I apply bold font on specific car names (for example just on "Hornet 4 Drive" and "Datsun 710")?

I would prefer a quite "general" answer, i.e an answer that makes it easy to apply a particular color or another font family instead of bold font.

bretauv
  • 7,756
  • 2
  • 20
  • 57

4 Answers4

13

ggtext allows you to use markdown and html tags for axis labels and other text. So we can create a function to pass to the labels argument of scale_y_discrete (as @RomanLuštrik suggested in their comment), through which we can select the labels to highlight, the color, and the font family:

library(tidyverse)
library(ggtext)
library(glue)

highlight = function(x, pat, color="black", family="") {
  ifelse(grepl(pat, x), glue("<b style='font-family:{family}; color:{color}'>{x}</b>"), x)
}

head(mtcars) %>% rownames_to_column("cars") %>% 
  ggplot(aes(y = reorder(cars, - drat), 
             x = drat)) +
  geom_col() +
  scale_y_discrete(labels= function(x) highlight(x, "Datsun 710|Hornet 4", "red")) +
  theme(axis.text.y=element_markdown())

enter image description here

iris %>% 
  ggplot(aes(Species, Petal.Width)) +
  geom_point() + 
  scale_x_discrete(labels=function(x) highlight(x, "setosa", "purple", "Copperplate")) +
  theme(axis.text.x=element_markdown(size=15))

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285
  • Nice answer, only drawback I see for now is that CRAN version of `ggtext` is not available on R 4.0.0 (unless I have a problem with my R installation) – bretauv May 11 '20 at 16:29
  • I'm not running R 4.0.0 yet, but maybe try installing the development version of ggtext: `remotes::install_github("wilkelab/ggtext")`. – eipi10 May 11 '20 at 16:30
  • In your answer, you inverted the `x` and `y` coordinates in `aes()` and you removed `coord_flip()`. If I invert these coordinates back (as in my post) and use `coord_flip()`, your code does not work anymore. Though this is not a big problem, I think it is worth mentioning in your answer – bretauv May 11 '20 at 16:48
  • 2
    In the current version of ggplot2, you don't need to specify `coord_flip`. If you map the discrete variable to the y-axis`, ggplot will automatically render the plot as a horizontal plot. – eipi10 May 11 '20 at 17:13
6

It would seem there's an easier way to approach this (no need to make your own labeller). Just specify specific face of the label in theme(axis.text.y). Notice that I had to define the x axis values as a factor to make order of labels predictable.

library(ggplot2)

mtcars$cars <- as.factor(rownames(mtcars))
bold.cars <- c("Merc 280", "Fiat 128")

bold.labels <- ifelse(levels(mtcars$cars) %in% bold.cars, yes = "bold", no = "plain")

ggplot(mtcars, aes(x = cars, y = drat)) +
  theme(axis.text.y = element_text(face = bold.labels)) +
  geom_col() +
  coord_flip()

enter image description here

Roman Luštrik
  • 69,533
  • 24
  • 154
  • 197
  • 2
    It works but produces a warning: ```Warning message: Vectorized input to `element_text()` is not officially supported. Results may be unexpected or may change in future versions of ggplot2. ``` Do you know if it is possible to fix it? – bretauv May 11 '20 at 16:16
  • I get no warnings running the code above. `ggplot2` version 3.2.1. – Roman Luštrik May 11 '20 at 17:16
  • 1
    I have a warning with `ggplot2` 3.3.0 – bretauv May 11 '20 at 17:48
4

One approach is to use expression in a labels argument.

library(ggplot2)
library(tibble)

ggplot(head(mtcars) %>% rownames_to_column("cars"),
       aes(x = reorder(cars, - drat), 
           y = drat)) +
  geom_col() +
  scale_x_discrete(labels = c("Mazda RX4",
                              "Mazda RX4 Wag",
                              expression(bold("Datsun 710")),
                              expression(bold("Hornet 4 Drive")),
                              "Hornet Sportabout",
                              "Valiant")) + 
  coord_flip()    

enter image description here

If you wanted to do this in an automated way, you could define a custom bolding function to make the expression:

library(ggplot2)
library(dplyr)
library(tibble)

MakeExp <- function(x,y){
  exp <- vector(length = 0, mode = "expression")
  for (i in seq_along(x)) {
    if (i %in% y) exp[[i]] <- bquote(bold(.(x[i])))
    else exp[[i]] <- x[i]
  }
return(exp)
}

ggplot(head(mtcars) %>% rownames_to_column("cars"),
       aes(x = reorder(cars, - drat), 
           y = drat)) +
  geom_col() +
  scale_x_discrete(labels = MakeExp(rownames(head(mtcars)),c(3,4))) + 
  coord_flip()            

bretauv
  • 7,756
  • 2
  • 20
  • 57
Ian Campbell
  • 23,484
  • 14
  • 36
  • 57
  • One drawback of this approach is that you need to write all the names of the cars (in this example), is it possible only to apply bold font to some car names? – bretauv May 11 '20 at 15:38
  • @bretauv my guess is that this is possible through a custom labeller function. – Roman Luštrik May 11 '20 at 15:41
  • @bretauv I edited my answer to give an example of a custom label expression function. – Ian Campbell May 11 '20 at 16:05
  • Your edit improved your answer, but still this requires to know the row number corresponding to the car name, which I found quite annoying when you have a lot of names – bretauv May 11 '20 at 16:20
  • 1
    You could easily change `y` to be the names you want to bold and the if statement to `if (x[i] %in% y)`. – Ian Campbell May 11 '20 at 16:21
  • Indeed, I see that you can replace `bold` by `italic` to put those names in italic font, but can you change their color? – bretauv May 11 '20 at 16:24
  • `plotmath` doesn't seem to manage colors unfortunately. I would probably go for an approach like [this question and answer](https://stackoverflow.com/questions/49735290/) for colors. – Ian Campbell May 11 '20 at 16:29
  • Okay, thanks for your answer anyway (@eipi10's answer allows color-formatting if you're interested) – bretauv May 11 '20 at 16:31
2

An "automated" (semi) approach that will throw a warning(see later, see this issue):

library(ggplot2)
library(dplyr)
library(tibble)

custom_face <- ifelse(row.names(mtcars) %in% c("Hornet 4 Drive","Datsun 710"),
                      "bold","plain")
head(mtcars) %>% rownames_to_column("cars") %>%
ggplot(aes(x = reorder(cars, - drat), 
           y = drat)) +
  geom_col() +
  coord_flip() +
  theme(axis.text.y = element_text(face=custom_face))

Warning(it is unclear to me from the discussion on the linked issue what the final decision regarding this "feature"'s future was)

Warning message: Vectorized input to element_text() is not officially supported. Results may be unexpected or may change in future versions of ggplot2.

Result:

enter image description here

bretauv
  • 7,756
  • 2
  • 20
  • 57
NelsonGon
  • 13,015
  • 7
  • 27
  • 57
  • 1
    I have the error `Error in element_text(face = custom_face): object 'custom_face' not found` (in a new session) – bretauv May 11 '20 at 16:08
  • Did you run the code as it is in this answer? Oh, let me edit. I had defined some other test function elswhere – NelsonGon May 11 '20 at 16:08
  • 1
    Yes I copied and pasted your code in a new session (just added the necessary `library()` calls) – bretauv May 11 '20 at 16:10
  • Thanks, I have edited and it should work now. Anyway, a similar answer has been added although I had answered this earlier. – NelsonGon May 11 '20 at 16:11