Export Graphics with exact size and font size - r

I need to export graphics from R for the use in different publications formats, i.e., scientific poster, Journal Articel, Powerpoint presentation. As Long as I'm not aware of using R-Markdown and Latex, Or Sweave to produce pdf slides, i need to use Microsoft applications.
I do graphics in R with ggplot2.
My MWE
df1 <- expand.grid(Year = c(2000, 2010),
Treat = c("TreatA","TreatB"),
Location = c("Berlin", "Munich", "Kansas", "Paris"),
rep = c(1,2,3,4,5,6,7,8,9,10))
df1 <- cbind(df1,
Var1 = runif(160, -10,25) + rnorm(160,8,4))
My Graphics Code:
p1 <- ggplot(aes(y = Var1, x = Treat, na.rm=TRUE, fill=Location), data = df1) +
stat_boxplot(geom ='errorbar', width= 0.5) +
geom_boxplot(outlier.shape = 1, outlier.size = 2, linetype="solid", size = 1) +
facet_grid( Year ~ Location) +
scale_y_continuous(limits=c(-20,60)) +
scale_fill_manual(values=c("#E69F00", "#009E73", "#0072B2", "#D55E00")) +
ylab("Magic Skills") +
xlab("Magic Juice") +
theme(text=element_text(family="Arial", size=18),
axis.text.x = element_text(angle = 30, hjust = 1),
line=element_line(size=1),
rect=element_rect(colour="red", size=1),
panel.background=element_rect(colour="black",fill="white", size=1),
strip.background=element_rect(colour="dark grey", fill="black"),
strip.text=element_text(face="bold", colour="white"),
panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),
legend.position="none");p1
How I save the graph:
ggsave(p1, file="Results/Figures/Abb2_Boxplots_LarvenPuppen2.svg", width=35, height=20, units = "cm", dpi=500)
I set the font size with "theme(text=element_text(family="Arial", size=18)" for the whole graph.
During ggsave I adjust the height and width, for the Poster the height should be 20 cm and width 35 cm. I save in .svg since I made best experience with the quality of the graph, furthermore I can edit anything at anytime in Inkscape, that's great!
So far so good, yet, it appears, that the text is by no means 18 pt, later in the powerpoint-poster, after exporting the graph (without editing!) as .png from Inkscape. It is even not 20 x 35 cm, I found that i loose about 5 cm or more during the saving and export process.
I guess R is doing write, when i say save 20x35 cm R will do it. Where in the whole process were my settings changed, and how can I handle it?
Best regards,
Pharcyde

There's a few things to consider:
SVG units – Note the discussion about user units, and pt vs px
grid graphics parameters and how they're interpreted by the device
ggplot2, and how its theme settings affect the font size
Here's a pure grid example that illustrates the second point,
library(grid)
g <- grobTree(rectGrob(), textGrob("test", gp=gpar(fontsize=12)))
ggplot2::ggsave("size.svg", g, width = 10, height=10, units = "in")
tmp <- readLines("size.svg")
grep("test", tmp, value = TRUE)
# "<text x='350.33' y='364.30' style='font-size: 12.00px; font-family: Arial;' textLength='19.34px' lengthAdjust='spacingAndGlyphs'>test</text>"
grep("viewBox", tmp, value = TRUE)
# "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 720.00 720.00'>"
So short answer is that the svg produced by R (svglite here) contains a seemingly consistent font size (12px), but the 10 inches is interpreted as 720px regardless of dpi. Looking at the source this 72 factor comes from R graphics conventions (typically devices follow the pdf device, which defaults to a 72DPI resolution. Why this is not affected by the various dpi/res/pointsize parameters is a mystery to me).
The gridSVG package by-passes the usual graphics engine system, and appears to produce more straight-forward sizes,
library(gridSVG)
gridsvg("size.svg", res = 1000, width=10, height=5)
grid.draw(g)
dev.off()
tmp <- readLines("size.svg")
grep("viewBox", tmp, value = TRUE)
# "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"10000px\" height=\"5000px\" viewBox=\"0 0 10000 5000\" version=\"1.1\">"
though this time the font size is scaled (it is 12 for the default 72DPI resolution),
grep("font-size", tmp, value = TRUE)
# "<text x=\"0\" y=\"0\" id=\"GRID.text.41.1.1.text\" text-anchor=\"middle\" font-size=\"166.67\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\">"
On the ggplot2 side, things get even murkier because of some built-in settings for the inheritance of theme elements, rel()ative scaling, scales, that may or may not be documented (and have changed in the past IIRC).

"18pt" in an SVG is not the same as 18pt on a printed page.
One point ("pt") in a CSS style attribute has been defined - like in the printing industry - as 1/72 of an inch. However in CSS, an inch is not a real-world inch, but instead it is 96 "CSS pixels".
That dpi of 96, is a arbitrary value chosen a long time ago. Computer monitors aren't 96 pixels per inch any more (if any ever were).
If you want something to match the real world - for example one real inch on a printed page or screen - you will have to test your environment and output method, and apply a scaling factor to your SVG units. Or to your conversion technique.

Here is how it worked:
p1 <- ggplot(aes(y = Var1, x = Treat, na.rm=TRUE, fill=Location), data = df1) +
stat_boxplot(geom ='errorbar', width= 0.5) +
geom_boxplot(outlier.shape = 1, outlier.size = 2, linetype="solid", size = 1) +
facet_grid( Year ~ Location) +
scale_y_continuous(limits=c(-20,60)) +
scale_fill_manual(values=c("#E69F00", "#009E73", "#0072B2", "#D55E00")) +
ylab("Magic Skills") +
xlab("Magic Juice") +
theme(text=element_text(family="Arial", size=36*96/72),
axis.text.x = element_text(angle = 30, hjust = 1),
line=element_line(size=1),
rect=element_rect(colour="red", size=1),
panel.background=element_rect(colour="black",fill="white", size=1),
strip.background=element_rect(colour="dark grey", fill="black"),
strip.text=element_text(face="bold", colour="white"),
panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),
legend.position="none");p1
ggsave(p1, file="Results/Figures/Trial.svg", width=35*1.25, height=20*1.25, units="cm", dpi=96)
With this code, I got an 35x20 cm plot both in Inkscape and in Powerpoint.
Furthermore the size of the letters was pretty much the size of letters in powerpoint.
This time I chose letter size 36pt, since this is more useful for Poster-presentations. I did not make further trials with changing the dpi.
Here my findings so far:
R uses 72 dpi by default, the formula: size * new resolution DPI / 72 DPI,
e.g.: 36 * 96/72, delivers almost similar pt sizes in Microsoft office documents. Irrespective of both the dpi settings used in ggsave as well as the height and width settings.
10 cm specified in R via ggsave() correspond to 8.060 cm in Inkscape. Multiplying the admired width/height by 1.25 delivers correct image sizes in Inkscape and also in MS Office, irrespective of the dpi set in the Export settings.
Something that makes me still wonder is that although I made the same settings for font size for the WHOLE plot via "text=element_text(family="Arial", size=36*96/72)"
Label titles are taller than tick labels or text in the facet boxes.
best wishes,
Pharcyde

The font size used by ggplot matches font size in word the problem is that the figure gets resized during saving, during importing, or both. The best solution for me has been to set a graphics window to the size the figure will be in the document (5.33" x 4" usually looks good in Word; 7.5" x 10" for a 4:3 PowerPoint), and then save the figure as that size. You may need to play around with file types (.tif doesn't play nice in PowerPoint and gets resized for some reason). That said, if you use this method and be sure that the figure isn't resized after it is exported from R it will have the exact size font as you had when you made your ggplot.
Here is the code I use,
dev.new(width = 10, height = 7.5, unit="in", noRStudioGD = T);last_plot() #Set new windo size and replot whatever plot you just made.
ggsave("workingdirectory/plot.png",width = dev.size()[1],height = dev.size()[2]);dev.off() #Save the plot and set the size using `dev.siz()` so you don't have to ever change that part and cannot possibly have a typo.
And here is a full example with output that you can bring into a 4:3 PowerPoint and see for yourself that it works.
measure1<-as.numeric(1:50)
category<-rep(c("a","b","c","d","e"),each=10)
df<-data.frame(measure1,category)
library(ggplot2)
library(extrafont)
ggplot(df, aes(x = category , y = measure1, fill = category, colour = category)) +
geom_boxplot(outlier.shape = NA, show.legend = F, notch = F) +
geom_point(aes(),size = 3, shape=16, colour="white", alpha=.6) +
stat_summary(fun.y=mean, show.legend = FALSE, geom="point", shape=18, size=5, colour = "white", na.rm = TRUE) +
ylab("Measure") +
xlab("Category") +
ggtitle("") +
scale_y_continuous (limits = c(0,50), expand = c(0,0), breaks=seq(0,50,5))+
scale_x_discrete(limits=c("a","b","c","d","e")) +
scale_fill_manual(values = rep("black",5))+
scale_colour_manual(values = rep("white",5))+
theme_bw() +
theme (text=element_text(size=24, family="Times New Roman"),
axis.title.y=element_text(size=24, vjust=2, colour="white"),
axis.title.x=element_text(size=24, hjust=0.5, colour="white"),
panel.border = element_blank(),
plot.title=element_blank(),
plot.margin = unit(c(.5, .5, .5, .5), "cm"),
panel.background = element_rect(fill = "black"),
plot.background = element_rect(fill = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none",
axis.ticks = element_line(size=0.8, colour = "white"),
axis.ticks.length=unit(0.2,"cm"),
axis.text.x=element_text(colour="white",size=24,angle=0,hjust=.5),
axis.text.y=element_text(colour="white",size=24),
axis.line = element_line(colour="white", size=0.8, lineend = "square"))
dev.new(width = 10, height = 7.5, unit="in", noRStudioGD = T);last_plot()
ggsave("C:/Users/s39f171/Documents/plot.png",width = dev.size()[1],height = dev.size()[2]);dev.off()

Related

Changing Plot Font with Cairo in R

I have found R's default plots to be poorly aliased. As a solution, I set Cairo as the graphics device, and now the plots look much better.
Unfortunately, using Cairo has created another issue, which is that for some reason, I am not able to apply the font that I was using when the graph was displayed in the plot window (in the left-hand diagram above, Cambria is used, but the right-hand diagram fails to apply this font).
Here is my code:
library(readxl)
library(scales)
library(ggplot2)
library(dplyr)
library('Cairo')
windowsFonts(Cam = windowsFont("Cambria"))
dataset <- read_excel('CW Data.xlsx')
colnames(dataset)[4] <- "Broadband Subs (%)"
options(scipen = 1000)
# Scatter plot FDI~GDP with regression line
CairoWin()
ggplot(dataset, aes(x=`2019 GDP ($bn)`, y=`2019 FDI ($m)`)) +
geom_point(size=3, shape=1) +
geom_smooth(method='lm',formula=y~x, se=FALSE, color='black') +
scale_x_continuous(label = comma) + scale_y_continuous(label=comma) +
theme(panel.background = element_rect(fill="peachpuff"),
plot.background = element_rect(fill="peachpuff")) +
theme(panel.grid.major = element_line(colour = "gray72"),
panel.grid.minor = element_line(colour = "gray72")) +
theme(text = element_text(family = "Cam"))
ggsave("FDI~GDP.png", device="png", type = "cairo")
And here is a OneDrive link for the Excel data that I am using
https://1drv.ms/x/s!AvGKDeEV3LOs4gNr714Ie0KbOjhO?e=bkdPvk
I suggest you have a look at the packages ragg and systemfonts. They make working with fonts extremly easy and the results are better than the output of the base options.
First, I suggest you query all available fonts using View(systemfonts::system_fonts()). You can select every font present here and use it for plotting or saving a plot.
I recreated your plot using a built in dataset as the onedrive link you shared was broken. I used the Cambria font like this.
plot <- ggplot(dataset, aes(x = mpg, y = hp)) +
geom_point(size = 3, shape = 1) +
geom_smooth(
method = 'lm',
formula = y ~ x,
se = FALSE,
color = 'black'
) +
scale_x_continuous(label = comma) +
scale_y_continuous(label = comma) +
labs(x = "2019 GDP ($bn)", y = "2019 FDI ($m)") +
theme(
panel.background = element_rect(fill = "peachpuff"),
plot.background = element_rect(fill = "peachpuff")
) +
theme(
panel.grid.major = element_line(colour = "gray72"),
panel.grid.minor = element_line(colour = "gray72")
) +
theme(text = element_text(family = "Cambria")) # relevant line
I prefer saving the plot in an object and passing it explicitly to the save function.
ggsave(
"FDI~GDP.png",
plot = plot,
device = ragg::agg_png, # this is the relevant part
width = 1920,
height = 1080,
units = "px"
)
Here is the result:
I would say it worked flawlessly. You can also use ragg as your graphics device in RStudio to make this more consistent. Have a look here.
If you want to output the plot to a PDF, you can use showtext to register system fonts with all newly opening graphics devices. So what you need to do is:
library(showtext)
showtext_auto()
ggsave(
"FDI~GDP.pdf",
plot = plot,
width = 1920,
height = 1080,
units = "px"
)

Saving plots from ggplot with customised dimensions

I am trying to save a plot created with ggplot from R to paste on my report in Word, but I am having issue with the dimensions.
plot_2.2 <- ggplot(data = data_2.2_melted, aes(x=value, y=variable, shape = Gender)) +
geom_point(size = 1.8, alpha = 0.5, col = "black")
#Change size of text and position of legend
plot_2.2_final <- plot_2.2 + theme(axis.text = element_text(size = 10, colour = "black")) +
theme(legend.position = "left",
axis.title.x=element_blank(),
axis.title.y=element_blank()) +
scale_shape_manual(values = c("M"= 17, "F" = 19))
I am trying
png(file="folder/saving_plot2.2.png",
width=790, height=840)
plot_2.2_final
dev.off()
But the dimensions are too small. If I try bigger dimension, eg
width=990, height=1140)
plot_2.2_final
dev.off()
I still get the dimensions of the first plot. It is like R is imposing size limits.
I have similar issues when I try to copy directly from R (i.e. Plot - Export - Copy to Clipboard) as I can get bigger plots.
I also tried to save in PDF to get a bigger plot, but it is smaller.
Could anyone help?
Thank you

How to set plot size in ggplot or ggarrange?

I work with Rmarkdown and I am creating many figures composed of different ggplot2 charts, using ggarrange. The problem is that I am not able to set different sizes for figures inside a single chunk. The only way I managed to set figure size is within chunk options, like:
{r Figure1, fig.height = 4, fig.width = 7}
Is there a way of setting the plot/grid size within each ggplot() or within ggarrange() function?
Details, data & code:
All my primary plots have the same size of the grid area, but the size of the final figures changes depending on the number of primary plots they contain. In addition, for annotation purposes (e.g. with annotation_figures() function), charts in the first row must have a larger top margin (for the title), while charts in the last row must have a larger bottom margin (for captions). The extra margins, to make room for title and captions (e.g. text grobs) should not alter the plot grid size, which I want to be the same for all plots inside a figure.
One example of a dataframe called "A1" that I have:
library("pacman")
p_load(ggplot2, ggpubr, hrbrthemes, tidyverse)
year <- rep(seq(2010,2019, 1), each=3)
name <- rep(c("A", "B", "C"), times=10)
n1 <- c(0,0,1,0,2,1,1,1,1,2,0,2,0,2,1,3,2,0,1,4,2,2,9,4,8,11,8,7,9,8)
n2 <- c(7,3,1,14,1,1, 15,4,4,19,9,4,26,9,4,46,4,3,52,12,3,37,12,5,45,10,5,47,18,4)
name2 <- name
A1 <-data.frame(year,name,n1,n2,name2)
With this data frame, I build the first row of plots inside a chunk with specifications {fig.height = 4.3, fig.width = 7}. This plot row has three plots (made with facet_wrap) and a top margin of 0.3 inches to make room for title annotation in the final figure, and no bottom margin. This plot row also has its own title, which will function like a subtitle or a tag in the final figure.
AA.1 <- A1 %>%
ggplot( aes(x=year, y=n1)) +
geom_line( data=A1 %>% dplyr::select(-name), aes(group=name2), color="grey", size=1.0, alpha=0.6) +
geom_line( aes(color=name), color="black", size=1.5 ) +
theme_ipsum() +
theme(
axis.text.x = element_text(size=12, angle=45),
axis.text.y = element_text(size=12),
legend.position="none",
plot.title = element_text(size=16),
panel.grid = element_blank(),
plot.margin = unit(c(0.3, 0.2, 0, 0.2), "in")) + #Top row charts have a 0.3 top margin
labs(title="A. TAK") +
scale_x_continuous(name ="",
limits=c(2010,2019),
breaks=c(seq(2010,2019,2)))+
scale_y_continuous(name ="",
limits=c(0,12),
breaks=c(seq(0,12,3)))+
facet_wrap(~name) +
theme(strip.text = element_text(size=13))
AA.1
Then I create the bottom row plots, inside another chunk, with a different figure height specification: {fig.height = 4.1, fig.width = 7}. This row is also made of three plots, which should be similar in all aesthetics aspects to the first row, although I am plotting a different variable, with different values (ref). This row has no top margins and a 0.1 inches bottom margin, to make room for captions.
AA.2 <- A1 %>%
ggplot( aes(x=year, y=n2)) +
geom_line( data=A1 %>% dplyr::select(-name), aes(group=name2), color="grey", size=1.0, alpha=0.6) +
geom_line( aes(color=name), color="black", size=1.5 )+
theme_ipsum() +
theme(
axis.text.x = element_text(size=12, angle=45),
axis.text.y = element_text(size=12),
legend.position="none",
plot.title = element_text(size=16),
panel.grid = element_blank(),
plot.margin = unit(c(0, 0.2, 0.1, 0.2), "in")) + #Margins are different
ggtitle("B. REF") +
scale_x_continuous(name ="",
limits=c(2010,2019),
breaks=c(seq(2010,2019,2)))+
scale_y_continuous(name ="",
limits=c(0,60),
breaks=c(seq(0,60,10)))+
facet_wrap(~name) +
theme(strip.text = element_text(size=13))
AA.2
Finally, I arrange both sets of plots within a figure using ggarange(), and write the final title and captions with annotate_figure(). In this chunk, I set the total fig.height to 8.4 (the sum of two previous chunks).
figureA1 <- ggarrange(AA.1, AA.2,
ncol=1, nrow=2,
heights=c(1,1))
annotate_figure(
figureA1,
top = text_grob("Figura A.1 - TAK & REF",
color = "black", face = "bold", size = 18),
bottom = text_grob("Source: My.Data (2020)", face="italic", color = "black",
hjust = 1, x = 1, size = 12),)
Possible solutions:
I would like that each plot had a total plot grid area of 4 inches. As the first plot has a top margin of 0.3 and a bottom margin of 0, I set fig.height to 4.3. As the second plot has a top margin of 0 and a bottom margin of 0.1, I set fig.height to 0.1. However, the plot grid area does not seem to be of the same size in both plots. Any ideas on how to fix this?
As the fig.height parameters are different for each plot, I need to split the code to build a figure into different chunks. I have many figures that are made in a similar fashion, and therefore, I would like to write a function to build and arrange plots within figures. However, I cannot write a function across different chunks.
I think of two possible solutions for this second problem:
Some way of setting the grid plot size within ggplot function (or the total plot area); or
Some way of setting each of the grid plots sizes within the ggarrange function;
Anyone has an idea of how can I do that?
Perhaps the second would be even better. I tried to set the heights of the rows in ggarrange to be the same with the same, with heights argument ("heights=c(1,1)"). Also tried to make them proportional to each fig.height ("heights=c(5.3/5.1,1)"), but the second plot row grid still looks taller than the first one.

Weird deformation of columns in ggplot2 when changing size of the plot

Look I will put 2 images of the same plot, the only thing I made was changing the size of it, expanding the size of the plot to the sizes before saving it as an image. But the code is the same in both plots. How can I make this not to happen?
here I attach same plot with different size, see how much change the columns, now there is a new column with máximum of 15 that was not before !!!:
Here I attach my code for the plot:
plot<-ggplot(DT,aes(x=Date,y=n_per_week,fill=var2))+
geom_bar(stat="identity", size= 4)+
theme(plot.title = element_text(hjust = 0.5))+
ylab("N")+xlab("Date (Month-Year)")+
scale_x_date(date_labels = "%m-%y")+
theme_minimal(base_size = 11, base_family = "")
How can I make this changes not to happen !!
Thanks in advance !

Change number of minor gridlines in ggplot2 (per major ones), r

I read this answer : How to control number of minor grid lines in ggplot2?
Although I couldn't figure out a way to reconcile it with my requirements.
I want there to be a way to input the number of minor gridlines between two major ticks. (or the ratio of the minor to major grid size) Say I want to divide it into 5 parts (4 minor gridlines). How do I do that?
Since there will be many graphs, of which I wouldn't know the axis limits, I can't explicitly define the size of one minor gridline step. I want to use whatever algo ggplot2 uses to pick the number of major gridlines, and just have 4 times as many minor ones.
I'd like the r graph on the right to look like the excel graph on the left
CODE (in case that helps solve the issue)
ggtheme <- theme(axis.line = element_line(size = 0.5, colour = "black"),
panel.background = element_rect(fill = "white"),
panel.grid.major = element_line(colour="grey90",size = rel(0.5)),
panel.grid.minor = element_line(colour="grey95",size = rel(0.25)));
ggp_sctr2 = ggplot( sub2_ac_data, aes(x=(sub2_ac_data[,i]),
y=sub2_ac_data[, rescol], colour = factor(sub2_ac_data[,topfac[1]]),
shape = factor(sub2_ac_data[,topfac[1]]) )) + geom_point(size = 2.5) +
scale_shape_manual(values=symlist[Nmsn_sub1+1:20]) +
scale_colour_manual(values = unname(cols[Nmsn_sub1+1:16])) +
geom_smooth(method="lm", formula = y ~ splines::bs(x, 3),
linetype = "solid", size = 0.25,fill = NA )
print(ggp_sctr2 + ggtitle( paste(scxnam[1],nomvar,
"vs",colnames(sub2_ac_data[i]),i, sep = " ")) +
theme(axis.text.x=element_text(angle = 90, hjust = 1,vjust = 0.5,size=8)) +
labs(x = colnames(sub2_ac_data[i]), y=colnames(sub2_ac_data[rescol]),
colour=colnames(sub2_ac_data[topfac[1]]),
shape=colnames(sub2_ac_data[topfac[1]])) + ggtheme +
theme(plot.title = element_text(face = "bold", size = 16,hjust = 0.5))) ;
I found one possible solution, although it's not very elegant.*
Basically you extract the major gridlines and then create a sequence of minor gridlines based on a multiplier.
I've replied to a related question (as a modification to an answer by Eric Watt). It's just a change of syntax, that wasn't explained too well in any documentation.
This is the link: ggplot2 integer multiple of minor breaks per major break
Here is the code:
library(ggplot2)
df <- data.frame(x = 0:10,
y = 10:20)
p <- ggplot(df, aes(x,y)) + geom_point()
majors <- ggplot_build(p)$layout$panel_params[[1]]$x.major_source;majors
multiplier <- 5
minors <- seq(from = min(majors),
to = max(majors),
length.out = ((length(majors) - 1) * multiplier) + 1);minors
p + scale_x_continuous(minor_breaks = minors)
*Not very elegant because:
It fails in case the graph doesn't get any data (which can happen in a loop, so exceptions need to be made for it).
It only starts minor gridlines from the min to max of the existing major gridlines, not throughout the extremities of the coordinate space where points can be placed

Resources