Henrik
Henrik

Reputation: 14460

Plot a legend outside of the plotting area in base graphics?

As the title says: How can I plot a legend outside the plotting area when using base graphics?

I thought about fiddling around with layout and produce an empty plot to only contain the legend, but I would be interested in a way using just the base graph facilities and e.g., par(mar = ) to get some space on the right of the plot for the legend.


Here an example:

plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")
legend(1,-1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

produces:

alt text

But as said, I would like the legend to be outside the plotting area (e.g., to the right of the graph/plot.

Upvotes: 224

Views: 460047

Answers (12)

ivan866
ivan866

Reputation: 582

require(Cairo)

cairo_pdf(sprintf('./plot01.pdf'), width=6.5, height=4, bg='white', antialias='none')
par(lab=c(11,11,6), cex.axis=1.05, cex.lab=.85, family='sans', font.axis=1, font.lab=2)
par(lend=1, ljoin=1, lheight=.8)
par(mar=c(3.15,3.15,1.25,5.25), oma=c(.2,.2,.2,.2), mgp=c(1.5,1,0))

plot(0, type='n', pty='m', xlim=c(.9, 3.1), ylim=c(-2,2), xaxt='n', yaxt='n', xaxs='i', yaxs='i', main='', xlab = '', ylab = '', bty='n')
abline(h=seq(-2,2), v=seq(1,3,.5), col='gray72', lty='longdash', lwd=.35, xpd=FALSE)
lines(1:3, rnorm(3), pch=1, lty=1, type='o', xpd=NA)
lines(1:3, rnorm(3), pch=2, lty=2, type='o', xpd=NA)

par(xaxt='s', yaxt='s', las=1)
text(grconvertX(.5, from='npc', to='user'), grconvertY(.5, from='lines', to='user'), 'X axis', adj=.5, cex=.85, srt=0, xpd=NA)
text(grconvertX(.75, from='lines', to='user'), grconvertY(.5, from='npc', to='user'), 'Y axis', adj=.5, cex=.85, srt=90, xpd=NA)
axis(1, at=seq(1,3,.5), labels=seq(1,3,.5), tick=FALSE, gap.axis=.65)
axis(1, at=seq(1,3,.5), labels=FALSE, tick=TRUE, tcl=-.5)
axis(1, at=seq(1,3,.1), labels=FALSE, tick=TRUE, tcl=-.25, lwd.ticks=.25)
axis(2, at=seq(-2,2,.5), labels=seq(-2,2,.5), tick=FALSE, gap.axis=.65)
axis(2, at=seq(-2,2,1), labels=FALSE, tick=TRUE, tcl=-.5)
axis(2, at=seq(-2,2,.5), labels=FALSE, tick=TRUE, tcl=-.25, lwd.ticks=.25)
arrows(grconvertX(.75, from='npc', to='user'), grconvertY(0, from='npc', to='user'), grconvertX(1.025, from='npc', to='user'), grconvertY(0, from='npc', to='user'), length=.1, code=2, angle=15, lwd=1, xpd=NA)
arrows(grconvertX(0, from='npc', to='user'), grconvertY(.75, from='npc', to='user'), grconvertX(0, from='npc', to='user'), grconvertY(1.04, from='npc', to='user'), length=.1, code=2, angle=15, lwd=1, xpd=NA)

legend('right', horiz=FALSE, cex=.75,
       c('group A', 'group B'),
       pch=c(1,2), lty=c(1,2),
       inset=c(-.2,0), bty='o', bg=rgb(1,1,1,.55), xpd=NA)
dev.off()

plot01.pdf

Upvotes: 1

Grant
Grant

Reputation: 1636

An old question, but I'd like to flag a new solution. The tinyplot package (homepage) is a lightweight extension of the base R graphics system that automatically supports group legends outside of the plot area. It is available on CRAN.

dat = data.frame(
  x   = rep(1:3, times = 2),
  y   = rnorm(6),
  grp = rep(c("A","B"), each = 3)
)

library(tinyplot)

plt(y ~ x | grp, dat, type = "o")

By default, grouping is displayed through colors. But you can adjust/add grouping aesthetics by invoking the "by" convenience argument.

plt(y ~ x | grp, dat, type = "o", pch = "by")

The default legend position is outside and to the right. But is easily moved using standard keywords. The trailing ! here causes the legend to be plotted outside of the plotting area.

plt(y ~ x | grp, dat, type = "o", pch = "by", legend = "bottom!")

You can can tweak the appearance of the legend by passing standard arguments via through a list, e.g. here adding a border.

plt(y ~ x | grp, dat, type = "o", pch = "by", legend = list("bottom!", bty = "o"))

etc.

tinyplot also supports continuous grouping variables through gradient legends, plus a bunch of other convenience features. But it the end result is always a base R plot and so it should be compatible with other base (i.e, "graphics") workflows and settings.

Upvotes: 1

Jan van der Laan
Jan van der Laan

Reputation: 8105

Another solution, besides the ones already mentioned (using layout or par(xpd=TRUE)) is to overlay your plot with a transparent plot over the entire device and then add the legend to that.

The trick is to overlay a (empty) graph over the complete plotting area and adding the legend to that. We can use the par(fig=...) option. First we instruct R to create a new plot over the entire plotting device:

par(fig=c(0, 1, 0, 1), oma=c(0, 0, 0, 0), mar=c(0, 0, 0, 0), new=TRUE)

Setting oma and mar is needed since we want to have the interior of the plot cover the entire device. new=TRUE is needed to prevent R from starting a new device. We can then add the empty plot:

plot(0, 0, type='n', bty='n', xaxt='n', yaxt='n')

And we are ready to add the legend:

legend("bottomright", ...)

will add a legend to the bottom right of the device. Likewise, we can add the legend to the top or right margin. The only thing we need to ensure is that the margin of the original plot is large enough to accomodate the legend.

Putting all this into a function;

add_legend <- function(...) {
  opar <- par(fig=c(0, 1, 0, 1), oma=c(0, 0, 0, 0), 
    mar=c(0, 0, 0, 0), new=TRUE)
  on.exit(par(opar))
  plot(0, 0, type='n', bty='n', xaxt='n', yaxt='n')
  legend(...)
}

And an example. First create the plot making sure we have enough space at the bottom to add the legend:

par(mar = c(5, 4, 1.4, 0.2))
plot(rnorm(50), rnorm(50), col=c("steelblue", "indianred"), pch=20)

Then add the legend

add_legend("topright", legend=c("Foo", "Bar"), pch=20, 
   col=c("steelblue", "indianred"),
   horiz=TRUE, bty='n', cex=0.8)

Resulting in:

Example figure shown legend in top margin

Upvotes: 40

Karolis Koncevičius
Karolis Koncevičius

Reputation: 9666

Adding another simple alternative that is quite elegant in my opinion.

Your plot:

plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

Legend:

legend("bottomright", c("group A", "group B"), pch=c(1,2), lty=c(1,2),
       inset=c(0,1), xpd=TRUE, horiz=TRUE, bty="n"
       )

Result:

picture with legend

Here only the second line of the legend was added to your example. In turn:

  • inset=c(0,1) - moves the legend by fraction of plot region in (x,y) directions. In this case the legend is at "bottomright" position. It is moved by 0 plotting regions in x direction (so stays at "right") and by 1 plotting region in y direction (from bottom to top). And it so happens that it appears right above the plot.
  • xpd=TRUE - let's the legend appear outside of plotting region.
  • horiz=TRUE - instructs to produce a horizontal legend.
  • bty="n" - a style detail to get rid of legend bounding box.

Same applies when adding legend to the side:

par(mar=c(5,4,2,6))
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

legend("topleft", c("group A", "group B"), pch=c(1,2), lty=c(1,2),
       inset=c(1,0), xpd=TRUE, bty="n"
       )

Here we simply adjusted legend positions and added additional margin space to the right side of the plot. Result:

picture with legend 2

Upvotes: 13

Vandka
Vandka

Reputation: 225

Recently I found very easy and interesting function to print legend outside of the plot area where you want.

Make the outer margin at the right side of the plot.

par(xpd=T, mar=par()$mar+c(0,0,0,5))

Create a plot

plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

Add legend and just use locator(1) function as like below. Then you have to just click where you want after load following script.

legend(locator(1),c("group A", "group B"), pch = c(1,2), lty = c(1,2))

Try it

Upvotes: 10

jbaums
jbaums

Reputation: 27408

I like to do it like this:

par(oma=c(0, 0, 0, 5))
plot(1:3, rnorm(3), pch=1, lty=1, type="o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch=2, lty=2, type="o")
legend(par('usr')[2], par('usr')[4], bty='n', xpd=NA,
       c("group A", "group B"), pch=c(1, 2), lty=c(1,2))

enter image description here

The only tweaking required is in setting the right margin to be wide enough to accommodate the legend.

However, this can also be automated:

dev.off() # to reset the graphics pars to defaults
par(mar=c(par('mar')[1:3], 0)) # optional, removes extraneous right inner margin space
plot.new()
l <- legend(0, 0, bty='n', c("group A", "group B"), 
            plot=FALSE, pch=c(1, 2), lty=c(1, 2))
# calculate right margin width in ndc
w <- grconvertX(l$rect$w, to='ndc') - grconvertX(0, to='ndc')
par(omd=c(0, 1-w, 0, 1))
plot(1:3, rnorm(3), pch=1, lty=1, type="o", ylim=c(-2, 2))
lines(1:3, rnorm(3), pch=2, lty=2, type="o")
legend(par('usr')[2], par('usr')[4], bty='n', xpd=NA,
       c("group A", "group B"), pch=c(1, 2), lty=c(1, 2))

enter image description here

Upvotes: 19

Mateo Sanchez
Mateo Sanchez

Reputation: 1654

You could do this with the Plotly R API, with either code, or from the GUI by dragging the legend where you want it.

Here is an example. The graph and code are also here.

x = c(0,1,2,3,4,5,6,7,8) 
y = c(0,3,6,4,5,2,3,5,4) 
x2 = c(0,1,2,3,4,5,6,7,8) 
y2 = c(0,4,7,8,3,6,3,3,4)

You can position the legend outside of the graph by assigning one of the x and y values to either 100 or -100.

legendstyle = list("x"=100, "y"=1)
layoutstyle = list(legend=legendstyle)

Here are the other options:

  • list("x" = 100, "y" = 0) for Outside Right Bottom
  • list("x" = 100, "y"= 1) Outside Right Top
  • list("x" = 100, "y" = .5) Outside Right Middle
  • list("x" = 0, "y" = -100) Under Left
  • list("x" = 0.5, "y" = -100) Under Center
  • list("x" = 1, "y" = -100) Under Right

Then the response.

response = p$plotly(x,y,x2,y2, kwargs=list(layout=layoutstyle));

Plotly returns a URL with your graph when you make a call. You can access that more quickly by calling browseURL(response$url) so it will open your graph in your browser for you.

url = response$url
filename = response$filename

That gives us this graph. You can also move the legend from within the GUI and then the graph will scale accordingly. Full disclosure: I'm on the Plotly team.

Legend on side of graph

Upvotes: 4

Spacedman
Spacedman

Reputation: 94317

Maybe what you need is par(xpd=TRUE) to enable things to be drawn outside the plot region. So if you do the main plot with bty='L' you'll have some space on the right for a legend. Normally this would get clipped to the plot region, but do par(xpd=TRUE) and with a bit of adjustment you can get a legend as far right as it can go:

 set.seed(1) # just to get the same random numbers
 par(xpd=FALSE) # this is usually the default

 plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2), bty='L')
 # this legend gets clipped:
 legend(2.8,0,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

 # so turn off clipping:
 par(xpd=TRUE)
 legend(2.8,-1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

Upvotes: 136

Mike T
Mike T

Reputation: 43742

No one has mentioned using negative inset values for legend. Here is an example, where the legend is to the right of the plot, aligned to the top (using keyword "topright").

# Random data to plot:
A <- data.frame(x=rnorm(100, 20, 2), y=rnorm(100, 20, 2))
B <- data.frame(x=rnorm(100, 21, 1), y=rnorm(100, 21, 1))

# Add extra space to right of plot area; change clipping to figure
par(mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE)

# Plot both groups
plot(y ~ x, A, ylim=range(c(A$y, B$y)), xlim=range(c(A$x, B$x)), pch=1,
               main="Scatter plot of two groups")
points(y ~ x, B, pch=3)

# Add legend to top right, outside plot region
legend("topright", inset=c(-0.2,0), legend=c("A","B"), pch=c(1,3), title="Group")

The first value of inset=c(-0.2,0) might need adjusting based on the width of the legend.

legend_right

Upvotes: 180

Marcelo Neves
Marcelo Neves

Reputation: 185

Sorry for resurrecting an old thread, but I was with the same problem today. The simplest way that I have found is the following:

# Expand right side of clipping rect to make room for the legend
par(xpd=T, mar=par()$mar+c(0,0,0,6))

# Plot graph normally
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

# Plot legend where you want
legend(3.2,1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

# Restore default clipping rect
par(mar=c(5, 4, 4, 2) + 0.1)

Found here: http://www.harding.edu/fmccown/R/

Upvotes: 18

Roman Luštrik
Roman Luštrik

Reputation: 70653

I can offer only an example of the layout solution already pointed out.

layout(matrix(c(1,2), nrow = 1), widths = c(0.7, 0.3))
par(mar = c(5, 4, 4, 2) + 0.1)
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")
par(mar = c(5, 0, 4, 2) + 0.1)
plot(1:3, rnorm(3), pch = 1, lty = 1, ylim=c(-2,2), type = "n", axes = FALSE, ann = FALSE)
legend(1, 1, c("group A", "group B"), pch = c(1,2), lty = c(1,2))

an ugly picture :S

Upvotes: 10

Dirk is no longer here
Dirk is no longer here

Reputation: 368599

Try layout() which I have used for this in the past by simply creating an empty plot below, properly scaled at around 1/4 or so and placing the legend parts manually in it.

There are some older questions here about legend() which should get you started.

Upvotes: 3

Related Questions