6

You can position the key legend manually in most plotting programs. For example, in gnuplot it's done using set key top right. In ggplot2, it's done like this.

Is there a plotting library, script, or simple algorithm that automatically positions the legend such that it overlaps the data in the plot minimally?

What I mean is: Suppose I plot the line y=x. A good place for the legend in that case would be top left or bottom right.

Community
  • 1
  • 1
Alan Turing
  • 12,223
  • 16
  • 74
  • 116
  • In R, I suspect the answer is no. But you're more likely to get the eyeballs of knowledgable R users with an [r] tag. – joran Aug 25 '11 at 23:30
  • I'm actually more interested in a standalone solution (script or algorithm that I'll implement) that will accomplish this for any plotting program (my weapon of choice is `gnuplot`) – Alan Turing Aug 25 '11 at 23:56

3 Answers3

8

Try this,

require(Hmisc)
?largest.empty

there are other discussions and functions proposed in R-help archives

baptiste
  • 75,767
  • 19
  • 198
  • 294
6
require(plotrix)

?emptyspace     # Find the largest empty space on a plot

This is the example from the help page:

x<-rnorm(100)
 y<-rnorm(100)
 plot(x,y,main="Find the empty space",xlab="X",ylab="Y")
 es<-plotrix::emptyspace(x,y)
 # use a transparent background so that any overplotted points are shown
 plotrix::boxed.labels(es,labels="Here is the\nempty space",bg="transparent")
IRTFM
  • 258,963
  • 21
  • 364
  • 487
2

A quick trick to get one of "topleft", "topright", "bottomleft" and "bottomright":

auto.legend.pos <- function(x,y,xlim=range(x),ylim=range(y)) {
  countIt <- function(a,zero.only=TRUE) {
    tl <- sum(x <= xlim[1]*(1-a)+xlim[2]*a & y >= ylim[1]*a+ylim[2]*(1-a))
    tr <- sum(x >= xlim[1]*a+xlim[2]*(1-a) & y >= ylim[1]*a+ylim[2]*(1-a))
    bl <- sum(x <= xlim[1]*(1-a)+xlim[2]*a & y <= ylim[1]*(1-a)+ylim[2]*a)
    br <- sum(x >= xlim[1]*a+xlim[2]*(1-a) & y <= ylim[1]*(1-a)+ylim[2]*a)
    c(topleft=tl,topright=tr,bottomleft=bl,bottomright=br)
  }
  for (k in seq(0.5,0.1,by=-0.05)) {
    a <- countIt(k)
    if (sum(a==0)>0) break
  }
  names(a)[which(a==0)][1]
}

Test:

plot(Sepal.Length~Petal.Length, data=iris)
auto.legend.pos(iris$Petal.Length, iris$Sepal.Length)
# [1] "topleft"

EDIT

par("usr") after plot returns the extremes of the user coordinates of the plotting regions: see ?par. So we can change the function to:

auto.legend.pos <- function(x,y,xlim=NULL,ylim=NULL) {
  if (dev.cur() > 1) {
    p <- par('usr')
    if (is.null(xlim)) xlim <- p[1:2]
    if (is.null(ylim)) ylim <- p[3:4]
  } else {
    if (is.null(xlim)) xlim <- range(x, na.rm = TRUE)
    if (is.null(ylim)) ylim <- range(y, na.rm = TRUE)
  }
  countIt <- function(a) {
    tl <- sum(x <= xlim[1]*(1-a)+xlim[2]*a & y >= ylim[1]*a+ylim[2]*(1-a))
    tr <- sum(x >= xlim[1]*a+xlim[2]*(1-a) & y >= ylim[1]*a+ylim[2]*(1-a))
    bl <- sum(x <= xlim[1]*(1-a)+xlim[2]*a & y <= ylim[1]*(1-a)+ylim[2]*a)
    br <- sum(x >= xlim[1]*a+xlim[2]*(1-a) & y <= ylim[1]*(1-a)+ylim[2]*a)
    c(topleft=tl,topright=tr,bottomleft=bl,bottomright=br)
  }
  for (k in seq(0.5,0.1,by=-0.05)) {
    a <- countIt(k)
    if (sum(a==0)>0) break
  }
  names(a)[which(a==0)][1]   # may delete "[1]"
}

plot(Sepal.Length~Petal.Length, data=iris, xlim=c(1,5), ylim=c(3.5,6))
auto.legend.pos(iris$Petal.Length, iris$Sepal.Length)
# [1] "bottomright"
chan1142
  • 609
  • 4
  • 13