DaniCee
DaniCee

Reputation: 3217

Highlight specific x-axis elements in ggplot2 by drawing a circle around them

I have a sequence myseq associated to some data that I want to plot. Besides, I have another sequence checkseq that I want to compare myseq to.

The objective is to make a plot like the one below (with myseq as x-axis labels), but highlighting somehow the letters in myseq that are different from checkseq in that position.

To do that, I cannot change their color with element_markdown cause I already do so for other purposes in my real life case. I think the next best thing is to draw a circle or square around such letters.

This is my MWE:

myseq <- "AGAATATTATACATTCATCT"
set.seed(123)
mydata <- data.frame(time=1:100, value=rnorm(100, mean=10, sd=2))
indices <- seq(5, 100, length.out=20)
mysplit <- unlist(strsplit(myseq, ""))
#
checkseq <- "AGATTATTATAGGTTCATAT"
checksplit <- unlist(strsplit(checkseq, ""))
#
ind_df <- data.frame(call=mysplit, check=checksplit, time=indices)
ind_df$highlight <- ifelse(ind_df$call!=ind_df$check, TRUE, FALSE)
#
finaldf <- dplyr::left_join(mydata, ind_df, by="time")

P <- ggplot2::ggplot(finaldf, ggplot2::aes(x=time, y=value)) +
  ggplot2::geom_line(linewidth=0.5) +
  ggplot2::scale_x_continuous(breaks=indices, labels=seqsplit) +
  ggplot2::theme_light()
grDevices::pdf(file="test.pdf", height=4, width=10)
print(P)
grDevices::dev.off()

which produces this plot:

plot1

What I would like is the following plot, highlighting the letters with highlight==TRUE by drawing a circle (or square) around them.

plot2

Upvotes: 1

Views: 62

Answers (3)

Mikko Marttila
Mikko Marttila

Reputation: 11908

You can post-process the circle annotations in with grid:

circle_x_axis_labels <- function(index, radius = 1, ...) {
  grid::grid.force() # Ensure ggplot2 content is generated.
  grid::seekViewport("axis.3-1-3-1")
  axis_grob <- grid::grid.get("axis.3-1-3-1")
  axis_text_grob <- grid::getGrob(axis_grob, "text", grep = TRUE)
  grid::grid.draw(
    grid::circleGrob(
      x = axis_text_grob$x[index],
      y = grid::unit(0.5, "npc"),
      r = radius,
      gp = grid::gpar(...)
    )
  )
}

ggplot2::ggplot(finaldf, ggplot2::aes(x=time, y=value)) +
  ggplot2::geom_line(linewidth=0.5) +
  ggplot2::scale_x_continuous(breaks=indices, labels=mysplit) +
  ggplot2::theme_light()

circle_x_axis_labels(which(mysplit != checksplit), col = "red", fill = NA, lwd = 2)

Upvotes: 1

Roland
Roland

Reputation: 132969

Plot them with geom_point, specify axis limits and turn off clipping:

library(ggplot2)
P + 
  coord_cartesian(ylim = c(5.5, 14), clip = 'off') +
  geom_point(data = ind_df[ind_df$highlight,], 
               aes(x = time,
                   y = 4.85), color = "red", shape = 1, size = 6) 

resulting plot highlighting some axis labels with red circles

Unfortunately, you need to adjust y according to the dimensions of the plotting device because the size of the label text is fixed.

Upvotes: 0

Mikko Marttila
Mikko Marttila

Reputation: 11908

I’d recommend adding the check sequence as secondary axis labels:

ggplot2::ggplot(finaldf, ggplot2::aes(x = time, y = value)) +
  ggplot2::geom_line(linewidth = 0.5) +
  ggplot2::scale_x_continuous(
    name = "Sequence",
    breaks = indices,
    labels = mysplit,
    sec.axis = ggplot2::sec_axis(
      transform = identity,
      name = "Check Sequence",
      breaks = indices,
      labels = checksplit
    )
  ) +
  ggplot2::geom_rect(
    data = finaldf[finaldf$highlight, ],
    ggplot2::aes(
      xmin = time - 0.5,
      xmax = time + 0.5,
      ymin = -Inf,
      ymax = Inf
    ),
    fill = "red", alpha = 0.2
  ) +
  ggplot2::theme_light()
#> Warning: Removed 80 rows containing missing values or values outside the scale range
#> (`geom_rect()`).

Very curious to see though if someone comes up with a way to add the annotations on the axis.

Upvotes: 2

Related Questions