user321627
user321627

Reputation: 2564

How to center boxes on top of lines in the legend of a plot?

I would like to superimpose transparent boxes on top of the lines in the legend.

A small example:

xdata <- 1:7
y1 <- c(1,4,9,16,25,36,49)
y2 <- c(1, 5, 12, 21, 34, 51, 72)
y3 <- c(1, 6, 14, 28, 47, 73, 106 )
y4 <- c(1, 7, 17, 35, 60, 95, 140 )

plot(xdata, y1, type = 'l', col = "red")
lines(xdata, y2, type = 'l', col = "red", lty = 3)
lines(xdata, y3, type = 'l', col = "blue") 
lines(xdata, y4, type = 'l', col = "blue",  lty = 3)

# add legend with lines
legend("topleft", legend = c("Plot 1", "Plot 2", "Plot 3", "Plot 4"),
      lty = c(1,3,1,3), lwd = rep(1.3 ,4),
      col = c("blue", "blue", "red", "red") ,
      pch = rep(NA, 4), cex = 0.8, 
      x.intersp = 0.7, y.intersp = 1.2, bty = 'n')

# add boxes
legend("topleft", legend = c("", "", "", ""), lty = rep(0, 4), 
       col = c(adjustcolor(blues9[3], alpha.f = 0.6), 
               adjustcolor(blues9[3], alpha.f = 0.6), 
               adjustcolor("red", alpha.f = 0.1), 
               adjustcolor("red", alpha.f = 0.1)),
        pch = rep(15, 4), cex = 0.8, pt.cex = rep(2, 4),
        x.intersp = 0.7, y.intersp = 1.2, bty = 'n')

which produces:

enter image description here

As you can see, the boxes are shifted to the left along the lines in the legend.

How can I set the alignment of the boxes, so that they become horizontally centered on top of the line symbols in the legend? Thanks!

Upvotes: 9

Views: 3083

Answers (3)

Henrik
Henrik

Reputation: 67778

The clue here is to specify the same lwd in both legend calls, i.e. also in the call for the boxes where lty = 0.

Here's a simpler example, with only the arguments relevant for your actual issue:

plot(1)

# lines
legend(x = "topleft",
       legend = c("Plot 1", "Plot 2", "Plot 3", "Plot 4"), bty = 'n',
       lty = c(1, 3), lwd = 1,
       pch = NA,
       col = rep(c("blue", "red"), each = 2))

# boxes
legend(x = "topleft", 
       legend = rep("", 4), bty = "n",
       lty = 0, lwd = 1, # <- lwd = 1  
       pch = 15, pt.cex = 2,
       col = c(rep(adjustcolor(blues9[3], alpha.f = 0.6), 2),
               rep(adjustcolor("red", alpha.f = 0.1), 2)))

enter image description here


If you're fine with a coloured outline of the boxes, one legend call is enough. Just use a pch which accepts pt.bg (here: 22):

plot(1)
legend(x = "topleft",
       legend = c("Plot 1", "Plot 2", "Plot 3", "Plot 4"), bty = 'n',
       lty = c(1, 3), col = rep(c("blue", "red"), each = 2),
       pch = 22, pt.cex = 2, 
       pt.bg = c(rep(adjustcolor(blues9[3], alpha.f = 0.6), 2),
                 rep(adjustcolor("red", alpha.f = 0.1), 2)))

enter image description here

Upvotes: 9

PKumar
PKumar

Reputation: 11128

Here is what I have done using base R:

Below are some of the settings which you can do at your end to get the desired result.

seg.len : It basically works with length of the legend line in the base R plotting graphics, I have taken this values as 1.25

pt.cex : rep(4.4,4), so we have taken box as squares, I have adjusted the value of thse squares using 4.4

x.intersp : It deals between the space between text of the legend and the legend shape

text.col : This is an optional setting for the colour of the text of the legend if you comment it , You will get legend text it as black (like your answer)

xjust: for the value of 0 the legend is left justified , for 0,5 it is centre justified and for 1 it is right justified

I have changed these settings iteratively to came with these as final result. I am hopeful that would be helpful to you. I have run this many times on machine to come up with this.

Above all the settings I have played with and came up with the below chart:

plot(xdata, y1, type = 'l', col = "red")
lines(xdata, y2, type = 'l', col = "red", lty = 3)
lines(xdata, y3, type = 'l', col = "blue") 
lines(xdata, y4, type = 'l', col = "blue",  lty = 3)
legend("topleft", legend = c("Plot 1", "Plot 2", "Plot 3", "Plot 4"), 
       lty = c(1,3,1,3), lwd = rep(1.3 ,4), 
       col = c("blue", "blue", "red", "red") , 
       pch = rep(NA,4), cex = 0.8, 
       text.col = c("blue", "blue", "red","red"),
       y.intersp=1.2, bty = 'n', x.intersp = .5,
       seg.len = 1.265, xjust =1)
legend("topleft", legend = c("", "", "", ""), lty = rep(0, 4),
       col = c(adjustcolor(blues9[3],alpha.f=0.6),
               adjustcolor(blues9[3],alpha.f=0.6),
               adjustcolor("red",alpha.f=0.1),
               adjustcolor("red",alpha.f=0.1)),
       pch = rep(15,4), bty ='n', cex = 0.8,
       pt.cex = rep(4.3, 4), y.intersp = 1.2, x.intersp =  0.1,
       xjust = 0)

enter image description here

Note: You have zoom the graph to see the correct effect in R Studio.

Upvotes: 5

Maurits Evers
Maurits Evers

Reputation: 50668

Since you explicitly welcome alternative solutions, here is a possibility using ggplot2

library(tidyverse);
data.frame(xdata, y1, y2, y3, y4) %>%
    gather(key, y, -xdata) %>%
    mutate(key = sub("y", "Plot ", key)) %>%
    ggplot(aes(xdata, y, colour = key, linetype = key)) +
    geom_line() +
    geom_ribbon(aes(ymin = y, ymax = y, fill = key), alpha = 0.2, colour = NA) +
    scale_colour_manual(
        values = c("Plot 1" = "red", "Plot 2" = "red", "Plot 3" = "blue", "Plot 4" = "blue"),
        name = "Legend") +
    scale_linetype_manual(
        values = c("Plot 1" = "solid", "Plot 2" = "dashed", "Plot 3" = "solid", "Plot 4" = "dashed"),
        name = "Legend") +
    scale_fill_manual(
        values = c("Plot 1" = "red", "Plot 2" = "red", "Plot 3" = "blue", "Plot 4" = "blue"),
        name = "Legend") +
    theme_bw() +
    theme(
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        legend.justification = c(0, 1),
        legend.position = c(0.02, 0.98),
        legend.spacing.x = unit(0.5, 'cm'),
        legend.title = element_blank())

enter image description here

Here is a summary of the key adjustments:

  1. Add a fill aesthetic through a zero width geom_ribbon. This will give us the coloured boxes in the legend. We use alpha = 0.2 to ensure transparency.
  2. We manually give all aesthetic scales the same name. This prevents three different legends, and combines colour, linetype, and fill in one single legend.
  3. We tweak the legend position through
    • legend.position and legend.justification to make sure the legend is within the plot region in the top-left corner. These parameters require a bit of fine-tuning depending on the legend text,
    • removing the legend title with legend.title = element_blank(),
    • increasing the distance between legend symbol and text with legend.spacing.x; again this parameter may require a bit of manual fine-tuning depending on your personal preference.

Lastly, we do some minor adjustments to increase aesthetic similarity with the base R plot, by removing grid lines (theme_bw in combination with panel.grid.major = element_blank() and panel.grid.minor = element_blank()).

Upvotes: 8

Related Questions