8

Is there any way to get the width of the plot area in the grid window? It grows or shrinks, for instance, if plot.margin is changed or if the y-axis labels' font-size is increased. Is is hidden somewhere in str(p)?

Any size measure would work. I need to be able to measure the relative change in the width of the plot area in different scenarios such as change of y-axis labels' font-size.

enter image description here

df = data.frame(x = (1:3),One=c(12, 8, 13),Two=c(13, 7, 11),Three=c(11, 9, 11))
df.melt = melt(df, id.vars="x")

p = ggplot(df.melt, aes(x=x, y=value, color=variable)) + 
  geom_line() +
  coord_cartesian(xlim=c(min(df.melt$x),max(df.melt$x))) +
  theme(legend.position="none", plot.margin = unit(c(1, 4, 1, 1), "cm")) 
p

UPDATE – To clarify: Please help me calculate a/b.

enter image description here

p = ggplot(df.melt, aes(x=x, y=value, color=variable)) + 
  geom_line() + coord_cartesian(xlim=c(min(df.melt$x),max(df.melt$x))) +
  theme(legend.position="none")

p1 = p + theme(plot.margin=unit(c(1,1,1,1),"cm"), axis.text.y=element_text(size=10))
p2 = p + theme(plot.margin=unit(c(1,1,1,2),"cm"), axis.text.y=element_text(size=30))
grid.arrange(p1, p2, ncol=2)
u17
  • 2,776
  • 4
  • 31
  • 43
  • you can get it from the gtable – baptiste Dec 13 '12 at 20:05
  • I'm a little fuzzy on what you need this info for, which will have some impact on the answer I think. But for starters, look in the output of `ggplot_build(p)`. Is any of that helpful? – joran Dec 13 '12 at 20:06
  • @joran: the goal is to place `geom_text` annotations at a fixed distance from the left and right end of the plot area at a specified y-value. My thinking: With the absolute width of the plot area known, it's easy to calculate the distance between two arbitrary points on the x-axis and consequently easy to position `geom_text` at an x-value that is at a fixed distance from the y-axis – u17 Dec 13 '12 at 22:56
  • @joran: `ggplot_build(p)` is interesting, but I have not found it helpful yet as the values seem to be stated relative to the plot area. Will look again later – u17 Dec 13 '12 at 22:58
  • Well, the reason I wanted some clarification is that there are always at least two coord systems you could be working in, that of your data, and that of the device itself. If your goal is to put something in the plot area, you should only need info on the coord system of your data, which `ggplot_build` will give you. If you need positioning _independent_ of your plot area then you'll need to delve into the grobs themselves. – joran Dec 13 '12 at 23:12
  • I am having some trouble delving into grobs and measurment conversions (see my comment to agstudy's answer). Any help would be appreciated. – u17 Dec 14 '12 at 16:51
  • I can't help much more, mainly because I clearly don't get what you're trying to do since I still don't understand why you can't just use the coord system of your data. – joran Dec 14 '12 at 16:54
  • Just the coord system would be difficult, I think, as everything in my chart can change (x-axis range, number of observations, y values etc.) and the annotations should always be a fixed distance (e.g., 0.3 cm) from the left and right end of the plot area. I updated the question. If no one answers here, I might post a new question focusing more on the annotations – u17 Dec 14 '12 at 19:27

3 Answers3

15

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)
agstudy
  • 119,832
  • 17
  • 199
  • 261
  • Looks promising! I might need some more help: Am I right to assume the result of `h$layoutvp$width` has to be converted using `convertX`? I can't seem to find the right property; `h$layoutvp$width` does not change on a change of the y-axis labels' font-size. `showGrob()` returns an error `no applicable method for 'absolute.units' applied to an object of class "unit"` – u17 Dec 14 '12 at 12:43
  • @Frank I haven't seen you comment before. Hope the answer is not tt late. I update my answer to give you the right width. – agstudy Dec 16 '12 at 01:11
9

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.

Greg Snow
  • 48,497
  • 6
  • 83
  • 110
  • 1
    Perfect, this appears to be the answer, thanks so much!! One final thing: Do you know if it's possible to get the width for a single chart (no `grid.arrange`; e.g. `p`) without drawing it first? – u17 Dec 15 '12 at 23:26
  • 1
    @Frank, I was hoping someone else might show you a way. Using my code the viewports at least need to be created and pushed before the code will work (the `gridPLT` function from the gridBase package is another possible way to get the info). There is probably a way using `gridDebug` or `ggplot_build`, but they are beyond my knowledge. – Greg Snow Dec 17 '12 at 22:30
  • Thanks for the starting point. I'll try to figure it out or else post it as a new question – u17 Dec 18 '12 at 02:51
5

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.

Greg Snow
  • 48,497
  • 6
  • 83
  • 110
  • Sorry but cannot reproduce your code at my R because qplot isn't available for R 2.15.2; nevertheless calling par()$pin every time I resize my plot gives me what I want which is the size of plot area (height x width), and again in my opinion - the solution I've given is simple and accurate so still I don't understand why u give my answer minuses - it's only the matter of calling plotting function and par()$pin in that particulate order, the you must call dev.off() and again plotting function, par()$pin – java_xof Dec 14 '12 at 20:49
  • 4
    @java_xof Uh...`qplot` is a function in the package **ggplot2** which is most definitely available for R 2.15.2. You might try installing that package, since it's what the OP is asking about. – joran Dec 14 '12 at 21:50
  • @java_xof, As mentioned the `qplot` function is from the ggplot2 package which I thought was implied to be loaded from the original question. I have edited the code to explicitly load it now. You could also replace the calls to `qplot` with the code from the original question and you would see the same thing. Even if you just do a base plot and the original `ggplot` in graphics devices side by side you can see that the plot regions are not the same, but `par()` reports the same things since the grid graphics functions do not update the information that par depends upon. – Greg Snow Dec 14 '12 at 23:43
  • Sorry it was tkrplot package that caused the error; but I'm far from installing it that's because studying your answers - simply - you all didn't convince me - and I think removing my answer was just that - @GregSnow's calling few buddies to help him with some "greenhorn" (in your opinion) - don't think it's good for your mental health, but sure why not - we can tormuent guy, he's just one ... it's a waste of time arguing you; cheers and God with you - Whoever He Is - except your atheist - in that case you can replace "God" with "neurons" or "psyche" – java_xof Dec 17 '12 at 21:23
  • @java_xof run the original posters code, including the update, and hold a ruler up to your screen after each graph. Compare that to the results from `par()$pin`. – Greg Snow Dec 17 '12 at 22:15
  • would it depend on screen pixel size of my pc and would it be in the proper size ratio? for example if I would have graph 400 px and par()$pin would give me 3.75 - then i would resize graph to 800 px and then would it be 2*3.75=7.5 - if not would it be considered as a bug? – java_xof Dec 19 '12 at 21:21
  • @java_xof, screen and pixel size could affect things, but even if the `par()$pin` results are not exact you can still hold a ruler to the screen to see that the plot area is different between a base plot of the original data and the different example plots in the edited version of the question while `par()$pin` returns the same values for each plot and does not return the correct size (possibly scaled) for grid graphics. – Greg Snow Dec 19 '12 at 22:35
  • @GregSnow As for edited part of the question it was out of my answer scope - as the answer was written before the question was updated - and as I remember you voted my answer down before even though it was correct as OP wasn't clear enough - just check (you can use material ruler if you need to) - 1) copy/paste code from question before update, 2) use par()$pin, 3) write down result, 4) resize plot window (maximize?) - just drag right bottom corner, 5) call par()$pin again, 6) write down result, 7) compare results, – java_xof Dec 20 '12 at 20:52
  • @java_xof, You seem to be relying on 2 main arguments, the first "my method gives an answer therefore it must be right", the same could be said for the magic 8 ball sitting on my shelf. The second "Greg Snow is leading a conspiracy to silence me, therefore what I say must have some worth", this is based on many false assumptions, I was not the first to downvote you (I have no idea who was), when I did downvote it was after the edits to the original post and you showing attitude. I did not have anything to do with your post being deleted, I would have voted against deleting. – Greg Snow Dec 20 '12 at 21:19
  • @GregSnow As I said (/wrote) it before - the reason why I answered this question was that OP asked about size of the plot and I thought my answer was suitable for the OP's purpose, which turned out to be the worst answer ever - (do people even read and check answers before they downvote it?) - and finally was deleted; my only mistake is that (and shame on me) I misinterpreted your comment below my answer, so ... I'm sorry I had you involved in this banter. – java_xof Dec 20 '12 at 21:48