Dynamically align plots (custom ggplot2 legend for spatial maps) in R - r

I have a number of maps that I'm generating in R using the sf library and I would like to have a nice looking legend. Unfortunately, it seems that the standard legend for geom_sf() are these ugly looking boxes. The only SO post I could find on adjusting shapes in ggplot2 legends is here.
The here is to use guides(colour = guide_legend(override.aes = list(shape = 16))); however, this only seems to work for geom_point() and not for geom_sf().
Unless someone can suggest an alternative method for changing the shape of legend objects I will need to design a custom legend in Inkscape and align this along with various maps.
Here's a snippet of the code to show what I've tried already:
legend <- image_read_svg('https://svgshare.com/i/FDV.svg')
p1 <- ggplot() +
geom_sf(data=otherroads, size = .45, aes(color=SUFTYPABRV)) +
geom_sf(data=allroads, size = .55, aes(color=SUFTYPABRV)) +
scale_color_manual(values = plotcolors, guide = "legend") + theme_map() +
labs(title = "Sydney")
ggdraw() +
draw_plot(p1) +
draw_image(legend, width = 0.4, hjust = -0.75, vjust = 0.43)
Good legend location example
The output looks good in this case; however, this won't work for me because it requires manual tweaking for every plot.
What I would like is for the location of this legend to be dynamically placed according to the ggplot object, which depends upon the city I'm plotting.
Bad legend location example
The code (and data) in it's entirety can be cloned from github: https://github.com/moldach/map-help.git

This answer is not to adress the legend placement, but changing the legend icons. From your examples, I gather your data produces a legend that looks like this by default:
# example from the geom_sf help page
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
# throwing in some random categorical data
nc$catvar <- sample(LETTERS[1:5], nrow(nc), replace = TRUE)
ggplot(nc) +
geom_sf(aes(colour = catvar))
Not too long ago, ggplot added the ability to set what legend shape you wanted by implicitly (via the ellipsis) adding the key_glyph argument to all layers, such as geoms and stats.
ggplot(nc) +
geom_sf(aes(colour = catvar), key_glyph = "timeseries")
You could use this, to set the glyph to points and then use the override.aes trick to get the shapes that you want.
ggplot(nc) +
geom_sf(aes(colour = catvar), key_glyph = "point") +
guides(colour = guide_legend(override.aes = list(size = 3, shape = c(15:19))))
It should work with all the regular legend placement tools that are already in ggplot. You might have to specify locations specific for the plot, but at least you won't have to export your legend to svg files first before adding them.

Related

Change title legend position using guides() without breaking the legend into discrete numbers

I was wondering if you can help me out with a (probably silly) problem with my code that is driving me nuts. I'm using R and my problems are with the package sf + ggplot2. Is there anyone out there that can help me?. I found a similar question here, but the solution is not exactly what I need.
I'm trying to plot a numeric variable on a map using the sf package. The variable is numeric and I want the legend to be on a continuous scale. I managed to do that, the problem started when I used the "guides" option to position the title of the legend on top. Here is a reproducible example:
library(sf)
demo(nc, ask = F, echo = F)
ggplot() +
geom_sf(data = nc, aes(fill = BIR74)) +
scale_y_continuous(breaks = 34:36) +
theme(legend.position="bottom",
legend.direction = "horizontal")
This code produces this map:. You can see that the scale of the Bir74 is continuous. If I use guides(fill = guide_legend(title.position = “top”), it will work for placing the title of the legend on top, but the legend label is on a discrete scale now. This is an example
ggplot() +
geom_sf(data = nc, aes(fill = BIR74)) +
scale_y_continuous(breaks = 34:36) +
theme(legend.position="bottom",
legend.direction = "horizontal") +
guides(fill = guide_legend(title.position = "top"))
and this is the map resulting:
What I really want is the legend placed at the bottom with the title on top as in map 2, but with the legend on the scale of map 1.
Any help on how to solve this would be massively appreciated!
Cheers!

How to add legend to ggplot loadings plot?

So created a loadings plot via arrow style using ggplot command. In order to make things easier for graphing, I added a column into the dataframe of my rr.pr$rotation code with colours so that it graphs those arrows based on the colour I specified. The colours that match the arrows are important which is why I did it that way. I am having trouble now adding a legend as ggplot isn't adding a legend.
Is there a way to add one or do I have to do something to the dataframe?
I was thinking of adding the colours manually, but I am getting stuck.
Green represents Sulfated, Orange represents Sialyllated, and Brown represents Neutral. And I would like the legend to show that.
Here is the code:
Dataframe
rrload<-data.frame(rr.pr$rotation[c(2,15,17,24,52),c(1:5)])
rrload$class<-c('orange','springgreen3','bisque3','bisque3','bisque3')
rrload1<-rrload[,c(1:5)]
rrload1<-as.numeric(as.matrix(rrload1))
rrload1<-matrix(rrload1,nrow=5,ncol=5,byrow = F)
rrload[,c(1:5)]<-rrload1
Code for plotting it:
ggplot(rrload)+geom_segment(aes(xend=PC1,yend=PC2),x=0,y=0,arrow = arrowstyle2,color=rrload$class)+
geom_text(aes(x=PC1,y=PC2,label=row.names(rrload)),hjust=0,nudge_x = -0.05,vjust=1,nudge_y = 0.025,size=3.5,color='black')+xlim(-0.3,0.3)+ylim(-0.3,0.3)+theme_light()+
theme_minimal()+theme(legend.title = element_text("Class"),axis.text.x = element_text(colour = "black",size = 10),axis.text.y = element_text(colour = "black",size = 10),axis.title.x = element_text(colour = "black",size = 10),axis.title.y = element_text(colour = "black",size = 10),axis.ticks = element_line(color = "black"),panel.grid = element_blank(), panel.border = element_rect(colour = "black",fill = NA,size = 1))+geom_hline(yintercept = 0,linetype="dashed",color="gray69")+geom_vline(xintercept = 0,linetype="dashed",color="gray69")
This is the graph:
Loadings plot
Without access to your full data (your code is unable to recreate the dataframe, rrload properly), it's hard to help. I managed to estimate the numbers based on the plot you shared. Here's the dataframe I used - note the naming conventions for the columns:
d <- data.frame(
PC1=c(-0.2,-0.2,0.1,0.15,-0.08),
PC2=c(0.13,-0.1,0.2,0.1,-0.2300),
class=c('Neutral','Neutral','Neutral','Sulfated','Silylated'),
name=c('o53','o18','o25','o15','o2')
)
To prepare the data for plotting, I included d$name and d$class. d$class is similar to the column you had, although instead of the color, I'm using the actual name. d$name is the name that I'm using to plot your labels.
Here's the code I used and resulting plot. Explanation will come after:
library(ggrepel)
ggplot(d) + theme_classic() +
geom_vline(xintercept=0, linetype=2, color='gray60') +
geom_hline(yintercept=0, linetype=2, color='gray60') +
geom_segment(
aes(xend=PC1,yend=PC2, color=class), x=0,y=0,
arrow=arrow(type='closed', angle=20, length=unit(0.02,'npc'))
) +
geom_text_repel(
aes(x=PC1, y=PC2, label=name), force=6, min.segment.length = 10, seed=123
) +
ylim(-0.3,0.3) + xlim(-0.3,0.3) +
scale_color_manual(
name='Legend Title',
values=c('Neutral'='bisque3','Sulfated'='springgreen3','Silylated'='orange'))
ggplot2 will create a legend for certain aesthetics, but they must be placed within aes(). Once you do that, ggplot2 will create the legend and automatically assign colors. This means that if we want to create a legend for color=, you need to put it within aes(). The interesting part is that you can put it within aes() anywhere in the call, or just apply to specific geom/geoms. This allows a lot of flexibility in creating your plot. In this case, I only want to color the arrows, so you include color=class within the geom_segment() call. If you put it within the ggplot() call, it would color both the line segment as well as the text geom.
I'm also paying attention to the ordering. We want to make sure the background dotted lines for the central axis at 0,0 are "behind" everything, so they go first. Then the segments, and then the text geom.
The scale_color_manual() function is used to specify the colors for the different d$class values explicitly and the name of the legend. You can also just let ggplot2 find a palette by default, or you can specify via a palette (there are a ton of other methods to specify color). BTW - you can also specify the name of the legend via labs(color=....
Finally, I decided to use geom_text_repel() rather than geom_text(). Since the lines go out in every direction, the "nudge" values for each text item are not going to work going in the same direction. In other words, if you plot the text at x=PC1, y=PC2, it will overlap the arrowheads. You noticed this too and applied nudge_ values, which happens to work, but if your data was a bit different, it would not have worked. geom_text_repel from the ggrepel package can work to do this by kind of "pushing" the text away from your points.

Draw legend with geom_sf when no aesthetic is specified

When drawing maps using ggplot/geom_sf, any layer mapped to an aesthetic is represented in the legend. I am also aware that using show.legend = ... can be used to force legend representation and to manipulate the symbology used (point, line, polygon). However, when plotting simple objects without the need to further map them to an aesthetic, i.e. using colour/fill to show aditional information, no legend entry is given. This also seems to be the case when using show.legend = TRUE. I found this question on GitHub: https://github.com/tidyverse/ggplot2/issues/3636 . I am not quite sure, whether the person asking was undestood correctly, but there didn't seem to be a good answer. So, using his code:
library(ggplot2)
library(sf)
nc <- st_read(system.file("shape/nc.shp", package = "sf"))
ggplot(nc) + geom_sf(show.legend = TRUE)
fails to produce a legend. I would like the legend to simply show a line in the same colour as shown in the map and give a name for that layer. I can add a legend, using this workaround:
ggplot() +
geom_sf(aes(fill = "US State borders"), nc, show.legend = "line")
However, this now changes the colour of the plotted layer. Ok, so let's try to specify a colour:
ggplot() +
geom_sf(aes(fill = "US State borders"), nc, fill = "grey", show.legend = "line")
Whoops, lost the legend again, probably because I have now specified fill twice. Is this really not possible using ggplot/geom_sf?
If I understand the question correctly, it should work if you specify the fill color in scale_fill_manual():
ggplot() +
geom_sf(aes(fill = "US State borders"), nc, show.legend = "line") +
scale_fill_manual(values = 'grey') +
labs(fill = '') # removing legend title

ggplot2 produces two legends instead of one

I am trying to do a simple logged ggplot, showing the change in tree and shrub density over time (site age). the tree species are split into native / exotic.
I have also downloaded the viridis package, to enable a type of coloration to the legend+line+points+confidence interval fill.
The problem is, when I do plot using the viridis code, I get two separate legends, which I don't want. I can't figure out how to keep the viridis legend, and remove the other legend.
I would love to provide a picture of my output - but can't figure out how to add it to this question template...
this is the code I have used:
attach(data.df4)
base <- ggplot(data.df4, aes(age, total_trees))
base +
theme_classic(base_size = 10, base_family = "times") +
scale_y_log10() +
geom_point(aes(color = status)) +
geom_smooth(aes(color = status, fill = status), method = "lm", se = TRUE) +
scale_colour_viridis(discrete = TRUE, option = "D")+
scale_fill_viridis(discrete = TRUE, option = "D") +
labs(title = "changes in planted canopy and subcanopy tree and shrub density over time",
x = "planting age",
y = "density (plot-level)")
Without seeing your data or a screenshot, it's hard to know what needs to change. You can remove legends you don't want in 2 different ways
turn off the fill legend ggplot() + guides(fill = FALSE)
specify not to create a legend within the layer geom_smooth(..., show.legend = FALSE)
This article can show you how to post some sample data:
https://reprex.tidyverse.org/articles/articles/datapasta-reprex.html

R, ggplot2: creating a single legend in a bubble chart with positive and negative values

I want to create a single legend for a bubble chart with positive and negative values like in plot below, generated using sp::bubble().
But, for various reasons I want to duplicate this in ggplot2. The closest I have gotten is to generate a single legend with scaled symbols, but the actual bubbles themselves are'nt scaled.
The above plot was created using the code below
# create data frame
x=sample(seq(1,50),50,T)
y=sample(seq(1,50),50,T)
plot_dat=data.frame(x=x,y=y,value=rnorm(50,0,25))
# plot
library(ggplot2)
ggplot(data=plot_dat, aes(x=x, y=y,colour=factor(sign(value)), size=value)) +
geom_point() +
scale_size(breaks = c(-40,-30,-20,-10,0,10,20,30,40,50), range = c(0.5,4)) +
scale_colour_manual(values = c("orange", "blue"), guide=F) +
guides(size = guide_legend(override.aes = list(colour = list("orange","orange","orange","orange","blue","blue","blue","blue","blue","blue"),size=c(3,2.5,2,1,0.5,1,2,2.5,3,4))))
Continue using abs(value) for size and sign(value) for color.
Provide the breaks= argument of scale_size_continuous() with duplicates of breaks required (e.g. c(10,10,20,20,...)). Next, provide labels= with the values you desire. Finally, use guides() and override.aes to set your own order of values and colours.
ggplot(data=plot_dat, aes(x=x, y=y,colour=factor(sign(value)), size=abs(value))) +
geom_point() +
scale_color_manual(values=c("orange","blue"),guide=FALSE)+
scale_size_continuous(breaks=c(10,10,20,20,30,30,40,40,50,50),labels=c(-50,-40,-30,-20,-10,10,20,30,40,50),range = c(1,5))+
guides(size = guide_legend(override.aes = list(colour = list("orange","orange","orange","orange","orange","blue","blue","blue","blue","blue"),
size=c(4.92,4.14,3.50,2.56,1.78,1.78,2.56,3.50,4.14,4.92))))
To assign exact values for the size= argument in the guides() function you could use function rescale() from the scales library. Rescale the entire range of values you are plotting, along with the break points provided to range= argument in scale_size_continuous().
set.seed(1234)
x=sample(seq(1,50),50,T)
y=sample(seq(1,50),50,T)
plot_dat=data.frame(x=x,y=y,value=rnorm(50,0,20))
library(scales)
rescale(c(abs(plot_dat$value),10,20,30,40,50),to=c(1,5))[51:55]
[1] 1.775906 2.562657 3.349409 4.136161 4.922912

Resources