Installing fonts so that R postscript() device can recognise them - r

Based on the advice in this post I am trying to get the serif font (or 'family' of fonts) installed into R so that I can save ggplots as .eps files. Though the suggestion provided worked I would like to try to resolve the issue for future use.
Here's code to generate the issue.
library(bayesplot)
df <- data.frame(xVar = rnorm(1e4,0,1), yVar = rnorm(1e4,2,1), zVar = rnorm(1e4,4,1))
t <- bayesplot::mcmc_trace(df)
t
Now when I go to save the figure I get this error
ggplot2::ggsave(filename = "tPlot.eps",
plot = t,
device = "eps",
dpi = 1200,
width = 15,
height = 10,
units = "cm")
Which throws the error
Error in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)) :
family 'serif' not included in postscript() device
In the previous post the answerer suggested I download the extrafont package.
I ran
View(fonttable())
But the serif font did not appear to be installed.
Then I tried
font_addpackage(pkg = "serif")
But I got the error
Error in font_addpackage(pkg = "serif") :
Unknown font package type: not type1 or ttf.
Does anyone know how to install the serif font so R can recognise/use it?

With package extrafont the fonts must be installed before being made available to users. This is done with function font_import.
library(extrafont)
font_import() # This takes several minutes
Now we can see what are the fonts installed and available. From the documentation, help("fonts").
Description
Show the fonts that are registered in the font table (and available for embedding)
fonts_installed <- fonts()
serif1 <- grepl("serif", fonts_installed, ignore.case = TRUE)
sans1 <- grepl("sans", fonts_installed, ignore.case = TRUE)
fonts_installed[serif1 & !sans1]
sum(serif1 & !sans1)
#[1] 458
There are 458 fonts available.
Another way to see the font table is with function fonttable but the fonts returned are not necessarily available for embedding. From help("fonttable").
Description
Returns the full font table
Note that the function returns a dataframe, hence the call to str below (output omitted).
df_font <- fonttable()
str(df_font)
serif2 <- grepl("serif", df_font$FontName, ignore.case = TRUE)
sans2 <- grepl("sans", df_font$FontName, ignore.case = TRUE)
df_font$FontName[serif2 & !sans2]
Finally see if the graphing functions work on a postscript device.
library(bayesplot)
df <- data.frame(xVar = rnorm(1e4,0,1), yVar = rnorm(1e4,2,1), zVar = rnorm(1e4,4,1))
p <- bayesplot::mcmc_trace(df)
p
ggplot2::ggsave(filename = "tPlot.eps",
plot = p,
device = "eps",
dpi = 1200,
width = 15,
height = 10,
units = "cm")

Related

Calculate the width of a string in a specific font and font size (without creating any files in batch mode)

There is a similar question here that is asking for the width of a string in the default font size. However, I want to calculate the width of a text string in a specific font and font size.
The Base R strwidth() function seems to be ignoring changes to the font and family parameters. Observe:
strwidth("This is cool.", units = "inches", font = 12, family = "sans")
# [1] 0.9479167
# font 10 gives same result as font 12
strwidth("This is cool.", units = "inches", font = 10, family = "sans")
# [1] 0.9479167
# monospace font gives same result as sans serif
strwidth("This is cool.", units = "inches", font = 10, family = "mono")
# [1] 0.9479167
So it appears the font and family parameters are not picked up by the function.
I have found that setting the graphics parameters with par() makes the strwidth() function work correctly:
par(ps = 12, family = "sans")
strwidth("This is cool.", units = "inches")
# [1] 0.8541667
par(ps = 10, family = "sans")
strwidth("This is cool.", units = "inches")
# [1] 0.7291667
par(ps = 10, family = "mono")
strwidth("This is cool.", units = "inches")
# [1] 1.083333
And these widths seem like decent estimates.
Now the issue is that par() is creating empty PDF files in my project, because no graphics device has been passed to it. My issue is, I'm not creating any graphics. I'm just trying to find the width of a string. I shouldn't have to set the graphics parameters to do this.
So the question still remains: How do I find the width of a string of a specific font and font size in R (without creating any files)?
For batch use, consider this test of the material in the pdf help page for the file parameter in grDevices-package which is loaded by default from the base packages installed as part of the core suite:
file
a character string giving the file path. If it is of the form "|cmd", the output is piped to the command given by cmd. If it is NULL, then no external file is created (effectively, no drawing occurs), but the device may still be queried (e.g., for size of text).
Running this file that I named "parwidth.R":
pdf(NULL) # and this could be opened with additional parameters
par(ps = 12, family = "sans")
strwidth("This is cool.", units = "inches")
dev.off()
... from a Terminal session with Rscript parwidth.R ...
does not produce a 'Rplots.pdf' file and does return the same value as above. I suspect that you may eventually need to look at the code of the first function called by pdf, namely getAnywhere(initPSandPDFfonts). I suspect it will vary from OS to OS.
After a few hours hacking away, I discovered one way to do it using the R.devices package:
get_text_width <- function(txt, font, font_size = 10, units = "inches") {
f <- "mono"
if (tolower(font) == "arial")
f <- "sans"
else if (tolower(font) == "times")
f <- "serif"
R.devices::devEval("nulldev", {
par(family = f, ps = font_size)
ret <- strwidth(txt, units = units)
})
return(ret)
}
And here is the function in action:
get_text_width("This is cool.", "Arial", 12)
# [1] 0.8798333
get_text_width("This is cool.", "Arial", 10)
# [1] 0.7331944
get_text_width("This is cool.", "Times", 10)
# [1] 0.6829167
get_text_width("This is cool.", "Courier", 10)
# [1] 1.083333
get_text_width(c("Hello", "Goodbye now!"), "Courier", 10)
# [1] 0.4166667 1.0000000
I'd very much like to know if there are other ways to do it, as this seems like an unnecessary amount of work. Also I'd very much like to know if this works on other operating systems besides Windows.

Weird characters appearing in the plot legend when using DoHeatmap

I was using Seurat to analyse single cell RNA-seq data and I managed to draw a heatmap plot with DoHeatmap() after clustering and marker selection, but got a bunch of random characters appearing in the legend. They are random characters as they will change every time you run the code. I was worrying over it's something related to my own dataset, so I then tried the test Seurat object 'ifnb' but still got the same issue (see the red oval in the example plot).
example plot
I also tried importing the Seurat object in R in the terminal (via readRDS) and ran the plotting function, but got the same issue there, so it's not a Rstudio thing.
Here are the codes I ran:
'''
library(Seurat)
library(SeuratData)
library(patchwork)
InstallData("ifnb")
LoadData("ifnb")
ifnb.list <- SplitObject(ifnb, split.by = "stim")
ifnb.list <- lapply(X = ifnb.list, FUN = function(x) {
x <- NormalizeData(x)
x <- FindVariableFeatures(x, selection.method = "vst", nfeatures = 2000)
})
features <- SelectIntegrationFeatures(object.list = ifnb.list)
immune.anchors <- FindIntegrationAnchors(object.list = ifnb.list, anchor.features = features)
immune.combined <- IntegrateData(anchorset = immune.anchors)
immune.combined <- ScaleData(immune.combined, verbose = FALSE)
immune.combined <- RunPCA(immune.combined, npcs = 30, verbose = FALSE)
immune.combined <- RunUMAP(immune.combined, reduction = "pca", dims = 1:30)
immune.combined <- FindNeighbors(immune.combined, reduction = "pca", dims = 1:30)
immune.combined <- FindClusters(immune.combined, resolution = 0.5)
DefaultAssay(immune.combined) <- 'RNA'
immune_markers <- FindAllMarkers(immune.combined, latent.vars = "stim", test.use = "MAST", assay = 'RNA')
immune_markers %>%
group_by(cluster) %>%
top_n(n = 10, wt = avg_log2FC) -> top10_immune
DoHeatmap(immune.combined, slot = 'data',features = top10_immune$gene, group.by = 'stim', assay = 'RNA')
'''
Does anyone have any idea how to solve this issue other than reinstalling everything?
I have been having the same issue myself and while I have solved it by not needing the legend, I think you could use this approach and use a similar solution:
DoHeatmap(immune.combined, slot = 'data',features = top10_immune$gene, group.by = 'stim', assay = 'RNA') +
scale_color_manual(
values = my_colors,
limits = c('CTRL', 'STIM'))
Let me know if this works! It doesn't solve the source of the odd text values but it does the job! If you haven't already, I would recommend creating a forum question on the Seurat forums to see where these characters are coming from!
When I use seurat4.0, I met the same problem.
While I loaded 4.1, it disappeared

What is the difference between printing and exporting a PDF and invoking a graphics device to save a PDF?

I have R 3.5.1 and RStudio 1.1.453 running on MacOS High Sierra 10.13.5, platform x86_64-apple-darwin17.6.0. I want to understand why I can print and export plots but can't use a graphics device to save the plots.
I can use R's extrafont package to embed the Corbel font into PDF graphs. I can save these graphs by using print() and then exporting via the Plot window. Using this method, the font embeds and displays perfectly.
However, I want R to save several dozen such graphs without having to manually print() and save each one. There are various methods for this.
One is R's PDF Graphics Device. Unfortunately, this device does not seem to interact nicely with spaces and other characters. Here's the function that produces the ggplot:
jobreport_grouped <- function(table, N)
{
#Group
table <- table %>%
group_by(Education, Desired) %>%
summarize(n())
#Order
table <- arrange(table, desc(`n()`))
#Response label
response_count <- sum(table$`n()`)
#Sample size label
samplesize <- paste("(N = ", sum(table$`n()`), ")", sep="")
#Grouped barplot
ggplot(table[which(table$`n()`>N),],
aes(fill=Education,
x=reorder(Desired,-`n()`),
y=`n()`)
) +
scale_fill_manual(values = paletteIDCN) +
IDCNtheme_grouped_dense +
xlab(paste("Number of Active Members: ",
member_count,
"\n",
"Number of Responses: ",
response_count,
sep="")
) +
ylab("Number of IDCN Members") +
ggtitle(paste(desired_title("Professional Areas"),
"\n",
today,
sep = ""
)
)+
geom_col(position="dodge")
}
And then a call of this function along with an attempt to write the plot
> getGroupedJobReport <- function(A,n){
+ p <- jobreport_grouped(A,n)
+ ggsave(p, filename = "~/TMP-R/TDP/plots/Desired_GROUPED.pdf", device = pdf,
+ width = 12, height = 7, units = "in")
+ }
> getGroupedJobReport(A1,2)
There were 50 or more warnings (use warnings() to see the first 50)
> warnings()
Warning messages:
1: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), ... :
font width unknown for character 0x20
2: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), ... :
font width unknown for character 0x27
3: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), ... :
font width unknown for character 0x20
After this, I tried a MacPorts installation of Cairo together with CRAN's Cairo package and got a different error:
> getGroupedJobReport <- function(A,n){
+ p <- jobreport_grouped(A,n)
+ ggsave(p, filename = "~/TMP-R/TDP/plots/Desired_GROUPED.pdf", device = cairo_pdf,
+ width = 12, height = 7, units = "in")
+ }
> getGroupedJobReport(A1,2)
Warning message:
In dev(file = filename, width = dim[1], height = dim[2], ...) :
failed to load cairo DLL
What alternatives to R's native PDF Graphics Device and Cairo are there? If any, which ones should I think of using?

R gif with function

I am trying to make a gif out of an R-Script using a function to generate the images.
I have a function that given some information creates a Map with dots on it.
I use this function on a Vector obtaining a series of different images, and I would like to put them together in a gif. It looks more or less like that:
createMap <- function(my_variable){
my_map <- a_map() + geom_point() # some variable missing
png(filename = paste(aDate, ".png", sep = ""), width = 3149, height = 2183, units = "px")
plot(mw_map)
dev.off()
}
ImageMagick is installed on my pc and the conversion file "converter.exe" also. Later I try to generate the gif using
saveGIF({
lapply(my_vector, createMap)
}, movie.name = "MY_GIF.gif")
but I get an error message:
> convert: improper image header `Rplot1.png' #
> error/png.c/ReadPNGImage/4362. convert: no images defined `MY_GIF.gif'
> # error/convert.c/ConvertImageCommand/3254.
an error occurred in the conversion...
does anybody know what I did wrong?
After creating the map png files. Use the below code. You don't need ImageMagick is installed on PC.
library(magick)
png.files <- sprintf("Rplot%02d.png", 1:10) #Mention the number of files to be read
GIF.convert <- function(x, output = "animation.gif")#Create a function to read, animate and convert the files to gif
{
image_read(x) %>%
image_animate(fps = 1) %>%
image_write(output)
}
GIF.convert(png.files)
For more details check this link: Link

R - images saved with rgl.snapshot turn blank after dev.off()

I have x, y, z coordinates for 15 markers that were collected via motion capture. I'm using the rgl package to create 3D images of the markers, and I would like to export each image as an individual png file. The motion capture recordings are 5-15 seconds in length, and I'm exporting images of the coordinates at a frame rate of 120 fps. So, I have to export 600-2000 images per recording.
I have written a loop that is supposed to plot the coordinates in each frame and save an image of each plot as a png. However, the "saved" images I'm getting are only temporary files - when I give dev.off(), the images disappear and the saved files convert to blank pages. Clearly there is something I'm misunderstanding about how to handle devices & write permanent image files!
I understand that rgl uses its own devices, and I've tried giving rgl.close() instead of dev.off() when the maximum device number is reached (i.e. after 63 images are exported). But without also giving dev.off(), I continue getting the "too many open devices" error.
My code looks like this:
for (i in seq(1,nrow(opti),by=2)) {
tframe <- data.frame(matrix(cbind(opti[i, c(seq(1,43,by=3))],
opti[i, c(seq(2,44,by=3))],
opti[i, c(seq(3,45,by=3))]),
nrow = 15, ncol = 3))
par3d("windowRect" = c(0,0,1200,800))
png("filename", res = 600, width = 1200, height = 800)
plot3d(tframe$X1, tframe$X2, tframe$X3, size = 4, box = F, axes = F,
xlab = "", ylab = "", zlab = "")
segments3d(x = as.vector(tframe$X1[c(5,6)]),
y = as.vector(tframe$X2[c(5,6)]),
z = as.vector(tframe$X3[c(5,6)]))
segments3d(x = as.vector(tframe$X1[c(5,7)]),
y = as.vector(tframe$X2[c(5,7)]),
z = as.vector(tframe$X3[c(5,7)]))
segments3d(x = as.vector(tframe$X1[c(6,7)]),
y = as.vector(tframe$X2[c(6,7)]),
z = as.vector(tframe$X3[c(6,7)]))
rgl.snapshot("filename")
}
And here is a sample frame:
tframe <- data.frame(matrix(c(1775.061, -1210.373, 901.3876, 2007.21,
-1324.62, 786.1902, 2021.581, -1122.637, 787.6369, 2020.769,
-1214.902, 953.5361, 1832.449, -1226.912, 1281.304, 1721.739,
-1207.299, 1225.152, 1839.68, -1116.221, 1208.916, 1869.173,
-1362.631, 1017.457, 1958.79, -1364.152, 972.9478, 1784.636,
-1489.286, 738.0247, 1874.096, -1460.005, 834.2597, 1880.647,
-1091.414, 1032.096, 1979.333, -1059.292, 958.2598, 1796.085,
-914.6528, 747.6667, 1865.53, -945.6564, 817.4066),
nrow = 15, ncol = 3, byrow = T))
How do I go about writing permanent image files that do not turn blank when devices are closed?

Resources