ggplot: clipping lines between facets - r

Say I have a plot like this:
# Load libraries
library(ggplot2)
library(grid)
# Load data
data(mtcars)
# Plot results
p <- ggplot(data = mtcars)
p <- p + geom_bar(aes(cyl))
p <- p + coord_flip()
p <- p + facet_wrap(~am)
print(p)
Now, I want to plot lines all the way across both facets where the bars are. I add this:
p <- p + geom_vline(aes(xintercept = cyl))
which adds the lines, but they don't cross both facets. So, I try to turn off clipping using this solution:
# Turn off clipping
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[gt$layout$name == "panel"] <- "off"
# Plot results
grid.draw(gt)
but that doesn't solve the problem: the lines are still clipped. So, I wondered if this is specific to geom_vline and tried approaches with geom_abline and geom_line (the latter with values across ±Inf), but the results are the same. In other posts, the clipping solution seems to work for text and points, but presumably in this case the lines are only defined within the limits of the figure. (I even tried gt$layout$clip <- "off" to switch off all possible clipping, but that didn't solve the problem.) Is there a workaround?

library(grid)
library(gtable)
# Starting from your plot `p`
gb <- ggplot_build(p)
g <- ggplot_gtable(gb)
# Get position of y-axis tick marks
ys <- gb$layout$panel_ranges[[1]][["y.major"]]
# Add segments at these positions
# subset `ys` if you only want to add a few
# have a look at g$layout for relevant `l` and `r` positions
g <- gtable_add_grob(g, segmentsGrob(y0=ys, y1=ys,
gp=gpar(col="red", lty="dashed")),
t = 7, l = 4, r=8)
grid.newpage()
grid.draw(g)
see ggplot, drawing multiple lines across facets for how to rescale values for more general plotting. ie
data2npc <- function(x, panel = 1L, axis = "x") {
range <- pb$layout$panel_ranges[[panel]][[paste0(axis,".range")]]
scales::rescale(c(range, x), c(0,1))[-c(1,2)]
}
start <- sapply(c(4,6,8), data2npc, panel=1, axis="y")
g <- gtable_add_grob(g, segmentsGrob(y0=start, y1=start),
t=7, r=4, l=8)

Related

ggplot, drawing multiple lines across facets

I drew two panels in a column using ggplot2 facet, and would like to add two vertical lines across the panels at x = 4 and 8. The following is the code:
library(ggplot2)
library(gtable)
library(grid)
dat <- data.frame(x=rep(1:10,2),y=1:20+rnorm(20),z=c(rep("A",10),rep("B",10)))
P <- ggplot(dat,aes(x,y)) + geom_point() + facet_grid(z~.) + xlim(0,10)
Pb <- ggplot_build(P);Pg <- ggplot_gtable(Pb)
for (i in c(4,8)){
Pg <- gtable_add_grob(Pg, moveToGrob(i/10,0),t=8,l=4)
Pg <- gtable_add_grob(Pg, lineToGrob(i/10,1),t=6,l=4)
}
Pg$layout$clip <- "off"
grid.newpage()
grid.draw(Pg)
The above code is modified from:ggplot, drawing line between points across facets.
And .
There are two problems in this figure. First, only one vertical line was shown. It seems that moveToGrob only worked once.. Second, the shown line is not exact at x = 4. I didn't find the Pb$panel$ranges variable, so is there a way that I can correct the range as well? Thanks a lot.
Updated to ggplot2 V3.0.0
In the simple scenario where panels have common axes and the lines extend across the full y range you can draw lines over the whole gtable cells, having found the correct npc coordinates conversion (cf previous post, updated because ggplot2 keeps changing),
library(ggplot2)
library(gtable)
library(grid)
dat <- data.frame(x=rep(1:10,2),y=1:20+rnorm(20),z=c(rep("A",10),rep("B",10)))
p <- ggplot(dat,aes(x,y)) + geom_point() + facet_grid(z~.) + xlim(0,10)
pb <- ggplot_build(p)
pg <- ggplot_gtable(pb)
data2npc <- function(x, panel = 1L, axis = "x") {
range <- pb$layout$panel_params[[panel]][[paste0(axis,".range")]]
scales::rescale(c(range, x), c(0,1))[-c(1,2)]
}
start <- sapply(c(4,8), data2npc, panel=1, axis="x")
pg <- gtable_add_grob(pg, segmentsGrob(x0=start, x1=start, y0=0, y1=1, gp=gpar(lty=2)), t=7, b=9, l=5)
grid.newpage()
grid.draw(pg)
You can just use geom_vline and avoid the grid mess altogether:
ggplot(dat, aes(x, y)) +
geom_point() +
geom_vline(xintercept = c(4, 8)) +
facet_grid(z ~ .) +
xlim(0, 10)

ggplot2 legend width and legend strings size

I have 2 plots of the exact same thing, excepts the colors filling the bars are different on each plot. Since the legend on the different plots have different widths because of the size of the names on it, the ratio between graph and legend becomes different on each plot. I need to make both look the same.
This is an example:
library(ggplot2)
x = c(rep("a",20),rep("b",10))
y = c(x = c(rep("BIGTEXT",20),rep("EVENBIGGERTEXT",10)))
df = data.frame(x,y)
p1 = ggplot(df,aes(x,fill=x)) + geom_bar()
p2 = ggplot(df,aes(y,fill=y)) + geom_bar()
p1
p2
You can set the gtable widths to a common value,
library(gtable)
library(grid)
gl <- lapply(list(p1, p2), ggplotGrob)
gwidth <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
gl <- lapply(gl, "[[<-", "widths", value = gwidth)
gridExtra::grid.arrange(grobs=gl)
Alternatively, you can set the panel size to a fixed value.
Following up #baptiste's comment: Fiddly but yes, it can be done. But no guarantees that this will work in future versions. The solution was taken from here, but that solution too needed updating.
library(ggplot2) # v2.2.1
library(gtable) # v0.2.0
library(grid)
library(gridExtra) # v2.2.1
x = c(rep("a",20),rep("b",10))
y = c(x = c(rep("BIGTEXT",20),rep("EVENBIGGERTEXT",10)))
df = data.frame(x,y)
p1 = ggplot(df,aes(x,fill=x)) + geom_bar()
p2 = ggplot(df,aes(y,fill=y)) + geom_bar()
# Get the grobs
gA <- ggplotGrob(p1)
gB <- ggplotGrob(p2)
# Get the widths of the legends
index = which(gA$layout$name == "guide-box")
leg1 <- convertX(sum(with(gA$grobs[[index]], grobs[[1]]$widths)), "mm")
leg2 <- convertX(sum(with(gB$grobs[[index]], grobs[[1]]$widths)), "mm")
# Add an empty column of width "abs(diff(c(leg1, leg2))) mm" to the right of
# legend box for gA (the smaller legend box)
gA$grobs[[index]] <- gtable_add_cols(gA$grobs[[index]], unit(abs(diff(c(leg1, leg2))), "mm"))
# Set widths to maximums of corresponding widths
gl <- list(gA, gB)
gwidth <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
gl <- lapply(gl, "[[<-", "widths", value = gwidth)
# Draw the plots
grid.newpage()
grid.draw(gl[[1]])
grid.newpage()
grid.draw(gl[[2]])

ggplot, drawing line between points across facets

Using ggplot2, how can I draw a trendline which runs between facets.
library(ggplot2)
df <- data.frame(y=c(1,2,3),x=1,Set=LETTERS[1:3])
ggplot(df,aes(x,y)) +
theme_bw() + theme(legend.position=c(0,1),legend.justification=c(0,1)) +
geom_point(aes(fill=Set),color="black",shape=21,size=3) +
facet_grid(~Set) +
xlim(1,5)
Which produces the following:
In the above, I would like to draw a line between the three points, moving across facets.
Updated to ggplot2 V3.0.0
You could do this, but turning clip off might have unwanted consequences,
library(ggplot2)
df <- data.frame(y=c(1,2,3),x=1,Set=LETTERS[1:3])
p <- ggplot(df,aes(x,y)) +
theme_bw() + theme(legend.position=c(.01,.99),legend.justification=c(0,1)) +
geom_point(aes(fill=Set),color="black",shape=21,size=3) +
facet_grid(~Set) +
xlim(1,5)
gb <- ggplot_build(p)
g <- ggplot_gtable(gb)
library(gtable)
library(grid)
# ggplot2 doesn't use native units in data space
# instead, the data is rescaled to npc, i.e from 0 to 1
# so we need to use the build info to convert from data to [0,1]
ranges <- gb$layout$panel_params
data2npc <- function(x, range) scales::rescale(c(range, x), c(0,1))[-c(1,2)]
start <- c(data2npc(1, ranges[[1]][["x.range"]]),
data2npc(1, ranges[[1]][["y.range"]]))
end <- c(data2npc(1, ranges[[3]][["x.range"]]),
data2npc(3, ranges[[3]][["y.range"]]))
# starting position in the first panel
g <- gtable_add_grob(g, moveToGrob(start[1],start[2]),
t = 8, l = 5)
# draw line to end position in last panel
g <- gtable_add_grob(g, lineToGrob(end[1],end[2]),
t = 8, l = 9, z=Inf)
# turn clip off to see the line across panels
g$layout$clip <- "off"
grid.newpage()
grid.draw(g)

Aligning and arranging charts in ggplot

I have two plots in ggplot with similar ranges on the axis that I would like to align.
Using:
library(grid)
grid.newpage()
grid.draw(rbind(ggplotGrob(g1), ggplotGrob(g2), size = "last"))
where size='last' ensures the grids are aligned for x, I get a layout such as this:
The top chart has an excessive vertical range for the simple row I want to depict.
I would like to have an image more in keeping with the following:
Or alternatively, with the g1 chart aligned below the axis of the g2 chart. (For generality, let's say I want to be able to choose either, or even overlay g1 on top of g2.
(My original model was a single chart with the g1 component simply set as a layer in the g2 chart with y=0; however, the chart is too cluttered to read with that layout, so I thought I should move the original y=0 elements into a separate panel (i.e. g1).
Minimal Example:
size=50
df1=data.frame(x=seq(1,size),y=sample(-100:100,size,rep=TRUE))
df2=data.frame(x=seq(1,size),r=replicate(size,paste(sample(c(letters, LETTERS),3),collapse='')))
g1=ggplot(df1)+geom_point(aes(x=x,y=y))
g2=ggplot(df2)+geom_text(aes(x=x,y=0,label=r),angle=45,size=2)
library(grid)
grid.newpage()
grid.draw(rbind(ggplotGrob(g1), ggplotGrob(g2), size = "last"))
I want the label strip (g2) to be positioned neatly with respect to the scatterplot. The g2 strip was part of the original chart as a geom_text() element at y=0 but the chart was too cluttered around there with the labels placed there too.
you simply need to edit the heights of the gtable,
library(ggplot2)
library(grid)
g1 <- g2 <- ggplotGrob(qplot(1,1))
g <- rbind(g1,g2,size="last")
id <- g$layout$t[g$layout$name == "panel"]
g$heights[id] <- lapply(c(1,4), "unit", "null")
grid.newpage()
grid.draw(g)
Edit: with the more specific instructions, I'd suggest a different strategy: add the first panel only to the gtable of the second plot,
p1 <- ggplot(df1)+geom_point(aes(x=x,y=y))
p2 <- ggplot(df2)+geom_text(aes(x=x,y=0,label=r),angle=45,size=2)
snug <- theme_bw() + theme(plot.margin=unit(c(0.5,0.5,0,0),"line"))
library(gtable)
g1=gtable_filter(ggplotGrob(p1 + snug), pattern = "panel", trim = TRUE, fixed=TRUE)
g2=ggplotGrob(p2 + snug)
g <- g2
g <- gtable_add_rows(g, unit(0.2, "null"), 0)
g <- gtable_add_grob(g, g1, 1, 4)
grid.newpage()
grid.draw(g)

Constant width in ggplot barplots

How to make the width of bars and spaces between them fixed for several barplots using ggplot, having different number of bars on each plot?
Here is a failed try:
m <- data.frame(x=1:10,y=runif(10))
ggplot(m, aes(x,y)) + geom_bar(stat="identity")
ggplot(m[1:3,], aes(x,y)) + geom_bar(stat="identity")
Adding width=1 to geom_bar(...) doesn't help as well. I need the second plot automatically to have less width and the same bar width and spaces as the first one.
Edit:
It appears the OP simply wants this:
library(gridExtra)
grid.arrange(p1,arrangeGrob(p2,widths=c(1,2),ncol=2), ncol=1)
I am not sure, if it's possible to pass absolute widths to geom_bar. So, here is an ugly hack:
set.seed(42)
m <- data.frame(x=1:10,y=runif(10))
p1 <- ggplot(m, aes(x,y)) + geom_bar(stat="identity")
p2 <- ggplot(m[1:3,], aes(x,y)) + geom_bar(stat="identity")
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
I used str to find the correct grob and child. You could use more sophisticated methods to generalize this if necessary.
#store the old widths
old.unit <- g2$grobs[[4]]$children[[2]]$width[[1]]
#change the widths
g2$grobs[[4]]$children[[2]]$width <- rep(g1$grobs[[4]]$children[[2]]$width[[1]],
length(g2$grobs[[4]]$children[[2]]$width))
#copy the attributes (units)
attributes(g2$grobs[[4]]$children[[2]]$width) <- attributes(g1$grobs[[4]]$children[[2]]$width)
#position adjustment (why are the bars justified left???)
d <- (old.unit-g2$grobs[[4]]$children[[2]]$width[[1]])/2
attributes(d) <- attributes(g2$grobs[[4]]$children[[2]]$x)
g2$grobs[[4]]$children[[2]]$x <- g2$grobs[[4]]$children[[2]]$x+d
#plot
grid.arrange(g1,g2)
Wrapped the other suggestions in a function that only requires a single graph.
fixedWidth <- function(graph, width=0.1) {
g2 <- graph
#store the old widths
old.unit <- g2$grobs[[4]]$children[[2]]$width[[1]]
original.attibutes <- attributes(g2$grobs[[4]]$children[[2]]$width)
#change the widths
g2$grobs[[4]]$children[[2]]$width <- rep(width,
length(g2$grobs[[4]]$children[[2]]$width))
#copy the attributes (units)
attributes(g2$grobs[[4]]$children[[2]]$width) <- original.attibutes
#position adjustment (why are the bars justified left???)
d <- (old.unit-g2$grobs[[4]]$children[[2]]$width[[1]])/2
attributes(d) <- attributes(g2$grobs[[4]]$children[[2]]$x)
g2$grobs[[4]]$children[[2]]$x <- g2$grobs[[4]]$children[[2]]$x+d
return(g2)
}

Resources