Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell if a plot is complete or not

Tags:

r

graphics

knitr

Update: I asked Paul Murrell (the "final boss"), and he told me the decision of opening a new page is made in src/library/graphics/src/graphics.c by GNewPlot:

pGEDevDesc GNewPlot(Rboolean recording)

Effectively it looks at par(mfg) and par(new) to decide whether to open a new page. How do I that test in an add-on package?

A solution using either pure R code or some C code is acceptable.


knitr uses the evaluate package to evaluate R code chunks, and capture results, including plots. Briefly speaking, evaluate calls recordPlot() to record a snapshot of the current plot after each code expression has been evaluated, and also when the plotting hooks like before.plot.new and before.grid.newpage are called (normally this happens before a new plot is drawn). For those "Luke's", please use the source if this is not clear enough.

After we make a snapshot, we need to decide whether to keep it, because the snapshot might not be changed after we evaluate a new expression, which is not involved with plotting. Now here comes my problem: when a plot contains sub-plots (for example, pairs(), coplot(), or par(mfrow = c(2, 3)) in base R graphics), we should not keep the incomplete snapshots. In the example below, the first three plots should be discarded:

par(mfrow = c(2, 2))
plot(rnorm(10))
plot(rnorm(10))
plot(rnorm(10))
plot(rnorm(10))

To achieve this, we compare par("mfg")[1:2] to par("mfg")[3:4] (as you can see in the source code), and this works well in some cases, but not all, for example, the issue #25:

layout(matrix(c(1,3,2,3), 2))
plot(rnorm(10))
plot(rnorm(10))
plot(rnorm(10))

Now how do I know the plot is not complete before line 4? The mfg trick no longer works.

I have been looking for an answer to this problem for a long time, and I will truly appreciate it if someone can give me a hint; further details are in the two links above, and I can clarify if anything is unclear. I believe there must exist a solution, because all R devices know when to start a new plot screen or file, and incomplete plots will not trigger new plot screens or files.

like image 202
Yihui Xie Avatar asked Aug 06 '13 07:08

Yihui Xie


2 Answers

Update: this is available in R 3.0.2 now.


In his recent commit, Paul Murrell added a new read-only parameter in par() named page, which gives TRUE or FALSE, indicating whether the next plot needs to open a new page.

This was achieved by copying some code from GNewPlot(), and is only available in R-devel at the moment.

like image 187
Yihui Xie Avatar answered Oct 19 '22 13:10

Yihui Xie


From ?par:

 ‘mfg’ A numerical vector of the form ‘c(i, j)’ where ‘i’ and ‘j’
      indicate which figure in an array of figures is to be drawn
      next (if setting) or is being drawn (if enquiring).  The
      array must already have been set by ‘mfcol’ or ‘mfrow’.

So, it seems that you'd need to use a pairing function to assign a unique id to the initial value of par("mfg")[1:2], and then test subsequent values of par("mfg")[1:2] against that key. A valid paring, for example, would be 2**par("mfg")[1] * 3**par("mfg")[2]:

Pid <- function(ij, test.id=NA){
        mi <- ij[1]
        mj <- ij[2]
        ijd <- 2**mi * 3**mj
        if (!is.na(test.id)) ijd <- ijd == test.id
        return(ijd)
}

Back to your example:

layout(matrix(c(1,3,2,3), 2))
x0 <- par("mfg")  # 2 1 2 2
id <- Pid(x0)     # 12
plot(rnorm(10))
x1 <- par("mfg")
p1 <- Pid(x1, id) # FALSE (layout is not full)
plot(rnorm(10))
x2 <- par("mfg")
p2 <- Pid(x2, id) # FALSE (layout is not full)
plot(rnorm(10))
x3 <- par("mfg")
p3 <- Pid(x3, id) # TRUE (layout is now full)
#
rbind(p1,p2,p3)
#    [,1]
#p1 FALSE
#p2 FALSE
#p3  TRUE

I suppose, however, that without the initial id, you'd be out of luck.

like image 4
Andy Barbour Avatar answered Oct 19 '22 14:10

Andy Barbour