Inconsistent size pdf device - r

The pdf device in R seems to have an inconsistent output size.
For example:
library(grid)
pdf("myplot1.pdf", width=.51, height=.255)
grid.rect(width = 1, height=1, gp=gpar(col="red"))
dev.off()
results in an incomplete rectangle:
When the width and height are pretty rounded,
pdf("myplot2.pdf", width=.5, height=.25)
grid.rect(width = 1, height=1, gp=gpar(col="red"))
dev.off()
the rectangle is depicted well:
This problem does not occur with other devices such as png. It seems like the size of the pdf file is rounded down, while R still uses the original size to plot.

I've found got the solution.
Apparently, the pdf document is rounded down to the nearest 1/72 of an inch. Still don't know why.
Anyway, this wrapper will do the trick:
pdf2 <- function(file, width, height, ...) {
rnd <- function(x) x %/% (1/72) / 72
do.call("pdf", c(list(file=file, width=rnd(width), height=rnd(height)), list(...)))
}

Depending on the application, you can shorten the dimensions of the box:
pdf("myplot3.pdf", width=.51, height=.255)
grid.rect(height=0.95,width=0.95, gp=gpar(col="red"))
dev.off()
pdf("myplot4.pdf", width=.5, height=.25)
grid.rect(height=0.95,width=0.95, gp=gpar(col="red"))
dev.off()
It's an imperfect solution but may be functional.

Related

Is it possible to plot images in a ggplot2 plot, that don't get distorted when you save to any non-standard aspect ratio?

I'm looking for any solution to this problem, regardless of packages used.
The problem at hand is that plotted images get distorted when you save them using ggsave. Let me give an example:
image_links = data.frame(id = c(1,2,3,4,5),
image = c("https://cdn.shopify.com/s/files/1/1061/1924/products/Smiling_Emoji_with_Eyes_Opened_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Smiling_Emoji_with_Smiling_Eyes_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Hushed_Face_Emoji_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Disappointed_but_Relieved_Face_Emoji_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Expressionless_Face_Emoji_large.png"))
mydata = data.frame(x = rnorm(100, mean = 50, sd = 20),
y = rnorm(100, mean = 50, sd = 5),
id = rep(c(1,2,3,4,5), 20))
mydata$y = mydata$y - 10*mydata$id
mydata = mydata %>% left_join(image_links, by='id')
g <- ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
ggsave(g, filename='[INSERT PATH HERE].png', width=width, height=height, dpi=300)
This works fine:
The problem arises when you adjust the width and height parameters of ggsave, for instance because you want the x and y-axis to be in the correct proportion:
width = (max(mydata$x) - min(mydata$x))/10
height = (max(mydata$y) - min(mydata$y))/10
ggsave(g, filename='[INSERT PATH HERE].png', width = width, height=height, dpi=300)
The x and y-axis are now fine, but the images are distorted:
This happens in ANY situation where you plot an image but the width/height aspect ratio is different than what was the original aspect ratio of the image you want to add.
I'm looking for any solution to this problem, not necessarily restricted to ggimage. It seems very weird to me that you can't properly add images to a ggplot, as I image it's very common for people to want to do that.
I don't know a lot about ggsave, but this seems like an issue related to relative versus absolute units. Probably the geom_image() calculates positions relative to the axes, which get distorted when the axes get resized (such as within ggsave). For example:
ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
Can look like:
Or can look like:
Depending on the device window that I can resize at will.
There are two ways I can see this getting fixed, both of which will involve re-calculating the sizes of the rasters at drawtime. The easier fix will be the one below.
# Get plot
g <- ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
# Convert to gtable
gt <- ggplotGrob(g)
# Get the imagegrobs, correct slots found by trial and error
imagegrobs <- gt$grobs[[6]]$children[[3]]$children
# Re-class them to a custom, made-up class
imagegrobs <- lapply(imagegrobs, function(image) {
class(image) <- c("fixasp_raster", class(image))
image
})
# Put them back into the gtable
gt$grobs[[6]]$children[[3]]$children <- imagegrobs
So now that we have a custom class for these images, we can write a piece of code that gets executed at the time of drawing by writing a method for our class using the S3 generic makeContent from the grid package.
library(grid)
makeContent.fixasp_raster <- function(x) {
# Convert from relative units to absolute units
h <- convertHeight(x$height, "cm", valueOnly = TRUE)
w <- convertWidth(x$width, "cm", valueOnly = TRUE)
# Decide how the units should be equal
x$height <- x$width <- unit(sqrt(h * w), "cm")
x
}
Note that taking the square root of the product is improvised, I don't know if this is the optimal procedure.
When we plot the data now, we'll have a consistent size of the images, regardless of the aspect ratio:
grid.newpage(); grid.draw(gt)
The second way to fix this is to file an issue in the github page of the ggimage package, motivating your use case and convice them to implement something that adresses your concerns. If they want, they could make a fix at the ggproto level, so that you don't have dabble with gtables.
#teunbrand's answer has been implemented in dev version 0.2.4 of ggimage. You can install the latest dev version like this:
setRepositories(ind=1:2)
## install.packages("devtools")
devtools::install_github("GuangchuangYu/ggimage")
This should fix the aspect ratio issues.

Output Stem and Leaf Plot to Image

I'm trying to output a Stem and Leaf plot in R as an image. I'm not sure if there's a nice library which can accomplish this but below is some of the code I've tried.
jpeg(filename="stem.jpeg",width=480,height=480, units="px",pointsize=12)
plot.new()
tmp <- capture.output(stem(men, scale = 1, width = 40))
text( 0,1, paste(tmp, collapse='\n'), adj=c(0,1), family='mono' )
dev.off()
This above code resulted in the data being saved, but it looks very blurry and the plot gets cut off pretty badly. When adding a histogram to an image, R seems to do a good job to scale everything to fit in the size of the image.
jpeg(filename="stem.jpeg",width=480,height=480,
units="px",pointsize=12)
stem(men, scale = 1, width = 40)
dev.off()
This created the image but had no content within it.
Any ideas? Thanks!
That's because stem and leaf plots produce text not images. You can save the text as follows using the sink command: http://stat.ethz.ch/R-manual/R-devel/library/base/html/sink.html
sink(file=“Stem.txt”)
stem(men, scale = 1, width = 40)
sink(file=NULL)
unlink("stem.txt")
To export a stemplot as graphics, you can use a vector graphics format, such
as .eps, .pdf, or .emf. For example, a windows metafile:
win.metafile("stem.wmf", pointsize = 10)
plot.new()
tmp <- capture.output(stem(mtcars$mpg))
text(0,1,paste(tmp,collapse='\n'),family='mono',adj=c(0,1))
dev.off()

ggplot Multiline Title with Different Font Sizes

<SlightlyLookingAway>I am attempting to reproduce an excel plot in R.</SlightlyLookingAway> The Excel plot has a two line title. I know how to handle this by putting a '\n' in the title text. What I do not know how to handle is that the first line has a larger font size than the second row of the title... I have done some google searching and have come up with a general lack of response.
I realize that I might be able to cobble something together with an annotation of some kind but that seemed like a kludge. If that is the only answer then it is, but I wanted to ask the community first.
Any ideas?
It looks as though I have found a hacked solution which gets the job done but does not offer a lot of flexability. The idea is to put in a math expression using the atop() command along with the bold() and scriptstyle() functions.
myplot +
ggtitle(expression(atop(bold("This is the Top Line"), scriptstyle("This is the second line")))) +
theme(plot.title = element_text(size = 20))
If you know of a better solution with more control over the line spacing and even being able to adjust the font face, please let me know...
try this,
library(gridExtra)
titleGrob <- function(x=c("First line", "second line"), size=10, ...){
n <- length(x)
size <- rep(size, length.out=n)
one_label <- function(x, size, ...)
textGrob(x, gp=gpar(fontsize=size), ...)
lg <- mapply(one_label, x=x, size=size, ..., SIMPLIFY=FALSE)
wg <- lapply(lg, grobWidth)
hg <- lapply(lg, grobHeight)
widths <- do.call(unit.c, wg)
heights <- do.call(unit.c, hg)
maxwidth <- max(widths)
g <- frameGrob(layout = grid.layout(n, 1, width=maxwidth, height=heights) )
for(ii in seq_along(lg))
g <- placeGrob(g, lg[[ii]], row=ii)
g
}
grid.newpage()
g <- titleGrob(size=c(18,12))
grid.arrange(qplot(1,1), top=g)
To perfectly center everything (which \n will not do), adapt every size of text whatever the number of lines and at the same time being able to adjust the interlinear space, use this instead:
e.g. for smaller to larger text size
ggtitle(expression(atop(scriptscriptstyle("whateverline1"),atop(scriptstyle("whateverline2"),atop(scriptscriptstyle(""),textstyle("whateverline3"))))))
Then use labeller=label_parsed
This also works for facet_grid, xlab and ylab
Note the scriptscriptstyle("") to control spacing between lines. You can also use varied relative sizes of text using scriptstyle or scriptscriptstyle or textstyle depending on your needs and of course use element_text(size=whatevernumber) in the theme section

How to determine the size of a pointsGrob (vs the one of a textGrob)?

Why does determining the size (in mm, for example) of a points grob (pointsGrob) fail, but not for a text grob (textGrob)?
### puzzled about grobs (not) knowning how big they are:
require(grid)
convertUnit(grobWidth(textGrob("some tex")), "mm") # 16.93mm => fine
convertUnit(grobWidth(pointsGrob(0.5, 0.5)), "mm") # 0mm => ?
convertUnit(grobWidth(pointsGrob(0.5, 0.5, size=unit(3, "mm"))), "mm") ## still 0mm...
The reason why I am asking is: If you place a text grob and a points grob side-by-side, and change the value of cex, then suddenly the two grobs overlap (unwanted behavior).
Here is an example showing a similar prolem:
gt <- grobTree(pointsGrob(x=.5, y=.5, gp=gpar(cex=4)),
linesGrob(x=0:1, y=.5, gp=gpar(cex=4)),
pointsGrob(x=.5, y=.5, gp=gpar(cex=1)))
pg <- packGrob(frameGrob(vp=NULL), gt,
width = unit(1, "char"),
height = unit(1, "char"))
grid.newpage()
grid.draw(pg)
grid.rect(width=grobWidth(pg), height=grobHeight(pg), gp=gpar(col="red"))
The rectangle reveals that the grob width and height are not correct; pg does not "see" the size of the point with large cex. How can this be achieved?
I do not know how to solve the problem of zero point size, presumably it would have to be defined in the internals of the grid source code at the C level.
However, I want to point out that regardless of the pointsGrob issue, the grobWidth and grobHeight are not defined for your packGrob / gTree and the approach would fail even if pointsGrob were replaced by a textGrob.
You probably want to define a gTree of a new class, say "mygrob", and define your own widthDetails.mygrob and heightDetails.mygrob methods,
library(grid)
gt <- grobTree(linesGrob(x=c(0.2, 0.8), y=.5, gp=gpar(col="grey", lwd=10)),
textGrob("some label", gp=gpar(cex=2)),
cl = "mygrob")
widthDetails.mygrob <- function(x)
do.call(max, lapply(x$children, grobWidth))
heightDetails.mygrob <- function(x)
do.call(max, lapply(x$children, grobHeight))
grid.newpage()
grid.draw(gt)
grid.rect(width=grobWidth(gt), height=grobHeight(gt), gp=gpar(col="red"))

small plot with no margins - border with line width (lwd) equal to 1 not visible

I've been trying to make some very tiny line graphs using base plotting functions, but am coming unstuck when trying to add a thin border.
This is via RGui on Windows 7, saving a png from the plot window.
Here's my code:
dev.new(width=1.3,height=0.3)
par(mar=c(0,0,0,0))
set.seed(13)
x <- 1:10
y <- runif(10)
plot(x,y,type="n",xaxs="i",yaxs="i",ylim=c(0,1))
polygon( c(1,x,max(x),0), c(0,y,0,0), col="lightblue", border=NA)
lines(x,y,lwd=1)
Everything is fine until I try to add a box with a line width of 1, giving:
box(lwd=1)
Now I can solve this by increasing the line width to 2, but this seems a bit of a hack.
box(lwd=2)
Using rect like rect(1,0,10,1) doesn't seem to give me an appropriate solution either, with the bottom and right borders not being visible.
Have you considered giving mar a small non-zero value:
dev.new(width=0.3,height=0.3)
par(mar=c(0.01,0.01,0.01,0.01))
set.seed(13)
x <- 1:10
y <- runif(10)
plot(x,y,type="n",xaxs="i",yaxs="i",ylim=c(0,1))
polygon( c(1,x,max(x),0), c(0,y,0,0), col="lightblue", border=NA)
lines(x,y,lwd=1)
box(lwd=1)
I admit I haven't quite figured out what the end-game might be, but when I do an interactive "stretch" of that very small screen-object, it does result in an all-around border.
I do recognize that I am on a Mac and saving this to a pdf file and converting it to a png file for SO-inclusion may not be precisely reproducible on a Linux or Windows device.
Another solution base in grid and gridBase package. The idea is to replace the box by grid.rect.
Use gridBase to get the base viewport
Introduce some offset (viewport y ) to show the bottom line
Reduce the width of the viewport to show the right line.
Here my code:
library(gridBase)
sp <- baseViewports()
vp <- sp$plot
vp$width <- unit(0.999,'npc')
vp$y <- unit(0.001,'npc')
pushViewport(vp)
grid.rect(gp=gpar(fill=NA))
upViewport(1)
EDIT thanks to #baptiste, you can simply get the same result using only grid.rect:
library(grid)
grid.rect(width = unit(0.999,'npc'),
y = unit(0.5001, "npc"),
gp=gpar(fill=NA))
To answer my own question thanks to #baptiste's tip-off, this is a device dependent issue due to RGui. If the image is saved directly out to file using png everything works as intended. E.g.:
dev.new(width=1.3,height=0.3)
# repeat from here onwards only for png call below
par(mar=c(0,0,0,0))
set.seed(13)
x <- 1:10
y <- runif(10)
plot(x,y,type="n",xaxs="i",yaxs="i",ylim=c(0,1),bty="n")
polygon( c(1,x,max(x),0), c(0,y,0,0), col="lightblue", border=NA)
lines(x,y,lwd=1)
box(lwd=1)
Saving out to png from the "R Graphics" window gives my original stuffed up image:
Going directly to file using png like:
png("textbox_direct.png",width=116,height=27)
# take code block from above
dev.off()
...gives the correct result:

Resources