Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Put y axis title in top left corner of graph

Tags:

r

ggplot2

I'm drawing a plot with ggplot2 in R and I'd like the title for the y axis to appear in the top left corner of the plot. Consider the following code for the default behaviour:

require(ggplot2)

xy = data.frame(x=1:10, y=10:1)

ggplot(data = xy) +
  geom_point(aes(x = x, y = y)) +
  ylab("very long label")

This produces the following graph:

Original graph

I would like to move and rotate the text "very long label". I can do this somewhat using the theme() function:

ggplot(data = xy) +
  geom_point(aes(x = x, y = y)) +
  ylab("very long label") +
  theme(axis.title.y = element_text(angle = 0, vjust = 1.1, hjust = 10))

Which gives me this:

modified graph

You can see where I'm going with this, but the margins are incorrect -- the left margin is too large because the space is reserved for a rotated label and the top margin is too small for the text.

How can I tell ggplot that I want the y axis title at that position without rotation and have it reserve the appropriate space for it?

like image 538
Lars Kotthoff Avatar asked Mar 19 '14 16:03

Lars Kotthoff


2 Answers

Put it in the main plot title:

ggplot(data = xy) +
  geom_point(aes(x = x, y = y)) +
  ggtitle("very long label") +
  theme(plot.title = element_text(hjust = 0))

You can shove it slightly more to the left if you like using negative hjust values, although if you go too far the label will be clipped. In that case you might try playing with the plot.margin:

ggplot(data = xy) +
  geom_point(aes(x = x, y = y)) +
  ggtitle("very long label") +
  theme(plot.title = element_text(hjust = -0.3),
        plot.margin = rep(grid::unit(0.75,"in"),4))

So obviously this makes it difficult to add an actual title to the graph. You can always annotate manually using something like:

grid.text("Actual Title",y = unit(0.95,"npc"))

Or, vice-versa, use grid.text for the y label.

like image 127
joran Avatar answered Sep 27 '22 16:09

joran


I just experienced the same problem and found a solution that can be useful to the community.

The idea is to create a custom theme that substitutes the axis label by a subtitle. The plot used as an example will be the following:

p <- ggplot2::ggplot(iris, ggplot2::aes(x = Sepal.Width, y = Sepal.Length,
                                        color = Species)) +
  ggplot2::geom_point()

The minimalist solution

Create a class mytheme that modifies the theme used by ggplot. Here, in the minimalist solution, no constraint is made over style (I will present limitations later).

custom_theme <- function(...){
  out <- ggplot2::theme_minimal() + ggplot2::theme(...)
  class(out) <- c("mytheme",class(ggplot2::theme_minimal()))
  return(out)
}

Associate to this class a ggplot_add method that overrides the default method. This custom method takes the existing y label and put it into subtitle. The substitution is made in the first two lines of the function. The third line of the function uses a update_theme function defined later on:

ggplot_add.mytheme <- function(object, plot, object_name) {

  plot$labels$subtitle <- plot$labels$y
  plot$labels$y <- NULL
  plot$theme <- update_theme(plot$theme, object)
  plot
}

update_theme function was part of ggplot2 package up to recently. The function disappeared recently (see this commit). The hack I implemented is to define the function like this:

update_theme <- function(oldtheme, newtheme) {

  # If the newtheme is a complete one, don't bother searching
  # the default theme -- just replace everything with newtheme
  if (isTRUE(attr(newtheme, "complete", exact = TRUE)))
    return(newtheme)

  # These are elements in newtheme that aren't already set in oldtheme.
  # They will be pulled from the default theme.
  newitems <- !names(newtheme) %in% names(oldtheme)
  newitem_names <- names(newtheme)[newitems]
  oldtheme[newitem_names] <- theme_get()[newitem_names]

  # Update the theme elements with the things from newtheme
  # Turn the 'theme' list into a proper theme object first, and preserve
  # the 'complete' attribute. It's possible that oldtheme is an empty
  # list, and in that case, set complete to FALSE.
  old.validate <- isTRUE(attr(oldtheme, "validate"))
  new.validate <- isTRUE(attr(newtheme, "validate"))
  oldtheme <- do.call(theme, c(oldtheme,
                               complete = isTRUE(attr(oldtheme, "complete")),
                               validate = old.validate & new.validate))

  oldtheme + newtheme
}

The theme can be used then to create the expected output:

p + ggplot2::labs(y = "A very very long label") + custom_theme() 

enter image description here

Limitations

Axis style elements are not reported to subtitle

If you want to change later on some style elements for the axis title (e.g. text size), that will be problematic for the y label since it is a subtitle element. For instance, the user that runs

p + custom_theme(axis.title = ggplot2::element_text(size = 24)) 

enter image description here

would expect the y label to also be of size 24. I don't think this a real problem because you can change the custom_theme with more constraints

When setting labs after calling the theme

This solution will be problematic if a user sets the theme before changing the labs, e.g.

p + ggplot2::labs(y = "A very very long label") + custom_theme()  + ggplot2::labs(y = "override")

enter image description here

like image 37
linog Avatar answered Sep 27 '22 15:09

linog