I'm trying to inset several plots on the same graph with different heights but have the same y scales. In the example below I have two plots with different heights. When I plot them the y-axis don't correspond to one another. How can I programmatically 'align' these two graphs?
Thanks
library(dplyr)
library(gridExtra)
plot_1 = data.frame(x = 1:250,
y = 1:250) %>%
ggplot(aes(x = x, y = y)) +geom_point()+ scale_y_continuous(limits=c(0,250), expand = c(0, 0)) +
theme(legend.position="none",axis.title.x=element_blank(),axis.text.x=element_blank(),axis.ticks.x=element_blank())
plot_2 = data.frame(x = 1:250,
y = 1:250*3) %>%
ggplot(aes(x = x, y = y)) +geom_point()+ scale_y_continuous(limits=c(0,750), expand = c(0, 0)) +
theme(legend.position="none",axis.title.x=element_blank(),axis.text.x=element_blank(),axis.ticks.x=element_blank())
base <- ggplot(data.frame(x = 1:1000,
y = 1:1000), aes(x, y)) +
geom_blank() +
theme_bw()
base = base +
annotation_custom(grob = ggplotGrob(plot_1),
ymin = 0,
ymax = 250,
xmin = 0,
xmax = 200)
base = base +
annotation_custom(grob = ggplotGrob(plot_2),
ymin = 0,
ymax = 750,
xmin = 300,
xmax = 500)
base
Note that the graph below shows the y-axis aren't on the same scale.
OP notes the y axes are not on the same scale in the inset plot - presumably, they mean compared to the original plot. The reasoning behind this is that the plot itself is not defined only by the size of the area used for plotting, but also includes all the border elements around the plot + a margin. The inset plots have no x axis labels, so to have the y axes align, all that is needed is to remove the margin around the plot which is added by default:
base <-
ggplot(data.frame(x = 1:1000, y = 1:1000), aes(x, y)) +
geom_blank() +
theme_bw() +
annotation_custom(
grob = ggplotGrob(plot_1 + theme(plot.margin = margin())),
ymin = 0, ymax = 250,
xmin = 0, xmax = 200) +
annotation_custom(
grob = ggplotGrob(plot_2 + theme(plot.margin = margin())),
ymin = 0, ymax = 750,
xmin = 300, xmax = 500)
base
If you notice... it's not perfect. There's a bit of space below each inset plot, so the 0 values do not align completely. What's going on here? Well, even though OP applied theme(axis.ticks.x = element_blank()), "blanking" basically means "drawing them as nothing". The ticks are actually still there, just not drawn. This means, the total plot area includes the size of the ticks. To remove this little space below the plot, we have to set the size of the axis ticks to zero:
base <-
ggplot(data.frame(x = 1:1000, y = 1:1000), aes(x, y)) +
geom_blank() +
theme_bw() +
annotation_custom(
grob = ggplotGrob(plot_1 + theme(plot.margin = margin(), axis.ticks.length.x = unit(0,'pt'))),
ymin = 0, ymax = 250,
xmin = 0, xmax = 200) +
annotation_custom(
grob = ggplotGrob(plot_2 + theme(plot.margin = margin(), axis.ticks.length.x = unit(0,'pt'))),
ymin = 0, ymax = 750,
xmin = 300, xmax = 500)
base
That fixes it.
This isn't perfect but might work with some tweaking.
base <- ggplot(data.frame(x = 1:1000,
y = 1:1000), aes(x, y)) +
geom_blank() +
theme_bw()
y_scaling <- 0.8
y_border <- 50
y_max <- function(plot) {
(diff(range(plot$data$y)) + y_border) * y_scaling
}
y_max(plot_1)
base = base +
annotation_custom(grob = ggplotGrob(plot_1),
ymin = 0,
ymax = y_max(plot_1),
xmin = 0,
xmax = 200)
base = base +
annotation_custom(grob = ggplotGrob(plot_2),
ymin = 0,
ymax = y_max(plot_2),
xmin = 300,
xmax = 500)
base
Related
I want to shade part of the background in each facet of a simple plot. If I omit faceting and run geom_rect + geom_point, the expected results appear as shown in the MRE below. If I omit the rectangle and run geom_point + facet_grid, the expected 4 panels have each point in the correct facet. But when I combine geom_rect + geom_point + and facet_grid, the points in the first category and only those get plotted in every facet. What is going on please???
library(ggplot2)
set.seed(42)
syn.dat <- data.frame(
category.1 = as.factor(rep(c("1A", "1B"), each = 8)),
category.2 = as.factor(rep(rep(c("2A", "2B"), times = 2), each = 4)),
x = rep(-1:2, each = 4) + runif(8, max = .4),
y = rep(-1:2, each = 4) + runif(8, max = .4))
ggplot() +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = .5,
ymax = Inf), fill = "lightyellow") +
geom_point(data = syn.dat, aes(x = x, y = y)) +
facet_grid(cols = vars(category.1),
rows = vars(category.2))
I'm not totally sure about this, but it may be that you need to explicitly provide the data argument to ggplot itself, in order for facet_grid to correctly pick up all the values?
ggplot(syn.dat) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 0.5, ymax = Inf), fill = "lightyellow") +
geom_point(aes(x = x, y = y)) +
facet_grid(rows = vars(category.2), vars(cols = category.1))
I'm looking for a way to move every second x-axis tick downwards and have the tick line go down with it.
I can change the general margin and tick length for all ticks with:
#MWE
library(ggplot2)
ggplot(cars, aes(dist, speed))+
geom_point()+
theme(
axis.ticks.length.x = unit(15, "pt")
)
But, I would like the x-axis ticks 0, 50, and 100 (i.e., every second tick) to be without the added top margin.
A generalized answer is preferred as my x-axis is categorical and not numerical (and contains 430 ticks, so nothing I can set by hand).
Any ideas?
Edit:
Output should be:
Edit2:
A more intricate example would be:
#MWE
ggplot(diamonds, aes(cut, price, fill = clarity, group = clarity))+
geom_col(position = 'dodge')+
theme(
axis.ticks.length.x = unit(15, "pt")
)
Edit -- added categorical approach at bottom.
Here's a hack. Hope there's a better way!
ticks <- data.frame(
x = 25*0:5,
y = rep(c(-0.2, -2), 3)
)
ggplot(cars, aes(dist, speed))+
geom_point()+
geom_rect(fill = "white", xmin = -Inf, xmax = Inf,
ymin = 0, ymax = -5) +
geom_segment(data = ticks,
aes(x = x, xend = x,
y = 0, yend = y)) +
geom_text(data = ticks,
aes(x = x, y = y, label = x), vjust = 1.5) +
theme(axis.ticks.x = element_blank()) +
scale_x_continuous(breaks = 25*0:5, labels = NULL, name = "") +
coord_cartesian(clip = "off")
Here's a similar approach used with a categorical x.
cats <- sort(as.character(unique(diamonds$cut)))
ticks <- data.frame(x = cats)
ticks$y = ifelse(seq_along(cats) %% 2, -500, -2000)
ggplot(diamonds, aes(cut, price, fill = clarity, group = clarity))+
geom_col(position = 'dodge') +
annotate("rect", fill = "white",
xmin = 0.4, xmax = length(cats) + 0.6,
ymin = 0, ymax = -3000) +
geom_segment(data = ticks, inherit.aes = F,
aes(x = x, xend = x,
y = 0, yend = y)) +
geom_text(data = ticks, inherit.aes = F,
aes(x = x, y = y, label = x), vjust = 1.5) +
scale_x_discrete(labels = NULL, name = "cut") +
scale_y_continuous(expand = expand_scale(mult = c(0, 0.05))) +
theme(axis.ticks.x = element_blank()) +
coord_cartesian(clip = "off")
I want to have an error bar for my geom_hline and thought that a geom_ribbon with opacity would look best. but i cant figure out how to make it reach the ends of the plot. I want the geom_ribbon to touch the sides of the plot as the geom_hline does. Here is the example code:
library('ggplot2')
x <- c(1,2,3,4,5,6,7,8,9,10)
y <- c(1,2,3,4,5,6,7,8,9,10)
data <- data.frame(x,y)
p1 <- ggplot(data,aes(x = x, y = y)) + geom_line() + geom_hline(yintercept=5)
p1 + geom_ribbon(aes(y = y[5],ymin = y[5]-0.5, ymax = y[5]+0.5, fill = 'red'), alpha = 0.4)
Use annotate with infinite x-values:
ggplot(data, aes(x, y)) +
geom_line() +
geom_hline(yintercept = 5) +
annotate('ribbon', x = c(-Inf, Inf), ymin = 5 - 0.5, ymax = 5 + 0.5,
alpha = 0.4, fill = 'red')
If you need a legend, use geom_ribbon directly, like so:
ggplot(data, aes(x, y)) +
geom_line() +
geom_hline(yintercept = 5) +
geom_ribbon(
aes(x, y = NULL, ymin = ymin, ymax = ymax, fill = 'my_label'),
data.frame(x = c(-Inf, Inf), ymin = 5 - 0.5, ymax = 5 + 0.5),
alpha = 0.4
)
There is no way in geom_hline() to set xlim, instead the horizontal line goes from end of the plot to the other. You can use geom_segment instead, which allows you to control the x-range and y-range of the line by specifying segment's start coordinate (x, y) and end coordinate (xend, yend)
This works:
library('ggplot2')
x <- c(1,2,3,4,5,6,7,8,9,10)
y <- c(1,2,3,4,5,6,7,8,9,10)
data <- data.frame(x,y)
p1 <- ggplot(data, aes(x = x, y = y)) + geom_line() + geom_segment(aes(x = x[1], xend = x[10], y = y[5], yend = y[5]))
p1 + geom_ribbon(aes(y = y[5],ymin = y[5]-0.5, ymax = y[5]+0.5, fill = 'red'), alpha = 0.4)
Couple options:
1) Use geom_hline instead of geom_ribbon like so (probably best option):
p1 + geom_hline(yintercept = y[5], color = 'red', size = 8, alpha = 0.4)
2) Remove area between plot area and axis by adding scale_x_continuous(expand=c(0,0)) like so (credit to https://stackoverflow.com/a/22945857/5727278):
p1 + geom_ribbon(aes(y = y[5],ymin = y[5]-0.5, ymax = y[5]+0.5, fill = 'red'), alpha = 0.4) +
scale_x_continuous(expand=c(0,0))
Problem
I want to draw a number of arrows on a map. With some googling, I've learned that annotation_custom with rasterGrob can do the thing:
library(ggplot2)
library(png)
library(grid)
img = readPNG('floorplan.png')
g = rasterGrob(img, interpolate = TRUE)
data = read.csv('est_err_structured.csv', header = FALSE)
x1 = data$V1
y1 = data$V2
x2 = data$V3
y2 = data$V4
p = ggplot() +
geom_segment(data = data, mapping = aes(x = x1, y = y1, xend = x2, yend = y2),
arrow = arrow(length = unit(0.2, 'cm'))) +
annotation_custom(g, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf) +
xlab('x (m)') +
ylab('y (m)') +
theme_bw()
pdf('err_on_map.pdf')
p
dev.off()
However, when I run this script, I only got arrows but not background image:
Attachment
est_err_structured.csv
floorplan.png
References
https://stackoverflow.com/a/16418186
https://stackoverflow.com/a/10769839
You need to study ggplot2 a bit more. E.g., the variables mapped within aes are taken from the data.frame defined as data and in this example you must pass it to ggplot. I think annotation_custom somehow needs it to get the proper coordinates or dimensions.
p = ggplot(data) +
annotation_custom(g, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf) +
geom_segment(aes(x = V1, y = V2, xend = V3, yend = V4),
arrow = arrow(length = unit(0.2, 'cm'))) +
xlab('x (m)') +
ylab('y (m)') +
theme_bw()
You'll need to pass the plot width and height to pdf in order to align the image correctly with the plot.
Edit:
#baptiste recommends annotation_raster, which makes the positioning easier:
ggplot(data) +
annotation_raster(img, xmin = 50, xmax = 600, ymin = 20, ymax = 400) +
geom_segment(aes(x = V1, y = V2, xend = V3, yend = V4),
arrow = arrow(length = unit(0.2, 'cm'))) +
coord_cartesian(xlim = c(50, 600), ylim = c(20, 400)) +
xlab('x (m)') +
ylab('y (m)') +
theme_bw()
Suppose I produce this scatter plot:
plot(x = runif(20,-10,10), y = runif(20,-10,10), xlim = c(-10,10), ylim = c(-10,10))
abline(h = 0, col = "black")
abline(v = 0, col = "black")
So the abline's divide the plane to the four Cartesian quadrants.
I would like to color the background of each quadrant in a different color. Say blue, red, green, and yellow for quadrants 1-4, respectively.
Any idea?
If you like a ggplot solution:
df <- data.frame(x = runif(20,-10,10), y = runif(20,-10,10))
ggplot(df, aes(x,y)) +
annotate("rect", xmin = Inf, xmax = 0, ymin = Inf, ymax = 0, fill= "red") +
annotate("rect", xmin = -Inf, xmax = 0, ymin = -Inf, ymax = 0 , fill= "blue") +
annotate("rect", xmin = 0, xmax = Inf, ymin = 0, ymax = -Inf, fill= "yellow") +
annotate("rect", xmin = 0, xmax = -Inf, ymin = Inf, ymax = 0, fill= "green") +
geom_point() + xlim(-10,10)+ ylim(-10,10)
Edit:
#Spacedman: I didn't know some devices can have problems with Inf here a function that does not use Inf, and also allows the choice of the crossing point and colors of the quadrants
colquad <- function(plot, crossAt = 0, xlims = c(-10,10), ylims = c(-10,10), colours = c("blue", "red","yellow", "green")) {
#colours of rects are from left to right starting at the top
library(ggplot2)
plot <- plot + coord_cartesian(xlim = c(xlims[1],xlims[2]), ylim = c(ylims[1], ylims[2]))
plot +
annotate("rect", xmin = xlims[1], xmax = crossAt, ymin = ylims[2], ymax = crossAt, fill = colours[1]) +
annotate("rect", xmin = crossAt, xmax = xlims[2], ymin = crossAt, ymax = ylims[2], fill = colours[2]) +
annotate("rect", xmin = xlims[1], xmax = crossAt, ymin = ylims[1], ymax = crossAt , fill= colours[3]) +
annotate("rect", xmin = crossAt, xmax = xlims[2], ymin = crossAt, ymax = ylims[1], fill = colours[4]) +
geom_point()
}
To use it (to make the previous graphs):
df <- data.frame(x = runif(20,-10,10), y = runif(20,-10,10))
plot <- ggplot(df, aes(x,y))
colquad(plot)
And an example of the advantage of ggplot2, change colour of the points to white
colquad(plot) %+% geom_point(colour = "white")
You are using base graphics so here's a base graphics solution.
Write a function that uses rect and gets the plot limits from par()$usr:
quads =
function(colours=c("blue","red","green","yellow")){
limits = par()$usr
rect(0,0,limits[2],limits[4],col=colours[1])
rect(0,0,limits[1],limits[4],col=colours[2])
rect(0,0,limits[1],limits[3],col=colours[3])
rect(0,0,limits[2],limits[3],col=colours[4])
}
Note you have to do this before plotting your points or it splats over them. Using type='n' in a plot function will set up an empty plot with some data:
> x = runif(20,-10,10) ; y = runif(20,-10,10)
> plot(x,y,type="n")
> quads()
> points(x,y)
Specify your own colours via the colours= arg.