Plot Labels At Ends of Lines

Plot labels at ends of lines

To use Baptiste's idea, you need to turn off clipping. But when you do, you get garbage. In addition, you need to suppress the legend, and, for geom_text, select Capex for 2014, and increase the margin to give room for the labels. (Or you can adjust the hjust parameter to move the labels inside the plot panel.) Something like this:

library(ggplot2)
library(grid)

p = ggplot(temp.dat) +
geom_line(aes(x = Year, y = Capex, group = State, colour = State)) +
geom_text(data = subset(temp.dat, Year == "2014"), aes(label = State, colour = State, x = Inf, y = Capex), hjust = -.1) +
scale_colour_discrete(guide = 'none') +
theme(plot.margin = unit(c(1,3,1,1), "lines"))

# Code to turn off clipping
gt <- ggplotGrob(p)
gt$layout$clip[gt$layout$name == "panel"] <- "off"
grid.draw(gt)

Sample Image

But, this is the sort of plot that is perfect for directlabels.

library(ggplot2)
library(directlabels)

ggplot(temp.dat, aes(x = Year, y = Capex, group = State, colour = State)) +
geom_line() +
scale_colour_discrete(guide = 'none') +
scale_x_discrete(expand=c(0, 1)) +
geom_dl(aes(label = State), method = list(dl.combine("first.points", "last.points")), cex = 0.8)

Sample Image

Edit To increase the space between the end point and the labels:

ggplot(temp.dat, aes(x = Year, y = Capex, group = State, colour = State)) + 
geom_line() +
scale_colour_discrete(guide = 'none') +
scale_x_discrete(expand=c(0, 1)) +
geom_dl(aes(label = State), method = list(dl.trans(x = x + 0.2), "last.points", cex = 0.8)) +
geom_dl(aes(label = State), method = list(dl.trans(x = x - 0.2), "first.points", cex = 0.8))

Plot labels at end of lines

Here is an example that should clarify how you can put the label at the end of each line.

library(ggplot2)
library(ggrepel)
library(dplyr)

# Create a dataset similar to your obs1
n <- 10
obs1 <- data.frame(Profundidad=rep(seq(0, 20000, length.out=n), n),
value = rep(sqrt(0:9),n)*rep(1:10, each=n),
variable=factor(rep(1:10, each=n)))

# Find the x and y position of the last point for each line
xy_labs <- obs1 %>%
group_by(variable) %>%
summarize(pos = which.max(Profundidad),
x = Profundidad[pos],
y = value[pos])

ggplot(obs1, aes(Profundidad, value, group=variable, col = variable)) +
geom_line() +
geom_point(alpha = 0.5)+
theme_linedraw() +
theme(axis.text = element_text(size = 12),
axis.title.x = element_text(size = 16),
axis.title.y = element_text(size = 16),
legend.position = "none") +
labs(x =" Profundidad de secuenciación", y = "ASVs observados")+
geom_label_repel(data=xy_labs, aes(x=x, y=y, label = y),
nudge_x = 0.1, inherit.aes=F,
na.rm = FALSE)

Sample Image

label end of lines outside of plot area

An alternative to ggrepel is to use geom_text and turn "clipping" off (similar to this question/answer), e.g.

covid %>%                                    
ggplot(aes(x = date, y = deaths_roll7_100k, color = Province_State)) +
geom_line() +
scale_y_continuous(breaks = seq(0, 2.4, .2)) +
scale_x_date(breaks = seq.Date(from=as.Date('2020-09-01'),
to=as.Date('2021-07-12'),
by="month"),
date_labels = "%b\n%Y",
limits = as.Date(c("2020-09-01", "2021-07-01"))) +
geom_text(data = . %>% filter(date == max(date)),
aes(color = Province_State, x = as.Date(Inf),
y = deaths_roll7_100k),
hjust = 0, size = 4, vjust = 0.7,
label = c("Arizona\n", "North Carolina")) +
coord_cartesian(expand = FALSE, clip = "off")

example_1.png

--

With some more tweaks and the Financial-Times/ftplottools R theme you can get the plot looking pretty similar to the Financial Times figure, e.g.

library(tidyverse)
#remotes::install_github("Financial-Times/ftplottools")
library(ftplottools)
library(extrafont)
#font_import()
#fonts()

covid %>%
ggplot() +
geom_line(aes(x = date, y = deaths_roll7_100k,
group = Province_State, color = Province_State)) +
geom_text(data = . %>% filter(date == max(date)),
aes(color = Province_State, x = as.Date(Inf),
y = deaths_roll7_100k),
hjust = 0, size = 4, vjust = 0.7,
label = c("Arizona\n", "North Carolina")) +
coord_cartesian(expand = FALSE, clip = "off") +
ft_theme(base_family = "Arimo for Powerline") +
theme(plot.margin = unit(c(1,6,1,1), "lines"),
legend.position = "none",
plot.background = element_rect(fill = "#FFF1E6"),
axis.title = element_blank(),
panel.grid.major.x = element_line(colour = "gray75"),
plot.caption = element_text(size = 8, color = "gray50")) +
scale_color_manual(values = c("#E85D8C", "#0D5696")) +
scale_x_date(breaks = seq.Date(from = as.Date('2020-09-01'),
to = as.Date('2021-07-01'),
by = "1 month"),
limits = as.Date(c("2020-09-01", "2021-07-01")),
date_labels = "%b\n%Y") +
scale_y_continuous(breaks = seq(from = 0, to = 2.4, by = 0.2)) +
labs(title = "New deaths attributed to Covid-19 in North Carolina and Arizona",
subtitle = "Seven-day rolling average of new deaths (per 100k)\n",
caption = "Source: Analysis of data from John Hopkins SSE\nUpdated: 12th July 2021 | CCBY4.0")

example_3.png

Plot labels at ends of lines on left-hand side

A simple solution is to modify the layout of the gtable associated to the p plot.

# Generate a toy dataset
set.seed(123)
temp = data.frame(year=rep(2001:2015,2), value=cumsum(rnorm(30)), variable=rep(c("A","B"),each=15))
library(scales)
temp$value <- rescale(temp$value, to=c(95*10^6,105*10^6))


library(ggplot2)
p <- ggplot(temp, aes(x=year, y=value/10^6, group=variable)) + geom_point(shape=1, size=2) + geom_line(size=1) +
geom_text(data=temp[temp$year==min(temp$year),], aes(label=c("MSA 2012", "MSA en cours"), x=year-.25, y=value/10^6),
hjust = 1, size=4.6) +
scale_x_continuous(breaks=seq(2001,2015,2)) + scale_y_continuous(breaks=seq(95,105,5), position="right") +
labs(x="Année", y="Emploi (millions)") +
theme(panel.grid.major=element_blank(), panel.grid.minor=element_blank(), panel.background=element_blank(),
axis.line = element_line(colour = "black"), legend.position="top", legend.direction="horizontal",
axis.text = element_text(color="black", size=13), axis.title = element_text(color="black", size=13),
aspect.ratio=.25)

library(grid)
gt <- ggplotGrob(p)
### Modify the layout of the gtable
gt$widths[[2]] <- unit(2.5, "cm")
###
gt$layout$clip[gt$layout$name == "panel"] <- "off"
grid.draw(gt)

Sample Image

How to label end of lines of plot area (Seaborn or Matplotlib)

Not perfect but here is the idea using ax.text(...):

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("https://github.com/owid/energy-data/raw/master/owid-energy-data.csv")

year_start, year_end = 2000, 2020
countries = ("Afghanistan", "Bhutan", "Cambodia", "Denmark", "Egypt", "Finland")
column = "energy_cons_change_pct"

df_ = df.copy()
df_ = df_[df_["country"].isin(countries)]
df_ = df_[df_["year"] >= year_start]
df_ = df_[df_["year"] <= year_end]
df_ = df_.fillna(method="ffill").fillna(method="bfill")


fig, ax = plt.subplots(figsize=(20, 20))

sns.lineplot(
data=df_,
x="year",
y=column,
hue="country",
style="country",
legend=False,
ax=ax,
)

last_values = df_[["country", column]][df_["year"] == year_end]

for country, value in last_values.itertuples(index=False):
ax.text(x=year_end + 0.2, y=value, s=country, va="center")

ax.grid()
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.spines["left"].set_visible(False)
ax.set_xticks(np.arange(year_start, year_end + 1))
ax.set_xlim([None, year_end + 0.2])

plt.show()

Legend on lines

Plot labels at ends of lines in stacked area chart

The question code is plotting the text labels in the value's of the last time, when in fact the areas are cumulative. And in reverse order.

Also, the following graph plots data created with the same code but with

set.seed(1234)

Then the data creation code is the same as in the question.

# stacked area chart
ggplot(data, aes(x=time, y=value, fill=group)) +
geom_area()+
geom_text(data = data %>%
filter(time == last(time)) %>%
mutate(value = cumsum(rev(value))),
aes(label = rev(group),
x = time + 0.5,
y = value,
color = rev(group))) +
guides(color = FALSE) + theme_bw() +
scale_x_continuous(breaks = scales::pretty_breaks(10))

Sample Image

Edit.

Following the discussion in the comments to this answer, I have decided to post code based on the comment by user Jake Kaupp.

ggplot(data, aes(x = time, y = value, fill = group)) + 
geom_area()+
geom_text(data = data %>% filter(time == last(time)),
aes(x = time + 0.5, y = value,
label = rev(group), color = rev(group)),
position = position_stack(vjust = 0.5)) +
guides(color = FALSE) +
theme_bw() +
scale_x_continuous(breaks = scales::pretty_breaks(10))

How to add horizontal line labels at end of line when using multiple plots in Roassal3

Following a tip from Alexandre Bergel, I now have a reasonable way to draw labels at the end of plot lines. For interest, I've included, in the playground code below, three ways to identify the plot lines, using the end of plot labels, using right side decorations and using a more standard legend. It's messy, in places but can be cleaned up a lot by moving code to more appropriate objects.

| chart plot dates values firstDate labels legend offset plotLabel renderedLabel canvasCopy maxOffset |
chart := RSChart new.
canvasCopy := RSCanvas new.
dates := (10 to: 1 by: -1) collect: [ :i | Date today subtractDays: i ].
firstDate := dates first.
offset := 20 @ -50.
maxOffset := 0.
values := #(
#(3.72 4.27 3.99 4.91 5.09 4.91 5.09 4.91 4.44 4.91)
#(4.29 4.01 3.82 3.91 4.01 3.73 4.47 4.28 4.18 4.00)
#(1.98 2.04 2.12 2.12 2.21 2.27 2.27 2.10 2.19 1.95)
#(2.5 2.3 2.7 2.73 2.15 2.6 2.63 2.57 2.4 2.8)
#(2.0 1.98 1.98 1.98 1.99 1.96 2.07 1.96 1.90 1.95)
).
labels := #('first series' 'second series' 'series number 4' 'the third series' 'a fifth series').
values with: labels do: [ :series :label |
plot := RSLinePlot new.
plot
x: (dates collect: [ :date | date julianDayNumber - firstDate julianDayNumber ])
y: series.
chart addPlot: plot.
"If adding a legend to the right, RSYLabel decoration is needed but with modified offsets to get them to lay out vertically."
plotLabel := RSYLabelDecoration new right;
title: '~' , label;
fontSize: 12;
rotationAngle: 90;
color: (chart colorFor: plot);
offset: offset;
yourself.
chart addDecoration: plotLabel.
renderedLabel := (plotLabel renderIn: canvasCopy) label.
maxOffset := maxOffset max: renderedLabel textWidth.
offset := (0 - maxOffset) @ (offset y + renderedLabel textHeight + 4).
].
canvasCopy := nil.
chart addDecoration: (RSHorizontalTick new labelConversion: [ :value |
Date julianDayNumber: firstDate julianDayNumber + value ]; useDiagonalLabel; yourself).
chart addDecoration: RSVerticalTick new.
"When adding labels at the end of the plot lines, the right hand line of the chart box will overlay the labels so alter the
spine to a polygon which only draws the x and y axes. "
spine := chart decorations
detect: [ :d | d class == RSChartSpineDecoration ].
spine shape: (RSPolygon new points: {(0 @ 0). (0 @ 0). (0 @ (chart extent y)). (chart extent x @ (chart extent y)). (chart extent x @ (chart extent y)). (0 @ (chart extent y)) }; noPaint; withBorder; yourself).
chart ylabel: 'The values'.
chart build.
"To use standard legend under the chart"
legend := RSLegend new defaultLabel: (RSLabel new fontSize: 8; yourself).
legend container: chart canvas.
labels with: chart plots do: [ :c : p |
legend text: c withShape: (RSPolygon new points: { 0 @ -2. 10 @ -2. 10 @ 2. 0 @ 2 }; color: (chart colorFor: p))
].
legend layout grid.
legend build.

rsLabels := Dictionary new: chart plots size.
chart plots with: labels do: [ :plot :label | rsLabels at: plot put: ((RSLabel text: label) fontSize: 5) ].
lastys := chart plots collect: [ :pl |
Association key: pl value: (pl yScale scale: pl yValues last) ].
lastys sort: [ :el1 :el2 |
el1 value = el2 value
"If the last y coordinate is the same for both plots, order by the second last y point (reverse ordering)"
ifTrue: [ (el1 key yValues at: el1 key yValues size - 1) > (el2 key yValues at: el2 key yValues size - 1) ]
ifFalse: [ el1 value < el2 value ] ].
lastys withIndexDo: [ :lasty :index |
numPlots := lastys size.
plot := lasty key.
label := rsLabels at: plot.
label color: plot color.
chart canvas add: label.
yPoint := lasty value.
textHeight := label textHeight.
index < numPlots ifTrue: [
"Reset the y point to be about textHeight away from the next one, if they are close"
(diff := (yPoint - (lastys at: index + 1) value) abs) < textHeight
ifTrue: [ yPoint := yPoint - (textHeight / 2) + (diff / 2) ] ].
index > 1 ifTrue: [
"Reset the y point to be about textHeight away from the last one, if they are close"
(diff := ((lastys at: index - 1) value - yPoint ) abs) < textHeight
ifTrue: [ yPoint := yPoint + (textHeight / 2) - (diff / 2) ] ].
label translateTo: ((plot xScale scale: plot xValues last) + (label textWidth / 2) + 1) @ yPoint.
].
^chart canvas

How to display labels at the end of added geom_lines?

The first step of making this a lot easier for yourself is by changing the shape of your data.

Try the "reshape2" package, made by Hadley Wickham, the maker of ggplot.

If you would apply the "melt" function on your data.frame, you would end up with a data.frame with two columns: one for the values (the numbers in you data.frame) and one for the type off the values (the column names of your data.frame).

As an example:

emp.data <- data.frame("emp_dayNumber" = 1:100,
"emp_monthly" = rnorm(100),
"emp_yearly" = rnorm(100),
"emp_WorkedDays" = sample(c(TRUE,FALSE), 100, replace = TRUE))
library(reshape2)

## Select the colums you want to plot:
select.data <- emp.data[ , 1:3]

## Change the data.frame to a long format, and state that you want to keep "emp_dayNumber" variable
## as a separate column (as you use it for the x-axis)
plot.data <- melt(emp.data, id.vars = "emp_dayNumber")

Your data should now look like this:

  emp_dayNumber    variable      value
1 1 emp_monthly 0.4231487
2 2 emp_monthly -1.0966351
3 3 emp_monthly 0.2761555
4 4 emp_monthly 0.8575178
5 5 emp_monthly -0.8528019
6 6 emp_monthly 0.4341048

Now plot your data, where "emp_dayNumber" should be your x, "value" your y and "variable" your color

ggplot(toplot.data, aes(x = "emp_dayNumber", y = "value", color = "variable")) +
geom_line()

Try to always apply this on all your plotting functions. This will in the end save you a lot of time.
For more explanations on long and wide format see: http://www.cookbook-r.com/Manipulating_data/Converting_data_between_wide_and_long_format/

Using this, you could now apply the solution stated in the post linked in the commment by "mnm", or using "ggrepel", as you now only use one y variable!



Related Topics



Leave a reply



Submit