Data and libraries used for this question:
library(tidyverse)
library(reshape2)
library(cowplot)
data("diamonds")
temp1_m <- temp2_m <- melt(diamonds[1:3])
temp1_m[3] <- temp2_m[3] <- NULL
colnames(temp1_m) <- colnames(temp2_m) <- c('Var1', 'Var2', 'value')
I'm trying to combine two geom_tile plots using the cowplot library.
The two individual plots are created using:
figMT <- ggplot(temp2_m, aes(Var1, Var2))+
geom_tile(aes(fill = value), colour = 'white')+
scale_fill_gradient(low = 'green', high = 'red', name = 'log fold change',
guide = guide_legend(title.vjust = 1, reverse = T))+
ggtitle('Mutant clusters')+
scale_x_discrete(expand = c(0, 0))+
scale_y_discrete(limits = rev(levels(temp2_m$Var2)))+
xlab('')+
ylab('')+
coord_equal()+
theme(axis.text.x = element_text(angle = 45, hjust = 1),
axis.text = element_text(size=12),plot.margin=grid::unit(c(0,0,0,0), "mm"))
and
figWT <- ggplot(temp1_m, aes(Var1, Var2))+
geom_tile(aes(fill = value), colour = 'white')+
scale_fill_gradient(low = 'green', high = 'red', name = 'log fold change',
guide = guide_legend(title.vjust = 1, reverse = T))+
ggtitle('WT clusters')+
scale_x_discrete(expand = c(0, 0))+
scale_y_discrete(limits = rev(levels(temp1_m$Var2)))+
xlab('')+
ylab('Cluster')+
coord_equal()+
theme(axis.text.x = element_text(angle = 45, hjust = 1),
axis.text = element_text(size=12),plot.margin=grid::unit(c(0,0,0,0), "mm"))
I then use cowplot to first make a title with:
title <- ggdraw()+
draw_label("Heatmaps over the average fold change of the clusters", fontface='bold')
and then to combine the title and the two plots with:
p <- plot_grid(figWT+ theme(plot.margin = unit(c(0, -10, 0, 0), "cm")),
figMT+ theme(plot.margin = unit(c(0, 0, 0, -5.1), "cm")),
labels=c('A', 'B'), hjust = c(-24, -.5))
plot_grid(title, p, nrow=2, rel_heights=c(0.1, 1))
I reduce a lot of whitespace between the two heatmaps after moving them closer together. But it creates a lot of whitespaces on the left and right margin which I don't manage to remove. That is, when I save the picture with:
ggsave('ex1.pdf', scale = 2)
Any suggestions?
By adding coord_equal(), you're setting a fixed-aspect-ratio coordinate system, which means you need to make the aspect ratio of the final output file match the aspect ratios of your underlying plots. By not specifying a width and a height in your ggsave() line you're essentially guaranteed to not get the right output. Also, if you find yourself setting large negative margins you know for sure you're doing something wrong.
Without knowing exactly what the intended output is, this seems reasonable to me:
p <- plot_grid(figWT, figMT, labels=c('A', 'B'))
plot_grid(title, p, nrow=2, rel_heights=c(0.1, 1))
ggsave("ex1.png", width = 8, height = 4.5, dpi = 300)
Related
I want to write the y-axis title horizontally on top of the y-axis line while keeping settings defined for the y-axis in the theme() function. The objective is to avoid wasting space while keeping a nice looking design (e.g. with the title left-aligned with y-axis-ticks-labels).
I know it is possible to use other "hacks" (see this using subtitle), but here I look for a solution using the grid approach. I am also aware that it is possible to move around the title by setting margins but it requires to uptade it all the time and I was not able to get a satisfying result with it.
Then, using grid functions as demonstrated in this SO post seems the way to go.
Here is what I would like to get:
It is possible to start with theme(axis.title.y = element_text(angle = 0, vjust = 1)) but the y-axis title is not on top of the line.
This code removes the y-axis title while keeping the theme() parameters defined for the grob:
library(ggplot2)
library(grid)
p <- ggplot(data = iris, aes(x = Sepal.Length, y = Petal.Length)) +
theme(axis.title.y = element_text(angle = 0, vjust = 1),
axis.title.x = element_blank())
# convert from ggplot to grob object
gp <- ggplotGrob(p)
# locate the grob that corresponds to y-axis title and save its parameters
y.label.grob <- gp$grobs[[which(gp$layout$name == "ylab-l")]]$children
# remove y-axis labels from the plot, & shrink the space occupied by them
gp$grobs[[which(gp$layout$name == "ylab-l")]] <- zeroGrob()
gp$widths[gp$layout$l[which(gp$layout$name == "ylab-l")]] <- unit(0, "cm")
But now I am stuck as I do not know how to use functions from the grid package to move the title where I would like to (i.e. on the top left corner).
You can exceed vjust beyond 1 and adapt the margins a bit. It's hard to get the alignment perfectly though.
library(ggplot2)
library(grid)
ggplot(data = iris, aes(x = Sepal.Length, y = Petal.Length)) +
theme(
plot.margin = margin(t = 30),
axis.title.y = element_text(
angle = 0, vjust = 1.1,
margin = margin(r = -50, t = 5.5, b = 5.5, l = 5.5)),
axis.title.x = element_blank()
)
If you know the title in advance you can use it's string width.
ggplot(data = iris, aes(x = Sepal.Length, y = Petal.Length)) +
theme(
plot.margin = margin(t = 30),
axis.title.y = element_text(
angle = 0, vjust = 1.07,
margin = unit.c(
unit(c(2.75), "pt"),
unit(-1, "strwidth", data = "Petal.Length"),
unit(c(2.75, 2.75), "pt")
)
),
axis.title.x = element_blank()
)
Created on 2021-09-09 by the reprex package (v2.0.1)
You could use cowplot as another approach:
library(ggplot2)
library(cowplot)
title <- ggdraw() +
draw_label(
"Petal.Length",
#fontface = 'bold',
x = 0,
hjust = 0
) +
theme(
# add margin on the left of the drawing canvas,
# so title is aligned with left edge of first plot
plot.margin = margin(0, 0, 0, 7)
)
p <- ggplot(data = iris, aes(x = Sepal.Length, y = Petal.Length)) +
labs(y = "")
plot_row <- plot_grid(p)
plot_grid(
title, plot_row,
ncol = 1,
rel_heights = c(0.1, 1)
)
I have decided to rephrase this question. (Editing would have taken more time and in my opinion would also not have helped the OP.)
How can one left-adjust (hjust = 0, i.e., in text direction) over facets, when scale = 'free_x'?
I don't really think that left-adjustment of x-labels is a very necessary thing to do (long labels generally being difficult to read, and right-adjusting probably the better choice) - but I find the problem interesting enough.
I tried with empty padding to the maximum character length, but this doesn't result in the same length for all strings. Also, setting axis.text.x = element.text(margin = margin()) doesn't help. Needless to say, hjust = 0 does not help, because it is adjusting within each facet.
library(ggplot2)
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~ cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90))
The red arrows and dashed line indicate how the labels should adjust. hjust = 0 or margins or empty padding do not result in adjustment of those labels over all facets.
Data modification from this famous question
I tried with empty padding to the maximum character length, but this
doesn't result in the same length for all strings.
This caught my attention. Actually, it would result in the same length for all strings if you padded the labels with spaces, made them all the same length, and ensured the font family was non-proportionally spaced.
First, pad the labels with spaces such that all labels have the same length. I'm going to ustilise the str_pad function from the stringr package.
library(ggplot2)
data("diamonds")
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
library(stringr)
diamonds$cut_label <- str_pad(diamonds$cut_label, side="right",
width=max(nchar(diamonds$cut_label)), pad=" ")
Then, you may need to load a non-proportionally-spaced font using the extrafont package.
library(extrafont)
font_import(pattern='consola') # Or any other of your choice.
Then, run the ggplot command and specify a proportionally spaced font using the family argument.
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90, family="Consolas"))
One way, and possibly the most straight forward hack, would be to annotate outside the coordinates.
Disadvantage is that the parameters would need manual adjustments (y coordinate, and plot margin), and I don't see how to automate this.
library(ggplot2)
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
ann_x <- data.frame(x = unique(diamonds$cut_label), y = -16, cut = unique(diamonds$cut))
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
geom_text(data = ann_x, aes(x, y, label = x), angle = 90, hjust = 0) +
theme(
axis.text.x = element_blank(),
plot.margin = margin(t = 0.1, r = 0.1, b = 2.2, l = 0.1, unit = "in")
) +
coord_cartesian(ylim = c(0, 14), clip = "off")
Created on 2020-03-14 by the reprex package (v0.3.0)
I'd approach this by making 2 plots, one of the plot area and one of the axis labels, then stick them together with a package like cowplot. You can use some theme settings to disguise the fact that the axis labels are actually made by a geom_text.
The first plot is fairly straightforward. For the second which becomes the axis labels, use dummy data with the same variables and adjust spacing how you want via text size and scale expansion. You'll probably also want to mess with the rel_heights argument in plot_grid to change the ratio of the two charts' heights.
library(ggplot2)
library(cowplot)
p1 <- ggplot(diamonds, aes(x = cut_label, y = carat)) +
facet_grid(cols = vars(cut), scales = "free_x") +
theme(axis.text.x = element_blank()) +
labs(x = NULL)
axis <- ggplot(dplyr::distinct(diamonds, cut_label, cut), aes(x = cut_label, y = 1)) +
geom_text(aes(label = cut_label), angle = 90, hjust = 0, size = 3.5) +
facet_grid(cols = vars(cut), scales = "free_x") +
scale_x_discrete(breaks = NULL) +
scale_y_continuous(expand = expansion(add = c(0.1, 1)), breaks = NULL) +
labs(y = NULL) +
theme(strip.text = element_blank(),
axis.text.x = element_blank(),
axis.ticks = element_blank(),
panel.background = element_blank())
plot_grid(p1, axis, ncol = 1, axis = "lr", align = "v")
We can edit the text grobs after generating the plot, using library(grid).
g <- ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))
gt <- cowplot::as_gtable(g)
axis_grobs <- which(grepl("axis-b", gt$layout$name))
labs <- levels(factor(diamonds$cut_label))[order(levels(diamonds$cut))]
for (i in seq_along(axis_grobs)) {
gt$grobs[axis_grobs[i]][[1]] <-
textGrob(labs[i], y = unit(0, "npc"), just = "left", rot = 90, gp = gpar(fontsize = 9))
}
grid.draw(gt)
I'd like to place annotations in the bottom left corner of a plot with polar coordinates.
Is there a way of doing this by using coordinates that are not part of the circular coordinate system (as shown, which doesn't allow me to place them far out) but a normal x and y system?
I don't need the axes to show but have left them in to help show what's going on.
library(tidyverse)
iris2 <- iris %>%
mutate(id = row_number())
ggplot(iris2) +
theme_minimal() +
theme(
legend.position = "none",
axis.title = element_blank()
) +
coord_polar() +
geom_segment(aes(x = id, xend = id,
y = 10,
yend = 10 + Sepal.Length),
size = 1, inherit.aes = FALSE) +
annotate(geom="text", x = 90, y = 10, label="Annotation",
color="Red") +
annotate(geom="text", x = 90, y = 15, label="Annotation",
color="Blue")
You could use the cowplot package to make the desired plot. You can make two text_grob that will be the text labels and then add them on the lower and left side of the plot you already have (appears as p1 in the following code).
library(cowplot)
#X axis text
x <- textGrob(label = "X text",
gp=gpar(fontsize=12,
fontface = "bold"),
hjust = 0.1)
#Y axis text
y <- textGrob(label = "Y text",
#rotate it 90 degrees
rot = 90,
gp=gpar(fontsize=12,
fontface = "bold"))
#Get X axis text and p1 together in a 2 row array
p2<-plot_grid(p1,x,
nrow = 2,
rel_widths = c(4,1),
rel_heights = c(20,1))
#Get Y axis text and p2 together in 2 column array
p3<-plot_grid(y,p2,
ncol = 2,
rel_widths = c(1,20),
rel_heights = c(1,4))
p3
Based on #Aexman's helpful comment to try cowplot, I did and came up with the following. It means you can add two layers together, the second with an independent coordinate system.
Coordinates are between 0 and 1 for both axes, relative the bottom left corner. Absolute positioning is also available.
library(tidyverse)
library(cowplot)
iris2 <- iris %>%
mutate(id = row_number())
g <- ggplot(iris2) +
theme_minimal() +
theme(
legend.position = "none",
axis.title = element_blank()
) +
coord_polar() +
geom_segment(aes(x = id, xend = id,
y = 10,
yend = 10 + Sepal.Length),
size = 1, inherit.aes = FALSE)
ggdraw(g) +
draw_label("Annotation", x = 0.05, y = 0.05, hjust = 0, vjust = 0,
fontfamily = "", fontface = "plain", color = "black", size = 14,
colour = "Red")
My data :
dat <- data.frame(x = c(1,2,3,4,5,6), y = c(2,3,4,6,2,3))
Breaks and labels of my plot :
breaks <- c(3,5)
labels <- c(paste(3,"(0.3)"), paste(5,"(0.5)"))
And my plot :
library(ggplot2)
ggplot() +
geom_point(data = dat, aes(x = x, y = y)) +
scale_y_continuous(breaks = breaks, labels = labels)
I wish to colour the same labels differently. For instance, I wish to colour the "3" with a different colour than the one of "(0.3)".
Here's a way to stick 2 plots together with patchwork, which is a package similar to cowplot but with a little more flexibility. I split the labels into 2 vectors, one with the integers and one with the decimals in parentheses. Then make 2 plots, one for the outer labels with no other markings, and one for the main plot.
After doing one round of trying to build this, I started adjusting the margins in each theme, realizing I needed to set the top and bottom margins the same, but making no margin on the right side of the left plot and the left side of the right plot, so there's very little space between them. There's definitely still ways to tweak this, but I'd start with some of the spacing.
library(tidyverse)
library(patchwork)
lbl_int <- str_extract(labels, "^\\d+")
lbl_frac <- str_extract(labels, "\\(.+\\)")
The main plot is fairly straightforward, just removing elements from the left side in the theme.
(main_plot <- ggplot(dat, aes(x = x, y = y)) +
geom_point() +
scale_y_continuous(breaks = breaks, labels = lbl_frac) +
theme(axis.text.y.left = element_text(color = "gray"),
axis.title.y.left = element_blank(),
plot.margin = margin(1, 1, 1, 0, "mm")))
The plot for the outer labels has most theme elements removed, but has the y-axis title and labels.
(int_plot <- ggplot(dat, aes(x = 0, y = y)) +
scale_y_continuous(breaks = breaks, labels = lbl_int) +
theme(axis.text.y.left = element_text(color = "black"),
axis.title.y.left = element_text(color = "black"),
axis.title.x = element_blank(),
panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
panel.background = element_blank(),
plot.margin = margin(1, 0, 1, 1, "mm")))
Then patchwork makes it easy to just add plots together—literally with +—and then set the widths. Again, here's something you can adjust as you need, but I made the left plot very very skinny compared to the right one.
int_plot + main_plot +
plot_layout(ncol = 2, widths = c(1e-3, 1))
Created on 2018-12-21 by the reprex package (v0.2.1)
This is something to get you going (credit to this answer which I adapted).
We use annotate to plot your labels, on two different x-axis coords, this will function as our labels (so we need to shut off the actual labelling in the theme).
First we create two vectors of the exact labels that we want in different colors.
dat <- data.frame(x = c(1,2,3,4,5,6), y = c(2,3,4,6,2,3))
breaks <- c(3,5)
labels_new1 <- c(NA, NA, 3, NA, 5, NA) # NA in order to skip that annotation
labels_new2 <- c(NA, NA, "(0.3)", NA, "(0.5)", NA)
Important parts:
coord_cartesian(xlim = c(0, 6), expand = FALSE) + # this will cut our plot properly
plot.margin = unit(c(1, 1, 1, 5), "lines") # this will give us some space on the left
Note that in coord_cartesian defined like that we are actually cutting off the two annotations (notice that the two x values you see in the next part (-1, -0.5) are outside the xlim range).
Plot object:
g1 <- ggplot() +
geom_point(data = dat, aes(x = x, y = y)) +
annotate(geom = "text", y = seq_len(nrow(dat)), x = -1, label = labels_new1, size = 4) +
#first the number add color = "blue" for example
annotate(geom = "text", y = seq_len(nrow(dat)), x = -0.5, label = labels_new2, size = 4, color = "red") +
#second the parenthesis (colored in red)
coord_cartesian(xlim = c(0, 6), expand = FALSE) +
scale_y_continuous(breaks = breaks) +
#now lets shut off the labels and give us some left space in the plot
theme(plot.margin = unit(c(1, 1, 1, 5), "lines"),
axis.title.y = element_blank(),
axis.text.y = element_blank())
Finally:
g2 <- ggplot_gtable(ggplot_build(g1)) # convert to grob
g2$layout$clip[g2$layout$name == "panel"] <- "off" # clipping of the axes
# this will show the two annotations that we left off before
grid::grid.draw(g2)
Remarks:
You can play around with x=-1 and x=-0.5 to move the two annotations, and with the last value in c(1, 1, 1, 5) to give you more space on the left side.
labels_new1 and labels_new2 are very important, they are doing all the heavy work of what and where you want to show something.
I'm looking to set up a mirrored bar chart with one set of axis labels in the middle. This image shows what I have so far (code to reproduce at the end):
I'd like the names to be centred between the charts. Methods tried:
using axis labels (best attempt shown here)
using annotation_custom (I found placing the labels to be very difficult and disliked the combination of ggplot references and base plot references)
creating a separate "chart object" to put into the grid.arrange panel (difficult to get the correct vertical spacing between labels without there being any bars)
I'd welcome any suggestions around the easiest way to achieve this layout. The base has to be ggplot, but happy to use other packages to arrange charts.
require("ggplot2")
require("gridExtra")
dataToPlot <- data.frame(
"Person" = c("Alice", "Bob", "Carlton"),
"Age" = c(14, 63, 24),
"Score" = c(73, 62.1, 21.5))
plot1 <- ggplot(dataToPlot) +
geom_bar(data = dataToPlot, aes(x = Person, y = Score), stat = "identity",
fill = "blue", width = 0.8) +
scale_y_continuous(trans = "reverse", expand = c(0, 0)) +
scale_x_discrete(position = "top") +
theme(
axis.text.y = element_blank()
) +
labs(x = NULL) +
coord_flip()
plot2 <- ggplot(dataToPlot) +
geom_bar(data = dataToPlot, aes(x = Person, y = Age), stat = "identity",
fill = "red", width = 0.8) +
scale_y_continuous(expand = c(0, 0)) +
theme(
axis.text.y = element_text(size = 20, hjust = 0.5)
) +
labs(x = "") +
coord_flip()
gridExtra::grid.arrange(plot1, plot2, ncol = 2, widths = c(1, 1.2))
There are two ways (perhaps in combination)...
Add a margin to the right of the axis labels in the right-hand chart...
element_text(size = 20, hjust = 0.5, margin=margin(r=30))
...or move the two charts closer together
grid.arrange(plot1, plot2, ncol = 2, widths = c(1, 1.2),padding=0)