ggplot2, legend on top and margin

40,646

Solution 1

Like you said, I can't see it in your example, but I'm guessing the margin is of the legend itself. You can eliminate the margin around the legend itself by adding:

+ theme(legend.margin=margin(t = 0, unit='cm'))

This applies to ggplot2 v2.1.0 or higher. Note that, at least for now, the old solution still works as well:

+ theme(legend.margin=unit(-0.6,"cm")) # version 0.9.x

Solution 2

If I exaggerate the margins for more visibility, and run showViewports, I get the following:

p + guides(fill=guide_legend(keyheight=unit(1,"cm"))) + theme(plot.margin=unit(c(1,1,1,1),"cm"))
showViewport(col="black",label=TRUE, newpage=TRUE, leaves=FALSE)

enter image description here

from which it would appear that the non-existent title is somehow taking space.

Edit: nope, it's just an unfortunate overlap of the labels. It's not the title.

Let's look at the legend itself, which seems to be causing the problem.

library(gtable)
g = ggplotGrob(p)
leg = gtable_filter(g, "guide")
plot(leg)
leg$heights
# sum(0.5lines, sum(1.5mm, 10mm, 0mm, 1.5mm), 0.5lines)+0cm
grid.rect(height=leg$heights) 
grid.rect(height=leg$heights - unit(1,"line"), gp=gpar(lty=2))

so, indeed, it's the legend that's adding some margins (0.5 + 0.5 = 1 line in total). I reckon it's a missing guide.margin option in the theme, that is being replaced by a default value of half a line.

enter image description here

Solution 3

In the year since this question was asked/answered, ggplot entered maintenance mode, so there won't be any future updates (meaning the OP's strategy of waiting for an update won't work).

The accepted answer relies on fudging the margin around the legend with legend.margin. However, this doesn't generalize well, especially when using ggsave() with different sizes or scale factors. There is fortunately a more generalizable, universal solution, though.

legend.margin only takes a single value for padding on all sides, while plot.margin takes four values for the top, right, bottom, and left margins. The default margins are based on lines (rather than mm or inches), like so: plot.margin=unit(c(c(1, 1, 0.5, 0.5)), units="line")

If you set legend.margin to 0, you can use negative plot.margin values, based on line units, to move the legend to the edge of the plot area. Setting the top margin to -0.5 works perfectly:

ggplot(diamonds, aes(clarity, fill=cut)) + 
  geom_bar() +   
  theme(
    plot.margin=unit(c(-0.5, 1, 0.5, 0.5), units="line"),
    legend.position="top",
    plot.background=element_rect(fill="red"),
    legend.margin=unit(0, "lines")) +
  guides(fill=guide_legend(title.position="top"))

Correct legend on top

The same idea works if the legend is positioned at the bottom:

ggplot(diamonds, aes(clarity, fill=cut)) + 
  geom_bar() +   
  theme(
    plot.margin=unit(c(1, 1, -0.5, 0.5), units="line"),
    legend.position="bottom",
    plot.background=element_rect(fill="red"),
    legend.margin=unit(0, "lines")) +
  guides(fill=guide_legend(title.position="top"))

Correct legend on bottom

As long as you set the margin of interest to -0.5 lines, the extra whitespace should disappear. This should work at any viewport size and any width/height/scale combination with ggsave()

Share:
40,646
balin
Author by

balin

Updated on July 26, 2022

Comments

  • balin
    balin almost 2 years

    Consider the following:

    library(ggplot2)
    library(grid)
    ggplot(diamonds, aes(clarity, fill=cut)) + 
      geom_bar() +   
      theme(
        plot.margin=unit(x=c(0,0,0,0),units="mm"),
        legend.position="top",
        plot.background=element_rect(fill="red")) +
      guides(fill=guide_legend(title.position="top"))
    

    The output of that looks something like this: ggplot2 output In the context of plot.margin=unit(x=c(0,0,0,0),units="mm") there's an unseemly amount of white (red) space above the legend. Does anyone know how to remedy that?

    Thanks for any hint.

    Sincerely, Joh

  • daroczig
    daroczig almost 11 years
    And there is a margin for the panel too (panel.margin) and setting all to zero would still leave some extra space on the top. If you insist on eliminating those, you might pass negative numbers as units, e.g.: theme(plot.margin = unit(x = c(-5, 0, 0, 0), units = "mm")
  • balin
    balin almost 11 years
    Changed the background in the example to illustrate the issue. Neither panel.margin nor legend.margin have any effect.
  • sc_evans
    sc_evans almost 11 years
    I realize this isn't an exact solution to your problem, but why don't you position the legend inside the plot window instead? If you have a lot of free space somewhere in the plot window, you could put the legend there. With this example dataset, theme(legend.position=c(0.9,0.8)) places the legend in the upper right corner.
  • sc_evans
    sc_evans almost 11 years
    Actually, theme(legend.margin=unit(-0.6,"cm")) all but eliminates the margin. I'll adjust my answer to reflect this.
  • balin
    balin almost 11 years
    Thanks, but that's possibly not generalizable ... there may be scale factors etc. and fine tuning this for every plot is not particularly satisfying. The in-plot solution also does not work in my actual case (I used an example from ggplot2's documentation for simplicity's sake here).
  • balin
    balin almost 11 years
    Hmmm ... neither setting title explicitly to NULL nor setting it's element_text's size and/or line height to 0 brings any gain ... any further ideas?
  • baptiste
    baptiste almost 11 years
    i was on the wrong track. I've added further hints that now point at the legend instead.
  • baptiste
    baptiste almost 11 years
    @sc_evans unit(-1, "line") is probably closer to the truth according to my investigations below.
  • sc_evans
    sc_evans almost 11 years
    Ah, that makes sense. @balin -- might that be the generalizable answer you're looking for?
  • baptiste
    baptiste almost 11 years
    and posted a bug report on github
  • balin
    balin almost 11 years
    Works ok now. Have build a ggplot2 version check into my code in the hope that future versions will make guide.margin available ...
  • Admin
    Admin about 10 years
    @baptiste: Do you know how to wrap the legend title to two lines?
  • flexponsive
    flexponsive about 7 years
    This changed to theme(legend.margin=margin(t = -0.5, unit='cm'));
  • sc_evans
    sc_evans about 7 years
    Thanks @user3096626. Though a minor addendum: t = -0.5 will push the legend title out of view. I've edited the answer to include the v2.1.0+ solution.
  • Mikko
    Mikko about 6 years
    While this solution certainly reduces the distance between the legend and top viewport border, making the legend closer to the main plot seems impossible. I tried to fiddle with parameters, but cannot reach the solution that worked pre-2.1.0. legend.key.height=unit(0, "cm") reduces the distance and works for top/bottom legends yet the little gap introduced by something remains.