independently move 2 legends ggplot2 on a map
Viewports can be positioned with some precision. In the example below, the two legends are extracted then placed within their own viewports. The viewports are contained within coloured rectangles to show their positioning. Also, I placed the map and the scatterplot within viewports. Getting the text size and dot size right so that the upper left legend squeezed into the available space was a bit of a fiddle.
library(ggplot2); library(maps); library(grid); library(gridExtra); library(gtable)
ny <- subset(map_data("county"), region %in% c("new york"))
ny$region <- ny$subregion
p3 <- ggplot(dat2, aes(x = lng, y = lat)) +
geom_polygon(data=ny, aes(x = long, y = lat, group = group))
# Get the colour legend
(e1 <- p3 + geom_point(aes(colour = INST..SUB.TYPE.DESCRIPTION,
size = Enrollment), alpha = .3) +
geom_point() + theme_gray(9) +
guides(size = FALSE, colour = guide_legend(title = NULL,
override.aes = list(alpha = 1, size = 3))) +
theme(legend.key.size = unit(.35, "cm"),
legend.key = element_blank(),
legend.background = element_blank()))
leg1 <- gtable_filter(ggplot_gtable(ggplot_build(e1)), "guide-box")
# Get the size legend
(e2 <- p3 + geom_point(aes(colour=INST..SUB.TYPE.DESCRIPTION,
size = Enrollment), alpha = .3) +
geom_point() + theme_gray(9) +
guides(colour = FALSE) +
theme(legend.key = element_blank(),
legend.background = element_blank()))
leg2 <- gtable_filter(ggplot_gtable(ggplot_build(e2)), "guide-box")
# Get first base plot - the map
(e3 <- p3 + geom_point(aes(colour = INST..SUB.TYPE.DESCRIPTION,
size = Enrollment), alpha = .3) +
geom_point() +
guides(colour = FALSE, size = FALSE))
# For getting the size of the y-axis margin
gt <- ggplot_gtable(ggplot_build(e3))
# Get second base plot - the scatterplot
plot2 <- ggplot(mtcars, aes(mpg, hp)) + geom_point()
# png("p.png", 600, 700, units = "px")
grid.newpage()
# Two viewport: map and scatterplot
pushViewport(viewport(layout = grid.layout(2, 1)))
# Map first
pushViewport(viewport(layout.pos.row = 1))
grid.draw(ggplotGrob(e3))
# position size legend
pushViewport(viewport(x = unit(1, "npc") - unit(1, "lines"),
y = unit(.5, "npc"),
w = leg2$widths, h = .4,
just = c("right", "centre")))
grid.draw(leg2)
grid.rect(gp=gpar(col = "red", fill = "NA"))
popViewport()
# position colour legend
pushViewport(viewport(x = sum(gt$widths[1:3]),
y = unit(1, "npc") - unit(1, "lines"),
w = leg1$widths, h = .33,
just = c("left", "top")))
grid.draw(leg1)
grid.rect(gp=gpar(col = "red", fill = "NA"))
popViewport(2)
# Scatterplot second
pushViewport(viewport(layout.pos.row = 2))
grid.draw(ggplotGrob(plot2))
popViewport()
# dev.off()
How do I position two legends independently in ggplot
From my understanding, basically there is very limited control over legends in ggplot2
. Here is a paragraph from the Hadley's book (page 111):
ggplot2 tries to use the smallest possible number of legends that accurately conveys the aesthetics used in the plot. It does this by combining legends if a variable is used with more than one aesthetic. Figure 6.14 shows an example of this for the points geom: if both colour and shape are mapped to the same variable, then only a single legend is necessary. In order for legends to be merged, they must have the same name (the same legend title). For this reason, if you change the name of one of the merged legends, you’ll need to change it for all of them.
ggplot - Multiple legends arrangement
The idea is to create each plot individually (color
, fill
& size
) then extract their legends and combine them in a desired way together with the main plot.
See more about the cowplot
package here & the patchwork
package here
library(ggplot2)
library(cowplot) # get_legend() & plot_grid() functions
library(patchwork) # blank plot: plot_spacer()
data <- seq(1000, 4000, by = 1000)
colorScales <- c("#c43b3b", "#80c43b", "#3bc4c4", "#7f3bc4")
names(colorScales) <- data
# Original plot without legend
p0 <- ggplot() +
geom_point(aes(x = data, y = data,
color = as.character(data), fill = data, size = data),
shape = 21
) +
scale_color_manual(
name = "Legend 1",
values = colorScales
) +
scale_fill_gradientn(
name = "Legend 2",
limits = c(0, max(data)),
colours = rev(c("#000000", "#FFFFFF", "#BA0000")),
values = c(0, 0.5, 1)
) +
scale_size_continuous(name = "Legend 3") +
theme(legend.direction = "vertical", legend.box = "horizontal") +
theme(legend.position = "none")
# color only
p1 <- ggplot() +
geom_point(aes(x = data, y = data, color = as.character(data)),
shape = 21
) +
scale_color_manual(
name = "Legend 1",
values = colorScales
) +
theme(legend.direction = "vertical", legend.box = "vertical")
# fill only
p2 <- ggplot() +
geom_point(aes(x = data, y = data, fill = data),
shape = 21
) +
scale_fill_gradientn(
name = "Legend 2",
limits = c(0, max(data)),
colours = rev(c("#000000", "#FFFFFF", "#BA0000")),
values = c(0, 0.5, 1)
) +
theme(legend.direction = "vertical", legend.box = "vertical")
# size only
p3 <- ggplot() +
geom_point(aes(x = data, y = data, size = data),
shape = 21
) +
scale_size_continuous(name = "Legend 3") +
theme(legend.direction = "vertical", legend.box = "vertical")
Get all legends
leg1 <- get_legend(p1)
leg2 <- get_legend(p2)
leg3 <- get_legend(p3)
# create a blank plot for legend alignment
blank_p <- plot_spacer() + theme_void()
Combine legends
# combine legend 1 & 2
leg12 <- plot_grid(leg1, leg2,
blank_p,
nrow = 3
)
# combine legend 3 & blank plot
leg30 <- plot_grid(leg3, blank_p,
blank_p,
nrow = 3
)
# combine all legends
leg123 <- plot_grid(leg12, leg30,
ncol = 2
)
Put everything together
final_p <- plot_grid(p0,
leg123,
nrow = 1,
align = "h",
axis = "t",
rel_widths = c(1, 0.3)
)
print(final_p)
Created on 2018-08-28 by the reprex package (v0.2.0.9000).
Assign color to 2 different geoms and get 2 different legends
If you use a filled plotting symbol, you can map one factor to fill and the other to colour, which then separates them into two scales and, therefore, legends.
ggplot(dat, aes(y = y, x = x)) +
geom_point(aes(fill = a, size = z), pch = 21) +
geom_boxplot(fill = NA, size=.75, aes(color=b)) +
scale_color_manual(values = c("orange", "purple")) +
scale_fill_manual(values = c("#F8766D", "#00BFC4"))
Secondary legend on chloropleth for points and polygons, ggplot
Final code for the ggplot section posted below. Thanks to aosmith.
ggplot() +
#create base plot all polygons in grey
geom_polygon(data = world3, aes(x = long, y = lat, group = group),
fill = "light grey") +
#create chloropleth layer for countries with data
geom_polygon(data = world4, aes(x = long, y = lat, group = group, fill = interest),
col = "white") +
#add map theme from ggthemes
theme_map() +
#Set the zoom
coord_fixed(xlim = c(-130, 160),
ylim = c(-50, 75), ratio = 1.4) +
#Add city layer - include col in aes() to get city as a separate legend item
geom_point(data = city, aes(x= long, y = lat, col = interest),
shape = 21, inherit.aes = F, size = 3, fill = "yellow") +
#set fill for countries by interest (include city (special) to have the correct number of aesthetics)
scale_fill_manual(name = NULL,
breaks = c("interest", "past", "current", "special"),
values = c(interest = "#a7ef88", past = "#3a7f1d", current = "#1b5104", special = "yellow"),
labels = c("interest", "past", "current", "city")) +
#set color for cities and labels for cities legend
scale_color_manual(name = NULL,
breaks = c("special"),
values = c(special = "black"),
labels = c("cities")) +
#set order of legend items (fill first)
guides(fill = guide_legend(order = 1), color = guide_legend(order = 2)) +
#set legend position and vertical arrangement
theme(legend.text = element_text(size = 9), legend.position = "bottom", legend.box = "vertical")
Gives the following:
Legend location in a combined ggplot
You don't need to combine two separate plots. The ggplot approach is to think of this as a single plot with two layers, a bar layer and a line layer. So we just need to figure out how to put those layers on a single plot. For example:
library(ggplot2)
library(reshape2)
df.test <- data.frame(
x_cat = factor(c(1, 2, 3, 4)),
count = seq(1:4),
line1 = seq(from = 1, to = 4, length.out = 4),
line2 = seq(from = 0, to = 3, length.out = 4)
)
df.test2 = melt(df.test, id.var=c("x_cat", "count"))
ggplot() +
geom_bar(data=subset(df.test2, variable=="line1"), aes(x=x_cat, y=count),
stat="identity" ) +
geom_line(data=df.test2, aes(x=x_cat, y=value, colour=variable, group=variable)) +
xlab( "X Label" ) +
ylab( "Y Label 1" ) +
theme(panel.background = element_rect(colour = "white"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "top")
It turned out that we didn't need any legend gymnastics here. However, if you ever do need to combine separate plots with a single legend, here, here, and here are some examples.
How to have two different size legends in one ggplot?
Using a highly experimental package I put together:
library(ggplot2) # >= 2.3.0
library(dplyr)
library(relayer) # install.github("clauswilke/relayer")
# make aesthetics aware size scale, also use better scaling
scale_size_c <- function(name = waiver(), breaks = waiver(), labels = waiver(),
limits = NULL, range = c(1, 6), trans = "identity", guide = "legend", aesthetics = "size")
{
continuous_scale(aesthetics, "area", scales::rescale_pal(range), name = name,
breaks = breaks, labels = labels, limits = limits, trans = trans,
guide = guide)
}
lev <- c("A", "B", "C", "D")
nodes <- data.frame(
ord = c(1,1,1,2,2,3,3,4),
brand = factor(c("A", "B", "C", "B", "C", "D", "B", "D"), levels=lev),
thick = c(16, 9, 9, 16, 4, 1, 4, 1)
)
edge <- data.frame(
ord1 = c(1, 1, 2, 3),
brand1 = factor(c("C", "A", "B", "B"), levels = lev),
ord2 = c(2, 2, 3, 4),
brand2 = c("C", "B", "B", "D"),
N1 = c(2, 1, 2, 1),
N2 = c(5, 5, 2, 1)
)
ggplot() +
(geom_segment(
data = edge,
aes(x = ord1, y = brand1, xend = ord2, yend = brand2, edge_size = N2/N1),
color = "blue"
) %>% rename_geom_aes(new_aes = c("size" = "edge_size"))) +
(geom_point(
data = nodes,
aes(x = ord, y = brand, node_size = thick),
color = "black", shape = 16
) %>% rename_geom_aes(new_aes = c("size" = "node_size"))) +
scale_x_continuous(
limits = c(1, 4),
breaks = 0:4,
minor_breaks = NULL
) +
scale_size_c(
aesthetics = "edge_size",
breaks = 1:5,
name = "edge size",
guide = guide_legend(keywidth = grid::unit(1.2, "cm"))
) +
scale_size_c(
aesthetics = "node_size",
trans = "sqrt",
breaks = c(1, 4, 9, 16),
name = "node size"
) +
ylim(lev) + theme_bw()
Created on 2018-05-16 by the reprex package (v0.2.0).
ggplot custom legend instead of default
You can manually build a shape legend using scale_shape_manual
:
library(ggplot2)
ggplot(data = df) +
geom_point(aes(x = id, y = value, color = factor(id), shape = 'value'), size = 6) +
geom_point(aes(x = id, y = ref, color = factor(id), shape = 'ref'), size = 8) +
geom_point(aes(x = id, y = min, color = factor(id), shape = 'min'), size = 8) +
scale_shape_manual(values = c('value' = 19, 'ref' = 0, 'min' = 2)) +
xlab("") +
ylab("Value")
Created on 2020-04-15 by the reprex package (v0.3.0)
But a better way to do this would be to reshape the df
to a long format, and map each aes
to a variable:
library(dplyr)
library(tidyr)
df %>%
pivot_longer(-id) %>%
ggplot() +
geom_point(aes(x = id, y = value, color = factor(id), shape = name, size = name)) +
scale_shape_manual(values = c('value' = 19, 'ref' = 0, 'min' = 2)) +
scale_size_manual(values = c('value' = 6, 'ref' = 8, 'min' = 8)) +
xlab("") +
ylab("Value")
Created on 2020-04-15 by the reprex package (v0.3.0)
To remove the legend for the color use guide_none()
:
library(tidyr)
library(ggplot2)
df %>%
pivot_longer(-id) %>%
ggplot() +
geom_point(aes(x = id, y = value, color = factor(id), shape = name, size = name)) +
scale_shape_manual(values = c('value' = 19, 'ref' = 0, 'min' = 2)) +
scale_size_manual(values = c('value' = 6, 'ref' = 8, 'min' = 8)) +
guides(color = guide_none()) +
xlab("") +
ylab("Value")
Created on 2020-04-16 by the reprex package (v0.3.0)
Data:
df = data.frame(id = c("A", "A", "B", "C", "C", "C"),
value = c(1,2,1,2,3,4),
ref = c(1.5, 1.5, 1, 2,2,2),
min = c(0.5, 0.5, 1,2,2,2))
Can ggplot legends be moved freely outside the plot?
you can try
# get legend
legend_p1 <- get_legend(bp1)
legend_p2 <- get_legend(bp)
# remove legend
bp1_wl <- bp1 + theme(legend.position='none')
bp_wl <- bp + theme(legend.position='none')
# plot
plot_grid(plot_grid(bp1_wl, bp_wl, align="h", rel_widths = c(1,0.675)),
plot_grid(legend_p1,legend_p2, rel_widths = c(1,0.675)), nrow=2, rel_heights = c(1,0.4))
Related Topics
Remove Some of the Axis Labels in Ggplot Faceted Plots
Ggplot Dotplot: What Is the Proper Use of Geom_Dotplot
Prevent Knitr/Rmarkdown from Interleaving Chunk Output with Code
Mapping the Shortest Flight Path Across the Date Line in R Leaflet/Shiny, Using Gcintermediate
Twitter Sentiment Analysis W R Using German Language Set Sentiws
Search for Corresponding Node in a Regression Tree Using Rpart
Error When Exporting Dataframe to Text File in R
Rstudio Shiny Not Able to Use Ggvis
R: Is There a Good Replacement for Plyr::Rbind.Fill in Dplyr
How to Get Dimnames in Xtable.Table Output
Fastest Way to Filter a Data.Frame List Column Contents in R/Rcpp
Fitting a Lognormal Distribution to Truncated Data in R
Removing Duplicate Values Row-Wise in R
Roracle Not Working in R Studio