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)
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"))
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))
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()
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:
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
iris_plot + annotation
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.
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))
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))
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"))
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:
Related Topics
Mgcv: How to Set Number And/Or Locations of Knots for Splines
Ggplot2 Theme with No Axes or Grid
Check If Each Row of a Data Frame Is Contained in Another Data Frame
Highlight (Shade) Plot Background in Specific Time Range
Add Moving Average Plot to Time Series Plot in R
Display Only Months in Daterangeinput or Dateinput for a Shiny App [R Programming]
Unnest a List Column Directly into Several Columns
Detect Non Ascii Characters in a String
Multiply Many Columns by a Specific Other Column in R with Data.Table
Conditional Assignment of One Variable to the Value of One of Two Other Variables
Create Convex Hull Polygon from Points and Save as Shapefile
Could Not Find Function Inside Foreach Loop
How to Get Factor Matrices in R
Using Trycatch and Rvest to Deal with 404 and Other Crawling Errors
Knitr: Getting a Parse_All Error in R When Converting Rmd File into HTML