Get width of plot area in ggplot2

Solution 1:

The plot in ggplot2 uses grid graphics. A graphical scene that has been produced using the grid graphics package consists of grobs and viewports.

You can use gridDebug package for the inspection of the grobs.

  1. showGrob show the locations and names of the grobs used to draw the scene

          showGrob()
    
  2. Get the gpath of the grob

      sceneListing <- grid.ls(viewports=T, print=FALSE)
      do.call("cbind", sceneListing)
    
       name                                gPath                                                      
    [1,] "ROOT"                              ""                                                         
    [2,] "GRID.gTableParent.45019"           ""                                                         
    [3,] "background.1-5-6-1"                "GRID.gTableParent.45019"                                  
    [4,] "spacer.4-3-4-3"                    "GRID.gTableParent.45019"                                  
    [5,] "panel.3-4-3-4"                     "GRID.gTableParent.45019"                                  
    [6,] "grill.gTree.44997"                 "GRID.gTableParent.45019::panel.3-4-3-4"                   
    
  3. Retrieve the gorb

    h <- grid.get(gPath="GRID.gTableParent.45019")
    
  4. get h properties (e.g)

    h$layoutvp$width
    

Application:

grid.get('x',grep=TRUE,global=T)
(polyline[panel.grid.minor.x.polyline.21899], polyline[panel.grid.major.x.polyline.21903], gTableChild[axis-l.3-3-3-3], gTableChild[axis-b.4-4-4-4], gTableChild[xlab.5-4-5-4]) 
>  grid.get('x',grep=TRUE,global=T)[[3]]
gTableChild[axis-l.3-3-3-3] 
>  xx <- grid.get('x',grep=TRUE,global=T)[[3]]
> grobWidth(xx)
[1] sum(1grobwidth, 0.15cm+0.1cm)

Solution 2:

This intrigued me enough to look into it deeper. I was hoping that the grid.ls function would give the information to navigate to the correct viewports to get the information, but for your example there are a bunch of the steps that get replaced with '...' and I could not see how to change that to give something that is easily worked with. However using grid.ls or other tools you can see the names of the different viewports. The viewports of interest are both named 'panel.3-4-3-4' for your example, below is some code that will navigate to the 1st, find the width in inches, navigate to the second and find the width of that one in inches.

grid.ls(view=TRUE,grob=FALSE)
current.vpTree()
seekViewport('panel.3-4-3-4')
a <- convertWidth(unit(1,'npc'), 'inch', TRUE)
popViewport(1)
seekViewport('panel.3-4-3-4')
b <- convertWidth(unit(1,'npc'), 'inch', TRUE)
a/b

I could not figure out an easy way to get to the second panel without poping the first one. This works and gives the information that you need, unfortunately since it pops the 1st panel off the list you cannot go back to it and find additional information or modify it. But this does give the info you asked for that could be used in future plots.

Maybe someone else knows how to navigate to the second panel without popping the first, or getting the full vpPath of each of them to navigate directly.

Solution 3:

This answer is mainly in reply to comments by @java_xof. The reply is too long and includes code so it will not fit in a comment. However, it may help with the original question as well (or at least give a starting place).

Here is a function and some code using it (it requires the tcltk and tkrplot packages):

library(ggplot2)
library(tkrplot)
TkPlotLocations <- function(FUN) {
    require(tkrplot)

    cl <- substitute(FUN)
    replot <- function() eval(cl)

    tt <- tktoplevel()
    img <- tkrplot(tt, replot, vscale=1.5, hscale=1.5)
    tkpack(img)

    tkpack(xfr <- tkframe(tt), side='left')
    tkpack(yfr <- tkframe(tt), side='left')

    xndc <- tclVar()
    yndc <- tclVar()
    xin <- tclVar()
    yin <- tclVar()

    tkgrid(tklabel(xfr, text='x ndc'), tklabel(xfr, textvariable=xndc))
    tkgrid(tklabel(yfr, text='y ndc'), tklabel(yfr, textvariable=yndc))
    tkgrid(tklabel(xfr, text='x inch'), tklabel(xfr, textvariable=xin))
    tkgrid(tklabel(yfr, text='y inch'), tklabel(yfr, textvariable=yin))

    iw <- as.numeric(tcl("image","width", tkcget(img, "-image")))
    ih <- as.numeric(tcl("image","height",tkcget(img, "-image")))

    cc <- function(x,y) {
        x <- (as.real(x)-1)/iw
        y <- 1-(as.real(y)-1)/ih
        c(x,y)
    }

    mm <- function(x, y) {
        xy <- cc(x,y)
        tclvalue(xndc) <- xy[1]
        tclvalue(yndc) <- xy[2]
        tclvalue(xin) <- grconvertX(xy[1], from='ndc', to='inches')
        tclvalue(yin) <- grconvertY(xy[2], from='ndc', to='inches')
    }

    tkbind( img, "<Motion>", mm)

    invisible()
}


x <- runif(25)
y <- rnorm(25, x, 0.25)
plot(x,y)
par()$pin
par()$plt
TkPlotLocations(plot(x,y))
qplot(x,y)
par()$pin
par()$plt
TkPlotLocations(print(qplot(x,y)))
qplot(x,y) + xlab('Multi\nline\nx\nlabel')
par()$pin
par()$plt
TkPlotLocations(print(qplot(x,y) + xlab('Multi\nline\nx\nlabel')))

Defining the above function, then running the following lines will produce 3 plots of the same random data. You can see that the results of par()$pin and par()$plt (and other parameters) are exactly the same for the 3 plots even though the plotting regions differ in the plots.

There will also be 3 new windows that have popped up, in the windows you can move the mouse pointer over the graph and at the bottom of the window you will see the current location of the pointer in normalized device coordinates and in inches (both from the bottom left corner of the device region). You can hover the mouse pointer over the corners of the graph (or any other part) to see the values and compare between the 3 graphs.

This may be enough to answer at least part of the original question (just not programatically, which would be more useful). The functon can be modified to print out other measurements as well. I may expand this and include it in a package in the future if others would be interested.