19

The code is as follows:

set.seed(123)
d1=data.frame(x=runif(10),y=runif(10),z=runif(10,1,10))
d2=data.frame(x=runif(10),y=runif(10),z=runif(10,100,1000))
ggplot()+geom_point(aes(x,y,size=z),data=d1)+
geom_line(aes(x,y,size=z),data=d2)

And the result is like this:

enter image description here

The size of points are too small so I want to change its size by scale_size. However, it seems both lines and points are influenced. So I wonder if there is a way to scale lines and points separately with a separate legend?

tonytonov
  • 25,060
  • 16
  • 82
  • 98
Tiger
  • 215
  • 2
  • 8
  • 2
    There is only one size legend. If you want to have separate legends you need to use something else for one or the other, like `linetype` or `color`. – Mike Wise Jan 20 '16 at 08:18
  • 3
    what if for points `size=z*100` – mtoto Jan 20 '16 at 08:30
  • Is it possible to create another size legend? Maybe for this simple example it can be replaced by `linetype` or `color`, but if `linetype` or `color` has been used or not suitable for the figure, then we still need to solve this problem. @MikeWise – Tiger Jan 20 '16 at 10:38
  • I would think it is possible to extend `ggplot2` with the new functionality in 2.0.0 to provide a new legend, but I have perused the available documentation and it is not obvious how to do it at this time. I am afraid for now you will have to make do with workarounds. Either abuse another legend, or construct something with `geom_text`, etc and / or `grid` grobs. – Mike Wise Jan 20 '16 at 10:44
  • In fact, I want to find a way to separate the legend between point and line, not just make the points more obvious.@mtoto – Tiger Jan 20 '16 at 11:14
  • If you have another issue, post another question, don't edit the answer – Thomas Ayoub Feb 11 '16 at 12:35

2 Answers2

5

The two ways I can think of are 1) combining two legend grobs or 2) hacking another legend aesthetic. Both of these were mentioned by @Mike Wise in the comments above.

Approach #1: combining 2 separate legends in the same plot using grobs.

I used code from this answer to grab the legend. Baptiste's arrangeGrob vignette is a useful reference.

library(grid); library(gridExtra)

#Function to extract legend grob
g_legend <- function(a.gplot){
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  legend
}

#Create plots
p1 <- ggplot()+  geom_point(aes(x,y,size=z),data=d1) + scale_size(name = "point")
p2 <- ggplot()+  geom_line(aes(x,y,size=z),data=d2) + scale_size(name = "line")
p3 <- ggplot()+  geom_line(aes(x,y,size=z),data=d2) + 
        geom_point(aes(x,y, size=z * 100),data=d1)  # Combined plot
legend1 <- g_legend(p1)
legend2 <- g_legend(p2)
legend.width <- sum(legend2$width)  

gplot <- grid.arrange(p3 +theme(legend.position = "none"), legend1, legend2,
             ncol = 2, nrow = 2,
             layout_matrix = rbind(c(1,2 ),  
                                   c(1,3 )), 
             widths = unit.c(unit(1, "npc") - legend.width, legend.width))
grid.draw(gplot)

Note for printing: use arrangeGrob() instead of grid.arrange(). I had to use png; grid.draw; dev.off to save the (arrangeGrob) plot.

grob_legends

Approach #2: hacking another aesthetic legend.

MilanoR has a great post on this, focusing on colour instead of size. More SO examples: 1) discrete colour and 2) colour gradient.

#Create discrete levels for point sizes (because points will be mapped to fill)
d1$z.bin <- findInterval(d1$z, c(0,2,4,6,8,10), all.inside= TRUE)  #Create bins

#Scale the points to the same size as the lines (points * 100).  
#Map points to a dummy aesthetic (fill)
#Hack the fill properties.
ggplot()+  geom_line(aes(x,y,size=z),data=d2) + 
  geom_point(aes(x,y, size=z * 100, fill = as.character(z.bin)),data=d1) +
  scale_size("line", range = c(1,5)) + 
  scale_fill_manual("points", values = rep(1, 10) , 
                    guide = guide_legend(override.aes = 
                              list(colour = "black", 
                              size = sort(unique(d1$z.bin)) )))

legend_hack

Community
  • 1
  • 1
oshun
  • 2,319
  • 18
  • 32
  • That's what I need. Thank you! – Tiger Feb 05 '16 at 02:43
  • 1
    Another problem is that `size` will control line and point at the same time, if I set `range = c(1,3)` in **Approach #2**, so how can I control them separately? @oshun – Tiger Feb 12 '16 at 04:08
  • You can't. If you want more control, use Approach 1. If you want to use Approach 2, you are stuck with the same scale for both points and lines. Notice that solution #2 transforms the points (range = 2 to 10) to the same scale as the lines (range 140-905) by multiplying by 100. To change their size relative to each other, you change this scaling factor. Try points*50 (smaller points relative to lines) or points*200 (larger points). Reducing your scale range to `c(1,3)`, limits the transformed sizes from 1-3 which seems counterproductive. Why not increase the range of possible sizes? – oshun Feb 14 '16 at 06:48
0

I'm a noob in programming, but you could try this methode. As you see, my code uses points and paths. I define a vector of the length of number of paths. My lines have the size 1. Then I add the sizes of my points at the back of that vector.

size_vec<-c(rep(1, length(unique(Data$Satellite))), 1.4, 4.6, 4.2, 5.5)

plot <- ggplot(data) +
geom_point(aes(x = x_cor, y = y_cor, shape=Type, size=Type)) +
geom_path(aes(x = x_cor, y = y_cor, group = Tour, size=factor(Satellite))) +
scale_size_manual(values = size_vec, guide ='none')