2017-02-05 5 views
3

Ich verwende R, um Daten zu analysieren, ggplot, um Diagramme zu erstellen, TikzDevice, um sie zu drucken, und schließlich Latex, um einen Bericht zu erstellen. Das Problem ist, dass große Plots mit vielen Punkten aufgrund der Speichergrenze von Latex nicht funktionieren. Ich fand hier https://github.com/yihui/tikzDevice/issues/103 eine Lösung, die das Diagramm vor dem Drucken der Tikz-Datei rastert, die die Punkte und den Text einzeln drucken können.Rasterise ggplot Bilder in R für tikzdevice

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

## generate data 
n=1000000; x=rnorm(n); y=rnorm(n) 

## first try primitive 
tikz("test.tex",standAlone=TRUE) 
plot(x,y) 
dev.off() 
## fails due to memory 
system("pdflatex test.tex") 


## rasterise points first 
png("inner.png",width=8,height=6,units="in",res=300,bg="transparent") 
par(mar=c(0,0,0,0)) 
plot.new(); plot.window(range(x), range(y)) 
usr <- par("usr") 
points(x,y) 
dev.off() 
# create tikz file with rasterised points 
im <- readPNG("inner.png",native=TRUE) 
tikz("test.tex",7,6,standAlone=TRUE) 
plot.new() 
plot.window(usr[1:2],usr[3:4],xaxs="i",yaxs="i") 
rasterImage(im, usr[1],usr[3],usr[2],usr[4]) 
axis(1); axis(2); box(); title(xlab="x",ylab="y") 
dev.off() 
## this works 
system("pdflatex test.tex") 


## now with ggplot 
p <- ggplot(data.frame(x=x, y=y), aes(x=x, y=y)) + geom_point() 
## what here? 

In diesem Beispiel ist die erste pdflatex ausfällt. Der zweite gelingt aufgrund der Rasterung.

Wie kann ich dies mit ggplot anwenden?

+1

Sie könnten das Plotpanel aus der gtable extrahieren, dieses auf einem randlosen PNG zeichnen und es dann als Hintergrund annotation_raster oder annotation_custom anzeigen. Vergessen Sie nicht, die Waage mit den gleichen Daten zu trainieren, zB mit einer geom_blank Schicht. Es ist unnötig zu sagen, dass dies fragil, fehleranfällig und begrenzt ist (z. B. Facetten). Ein ggplot + -Grid-Level-Weg, um bestimmte Layer zu rasterisieren, wäre nett und wurde in der Vergangenheit vorgeschlagen, aber nie in den Griff bekommen. – baptiste

+0

hmm, ja hört sich nach viel Aufwand an, der am Ende nicht funktioniert ... ich hatte gehofft für etw wie 'geom_rasterise', oder' geom_point (raster = T) ';-) – Jonas

+0

Es würde nicht viel dauern ein solches Argument bis zur Bauphase durchgehen, aber das würde Grid-Grobs erfordern, um diese Low-Level-Fähigkeit zu haben. Und hier ist es wahrscheinlich nicht so weit hergeholt, da grid.cap ähnliche Funktionen bietet. – baptiste

Antwort

3

Hier ist ein Proof-of-Prinzip, um die Schritte zu veranschaulichen, die beteiligt sein würden. Wie in den Kommentaren erwähnt, ist es nicht empfehlenswert oder praktisch, könnte aber die Grundlage für eine Implementierung auf einer niedrigeren Ebene sein.

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) 

enter image description here

## 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") 

enter image description here

wir können die Ansicht vergrößern und prüfen, ob der Text Vektor-basierte, während die Schicht (hier in niedriger Auflösung für die Demonstration) Raster ist.

enter image description here

+0

FWIW Das gridSVG-Paket macht etwas ganz ähnliches, mit dem zusätzlichen Trick, die Rasterdaten in base64 zu integrieren. – baptiste

0

ok, werde ich es hier schreiben, weil es zu groß für das Kommentarfeld war. Anstatt die gerasterten Punkte zu einem neuen Plot mit neuen Skalen hinzuzufügen, können Sie den Original-Grob durch den gerasterten Grob ersetzen durch g$grobs[[6]]$children[[3]] <- rasterGrob(rl). Das Problem ist, dass es nicht skaliert, so dass Sie die Größe des endgültigen Bildes vorher wissen müssen. Dann können Sie etw wie diese klagen:

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() 
} 

und verwenden Sie es dann mit

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") 

Das Problem bleibt die ID des grob Sie rastern wollen. Ich habe keinen guten Weg gefunden, den richtigen automatisch zu finden. Es hängt davon ab, welche Ebenen Sie dem Diagramm hinzufügen.