Relative Positioning of Geom_Text in Ggplot2

Specify position of geom_text by keywords like top , bottom , left , right , center

geom_text wants to plot labels based on your data set. It sounds like you're looking to add a single piece of text to your plot, in which case, annotate is the better option. To force the label to appear in the same position regardless of the units in the plot, you can take advantage of Inf values:

sp <- ggplot(mpg, aes(hwy, cty, label = "sometext"))+
geom_point() +
annotate(geom = 'text', label = 'sometext', x = -Inf, y = Inf, hjust = 0, vjust = 1)
print(sp)

Sample Image

Relative positioning of geom_text in ggplot2?

If you know the range of the data in your plot, you can calculate the "true" x and y limits using the fact that ggplot using an additive expansion factor of 0.05 by default, so that the extents of the graph extend just slightly beyond the actual data values.

You can specify and multiplicative and additive expansion factor when specifying scales using expand = c(mult, add) where mult is the multiplicative factor and so on. So the default setting is expand = c(0,0.05).

R: place geom_text() relative to plot borders rather than fixed position on the plot

You can use the y-range of the data to position to the text labels. I've set the y-limits explicitly in the example below, but that's not absolutely necessary unless you want to change them from the defaults. You can also adjust the x-position of the text labels using the x-range of the data. The code below will position the labels at the bottom of the plot, regardless of the y-range of the data.

I've also switched from geom_text to annotate. geom_text overplots the text labels multiple times, once for each row in the data. annotate plots the label once.

ypos = min(ggdata$measure1) + 0.005*diff(range(ggdata$measure1))
xv = 0.02
xh = 0.01
xadj = diff(range(ggdata$Year))

ggplot(data=ggdata, aes(x=Year, y=measure1, group=Area, color=Area)) +
geom_vline(xintercept=2011, color="#EE0000") +
geom_vline(xintercept=2007, color="#000099") +
geom_line(size=.75) +
geom_point(size=1.5) +
annotate(geom="text", x=2011 - xv*xadj, label="City1", y=ypos, color="#EE0000", angle=90, hjust=0, family="serif") +
annotate(geom="text", x=2007 - xh*xadj, label="City2", y=ypos, color="#000099", angle=0, hjust=1, family="serif") +
scale_y_continuous(limits=range(ggdata$measure1),
breaks=round(seq(min(ggdata$measure1, na.rm=T), max(ggdata$measure1, na.rm=T), by=1), 0)) +
scale_x_continuous(breaks=min(ggdata$Year):max(ggdata$Year)) +
scale_color_manual(values=c("#EE0000", "#00DDFF", "#009900", "#000099")) +
theme(axis.text.x = element_text(angle=90, vjust=1),
panel.background = element_rect(fill="white", color="white"),
panel.grid.major = element_line(color="grey95"),
text = element_text(size=11, family="serif"))

Sample Image

UPDATE: To respond to your comment, here's how you can create a separate plot for each "measure" column in your data frame.

First, we create reproducible data with three measure columns:

library(ggplot2)
library(gridExtra)
library(scales)

set.seed(4)
ggdata <- data.frame(Year=rep(2006:2012,each=4),
Area=rep(paste0("City",1:4), 7),
measure1=rnorm(28,10,2),
measure2=rnorm(28,50,10),
measure3=rnorm(28,-50,5))

Now, we take the code from above and package it in a function. The function take an argument called measure_var. This is the data column, provided as a character_string, that will provide the y-values for the plot. Note that we now use aes_string instead of aes inside ggplot.

plot_func = function(measure_var) {

ypos = min(ggdata[ , measure_var]) + 0.005*diff(range(ggdata[ , measure_var]))
xv = 0.02
xh = 0.01
xadj = diff(range(ggdata$Year))

ggplot(data=ggdata, aes_string(x="Year", y=measure_var, group="Area", color="Area")) +
geom_vline(xintercept=2011, color="#EE0000") +
geom_vline(xintercept=2007, color="#000099") +
geom_line(size=.75) +
geom_point(size=1.5) +
annotate(geom="text", x=2011 - xv*xadj, label="City1", y=ypos,
color="#EE0000", angle=90, hjust=0, family="serif") +
annotate(geom="text", x=2007 - xh*xadj, label="City2", y=ypos,
color="#000099", angle=0, hjust=1, family="serif") +
scale_y_continuous(limits=range(ggdata[ , measure_var]),
breaks=pretty_breaks(5)) +
scale_x_continuous(breaks=min(ggdata$Year):max(ggdata$Year)) +
scale_color_manual(values=c("#EE0000", "#00DDFF", "#009900", "#000099")) +
theme(axis.text.x = element_text(angle=90, vjust=1),
panel.background = element_rect(fill="white", color="white"),
panel.grid.major = element_line(color="grey95"),
text = element_text(size=11, family="serif")) +
ggtitle(paste("Plot of", measure_var))
}

We can now run the function once like this: plot_func("measure1"). However, let's run it on all the measure columns in one go by using lapply. We give lapply a vector with the names of the measure columns (names(ggdata)[grepl("measure", names(ggdata))]), and it runs plot_func on each of these columns in turn, storing the resulting plots in the list plot_list.

plot_list = lapply(names(ggdata)[grepl("measure", names(ggdata))], plot_func)

Now if we wish, we can lay them all out together using grid.arrange. In this case, we only need one legend, rather than a separate legend for each plot, so we extract the legend as a separate graphical object and lay it out beside the three plots.

# Function to get legend from a ggplot as a separate graphical object
# Source: https://github.com/tidyverse/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs/047381b48b0f0ef51a174286a595817f01a0dfad
g_legend<-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)
}

# Get legend
leg = g_legend(plot_list[[1]])

# Lay out all of the plots together with a single legend
grid.arrange(arrangeGrob(grobs=lapply(plot_list, function(x) x + guides(colour=FALSE))),
leg,
ncol=2, widths=c(10,1))

Sample Image

How to add geom_text or geom_label with a position relative to the size of a geom_point?

I hope this is sufficiently different to merit another answer. I admit I have overlooked the bit to what exactly you wanted to position the labels. So basically you want it not relative to the point centre, but to the radius.

I felt quite nostalgically reminded of my very own first question in this community, of which I admit I haven't understood its answer for a very long time.

The basic idea is to not use geom_point, but to use ggforce::geom_circle (or: geom_ellipse). You can use the radius which you use for creation for positioning of your labels. It requires a bit of hard coding, but I am sure there would be ways to programmatically define the radius based on your general coordinates.

library(ggplot2)
library(ggforce)

foo <- data.frame(var1 = rep(1:3, 2), var2 =rep(1:2, each = 3),
var3 = c(3, 10, 2, 1, 1, 10))
# Create cuts - you can also assign other values of course. factor of 0.01 was chosen randomly.
foo$rad <- 0.01 * findInterval(foo$var3, 1:5)

ggplot(foo) +
geom_circle(aes(x0 = var1, y0 = var2, r = rad),
fill = "Grey50") +
geom_text(aes(x = var1, y = var2 - rad - 0.03, label = var3)) +
coord_equal()

Sample Image

Created on 2021-01-20 by the reprex package (v0.3.0)

R how to place text on ggplot relative to axis

You can use y = -Inf to orient the text position. In order to keep the text inside the plot, you must couple a vjust factor with Inf, otherwise, the text will fall out of the plot.

Here is the code:

ggplot(data = data, aes(fill=Shelter, y=mean, x=Site_long)) + 
geom_bar(position = "dodge", stat = "identity", width = .8) +
geom_errorbar(aes(ymin = lower, ymax = upper), position = position_dodge(.8), width = .1) +
geom_text(aes(label = mult_compare_growth_all, y = data$upper), vjust = -.5,
position = position_dodge(width = .8), size = 4) +
geom_text(aes(label = sample_size, y = -Inf), vjust = -.3,
position = position_dodge(width = .8)) +
scale_fill_grey(name = "Shelter", start = .8, end = .2) +
labs(x = "Site", y = expression(paste("Coral growth (cm"^"2","/quarter)"))) +
theme_classic(base_size = 14.5) +
theme(text = element_text(size = 18), legend.position = "none",
axis.title.x = element_blank(),
axis.text.y = element_text(angle = 90),
axis.text.x = element_blank())

And the output:

Sample Image

Let me know if this is what you are looking for.

Consistent positioning of text relative to plot area when using different data sets

You can use annotation_custom. This allows you to plot a graphical object (grob) at specified co-ordinates of the plotting window. Just specify the position in "npc" units, which are scaled from (0, 0) at the bottom left to (1, 1) at the top right of the window:

library(ggplot2)

mpg_plot <- ggplot(mpg) + geom_point(aes(displ, hwy))
iris_plot <- ggplot(iris) + geom_point(aes(Petal.Width, Petal.Length))
annotation <- annotation_custom(grid::textGrob(label = "example watermark",
x = unit(0.75, "npc"), y = unit(0.25, "npc"),
gp = grid::gpar(cex = 2)))
mpg_plot + annotation

Sample Image

iris_plot + annotation

Sample Image

Created on 2020-07-10 by the reprex package (v0.3.0)

How to fix the geom_text label position so it is always on the middle of the plot?

Setting either x or y position in geom_text(...) relative to the plot scale in a facet is actually a pretty big problem. @agstudy's solution works if the y scale is the same for all facets. This is because, in calculating range (or max, or min, etc), ggplot uses the unsubsetted data, not the data subsetted for the appropriate facet (see this question).

You can achieve what you want using auxiliary tables, though.

data1 <- data.table(x=1:5, y=1:5, z=c(1,2,1,2,1))
data2 <- data.table(x=1:5, y=11:15, z=c(1,2,1,2,1))

myfun <- function(data){
label.pos <- data[,ypos:=min(y)+0.75*diff(range(y)),by=z] # 75% to the top...

ggplot(data, aes(x=x, y=y)) +
geom_point() +
# geom_text(aes(label=y), y=3) +
geom_text(data=label.pos, aes(y=ypos, label=y)) +
facet_grid(z~., scales="free") # note scales = "free"
}

myfun(data2)

Produces this.

Sample Image

If you want scales="fixed", then @agstudy's solution is the way to go.

Position ggplot text in each corner

This example uses the Inf & -Inf values to position the text at the corners and then hjust and vjust arguments in the geom_text to position the text inside the plot. Use the hjustvar and vjustvar to position them further into or outside the plot.

As mentioned by @baptiste it's best to use a new data set for the annotations

df <- data.frame(x2=rnorm(100),y2=rnorm(100));library(ggplot2)

annotations <- data.frame(
xpos = c(-Inf,-Inf,Inf,Inf),
ypos = c(-Inf, Inf,-Inf,Inf),
annotateText = c("Bottom Left (h0,v0)","Top Left (h0,v1)"
,"Bottom Right h1,v0","Top Right h1,v1"),
hjustvar = c(0,0,1,1) ,
vjustvar = c(0,1,0,1)) #<- adjust

ggplot(df, aes(x2, y2)) + geom_point()+
geom_text(data=annotations,aes(x=xpos,y=ypos,hjust=hjustvar,vjust=vjustvar,label=annotateText))

Example of Text Annotations in Corner

If we wanted to change any of the text positions, we would adjust the horizontal positions with hjustvar and the vertical positions with vjustvar.

# How To Adjust positions (away from corners)
annotations$hjustvar<-c(0, -1.5, 1, 2.5) # higher values = right, lower values = left
annotations$vjustvar<-c(0,1,0,1) # higher values = up, lower values = down

ggplot(df, aes(x2, y2)) + geom_point()+
geom_text(data = annotations, aes(x=xpos,y=ypos,hjust=hjustvar,
vjust=vjustvar,label=annotateText))

Height Adjustment away from Corners

Hope this works!

Aligning a geom_text layer vertically on a bar chart

You can set a uniform label height for each group using if_else (or case_when for >2 groups). For a single plot, you can simply set a value, e.g., label_height = if_else(college_enrolled == "Enrolled", 20000, 3000). To make the relative height consistent across multiple plots, you can instead set label_height as a proportion of the y-axis range:

library(tidyverse)

# make a fake dataset
enroll_cohort <- expand_grid(
chrt_grad = factor(2014:2021),
college_enrolled = factor(c("Enrolled", "Not Enrolled")),
) %>%
mutate(
n = sample(18000:26000, 16),
n = if_else(college_enrolled == "Enrolled", n, as.integer(n / 3))
)

enroll_bar <- enroll_cohort %>%
group_by(chrt_grad) %>% # find each bar's height by summing up `n`
mutate(bar_height = sum(n)) %>% # within each year
ungroup() %>%
mutate(label_height = if_else(
college_enrolled == "Enrolled",
max(bar_height) * .6, # axis height is max() of bar heights;
max(bar_height) * .1 # set label_height as % of axis height
)) %>%
ggplot() +
geom_col(aes(x = chrt_grad, y = n, fill = college_enrolled), color = NA) +
geom_text(
aes(x = chrt_grad, y = label_height, label = n),
color = "white"
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
labs(x = NULL, y = NULL) +
scale_fill_manual(values = c("#00aeff", "#005488"))
A stacked bar chart with y axis 0 to about 30,000. Value labels are at the same height for each group regardless of y value.

If we generate another dataset with a different range of n values -- e.g., ~1200 - ~2000 -- the text labels stay at the same relative positions:
A stacked bar chart with y axis 0 to 2500. Value labels are at the same height for each group regardless of y value.



Related Topics



Leave a reply



Submit