I am new to R, and I am trying to generate scatter plots with two variables, with the values of each variable grouped into 4 classes.
In particular, I am trying to achieve the following:
Display two groups as data points, two groups as confidence ellipses
Generate and save scatter plots having the same dimensions in term of plot frame size and plot area (i.e., x-axis long 8 cm, y-axis long 6 cm.).
Below you can find a reproducible version (you just need to define the output for the png file) of the code that works, but it shows data points and confidence ellipses for all data:
library(ggplot2)
out_path = YOUR OUTPUT DIRECTORY
#data frame
gr1 <- (rep(paste('B-12-B-002'), 10))
gr2 <- (rep(paste('B-12-M-03'), 10))
gr3 <- (rep(paste('b-b-d-3'), 10))
gr4 <- (rep(paste('h-12-b-01'), 10))
Run_type <- c(gr1,gr2,gr3,gr4)
axial_ratio <- runif(40,0,1)
Solidity <- runif(40,0,1)
Convexity <- runif(40,0,1)
sel_data_all <- data.frame(Run_type,axial_ratio,Solidity,Convexity)
fill_colors <- c('red','blue','green','orange');
#Plot
one_plot = ggplot(sel_data_all,aes(x = axial_ratio,y = Solidity))+
geom_point(aes(x = axial_ratio,y = Solidity, fill = Run_type, shape = Run_type), color = "black", stroke = 1,
size = 5, alpha = 0.4)+
stat_ellipse(data = sel_data_all, aes(x = axial_ratio, y = Solidity, fill = Run_type,colour=Run_type),geom = "polygon",alpha = 0.4,type = "norm",level = 0.6,
show.legend = FALSE) + #, group=Run_type , data = subset(sel_data_all, Run_type %in% leg_keys_man[1:7]),
scale_shape_manual(values=c(21,21,23,23))+
scale_fill_manual(values = fill_colors)+
scale_color_manual(values = fill_colors)+
coord_fixed(ratio = 1)+
theme(legend.position="top", # write 'none' to hide the legend
legend.key = element_rect(fill = "white"), # Set background of the points in the legend
legend.title = element_blank(), # Remove legend title
panel.background=element_rect(fill = "white", colour="black"),
panel.grid.major=element_line(colour="lightgrey"),
panel.grid.minor=element_line(colour="lightgrey"),
axis.title.x = element_text(margin = margin(t = 10), size = 12,face = "bold"), # margin = margin(t = 10) vjust = 0
axis.title.y = element_text(margin = margin(r = 10), size = 12,face = "bold"), # margin = margin(r = 10) vjust = 2
axis.text = element_text(color = "black", size = 10), # To hide the text from a specific axis do: axis.text.y = element_blank()
axis.ticks.length=unit(-0.15, "cm"), # To hide the ticks from a specific axis do: axis.ticks.y = element_blank()
#plot.margin = margin(t = 0, r = 1, b = 0.5, l = 0.5, unit = "cm"), # define margine of the plot frame t = top, r = right, b = bottom, l = left
)
#expand_limits(x = 0, y = 0)+ #Force the origin of the plot to 0
#xlim(c(0,1))+
#ylim(c(0,1)) # or xlim, limit the axis to the values defined
show(one_plot)
# Save plots
ggsave(
filename=paste("Axial_ratio","_vs_","Solidity",".png",sep=""),
plot = one_plot,
device = "png",
path = out_path,
scale = 1,
width = 8, # Refers to the plot frame, not the area
height = 6, # Refers to the plot frame, not the area
units = "cm",
dpi = 300,
limitsize = FALSE,
bg = "white")
Unfortunately, after several days of trying and reading the R documentation and forums, I cannot achieve this.
For the first task, I tried subsetting the data by modifying the geom_point and stat_ellipse functions,
geom_point(data = subset(sel_data_all, Run_type %in% c('B-12-B-002','B-12-M-03')),aes(x = axial_ratio,y = Solidity, fill = Run_type, shape = Run_type), color = "black", stroke = 1,
size = 5, alpha = 0.4)+ #
stat_ellipse(data = subset(sel_data_all, Run_type %in% c('b-b-d-3','h-12-b-01')), aes(x = axial_ratio, y = Solidity, fill = Run_type,colour=Run_type),geom = "polygon",alpha = 0.4,type = "norm",level = 0.6,
show.legend = FALSE) + #
but I end up with a duplicate of the legend (in grey colour).
Like this.
For my second issue, with the working version of the script at the top of the message,
Here is the plot that shows in the "Plots" window in RStudio:
But this is what is saved in the output directory.
A final note about the second issue: the script presented here is actually inserted in a for loop that generates multiple scatter plots made by unique pairs of two variables, and the data frame provided here is only partial, to make it easier for you to help. Unfortunately, this is what ggsave generates:
axial ratio vs convexity
ves_pct vs axial ratio
Can anybody help?
Thank you in advance to everyone!
EDIT:
So, thanks to MarBlo (which I thank a lot), I managed to get almost what I want, but there is still yet something I cannot figure out.
This is the last version of the code, with some adaptation to better fit the reasoning:
library(tidyverse)
set.seed(123)
gr1 <- (rep(paste("B-12-B-002"), 10))
gr2 <- (rep(paste("B-12-M-03"), 10))
gr3 <- (rep(paste("b-b-d-3"), 10))
gr4 <- (rep(paste("h-12-b-01"), 10))
Sample_ID <- c(gr1, gr2, gr3, gr4)
axial_ratio <- runif(40, 0, 1)
Solidity <- runif(40, 0, 1)
Convexity <- runif(40, 0, 1)
sel_data_all <- data.frame(Sample_ID, axial_ratio, Solidity, Convexity)
fill_colors <- c("#5bd9ca",
"#1e99d6","#1e49d6","#f2581b80","#e8811280","#e3311280","#fc000080")
sel_data_all <- sel_data_all |> add_column(Run_type = c(
rep("MAG", 10), rep("PMAG", 10),
rep("MAG", 10), rep("PMAG", 10)), .before = "Sample_ID")
one_plot = ggplot(
data = sel_data_all |> dplyr::filter(Run_type == "PMAG"),
aes(x = axial_ratio, y = Solidity)
) +
# CONFIDENCE ELLIPSE
stat_ellipse(
data = sel_data_all |> dplyr::filter(Run_type == "MAG"),
aes(x = axial_ratio, y = Solidity,
fill = Sample_ID),
geom = "polygon", type = "norm",
level = 0.6,
colour = 'white', # ellipse border
) +
# DATA POINTS
geom_point(aes(colour = Sample_ID,
shape = Sample_ID),
stroke = 0.5,
size = 3,
) +
scale_color_manual(values = fill_colors[1:3]) + # of Data points
scale_shape_manual(values = c(21, 21, 23, 23,21,23,22)) + # of data points
scale_fill_manual(values = fill_colors[4:7]) + # of ellipses
coord_cartesian(xlim=c(0,1))+
#scale_x_continuous(expand = expansion(mult = c(0.001, 0.05)))+
coord_cartesian(ylim=c(0,1))+
#scale_y_continuous(expand = expansion(mult = c(0.001, 0.05)))+
# Theme
theme(
legend.position = "top",
legend.key.size = unit(5, 'mm'), #change legend key size
# legend.key.height = unit(1, 'cm'), #change legend key height
# legend.key.width = unit(1, 'cm'), #change legend key width
legend.text = element_text(size=8),
legend.key = element_rect(fill = "white", colour = 'white'),
legend.background = element_rect(fill = "transparent"),
legend.title = element_blank(),
panel.background = element_rect(fill = "white", colour = "black"),
panel.grid.major = element_line(colour = "lightgrey"),
panel.grid.minor = element_line(colour = "lightgrey"),
axis.title.x = element_text(vjust = -1, size = 12, face = "bold"),
axis.title.y = element_text(vjust = 4, size = 12, face = "bold"),
axis.text = element_text(color = "black", size = 10),
axis.ticks.length = unit(-0.15, "cm"),
plot.margin = margin(t = 2, # Top margin
r = 4, # Right margin
b = 4, # Bottom margin
l = 4, # Left margin
unit = "mm"),
)+
guides(colour = guide_legend(nrow=2, byrow=TRUE)+
coord_fixed(ratio = 1))
ggsave(
filename=paste("snap",".png",sep=""),
plot = one_plot,
device = "png",
path = here::here(),
width = 8, # Refers to the plot frame, not the area
height = 8, # Refers to the plot frame, not the area
units = "cm",
#dpi = 300,
#limitsize = FALSE,
bg = "white")
Here is the saved plot
What I need, are the data points filled with the colour currently used for their border, and the border of all data points in black.
I tried to move around the aesthetics, but I ended up with the duplicate legend and more confusion.
Thanks in advance again for your help.
I have taken your data and added a variable called group which makes filtering in ggplot easier.
If you define x and y in ggplot(..,aes()) you do not have to define it again in geom_point.
In geom_point you give already a color to Run_type , the variable from which the legend should be made up. Because you use in geom_ellipse a different subset of the DF the legend would be updated and make again 4 legend entries instead of 2 for the variables only. color = Run_type can therefore be skipped.
I have added set.seed() which ensures that results are being comparable, although random numbers are generated for making up the DF.
library(tidyverse)
set.seed(123)
gr1 <- (rep(paste("B-12-B-002"), 10))
gr2 <- (rep(paste("B-12-M-03"), 10))
gr3 <- (rep(paste("b-b-d-3"), 10))
gr4 <- (rep(paste("h-12-b-01"), 10))
Run_type <- c(gr1, gr2, gr3, gr4)
axial_ratio <- runif(40, 0, 1)
Solidity <- runif(40, 0, 1)
Convexity <- runif(40, 0, 1)
sel_data_all <- data.frame(Run_type, axial_ratio, Solidity, Convexity)
fill_colors <- c("red", "blue", "green", "orange")
df <- sel_data_all |> mutate(group = c(
rep("Data", 10), rep("Conf", 10),
rep("Data", 10), rep("Conf", 10)
))
ggplot(
data = df |> dplyr::filter(group == "Data"),
aes(x = axial_ratio, y = Solidity)
) +
geom_point(aes(color = Run_type, shape = Run_type),
stroke = 1,
size = 5, alpha = 0.4
) +
stat_ellipse(
data = df |> dplyr::filter(group != "Data"),
aes(
x = axial_ratio, y = Solidity,
fill = Run_type
),
geom = "polygon", alpha = 0.4, type = "norm", level = 0.6,
show.legend = FALSE
) +
scale_shape_manual(values = c(21, 21, 23, 23)) +
scale_fill_manual(values = fill_colors) +
scale_color_manual(values = fill_colors) +
coord_fixed(ratio = 1) +
theme(
legend.position = "top",
legend.key = element_rect(fill = "white"),
legend.title = element_blank(),
panel.background = element_rect(fill = "white", colour = "black"),
panel.grid.major = element_line(colour = "lightgrey"),
panel.grid.minor = element_line(colour = "lightgrey"),
axis.title.x = element_text(margin = margin(t = 10), size = 12, face = "bold"),
axis.title.y = element_text(margin = margin(r = 10), size = 12, face = "bold"),
axis.text = element_text(color = "black", size = 10),
axis.ticks.length = unit(-0.15, "cm"),
)
The plot was then saved with equal width and height.
ggsave(
filename=paste("Axial_ratio","_vs_","Solidity",".png",sep=""),
plot = last_plot(),
device = "png",
path = here::here(),
width = 8, # Refers to the plot frame, not the area
height = 8, # Refers to the plot frame, not the area
units = "cm",
#dpi = 300,
#limitsize = FALSE,
bg = "white")
the saved png looks like this.
NEW EDIT
My understanding now is that you want two colors for geom_points and 2 different colors for stat_ellipse.
So the following will show a new attempt. If this is the right answer, I will erase most from above, to make this post better readable.
The ggsave -issue I regard as solved.
I have defined two different color-sets; one for geom_point and one for stat_ellipse. (There are 3 and 4 colors defined, although later in scale_color_manual and scale_fill_manual only 2 colors are needed.)
As for both geom_point and stat_ellipse a different DF is used, ggplot is called without any data or aes. Both will be defined individually when geom_point and stat_ellipse are called.
For stat_ellipse fill is used and for geom_point color is used as aes.
If you want to leave the one or the other legend out, you may use
show.legend = F in the respective geom.
xlim and ylim define axis limits.
guides() and theme_bw() make sure that the dark background of legend.key is erased.
I have tried to make the theme aa bit more concise.
library(tidyverse)
set.seed(123)
gr1 <- (rep(paste("B-12-B-002"), 10))
gr2 <- (rep(paste("B-12-M-03"), 10))
gr3 <- (rep(paste("b-b-d-3"), 10))
gr4 <- (rep(paste("h-12-b-01"), 10))
Sample_ID <- c(gr1, gr2, gr3, gr4)
axial_ratio <- runif(40, 0, 1)
Solidity <- runif(40, 0, 1)
Convexity <- runif(40, 0, 1)
sel_data_all <- data.frame(Sample_ID, axial_ratio, Solidity, Convexity)
fill_colors_points <- c("#5bd9ca", "#1e99d6", "#1e49d6")
fill_colors_ellipse <- c("#f2581b80", "#e8811280", "#e3311280", "#fc000080")
sel_data_all <- sel_data_all |> mutate(Run_type = c(
rep("MAG", 10), rep("PMAG", 10),
rep("MAG", 10), rep("PMAG", 10)
))
ggplot() +
stat_ellipse(
data = sel_data_all |> dplyr::filter(Run_type == "MAG"),
aes(
x = axial_ratio, y = Solidity,
fill = Sample_ID
),
geom = "polygon", type = "norm",
level = 0.6, show.legend = T
) +
geom_point(
data = sel_data_all |> dplyr::filter(Run_type == "PMAG"),
aes(
x = axial_ratio, y = Solidity,
color = Sample_ID,
shape = Sample_ID
),
stroke = 0.5, size = 3,
) +
scale_color_manual(values = fill_colors_points[1:2]) + # of Data points
scale_fill_manual(values = fill_colors_ellipse[1:2]) + # of ellipses
xlim(0,1) + ylim(0,1) +
guides(color = guide_legend(override.aes = list(fill = NA))) +
theme_bw() +
theme(
legend.position = "top",
legend.key.size = unit(5, "mm"), # change legend key size
legend.text = element_text(size = 8),
legend.title = element_blank(),
panel.background = element_rect(fill = "white", colour = "black"),
panel.grid = element_line(colour = "lightgrey"),
axis.title.x = element_text(vjust = -1, size = 12, face = "bold"),
axis.title.y = element_text(vjust = 4, size = 12, face = "bold"),
axis.text = element_text(color = "black", size = 10),
axis.ticks.length = unit(-0.15, "cm"),
) +
coord_fixed(ratio = 1)
I am wondering if there is any way to get a manual right-side y-axis label when there is no scale, only facet headings.
Here's an example
library(dplyr)
library(Hmisc)
# Plot power vs. n for various odds ratios (base prob.=.1)
(n <- seq(10, 1000, by=10)) # candidate sample sizes
(OR <- as.numeric(sort(c(seq(1/0.90,1/0.13,length.out = 9),2.9)))) # candidate odds ratios, spanning the 95% CI centered around an odds ratio of 2.9
alpha <- c(.001, .01, .05)
# put all of these into a dataset and calculate power
powerDF <- data.frame(expand.grid(OR, n, alpha)) %>%
rename(OR = Var1, num = Var2, alph = Var3) %>%
arrange(OR) %>%
mutate(power = as.numeric(bpower(p1=.29, odds.ratio=OR, n=num, alpha = alph))) %>%
transform(OR = factor(format(round(OR,2),nsmall=2)))
# now plot
pPower <- ggplot(powerDF, aes(x = num, y = power, colour = factor(OR))) +
geom_line() +
facet_grid(factor(alph)~.) +
labs(x = "sample size") +
scale_colour_discrete(name = "Odds Ratio") +
scale_x_continuous(breaks = seq(0,1000,100)) +
scale_y_continuous(breaks = seq(0,1,.1)) +
theme_light() +
theme(axis.title.x = element_text(size = 12, face = "bold"),
axis.title.y = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 11),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_line(colour = "gray95"),
panel.grid.major.x = element_line(colour = "gray95"),
strip.text = element_text(colour = 'black', face = 'bold', size = 12),
legend.text = element_text(size = 12),
legend.title = element_text(size = 12, face = "bold"))
(Please forgive the cluttered axes labels, I had to reduce the size of the image to allow it to be uploaded).
I was wondering if there was any way to have an axis label saying 'significance level' down the right hand side of the graph?
Adding the following to scale_y_continuous seems one way to go (although a bunch of warnings)
sec.axis = sec_axis(trans=I, breaks=NULL, name="Significance")
Alternatively, you can add an additional strip that spans all the panels:
library(grid)
library(gtable)
g <- ggplotGrob(pPower)
rect <- grobTree(rectGrob(gp = gpar(fill = "grey70", col="grey70")),
textGrob("Significance", rot=-90, gp = gpar(col="black")))
g <- gtable_add_cols(g, g$widths[6], 6)
g <- gtable_add_grob(g, rect, l=7, t=7, b=11)
grid.newpage() ; grid.draw(g)
For a final article submission I have been asked to update my figures so that they meet the following specifications:
axis lines are 0.25 mm
axis lines all around with ticks facing in
data lines are 0.5 mm
font is 10pt
figures should be 80 or 169 mm wide
must be 300 dpi
What I've tried:
library(ggplot2)
library(cowplot)
theme_set(theme_bw())
x <- rnorm(100)
mydata <- data.frame(x = x,
y = x^2 + runif(100),
z = rep(letters[1:4], 25))
p <- ggplot(data = mydata, aes(x, y)) +
geom_point(aes(color = z)) +
geom_smooth(color = 'black', se = FALSE, size = 0.5) +
theme(text = element_text(family = 'Times', size = 10, color = 'black'),
axis.ticks.length = unit(-0.1, 'cm'),
axis.text.x = element_text(margin = margin(t = 4, unit = 'mm')),
axis.text.y = element_text(margin = margin(r = 4, unit = 'mm')),
panel.grid = element_blank(),
axis.line = element_line(size = 0.25),
legend.position = c(0.5, 0.75))
p
ggsave(plot = p,
filename = 'myplot.png',
width = 80, height = 50, dpi = 300, units = 'mm')
p2 <- cowplot::plot_grid(plotlist = list(p, p, p, p), nrow = 1)
ggsave(plot = p2,
filename = 'mymultipleplot.png',
width = 169, height = 50, dpi = 300, units = 'mm')
Which returns the following two plots:
I can figure out how to handle some of the issues here (e.g. legend positions), but am having difficulty with the following:
How can I get ticks around top and right axes?
How can I get the sizes correct ...
These look much bigger than 10 pt. (download them or open in new window to see unscaled version)
The sizes are not maintained in the two figures despite being specified in the theme (font, line).
I don't know how to confirm that the lines are the correct size (in points or mm)... does ggsave do its own scaling?
update For my present task I exported as svg files and edited them in Inkscape. It took a few hours but was easier than getting ggplot to contort to the specifications.
But, it would be helpful to know for the future how to do this programmatically within ggplot2.
Answer to question:
1) as Henrik told in comments:
For question 1 (How can I get ticks around top and right axes?), see the new sec.axis argument in scale_ in ggplot 2.2.0. Try e.g. ggplot(mpg, aes(displ, hwy)) + geom_point() + scale_x_continuous(sec.axis = dup_axis()) + scale_y_continuous(sec.axis = dup_axis())
2) the problem here is that you have the same resolution with different sizes. Since the height of the two figures is the same, you can fix this problem scaling the font size by hand multiplying the font-size with the ratio of the width: e.g.
theme(text = element_text(family = 'Times', size = 10*(80/169), color = 'black')
The whole code should look like this:
library(ggplot2)
library(cowplot)
theme_set(theme_bw())
x <- rnorm(100)
mydata <- data.frame(x = x,
y = x^2 + runif(100),
z = rep(letters[1:4], 25))
p1 <- ggplot(data = mydata, aes(x, y)) +
geom_point(aes(color = z)) + scale_x_continuous(sec.axis = dup_axis()) +
scale_y_continuous(sec.axis = dup_axis()) +
geom_smooth(color = 'black', se = FALSE, size = 0.5) +
theme(text = element_text(family = 'Times', size = 10*(80/169), color = 'black'),
axis.ticks.length = unit(-0.1, 'cm'),
axis.text.x = element_text(margin = margin(t = 4, unit = 'mm')),
axis.text.y = element_text(margin = margin(r = 4, unit = 'mm')),
panel.grid = element_blank(),
axis.line = element_line(size = 0.25),
legend.position = c(0.5, 0.75))
p2 <- ggplot(data = mydata, aes(x, y)) +
geom_point(aes(color = z)) + scale_x_continuous(sec.axis = dup_axis()) +
scale_y_continuous(sec.axis = dup_axis()) +
geom_smooth(color = 'black', se = FALSE, size = 0.5) +
theme(text = element_text(family = 'Times', size = 10, color = 'black'),
axis.ticks.length = unit(-0.1, 'cm'),
axis.text.x = element_text(margin = margin(t = 4, unit = 'mm')),
axis.text.y = element_text(margin = margin(r = 4, unit = 'mm')),
panel.grid = element_blank(),
axis.line = element_line(size = 0.25),
legend.position = c(0.5, 0.75))
p1
ggsave(plot = p1,
filename = 'myplot.png',
width = 80, height = 50, dpi = 300, units = 'mm')
p2multi <- cowplot::plot_grid(plotlist = list(p2, p2, p2, p2), nrow = 1)
ggsave(plot = p2multi ,
filename = 'mymultipleplot.png',
width = 169, height = 50, dpi = 300, units = 'mm')
I'm trying to add percentage labels to a donut chart but have been unsuccessful in plotting a clear representation of percentage values (rounded and not overlapping)
## my data
library(ggplot2)
col <- c("white", "black", "transparent", "grey", "blue", "yellow", "green", "red", "pink", "orange", "brown")
freq <- c(101, 68, 34, 18, 14, 5, 5, 3, 2, 1, 1)
## create data frame
colour.df <- data.frame(col, freq)
colour.df
## calculate percentage
colour.df$percentage = colour.df$freq / sum(colour.df$freq)* 100
colour.df = colour.df[rev(order(colour.df$percentage)), ]
colour.df$ymax = cumsum(colour.df$percentage)
colour.df$ymin = c(0, head(colour.df$ymax, n = -1))
colour.df
## reorder colour levels
colour.df$col <- reorder(colour.df$col,
new.order = c(10, 1, 9, 5, 2, 11, 4, 8, 7, 6, 3))
All prepared for plotting. I may have done this a idiosyncratic way as I have to produce multiple donuts for other categories that involve colour, but I cant get my head round that (facets?).
## DONUNT ##
donut = ggplot(colour.df, aes(fill = col, ymax = ymax, ymin = ymin, xmax = 100, xmin = 80)) +
geom_rect(colour = "black") +
coord_polar(theta = "y") +
xlim(c(0, 100)) +
geom_label(aes(label = paste(percentage,"%"), x = 100, y = (ymin + ymax)/2),
inherit.aes = F, show.legend = F, size = 5) +
theme(legend.title = element_text(colour = "black", size = 16, face = "bold"),
legend.text = element_text(colour = "black", size = 15),
panel.grid = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank()) +
annotate("text", x = 0, y = 0, size = 15, label = "Micro")
donut
I have played around with the following code:
colour.df$percentage = colour.df$freq / sum(colour.df$freq)* 100
## to this
colour.df$percentage = round(colour.df$freq / sum(colour.df$freq)* 100, digits = 1)
But it it knocks up the ymax to 100.1. Taking it to 3 decimal points helps, but doesn't sort the overlapping.
I've also been bumping heads with the geom_label & geom_text ggplot2: How to add percentage labels to a donut chart & Rounding % Labels on bar chart in ggplot2
Anyway long story short. Any tips to help shape the above ^ code so I get rounded percentage labels next to my donut chart, without overlapping?
Thank you
donutchart
For rounding we could replace percentage with round(percentage,2) and for the overlap we could use geom_label_repel from the ggrepel package
library(ggrepel)
donut = ggplot(colour.df, aes(fill = col, ymax = ymax, ymin = ymin, xmax = 100, xmin = 80)) +
geom_rect(colour = "black") +
coord_polar(theta = "y") +
xlim(c(0, 100)) +
geom_label_repel(aes(label = paste(round(percentage,2),"%"), x = 100, y = (ymin + ymax)/2),inherit.aes = F, show.legend = F, size = 5)+
theme(legend.title = element_text(colour = "black", size = 16, face = "bold"),
legend.text = element_text(colour = "black", size = 15),
panel.grid = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank()) +
annotate("text", x = 0, y = 0, size = 15, label = "Micro")
donut
Note that there are warnings produced with ggrepel (Also I skipped the reorder colour levels step, feel free to correct/comment):
In min(x) : no non-missing arguments to min; returning Inf
In max(x) : no non-missing arguments to max; returning -Inf
I have arranged two plots: a line chart on top and a heatmap below.
I want the heatmap legend to have the same height as the plot area of the heatmap, i.e. the same length as the y-axis. I know that I can change the height and size of the legend using theme(legend.key.height = unit(...)), but this would take many trial and errors before I find an adequate setting.
Is there a way to specify the height of the legend so that it is exactly the same height of the plot area of the heatmap and would retain that ratio when plotting to a pdf?
A reproducible example with code I have tried:
#Create some test data
pp <- function (n, r = 4) {
x <- seq(1:100)
df <- expand.grid(x = x, y = 1:10)
df$z <- df$x*df$y
df
}
testD <- pp(20)
#Define groups
colbreaks <- seq(min(testD[ , 3]), max(testD[ , 3] + 1), length = 5)
library(Hmisc)
testD$group <- cut2(testD[ , 3], cuts = c(colbreaks))
detach(package:Hmisc, unload = TRUE)
#Create data for the top plot
testD_agg <- aggregate(.~ x, data=testD[ , c(1, 3)], FUN = sum)
#Bottom plot (heatmap)
library(ggplot2)
library(gtable)
p <- ggplot(testD, aes(x = x, y = y)) +
geom_tile(aes(fill = group)) +
scale_fill_manual(values = c("red", "orange", "yellow", "lightgreen")) +
coord_cartesian(xlim = c(0, 100), ylim = c(0.5, 10.5)) +
theme_bw() +
theme(legend.position = "right",
legend.key = element_blank(),
legend.text = element_text(colour = "black", size = 12),
legend.title = element_blank(),
axis.text.x = element_text(size = 12, angle = 45, vjust = +0.5),
axis.text.y = element_text(size = 12),
axis.title = element_text(size = 14),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
plot.margin = unit(c(0, 0, 0, 0), "line"))
#Top plot (line)
p2 <- ggplot(testD_agg, aes(x = x, y = z)) +
geom_line() +
xlab(NULL) +
coord_cartesian(xlim = c(0, 100), ylim = c(0, max(testD_agg$z))) +
theme_bw() +
theme(legend.position = "none",
legend.key = element_blank(),
legend.text = element_text(colour = "black", size = 12),
legend.title = element_text(size = 12, face = "plain"),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 12),
axis.title = element_text(size = 14),
axis.ticks.x = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
plot.margin = unit(c(0.5, 0.5, 0, 0), "line"))
#Create gtables
gp <- ggplotGrob(p)
gp2 <- ggplotGrob(p2)
#Add space to the right of the top plot with width equal to the legend of the bottomplot
legend.width <- gp$widths[7:8] #obtain the width of the legend in pff2
gp2 <- gtable_add_cols(gp2, legend.width, 4) #add a colum to pff with with legend.with
#combine the plots
cg <- rbind(gp2, gp, size = "last")
#set the ratio of the plots
panels <- cg$layout$t[grep("panel", cg$layout$name)]
cg$heights[panels] <- unit(c(2,3), "null")
#remove white spacing between plots
cg <- gtable_add_rows(cg, unit(0, "npc"), pos = nrow(gp))
pdf("test.pdf", width = 8, height = 7)
print(grid.draw(cg))
dev.off()
#The following did not help solve my problem but I think I got close
old.height <- cg$grobs[[16]]$heights[2]
#It seems the height of the legend is given in "mm", change to "npc"?
gp$grobs[[8]]$grobs[[1]]$heights <- c(rep(unit(0, "npc"), 3), rep(unit(1/4, "npc"), 4), rep(unit(0, "mm"),1))
#this does allow for adjustment of the heights but not the exact control I need.
My actual data has some more categories, but the gist is the same.
Here is an image produced with the code above and annotated with what I would like to do.
Thanks in advance!
Maarten
It seems there are two sets of heights that need adjustment: the heights of the legend keys, and the overall height of the legend. Picking up from your cg grob, I extract the legend, make the adjustments to the heights, then insert the legend grob back into the layout.
leg = gtable_filter(cg, "guide-box")
library(grid)
# Legend keys
leg[[1]][[1]][[1]][[1]]$heights = unit.c(rep(unit(0, "mm"), 3),
rep(unit(1/4, "npc"), 4),
unit(0, "mm"))
# Legend
leg[[1]][[1]]$heights[[3]] = sum(rep(unit(0, "mm"), 3),
rep(unit(1/4, "npc"), 4),
unit(0, "mm"))
# grid.draw(leg) # Check that heights are correct
cg.new = gtable_add_grob(cg, leg, t = 17, l = 8)
grid.newpage()
grid.draw(cg.new)