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)
Related
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)
I'm using R to generate some plots of some metrics and getting nice results like this for data that has > 3 data points:
However, I'm noticing that for data with only a few values - I get very poor results.
If I draw a plot with only two data points, I get a blank plot.
foo_two_points.dat
cluster,account,current_database,action,operation,count,day
cluster19,col0063,col0063,foo_two,two_bar,10,2016-10-04 00:00:00-07:00
cluster61,dwm4944,dwm4944,foo_two,two_bar,2,2016-12-14 00:00:00-08:00
If I draw one data point, it works.
foo_one_point.dat
cluster,account,current_database,action,operation,count,day
cluster1,foo0424,foo0424,fooone,,2,2016-11-01 00:00:00-07:00
Three, it almost works, but isn't accurate.
foo_three_points.dat
cluster,account,current_database,action,operation,count,day
cluster23,col2225,col2225,foo_three,bar,9,2016-12-22 00:00:00-08:00
cluster23,col2225,col2225,foo_three,bar,1,2016-12-29 00:00:00-08:00
cluster12,red1782,red1782,foo_three,bar,2,2016-10-25 00:00:00-07:00
4, 5, etc. all seem fine
But two or three points - nope.
Here is my plot.r file:
library(ggplot2)
library(scales)
args<-commandArgs(TRUE)
filename<-args[1]
n = nchar(filename) - 4
thetitle = substring(filename, 1, n)
print(thetitle)
png_filename <- stringi::stri_flatten(stringi::stri_join(c(thetitle,'.png')))
wide<-as.numeric(args[2])
high<-as.numeric(args[3])
legend_left<-as.numeric(args[4])
pos <- if(legend_left == 1) c(1,0) else c(0,1)
place <- if(legend_left == 1) 'left' else 'right'
print(wide)
print(high)
print(filename)
print(png_filename)
dat = read.csv(filename)
dat$account = as.character(dat$account)
dat$action=as.character(dat$action)
dat$operation = as.character(dat$operation)
dat$count = as.integer(dat$count)
dat$day = as.Date(dat$day)
dat[is.na(dat)]<-"N/A"
png(png_filename,width=wide,height=high)
p <- ggplot(dat, aes(x=day, y=count, fill=account, labels=TRUE))
p <- p + geom_histogram(stat="identity")
p <- p + scale_x_date(labels=date_format("%b-%Y"), limits=as.Date(c('2016-10-01','2017-01-01')))
p <- p + theme(legend.position="bottom")
p <- p + guides(fill=guide_legend(nrow=5, byrow=TRUE))
p <- p + theme(text = element_text(size=15))
p<-p+labs(title=thetitle)
print(p)
dev.off()
Here's the command I use to run it:
RScript plot.r foo_five_points.dat 1600 800 0
What am I doing wrong?
I don't know if this is a bug, I think it is actually by design and the bars are getting clipped as they spill over into the limits.
I also think this is more of a geom_bar than a geom_histogram as this doesn't seem to be distribution data, but that is irrelevant to the issue, both behave the same.
One solution it is to set the width parameter explicitly in geom_histo instead of letting it be calculated:
p <- ggplot(dat, aes(x=day, y=count, fill=account, labels=TRUE))
p <- p + geom_histogram(stat="identity",width=1)
p <- p + scale_x_date(labels=date_format("%b-%Y"), limits=as.Date(c('2016-10-1','2017-01-01')))
p <- p + theme(legend.position="bottom")
p <- p + guides(fill=guide_legend(nrow=5, byrow=TRUE))
p <- p + theme(text = element_text(size=15))
p<-p+labs(title=thetitle)
Then your two point example that is blank above gives you this - which seems right:
Can't be sure that setting the width explicitly will work when you have a lot of data though and the bars keep needing to get smaller - I suppose you could set it conditionally.
I'm trying to display three time series using facet_grid() and in order to save space, I'm reducing panel spacing between them. The problem is that their vertical axis overlap so I want to move it to the right only on the plot in the middle.
Since this seem impossible in ggplot2, what I'm trying to do is to render every axis and then remove it editing the gtable but so far I was not successful.
This is a minimal example:
library(ggplot2)
set.seed(123)
df <- data.frame(expand.grid(x = 1:150, type = letters[1:3]))
df$y <- df$x*0.016 + rnorm(150, sd = .5)
ggplot(df, aes(x, y)) + geom_line() +
facet_grid(type~.) +
theme(panel.spacing.y = unit(-3, "lines"), strip.text = element_blank()) +
scale_y_continuous(sec.axis = dup_axis(name = ""), name = "y")
Which produces this:
And I want to delete each axis text to get to this:
Thanks!
The solution was to assign a nullGrob() to the relevant elements of the gTable.
gt <- ggplotGrob(g)
t <- gt[["grobs"]][[8]][["children"]][[2]]
# Found those grobs by looking around the table.
gt[["grobs"]][[8]][["children"]][[2]] <- nullGrob()
gt[["grobs"]][[10]][["children"]][[2]] <- nullGrob()
gt[["grobs"]][[12]][["children"]][[2]] <- nullGrob()
grid.newpage()
grid.draw(gt)
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)
Is there a way to modify the legend of a heat map that was generated with geom_tile from the ggplot2 package? I would like to increase the number of tiles in the legend and to set the minimum and maximum of the shown value there.
In this example from the manual page the legend contains five colored tiles representing values from -0.4 to 0.4. How could I let e.g. 9 tile be displayed instead?
library (ggplot2)
pp <- function (n,r=4) {
x <- seq(-r*pi, r*pi, len=n)
df <- expand.grid(x=x, y=x)
df$r <- sqrt(df$x^2 + df$y^2)
df$z <- cos(df$r^2)*exp(-df$r/6)
df
}
p <- ggplot(pp(20), aes(x=x,y=y))
p + geom_tile(aes(fill=z))
I guess there are several possible ways to archive this. One solution would be to specify the breaks for the legend manually.
d = pp(20)
ggplot(d, aes(x=x,y=y,fill=z)) + geom_tile() +
scale_fill_continuous( breaks = round( seq(-.4, .4, length.out = 10 ), 1) )