I have a series of ggplot2 graphics with a constant number of horizontal but differing number of vertical facets. I would like to save the graphics as .pdf on landscape a4 format.
However, I don't know how I can achieve identical proportions of the facets. If I try to tweak it manually and vary width and height for different numbers of vertical facets, the scales vary between plots, i.e., I get different points sizes and line widths.
In essence, how can I achieve identical facets sizes and scales for plots with a variable number of (vertical) facets?
Here is an example:
df <- expand.grid(a = 1:2, b = 1:5, x = 1:10)
df$y <- df$x
plot <- ggplot(data = df, mapping = aes(x = x, y = y)) +
geom_point()
plot1 <- plot + facet_grid(facets = "a ~ b")
plot2 <- plot + facet_grid(facets = ". ~ b")
ggsave(filename = "./figures/plot1.pdf", plot = plot1,
height = 210, width = 297, units = "mm")
ggsave(filename = "./figures/plot2.pdf", plot = plot2,
height = 210, width = 297, units = "mm")
I use this code to set panel sizes to absolute values, maybe it helps here
set_panel_size <- function(p=NULL, g=ggplotGrob(p), file=NULL,
margin = unit(1,"mm"),
width=unit(4, "cm"),
height=unit(4, "cm")){
panels <- grep("panel", g$layout$name)
panel_index_w<- unique(g$layout$l[panels])
panel_index_h<- unique(g$layout$t[panels])
nw <- length(panel_index_w)
nh <- length(panel_index_h)
if(getRversion() < "3.3.0"){
# the following conversion is necessary
# because there is no `[<-`.unit method
# so promoting to unit.list allows standard list indexing
g$widths <- grid:::unit.list(g$widths)
g$heights <- grid:::unit.list(g$heights)
g$widths[panel_index_w] <- rep(list(width), nw)
g$heights[panel_index_h] <- rep(list(height), nh)
} else {
g$widths[panel_index_w] <- rep(width, nw)
g$heights[panel_index_h] <- rep(height, nh)
}
if(!is.null(file))
ggsave(file, g,
width = convertWidth(sum(g$widths) + margin,
unitTo = "in", valueOnly = TRUE),
height = convertHeight(sum(g$heights) + margin,
unitTo = "in", valueOnly = TRUE))
invisible(g)
}
print.fixed <- function(x) grid.draw(x)
Related
The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
Electrino wants to draw more attention to this question.
I am trying to create a plot that combines 2 separate legends and a grid of multiple plots. The issue I'm having is I'm finding it difficult to align the legends so they are visible and not overlapping. hopefully the example below will explain what I mean.
To begin I am going to create 2 plots. In these two plots I am only interested in the legends, and I am discarding the actual plot (so please ignore the actual plots in these two plots). To get just the legend I am using the cowplot package.
library(ggplot2)
library(cowplot)
# -------------------------------------------------------------------------
# plot 1 ------------------------------------------------------------------
# create fake data
dfLegend_1 <- data.frame(x = LETTERS[1:10], y = c(1:10))
# set colours
pointColours <- c(A = "#F5736A", B = "#D58D00", C = "#A0A300",
D = "#36B300", E = "#00BC7B", F = "#00BCC2",
G = "#00ADF4", H = "#928DFF", I = "#E568F0",
J = "#808080")
# plot
ggLegend_1 <- ggplot(dfLegend_1, aes(x=x, y=y))+
geom_point(aes(fill = pointColours), shape = 22, size = 10) +
scale_fill_manual(values = unname(pointColours),
label = names(pointColours),
name = 'Variable') +
theme(legend.key.size = unit(0.5, "cm")) +
theme_void()
# get legend
legend_1 <- get_legend(ggLegend_1)
# -------------------------------------------------------------------------
# plot 2 ------------------------------------------------------------------
# Create fake data
dflegend_2 <- data.frame(
x = runif(100),
y = runif(100),
z2 = abs(rnorm(100))
)
# plot
ggLegend_2 <- ggplot(dflegend_2, aes(x=x, y = y))+
geom_point(aes(color = z2), shape = 22, size = 10) +
scale_color_gradientn(
colours = rev(colorRampPalette(c('steelblue', '#f7fcfd', 'orange'))(5)),
limits = c(0,10),
name = 'Gradient',
guide = guide_colorbar(
frame.colour = "black",
ticks.colour = "black"
))
# get legend
legend_2 <- get_legend(ggLegend_2)
Then I am creating many plots (in this example, I am creating 20 individual plots) and plotting them on a grid:
# create data
dfGrid <- data.frame(x = rnorm(10), y = rnorm(10))
# make a list of plots
plotList <- list()
for(i in 1:20){
plotList[[i]] <- ggplot(dfGrid) +
geom_ribbon(aes(x = x, ymin = min(y), ymax = 0), fill = "red", alpha = .5) +
geom_ribbon(aes(x = x, ymin = min(0), ymax = max(y)), fill = "blue", alpha = .5) +
theme_void()
}
# plot them on a grid
gridFinal <- cowplot::plot_grid(plotlist = plotList)
Finally, I am joining the two legends together and adding them to the grid of many plots:
# add legends together into on single plot
legendFinal <- plot_grid(legend_2, legend_1, ncol = 1)
# plot everything on the same plot
plot_grid(gridFinal, legendFinal, rel_widths = c(3, 1))
This results in something that looks like this:
As you can see, the legends overlap and are not very well spaced. I was wondering if there is any way to fit everything in whilst having the legends appropriately spaced and readable?
I should also note, that, in general, there can be any number of variables and any number of gridded plots.
One option to fix your issue would be to switch to patchwork to glue your plots and the legends together. Especially I make use of the design argument to assign more space to the Variable legend. However, you should be aware that legends are much less flexible compared to plots, i.e. the size of legends is in absolute units and will not adjust to the available space. Hence, I'm not sure whether my solution will fit your desire for a "one-size-fits-all" approach.
library(patchwork)
design <-
"
ABCDEU
FGHIJV
KLMNOV
PQRSTV
"
plotList2 <- c(plotList, list(legend_2, legend_1))
wrap_plots(plotList2) +
plot_layout(design = design)
With ggplot2 and geom_density_ridges2, I try to plot two graphs. One with 2 rows and one with 9 rows.
On the two graphs I would like to keep the same height for each row. So the second graph should have the same width but it should be more than 4 times taller.
Unfortunately, Rstudio or ggsave give my graphs withs the same scale (same width, same height).
Code
data_df = data.frame(text = character(), position = numeric())
# Plot
theme_set(theme_bw())
g = data_df %>%
ggplot( aes(y=text, x=position, fill=text) ) +
coord_cartesian(xlim = c(0, max_position)) +
geom_density_ridges2(alpha=1, stat="binline", scale=0.95, bins=200, show.legend = FALSE) +
theme_ridges(font_size = 8, grid = TRUE, font_family = "",line_size = 0.5) +
labs(x = "positions", y = author)
# Save image
image = paste0(author, ".png")
unlink(image)
ggsave(
image,
plot = g,
device = "png",
path = "graphs/",
units = "mm",
width = 100,
scale = 1,
dpi = 320,
limitsize = FALSE
)
Is it possible to fix the height of the rows ?
Maybe this or another approach with patchwork could solve the problem?
library(patchwork)
p1 = ggplot(mtcars, aes(x = mpg, y =cyl)) +
geom_point()
p1 + (p1 / plot_spacer() / plot_spacer() / plot_spacer())
When making faceted plots in ggplot and changing the aspect ratio, usually there is a lot of white space either left and right or above and below the graph. E.g:
library(ggplot2)
df <- data.frame(x=rep(1,3), y=rep(1,3), z=factor(letters[1:3]))
p <- ggplot(df, aes(x, y)) + geom_point() + coord_fixed(ratio=1) + facet_grid(z ~ .)
ggsave("plot.jpg", p, scale=1, device="jpeg")
Is there a way to autocrop the graph?
This is what I came up with. I tested it on your sample & it seems to work okay there, but apologies in advance if it breaks somewhere else. I had to dig into the grob version for the width/height information, and depending on whether the plot is faceted or not, the attribute information for "unit" is located at different places.
Hopefully someone more well-versed with the grid package's unit object can chip in, but here's what I've got:
# Note that you need to set an upper limit to the maximum height/width
# that the plot can occupy, in the max.dimension parameter (defaults to
# 10 inches in this function)
ggsave_autosize <- function(filename, plot = last_plot(), device = NULL, path = NULL, scale = 1,
max.dimension = 10, units = c("in", "cm", "mm"),
dpi=300, limitsize = TRUE){
sumUnitNull <- function(x){
res <- 0
for(i in 1:length(x)){
check.unit <- ifelse(!is.null(attr(x[i], "unit")), attr(x[i], "unit"),
ifelse(!is.null(attr(x[i][[1]], "unit")), attr(x[i][[1]], "unit"), NA))
if(!is.na(check.unit) && check.unit == "null") res <- res + as.numeric(x[i])
}
return(res)
}
# get width/height information from the plot object (likely in a mixture of different units)
w <- ggplotGrob(plot)$widths
h <- ggplotGrob(plot)$heights
# define maximum dimensions
w.max <- grid::unit(max.dimension, units) %>% grid::convertUnit("in") %>% as.numeric()
h.max <- grid::unit(max.dimension, units) %>% grid::convertUnit("in") %>% as.numeric()
# sum the inflexible size components of the plot object's width/height
# these components have unit = "in", "mm", "pt", "grobheight", etc
w.in <- w %>% grid::convertUnit("in") %>% as.numeric() %>% sum()
h.in <- h %>% grid::convertUnit("in") %>% as.numeric() %>% sum()
# obtain the amount of space available for the flexible size components
w.avail <- w.max - w.in
h.avail <- h.max - h.in
# sum the flexible sized components of the plot object's width/height
# these components have unit = "null"
w.f <- sumUnitNull(w)
h.f <- sumUnitNull(h)
# shrink the amount of avilable space based on what the flexible components would actually take up
if(w.f/h.f > w.avail/h.avail) h.avail <- w.avail/w.f*h.f else w.avail <- h.avail/h.f*w.f
w <- w.in + w.avail
h <- h.in + h.avail
ggsave(filename, plot = plot, device = device, path = path, scale = scale,
width = w, height = h, units = units, dpi = dpi, limitsize = limitsize)
}
p <- ggplot(mpg, aes(displ, cty)) + geom_point() + coord_fixed(ratio=1)
p <- p + facet_grid(. ~ cyl)
ggsave("pOriginal.png", p + ggtitle("original"))
ggsave_autosize("pAutoSize.png", p + ggtitle("auto-resize"))
ggsave_autosize("pAutoSize8.png", p + ggtitle("auto-resize, max dim = 8in x 8in"), max.dimension = 8, units = "in")
Original version w/o cropping. There's black space on the left / right:
Automatically cropped version. Height = 10 inches:
Automatically cropped version. Height = 8 inches (so the font looks slightly larger):
I would like to combine vertically two figures using viewport. Figures are created with ggplot and facet_grid().
The problem which arises is that the legend of the categorical variable differ in lengths. This result in plots with different width since
the legend takes more places. I would like that the width of the plots are identically.
How can I solve this problem?
Here is an example of a figures with not aligned plots:
Here is the code to produce the figure:
# dataframe
x <- rep(1:10,2)
y <- x + rep(c(0,2),each=10)
sex <- rep(c("f","m"), each=10)
sex2 <- rep(c("fffffffff","mmmmmmmmm"), each=10)
df0 <- data.frame(x = x, y = y, sex = sex, sex2 = sex2)
# libraries
library("grid")
library("gridExtra")
library("ggplot2")
# Viewport
Layout <- grid.layout(nrow = 2, ncol = 1, heights = unit(c(1,1), c("null","null")))
vplayout <- function(x,y) {
viewport(layout.pos.row=x, layout.pos.col=y)
}
# plot object
p1 <- ggplot(df0,aes(x = x, y = y,linetype=sex)) +
geom_line()
p2 <- ggplot(df0,aes(x = x, y = y,linetype=sex2)) +
geom_line()
# figures
tiff("test0.tiff", width=5, height=10, units="cm", res=300, compression = 'lzw')
grid.newpage()
pushViewport(viewport(layout= Layout))
print(p1 + theme_bw(base_size=5), vp = vplayout(1,1))
print(p2 + theme_bw(base_size=5), vp = vplayout(2,1))
dev.off()
You can use cowplot::plot_grid
# figures
library(cowplot)
tiff("test0.tiff", width=5, height=10, units="cm", res=300, compression = 'lzw')
grid.newpage()
plot_grid(p1, p2, align = "v", nrow = 2, rel_heights = c(1/2, 1/2))
dev.off()
Note: I don't know how you set up df0 so cannot present exported plot.
I'm producing a whole pile of graphs of changing sizes. I want each graph to display a symbol (say, asterisk) at a specific point on the graph margin (top y-axis value), regardless of plot size. Right now I do it manually by defining x/y for each textGrob, but there has got to be a better way.
Plot size is determined by number of categories in the dataset (toy data below). Ideally, the output plots would have identical panel sizes (I'm assuming that can be controlled through defining margin sizes in inches and adding that value to the height parameter?). Widths don't usually change, but it would be nice to automate both x and y placements based on the defined device width (and plot margins).
Thanks so much!
library(ggplot2)
library(gridExtra)
set.seed(123)
df <- data.frame(x = rnorm(20, 0, 1), y = rnorm(20, 0, 1), category = rep(c("a", "b"), each = 10))
## plot 1
sub <- df[df$category == "a",]
height = 2*length(unique(sub$category))
p <- ggplot(sub) +
geom_point(aes(x = x, y = y)) +
facet_grid(category ~ .)
jpeg(filename = "fig1.jpg",
width = 6, height = height, units = "in", pointsize = 12, res = 900,
quality = 100)
g <- arrangeGrob(p, sub = textGrob("*", x = 0.07, y = 10.15, hjust = 0, vjust=0, #### puts the top discharge value; might need to be adjusted manually in following years
gp = gpar(fontsize = 15)))
grid.draw(g)
dev.off()
## plot 2
height = 2*length(unique(df$category))
p <- ggplot(df) +
geom_point(aes(x = x, y = y)) +
facet_grid(category ~ .)
jpeg(filename = "fig2.jpg",
width = 6, height = height, units = "in", pointsize = 12, res = 900,
quality = 100)
g <- arrangeGrob(p, sub = textGrob("*", x = 0.07, y = 23.1, hjust = 0, vjust=0, #### puts the top discharge value; might need to be adjusted manually in following years
gp = gpar(fontsize = 15)))
grid.draw(g)
dev.off()