6
votes

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.

3
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
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

3 Answers

8
votes

Try this,

require(Hmisc)
?largest.empty

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

6
votes
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")
2
votes

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"