Rasterise ggplot images in R for tikzdevice

北城以北 提交于 2019-11-29 14:34:59

here's a proof-of-principle to illustrate the steps that would be involved. As pointed out in the comments it's not recommendable or practical, but could be the basis of a lower-level implementation.

require(png)
require(ggplot2)
require(tikzDevice)

n=100; 
d <- data.frame(x=rnorm(n), y=rnorm(n), z=rnorm(n))

p <- ggplot(d, aes(x=x, y=y, colour=z, size=z, alpha=x)) + geom_point()

## draw the layer by itself on a png file
library(grid)
g <- ggplotGrob(p)
# grid.newpage()
gg <- g$grobs[[6]]$children[[3]]
gg$vp <- viewport() # don't ask me
tmp <- tempfile(fileext = "png")
png(tmp, width=10, height=4, bg = "transparent", res = 30, units = "in")
grid.draw(gg)
dev.off()
## import it as a raster layer
rl <- readPNG(tmp, native = TRUE)
unlink(tmp)

## add it to a plot - note that the positions match, 
## but the size can be off unless one ensures that the panel has the same size and aspect ratio
ggplot(d, aes(x=x, y=y)) + geom_point(shape="+",  colour="red") +
  annotation_custom(rasterGrob(rl, width = unit(1,"npc"), height=unit(1,"npc"))) +
  geom_point(aes(size=z), shape=1, colour="red", show.legend = FALSE)

## to illustrate the practical use, we use a blank layer to train the scales
## and set the panel size to match the png file
pf <-  ggplot(d, aes(x=x, y=y)) + geom_blank() +
  annotation_custom(rasterGrob(rl, width = unit(1,"npc"), height=unit(1,"npc"), interpolate = FALSE))

tikz("test.tex", standAlone=TRUE)
grid.draw(egg::set_panel_size(pf, width=unit(10, "cm"), height=unit(4, "cm")))
dev.off()

system("lualatex test.tex")
system("open test.pdf")

we can zoom in and check that the text is vector-based while the layer is (here low-res for demonstration) raster.

ok, I will write it here because it was too big for the comment box. Instead of adding the rasterised points to a nw plot with new scales you can actually replace the original grob with the rasterised grob by g$grobs[[6]]$children[[3]] <- rasterGrob(rl). The problem is that it doesn't scale, so you have to know the size of the final image before. Then you can sue sth like this:

rasterise <- function(ggp,
                      width  = 6,
                      height = 3,
                      res.raster = 300,
                      raster.id=  c(4,3),
                      file = ""){
    ## RASTERISE
    require(grid)
    require(png)
    ## draw the layer by itself on a png file
    gb <- ggplot_build(ggp)
    gt <- ggplot_gtable(gb)
    ## calculate widths
    h <- as.numeric(convertUnit(sum(gt$heights), unitTo="in"))
    w <- as.numeric(convertUnit(sum(gt$widths) , unitTo="in"))
    w.raster <- width-w
    h.raster <- height-h
    ## print points as png
    grid.newpage()
    gg <- gt$grobs[[raster.id[1]]]$children[[raster.id[2]]]
    gg$vp <- viewport() # don't ask me
    tmp <- tempfile(fileext = "png")
    png(tmp, width=w.raster, height=h.raster, bg = "transparent", res = res.raster, units = "in")
    grid.draw(gg)
    dev.off()
    ## import it as a raster layer
    points <- readPNG(tmp, native = TRUE)
    points <- rasterGrob(points, width = w.raster, height = h.raster, default.units = "in")
    unlink(tmp)
    ## ADD TO PLOT
    gt$grobs[[raster.id[1]]]$children[[raster.id[2]]] <- points
    ## PLOT TMP
    ### HERE YOU CAN ONLY PRINT IT IN THIS DIMENSIONS!
    pdf(file, width = width, height = height)
    grid.draw(gt)
    dev.off()
}

And then use it with

data <- data.frame(x = rnorm(1000), y = rnorm(1000))
plot <- ggplot(data, aes(x = x, y = y)) +
    geom_point() +
    annotate("text", x = 2, y = 2, label = "annotation")

rasterise(ggp        = plot,
          width      = 6,
          height     = 3,
          res.raster = 10,
          raster.id  = c(4,2),
          file       = "~/test.pdf")

The problem remains the ID of the grob you want to rasterise. I didn't figure out a good way to find the correct one automatically. It depends on which layers you add to the plot.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!