Search code examples
rpdfplotcontourvector-graphics

Saving filled contour plots to PDF in R gives pixelated results


Saving the output of one of R's built-in examples for the filled.contour function to PDF:

pdf('test.pdf')
require("grDevices")
filled.contour(volcano, asp = 1)
dev.off()

produces a discretised result (see below). Is there any way to fix this? System info:

> sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.1 LTS

enter image description here

Edit after the accepted answer:

The problem is actually reported in the documentation of the pdf function:

If you see problems with PDF output, do remember that the
problem is much more likely to be in your viewer than in R ... 
Symptoms for which the viewer has been at fault are apparent 
grids on image plots ...
    
Unfortunately the default viewers on most Linux and macOS 
systems have these problems, and no obvious way to turn off 
graphics anti-aliasing.

It's an odd place to document the problem because the subdivision into rectangular segments is clearly caused by filled.contour and not by pdf. Otherwise ggplot2's output would also suffer from the same issue.


Solution

  • That is likely a result of antialiasing: when you display the image, it draws squares one at a time. As they are being drawn, the edge is a mix of the square colours and the white background, so it is drawn lighter.

    Unfortunately, this isn't something that's really under your control. It's the PDF previewer that is introducing the artifacts. See this page https://codedocean.wordpress.com/2014/02/03/anti-aliasing-and-image-plots/ for a discussion.

    The recommendation there worked for me: use the png() device with type = "cairo". This gives bitmapped output rather than the vector output of a pdf().

    png('test.png',type="cairo")
    filled.contour(volcano, asp = 1)
    dev.off()
    

    enter image description here

    Edited to add:

    I don't think you can do better with filled.contour, but if you are willing to switch to ggplot2 graphics you can. When it draws filled contours it appears to do it using polygons, not the image style plot that filled.contour uses. This still shows the same antialiasing bugs in the previewer, but now the lines appear along the borders between colours, which is much less irritating. For example:

    df <- data.frame(x = as.numeric(row(volcano)-1)/(nrow(volcano)-1),
                     y = as.numeric(col(volcano)-1)/(ncol(volcano)-1),
                     z = as.numeric(volcano))    
    pdf('test.pdf')
    library(ggplot2)
    ggplot(df, aes(x=x, y=y, z=z)) + 
      geom_contour_filled()
    dev.off()
    

    screenshot

    I don't know how to get the same palette as filled.contour uses, i.e. function(n) hcl.colors(n, "YlOrRd", rev = TRUE). Maybe someone else can show us in a comment.