y-axis for each subplot using facet_grid
Asked Answered
M

2

1

I can't get the answer to this question to work. What both me and that user want is to add axis ticks and labels to all columns when using facet_grid().

Display y-axis for each subplot when faceting

When I run the reproducable example and the solution (after adding abc=as.data.frame(abc) to fix the initial error) I receive an error message

Error in gtable_add_grob(g, grobs = list(segmentsGrob(1, 0, 1, 1), segmentsGrob(1, : Not all inputs have either length 1 or same length same as 'grobs

I made my own reproducible example because the original one is ehhm, a bit odd :-). It results in the same error message

require(ggplot2)
require(reshape)
require(grid)
require(gtable)
data(iris)
iris$category=rep(letters[1:4],length.out=150)
plot1=ggplot(data=iris,aes(x=1,y=Sepal.Width))+geom_boxplot()+facet_grid(Species~category)

The answer should be this:

g <- ggplotGrob(plot1)

require(gtable)
axis <- gtable_filter(g, "axis-l")[["grobs"]][[1]][["children"]][["axis"]][,2]
segment <- segmentsGrob(1,0,1,1)
panels <- subset(g$layout, name == "panel")
g <- gtable_add_grob(g, grobs=list(axis, axis), name="ticks",
                     t = unique(panels$t), l=tail(panels$l, -1)-1)

g <- gtable_add_grob(g, grobs=list(segmentsGrob(1,0,1,1), 
                                   segmentsGrob(1,0,1,1)), 
                     t = unique(panels$t), l=tail(panels$l, -1)-1, 
                     name="segments")
Mila answered 3/6, 2016 at 16:40 Comment(0)
C
2

The answer you refer to does not apply to your situation.

To get nice placement of the tick marks and tick mark labels, I would add columns to the gtable to take the axis material. The new columns have the same width as the original y axis.

You might want to add more margin space between the panels. Do so with theme(panel.margin.x = unit(1, "lines")).

require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
   geom_boxplot()+
   facet_grid(Species~category)

# Get the ggplot grob
g <- ggplotGrob(plot1)

# Get the yaxis
yaxis <- gtable_filter(g, "axis-l")

# Get the width of the y axis
Widths = yaxis$widths

# Add columns to the gtable to the left of the panels, 
# with a width equal to yaxis width
panels <- g$layout[grepl("panel", g$layout$name), ]
pos = rev(unique(panels$l)[-1] - 1)
for(i in pos) g = gtable_add_cols(g, Widths, i)

# Add y axes to the new columns
panels <- g$layout[grepl("panel", g$layout$name), ]
posx = rev(unique(panels$l)[-1] - 1)
posy = unique(panels$t)

g = gtable_add_grob(g, rep(list(yaxis), length(posx)), 
     t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)

# Draw it
grid.newpage()
grid.draw(g)

enter image description here

Alternatively, place the axis in a viewport of the same width as the original y axis, but with right justification. Then, add the resulting grob to the existing margin columns between the panels, adjusting the width of those columns to suit.

require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
   geom_boxplot() + 
   facet_grid(Species ~ category ) 

# Get the ggplot grob
g <- ggplotGrob(plot1)

# Get the yaxis
axis <- gtable_filter(g, "axis-l")

# Get the width of the y axis
Widths = axis$width

# Place the axis into a viewport, 
# of width equal to the original yaxis material,
# and positioned to be right justified
axis$vp = viewport(x = unit(1, "npc"), width = Widths, just = "right")

# Add y axes to the existing margin columns between the panels
panels <- g$layout[grepl("panel", g$layout$name), ] 
posx = unique(panels$l)[-1] - 1
posy = unique(panels$t)

g = gtable_add_grob(g, rep(list(axis), length(posx)), 
     t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)

# Increase the width of the margin columns
g$widths[posx] <- unit(25, "pt") 
# Or increase width of the panel margins in the original construction of plot1

# Draw it
grid.newpage()
grid.draw(g)
Chor answered 3/6, 2016 at 22:25 Comment(8)
Thank you for putting the comments in the answer explaining the steps. Really helpful. I put a link to this answer in the original question.Mila
Why add columns the gtable when you can just increase the width of the existing cols?Conatus
@Conatus You can. But in order to get the tick marks against the y-axes line, the width of the existing columns would need to be set to the width of the existing y-axis material. When you do that, you get the tick marks nicely placed, but IMO, the y-axis material is too close to the panels to the left.Chor
But you don't need to add a new gtable column. What you're doing is adding a second column of white space where the existing column of white space is. ggplot (or at least, ggplot2_2.1.0) already sets up columns for the y-axis labels to go in, and everything lines up as one would expect...The reason why it may not look lined up is the font size.Conatus
Except, everything is not lined up nicely. In your second chart, the tick marks do not butt up against the y-axes. They will do so when the column widths are set to be equal to width of the original y-axis material. As I said in the earlier comment, nothing wrong with that, except the panel margins can no longer be adjusted to add some margin space between the panels.Chor
Maybe you can answer the original question too? People googling this issue end up at that question before mine probably. I would copy paste your code and put it their as an answer but don't want to take your credit. I put a link to this answer in the comments but nobody will see it probably :-)Mila
Ahhh, you're talking about the horizontal justification...Yes, ticks will not be justified unless the column grob same size as the far left label colum. This is due to the fact that grobs in a gtable occupy the full extent of the viewport and thus cannot be justified...I assume somewhere in ggplot there is a step for sizing the far left y axis labs so that they are horizontally justified. I will edit my response.Conatus
@Conatus There is for the grob: x = unit(1, "npc"), just = "right". For ggplot, I'm not sure - you could adjust the left margin for the axis text.Chor
C
2

This is what I came up (using ggplot2_2.1.0):

g <- ggplotGrob(plot1)
axis <- gtable_filter(g, "axis-l")
newG <- gtable_add_grob(g, list(axis, axis, axis), 
                t = rep(4, 3), b = rep(8, 3), l = c(5, 7, 9))
grid.draw(newG)

..Which looks like this:

enter image description here

This is the process I went through:

  1. g <- ggplotGrob(plot1) Create a gtable.
  2. print(g) Look over the elements of the gtable...I'm looking for the names of the grobs that I want to mess around with. Here, it is the three grobs called "axis-l".
  3. axis <- gtable_filter(g, "axis-l") I select my three grobs from the larger gtable object, g, and save them in a gtable called axis. Note that gtable_filter is actually selecting the grobs, not filtering them from g.
  4. gtable_show_layout(g) Look over the layout of g so I can figure out where I want to put axis in relationship to the overall plot.
  5. gtable_add_grob, etc. Now that I know where I'm going with it, I can append the original plot with axis.

I think that those steps are a pretty common workflow when it comes to gtable. Of course you'll have other stuff that you may what to mess around with. For example, the space that is given for all but the left-most y axis labels is not sufficient in this case. So maybe just:

newG$widths[c(5, 7, 9)] <- grid:::unit.list(axis$widths) # you won't need to wrap this in grid
grid.draw(newG)

enter image description here

Conatus answered 3/6, 2016 at 21:48 Comment(3)
What version of ggplot are you using? Using ggplot2_2.1.0 the graph gets rendered as shown.Conatus
Sorry, I was using the dev version. I'll remove the comment.Chor
I still see a little bit of white space between the tick marks and the graph area. That is why I preferred Sandy's solution over yours. But I really appreciate the description of the process of the gtable workflow. Thank you for making such an effort!Mila
C
2

The answer you refer to does not apply to your situation.

To get nice placement of the tick marks and tick mark labels, I would add columns to the gtable to take the axis material. The new columns have the same width as the original y axis.

You might want to add more margin space between the panels. Do so with theme(panel.margin.x = unit(1, "lines")).

require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
   geom_boxplot()+
   facet_grid(Species~category)

# Get the ggplot grob
g <- ggplotGrob(plot1)

# Get the yaxis
yaxis <- gtable_filter(g, "axis-l")

# Get the width of the y axis
Widths = yaxis$widths

# Add columns to the gtable to the left of the panels, 
# with a width equal to yaxis width
panels <- g$layout[grepl("panel", g$layout$name), ]
pos = rev(unique(panels$l)[-1] - 1)
for(i in pos) g = gtable_add_cols(g, Widths, i)

# Add y axes to the new columns
panels <- g$layout[grepl("panel", g$layout$name), ]
posx = rev(unique(panels$l)[-1] - 1)
posy = unique(panels$t)

g = gtable_add_grob(g, rep(list(yaxis), length(posx)), 
     t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)

# Draw it
grid.newpage()
grid.draw(g)

enter image description here

Alternatively, place the axis in a viewport of the same width as the original y axis, but with right justification. Then, add the resulting grob to the existing margin columns between the panels, adjusting the width of those columns to suit.

require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
   geom_boxplot() + 
   facet_grid(Species ~ category ) 

# Get the ggplot grob
g <- ggplotGrob(plot1)

# Get the yaxis
axis <- gtable_filter(g, "axis-l")

# Get the width of the y axis
Widths = axis$width

# Place the axis into a viewport, 
# of width equal to the original yaxis material,
# and positioned to be right justified
axis$vp = viewport(x = unit(1, "npc"), width = Widths, just = "right")

# Add y axes to the existing margin columns between the panels
panels <- g$layout[grepl("panel", g$layout$name), ] 
posx = unique(panels$l)[-1] - 1
posy = unique(panels$t)

g = gtable_add_grob(g, rep(list(axis), length(posx)), 
     t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)

# Increase the width of the margin columns
g$widths[posx] <- unit(25, "pt") 
# Or increase width of the panel margins in the original construction of plot1

# Draw it
grid.newpage()
grid.draw(g)
Chor answered 3/6, 2016 at 22:25 Comment(8)
Thank you for putting the comments in the answer explaining the steps. Really helpful. I put a link to this answer in the original question.Mila
Why add columns the gtable when you can just increase the width of the existing cols?Conatus
@Conatus You can. But in order to get the tick marks against the y-axes line, the width of the existing columns would need to be set to the width of the existing y-axis material. When you do that, you get the tick marks nicely placed, but IMO, the y-axis material is too close to the panels to the left.Chor
But you don't need to add a new gtable column. What you're doing is adding a second column of white space where the existing column of white space is. ggplot (or at least, ggplot2_2.1.0) already sets up columns for the y-axis labels to go in, and everything lines up as one would expect...The reason why it may not look lined up is the font size.Conatus
Except, everything is not lined up nicely. In your second chart, the tick marks do not butt up against the y-axes. They will do so when the column widths are set to be equal to width of the original y-axis material. As I said in the earlier comment, nothing wrong with that, except the panel margins can no longer be adjusted to add some margin space between the panels.Chor
Maybe you can answer the original question too? People googling this issue end up at that question before mine probably. I would copy paste your code and put it their as an answer but don't want to take your credit. I put a link to this answer in the comments but nobody will see it probably :-)Mila
Ahhh, you're talking about the horizontal justification...Yes, ticks will not be justified unless the column grob same size as the far left label colum. This is due to the fact that grobs in a gtable occupy the full extent of the viewport and thus cannot be justified...I assume somewhere in ggplot there is a step for sizing the far left y axis labs so that they are horizontally justified. I will edit my response.Conatus
@Conatus There is for the grob: x = unit(1, "npc"), just = "right". For ggplot, I'm not sure - you could adjust the left margin for the axis text.Chor

© 2022 - 2024 — McMap. All rights reserved.