Adjust spacing between text in horizontal legend

2019-03-09 17:43发布

问题:

I have a plot with a horizontal legend:

 legend("bottomleft", inset = c(0, -0.3), bty = "n",
        x.intersp=0, xjust=0,yjust=0,
        legend=c("AAPL", "Information Technology",
                 "Technology Hardware and Equipment", "S&P 500"),
        col=c("black", "red", "blue3", "olivedrab3"),
        lwd=2, cex = 0.5, xpd = TRUE, ncol = 4)

The problem is that there is a huge spacing between the first item of the legend, "AAPL", and the second item "Information Technology".

I tried adjusting the spacing using txt.width(), but it didn't work at all. Or maybe I am not using this option as directed. This is how I have introduced the txt.width option inside legend():

txt.width = c(2,1,1)

I am not sure if it relevant to mention, but my x-axis is an axis of dates!

Is there an easy way of customizing the spaces between the text in the legend?

Thanks!

回答1:

text.width can give you control over the width of each column in your legend, but it's not straightforward. Basically, text.width is a vector that will be multiplied by another vector that is as long as your vector of legend strings. The elements of that second vector are integers from 0 to length(legend)-1. See the code to legend() for the gory details. The important thing is that you can think of this product of text.width and the second vector as, approximately, the x coordinates for your legend elements. Then if you know which x coordinates you want, you can calculate what needs to be passed in the text.width argument.

legtext <- c("AAPL", "Information Technology", 
             "Technology Hardware and Equipment", "S&P 500")
xcoords <- c(0, 10, 30, 60)
secondvector <- (1:length(legtext))-1
textwidths <- xcoords/secondvector # this works for all but the first element
textwidths[1] <- 0 # so replace element 1 with a finite number (any will do)

And then your final code could look something like this (except that we don't know your original plotting data or parameters):

plot(x=as.Date(c("1/1/13","3/1/13","5/1/13"), "%m/%d/%y"), y=1:3, ylim=c(0,3))

legend(x="bottomleft", bty = "n", x.intersp=0, xjust=0, yjust=0,
   legend=legtext, 
   col=c("black", "red", "blue3", "olivedrab3"), 
   lwd=2, cex = 0.5, xpd = TRUE, ncol = 4,
   text.width=textwidths)

As Andre Silva mentioned, the values you'll want in xcoords and textwidths will depend on the current size of your plot, the range of values specified for your x axis, etc.

Also, secondvector above would look different if you had more than one element per column. For example, for two columns with two legend elements apiece, secondvector == c(0,0,1,1).



回答2:

plot(1,1,xlab="",ylab="",xlim=c(0,2),ylim=c(0,2))

legend("bottomleft", text.width=c(0,0.085,0.235,0.35),
       inset = c(0, -0.2), bty = "n", x.intersp=0.5,
       xjust=0, yjust=0,
       legend=c("AAPL", "Information Technology",
                "Technology Hardware and Equipment", "S&P 500"),
       col=c("black", "red", "blue3", "olivedrab3"),
       lwd=3, cex = 0.75, xpd = TRUE, horiz=TRUE)

I used text.width with four arguments to set the space between strings in the legend. The second argument inside text.width managed to set the distance between "AAPL" and "Information technology", and so on for the third and fourth arguments.

Unfortunately, I needed to reset the values inside text.width every time I changed the plot size.



回答3:

On my system (platform: x86_64-w64-mingw32, R version: 3.4.1 (2017-06-30)) the solutions provided so far by Andre Silva and pangja are not satisfactory. Both solutions require user input and are dependent on the device size. Since I get never used to the text.width command and always had to adjust the values with try-and-error, I wrote the function (f.horlegend). The function has similar arguments as the function legend and is based on the idea posted here.

The function creates a horizontal (one row) legend, which can be positioned by the commands known from the function legend, e.g. "bottomleft"

f.horlegend <- function(pos, legend, xoff = 0, yoff = 0, 
  lty = 0, lwd = 1, ln.col = 1, seg.len = 0.04, 
  pch = NA, pt.col = 1, pt.bg = NA, pt.cex = par("cex"), pt.lwd = lwd, 
  text.cex = par("cex"), text.col = par("col"), text.font = NULL, text.vfont = NULL, 
  bty = "o", bbord = "black", bbg = par("bg"), blty = par("lty"), blwd = par("lwd"), bdens = NULL, bbx.adj = 0, bby.adj = 0.75 
) {

  ### get original par values and re-set them at end of function
  op <- par(no.readonly = TRUE)
  on.exit(par(op))

  ### new par with dimension [0,1]
  par(new=TRUE, xaxs="i", yaxs="i", xpd=TRUE)
  plot.new()

  ### spacing between legend elements
  d0 <- 0.01 * (1 + bbx.adj)
  d1 <- 0.01
  d2 <- 0.02
  pch.len <- 0.008
  ln.len <- seg.len/2

  n.lgd <- length(legend)

  txt.h <- strheight(legend[1], cex = text.cex, font = text.font, vfont = text.vfont) *(1 + bby.adj)
  i.pch <- seq(1, 2*n.lgd, 2)
  i.txt <- seq(2, 2*n.lgd, 2)

  ### determine x positions of legend elements
  X <- c(d0 + pch.len, pch.len + d1, rep(strwidth(legend[-n.lgd])+d2+pch.len, each=2))
  X[i.txt[-1]] <- pch.len+d1

  ### adjust symbol space if line is drawn
  if (any(lty != 0)) {
    lty <- rep(lty, n.lgd)[1:n.lgd]
    ln.sep <- rep(ln.len - pch.len, n.lgd)[lty]
    ln.sep[is.na(ln.sep)] <- 0
    X <- X + rep(ln.sep, each=2)
    lty[is.na(lty)] <- 0
  } 

  X <- cumsum(X)

  ### legend box coordinates
  bstart <- 0
  bend <- X[2*n.lgd]+strwidth(legend[n.lgd])+d0

  ### legend position
  if (pos == "top" | pos == "bottom" | pos == "center") x_corr <- 0.5 - bend/2 +xoff
  if (pos == "bottomright" | pos == "right" | pos == "topright") x_corr <- 1. - bend + xoff
  if (pos == "bottomleft" | pos == "left" | pos == "topleft") x_corr <- 0 + xoff

  if (pos == "bottomleft" | pos == "bottom" | pos == "bottomright") Y <- txt.h/2 + yoff
  if (pos == "left" | pos == "center" | pos =="right") Y <- 0.5 + yoff
  if (pos == "topleft" | pos == "top" | pos == "topright") Y <- 1  - txt.h/2 + yoff

  Y <- rep(Y, n.lgd)
  ### draw legend box
  if (bty != "n") rect(bstart+x_corr, Y-txt.h/2, x_corr+bend, Y+txt.h/2, border=bbord, col=bbg, lty=blty, lwd=blwd, density=bdens)

  ### draw legend symbols and text
  segments(X[i.pch]+x_corr-ln.len, Y, X[i.pch]+x_corr+ln.len, Y, col = ln.col, lty = lty, lwd = lwd)
  points(X[i.pch]+x_corr, Y, pch = pch, col = pt.col, bg = pt.bg, cex = pt.cex, lwd = pt.lwd)
  text(X[i.txt]+x_corr, Y, legend, pos=4, offset=0, cex = text.cex, col = text.col, font = text.font, vfont = text.vfont)

}

Arguments

pos position of the legend (c("bottomleft", "bottom", "bottomright", "left", "center", "right", "topleft", "top", "topright"))

legend legend text

xoff adjustement of position in x-direction. NB: the legend is plotted on a plot with limits = c(0,1)

yoff same as xoff but in y-directin

lty The line type. Line types can only be specified as an integer (0=blank, 1=solid (default), 2=dashed, 3=dotted, 4=dotdash, 5=longdash, 6=twodash)

lwd The line width, a positive number, defaulting to 1

ln.col The line color

seg.len The length of the line, deafult to 0.04

pch An integer specifying the symbol.

pt.col The symbol color.

pt.bg The background color of the symbol.

pt.cex expansion factor for the symbol

pt.lwd line width of symbol

text.cex expansion factor for the text

text.col text color

text.font text font

text.vfont see vfont in text-help

bty the type of box to be drawn around the legend. The allowed values are "o" (the default) and "n"

bbord color of the legend box border

bbg background color

blty border style

blwd border line width

bdens density of lines, see segment-help

bbx.adj relative value to increase space between text and horizontal box line

bby.adj same as bbx.adj but for vertical boc line

Unfortunately, I don't have time to create a package at the moment. But feel free to use the function. Any comments and ideas to improve the functions are welcome.

Some examples

plot(1:100, rnorm(100))
lgd.text <- c("12", "12")
sapply(c("bottomleft", "bottom", "bottomright", "left", "center", "right", "topleft", "top", "topright"), function(x) f.horlegend(x, lgd.text, pch=16, lty=c(NA, 1), bbg="orange"))


plot(1:100, rnorm(100))
lgd.text <- c("12", "132", "12345")
f.horlegend("topleft", lgd.text, pch=NA)
f.horlegend("top", lgd.text, pch=NA, bby.adj=1.5, bbord="red")
f.horlegend("left", lgd.text, xoff=0.2, pch=1, bby.adj=0, bbord="red", bbg="lightgreen")
f.horlegend("left", lgd.text, xoff=0.2, yoff=-0.05, pch=c(NA, 1, 6), lty=1, bbx.adj=2, bby.adj=0, bbord="red", bbg="lightgreen")

f.horlegend("topright", lgd.text, yoff=0.1, pch=c(1,2,NA), lty=c(NA, NA, 2), bbord="red", blty=2, blwd=2)

lgd.text <- c("12", "123456", "12345", "123")
f.horlegend("bottom", lgd.text, yoff=0.1, pch=c(1,2,NA), lty=c(NA, 1, 2), text.font=2)
f.horlegend("bottom", lgd.text, pch=c(1,2,NA), lty=c(NA, 1, 2), text.font=c(1,2,3))

plot(seq(as.POSIXct("2017-08-30"), as.POSIXct("2017-09-30"), by="weeks"), rnorm(5), type="l")
f.horlegend("topleft", "random values", lty=1)


回答4:

For my case, there were 5 legends in horizontal way. I have to customize the spacing between each legend. Following was the code snippet for this purpose.

legend("topright",horiz = T, legend = df2, fill = col_box,
       inset = c(-0.2,-0.1), xpd = TRUE, bty = 'n', density = density_value, angle = angle_value, x.intersp=0.3,
       text.width=c(3.5,3.4,3.7,4.3,7))

It was the text.width function that do the magic



标签: r plot legend