I'm curious if there is a way to overlay labels on a line graph on ggplot2 to avoid going through the line. I've used vjust, which works in most instances, but when there is a large increase or decrease between two dates the line goes through the label making it hard to read. I will put the plot I'm currently using below and the code. In this instance, I'm wanting to move the 920; 1,467; and 1,480 off the line. I'm exporting the plot to powerpoint via the Reporters package, so I can move it manually, I was just wondering if there was a way to avoid that.
Plot:

Code:
library("ggplot2")
library("scales")
line_data <- c(276, 475, 753, 840, 931, 962, 801, 920, 1467, 1840, 1737, 1638, 1789, 1733, 1480, 1464, 1538)
year_data <- c(2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017)
line_data_total <- as.data.frame(cbind(line_data, year_data))
limit_func <- function(x) {
if (nchar(max(x)) == 2){
round(max(x +5), digits = -1)
} else if (nchar(max(x)) == 3){
round(max(x +50), digits = -2)
} else if (nchar(max(x)) == 4){
round(max(x +500), digits = -3)
}
}
ggplot(data = line_data_total, aes(x = year_data, y = line_data, group = 1)) +
geom_line(color = "red", size = 1.2)+
geom_point(color = "red", fill = "red", shape = 23, size = 1.75) +
geom_text(aes(label = paste0(format(round(as.numeric(line_data), 1), nsmall = 0, big.mark = ","))),
size = 3, fontface = "bold", vjust = -2) +
labs(x = '', y = '') +
expand_limits(y = c(0, limit_func(line_data_total$line_data))) +
scale_x_continuous(breaks = seq(min(line_data_total$year_data), max(line_data_total$year_data), 1)) +
scale_y_continuous(labels = comma) +
theme(panel.grid.major.x = element_blank() ,
panel.grid.major.y = element_line( size=.1, color="light gray"),
panel.background = element_rect(fill = "transparent"),
plot.background = element_rect(fill = "transparent"),
axis.text.x = element_text(face = "bold", size = 10),
axis.text.y = element_text(face = "bold", size = 10),
axis.ticks.y = element_blank())
How about using the ggplot2 extension ggrepel:
require(ggrepel)
Replace your geom_text() line with:
geom_text_repel(aes(label = paste0(format(round(as.numeric(line_data), 1), nsmall = 0, big.mark = ","))),
size = 3, fontface = "bold", nudge_y=150)
nudge_y is what is pushing the labels off the line, you could use a combination of nudge_x and nudge_y for more control. And see the package vignette: https://github.com/slowkow/ggrepel/blob/master/vignettes/ggrepel.md

Without fiddling with the settings for geom_text_repel, I wanted to try a solution that was generalized for time series like this. I wrote a function that looks at the two x-neighboring points and calculates the slope between them and whether the middle point is lower, higher, or between its x-neighbors in the y direction.
library(dplyr)
adjust_away_from_line <- function(df, x, y, vextend = 0.5) {
if(!is.data.frame(df)) {return(df)}
x <- enquo(x)
y <- enquo(y)
if(!(quo_name(x) %in% names(df))) {
warning(paste0("Column '", quo_name(x), "' not found in data."))
return(df)
}
if(!(quo_name(y) %in% names(df))) {
warning(paste0("Column '", quo_name(y), "' not found in data."))
return(df)
}
df %>% arrange(!!x) %>%
mutate(nb.slope = case_when(
is.na(lead(!!y)) ~ ( (!!y) - lag(!!y))/( (!!x) - lag(!!x)),
is.na(lag(!!y)) ~ (lead(!!y) - (!!y))/(lead(!!x) - (!!x)),
TRUE ~ (lead(!!y) - lag(!!y))/(lead(!!x) - lag(!!x))
),
nb.pos = case_when(
is.na(lead(!!y)) ~ -sign(nb.slope),
is.na(lag(!!y)) ~ -sign(nb.slope),
(lead(!!y) >= (!!y)) & (lag(!!y) >= (!!y)) ~ 1.1,
!(lead(!!y) >= (!!y)) & !(lag(!!y) >= (!!y)) ~ -1.1,
TRUE ~ -1
),
hjust = case_when(
nb.pos > 1 ~ 0.5,
nb.pos < -1 ~ 0.5,
nb.slope > 0 ~ 1,
nb.slope < 0 ~ 0,
TRUE ~ 0.5
),
vjust = scales::rescale(round(nb.pos), to = c(0-vextend, 1+vextend))) %>%
select(-nb.slope, -nb.pos)
}
Since it takes a dataframe as its first argument, you can use this function in a pipe, giving it the bare names of your x variable and y variable in order:
data.frame(line_data = c(276, 475, 753, 840, 931, 962, 801, 920, 1467,
1840, 1737, 1638, 1789, 1733, 1480, 1464, 1538),
year_data = c(2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017)
) %>%
adjust_away_from_line(year_data, line_data) %>%
ggplot(aes(year_data, line_data)) +
geom_line() +
geom_point() +
geom_text(aes(label = line_data, hjust = hjust, vjust = vjust))

If you want to move the labels further up and down away from the lines, you can do that with the adjust_away_from_line(..., vextend = ##) argument. The default is 0.5, but you might want 0.75 or 1 in different applications.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With