ggplot2: manually add a legend - r

How can I map any (unrelated) legend to an existing ggplot?
Disclaimer: please don't hate me. I know the best way to create a legend with 'ggplot2' is to map your data right and I do it 99% of the time. Here however I am asking for something that in general can give me any legend I want.
As an example I have a plot that looks somewhat like this:
created from this code:
set.seed(42)
temp1 = cbind.data.frame(begin = rnorm(10, 0, 1), end = rnorm(10, 2, 1), y1 = 1:10, y2 = 1:10, id = as.character(1:10))
temp2 = cbind.data.frame(x = 0:2, y = 1:3*2)
temp3 = cbind.data.frame(x = seq(0.5, 1.5, 0.33))
temp = c()
plot1 = ggplot(data = temp, aes(x = x)) +
geom_vline(data = temp3, aes(xintercept = x), color = "red", linetype = "longdash") +
geom_segment(data = temp1, aes(y = y1, yend = y2, x = begin, xend = end, color = id)) +
geom_point(data = temp2, aes(x = x, y = y), shape = 4, size = 4) +
scale_color_discrete(guide = F)
plot1
and I want to add a legend that contains:
a red, longdashed vertical line called "l1"
a black, solid horizontal line called "l2"
a green filled block called "l3"
ideally I would produce that somewhat like this (pseudo-code ahead):
plot2 = plot1 + guide(elements = list(list(type = "line", color = "red", linetype = "longdash", direction = "vertical", label = "l1"), list(type = "line", label = "l2"), list(type = "rect", fill = "green", label = "l3"))
my best guess how to approach this would be to create some auxiliary pseudo-data temp that is plotted/mapped somewhere invisible on the plot and then used to create the legend, but I was not successful in getting anything like this to plot me a legend.
Once more, the idea is how can I add any unrelated legend to an existing plot, i.e. without clever mapping of the original data to the plot variables?

A legend can be constructed from scratch: use grid to construct the elements of legend; then use gtable to position the elements within the legend, and the legend within the plot. This is a bit crude, but gives the general idea.
set.seed(42)
temp1 = cbind.data.frame(begin = rnorm(10, 0, 1), end = rnorm(10, 2, 1), y1 = 1:10, y2 = 1:10, id = as.character(1:10))
temp2 = cbind.data.frame(x = 0:2, y = 1:3*2)
temp3 = cbind.data.frame(x = seq(0.5, 1.5, 0.33))
temp = c()
library(ggplot2)
library(grid)
library(gtable)
plot1 = ggplot(data = temp, aes(x = x)) +
geom_vline(data = temp3, aes(xintercept = x), color = "red", linetype = "longdash") +
geom_segment(data = temp1, aes(y = y1, yend = y2, x = begin, xend = end, color = id)) +
geom_point(data = temp2, aes(x = x, y = y), shape = 4, size = 4) +
scale_color_discrete(guide = F)
# Construct the six grobs - three symbols and three labels
L1 = linesGrob(x = unit(c(.5, .5), "npc"), y = unit(c(.25, .75), "npc"),
gp = gpar(col = "red", lty = "longdash"))
L2 = linesGrob(x = unit(c(.25, .75), "npc"), y = unit(c(.5, .5), "npc"))
L3 = rectGrob(height = .5, width = .5, gp = gpar(fill = "green", col = NA))
T1 = textGrob("l1", x = .2, just = "left")
T2 = textGrob("l2", x = .2, just = "left")
T3 = textGrob("l3", x = .2, just = "left")
# Construct a gtable - 2 columns X 4 rows
leg = gtable(width = unit(c(1,1), "cm"), height = unit(c(1,1,1,1), "cm"))
leg = gtable_add_grob(leg, rectGrob(gp = gpar(fill = NA, col = "black")), t=2,l=1,b=4,r=2)
# Place the six grob into the table
leg = gtable_add_grob(leg, L1, t=2, l=1)
leg = gtable_add_grob(leg, L2, t=3, l=1)
leg = gtable_add_grob(leg, L3, t=4, l=1)
leg = gtable_add_grob(leg, T1, t=2, l=2)
leg = gtable_add_grob(leg, T2, t=3, l=2)
leg = gtable_add_grob(leg, T3, t=4, l=2)
# Give it a title (if needed)
leg = gtable_add_grob(leg, textGrob("Legend"), t=1, l=1, r=2)
# Get the ggplot grob for plot1
g = ggplotGrob(plot1)
# Get the position of the panel,
# add a column to the right of the panel,
# put the legend into that column,
# and then add another spacing column
pos = g$layout[grepl("panel", g$layout$name), c('t', 'l')]
g = gtable_add_cols(g, sum(leg$widths), pos$l)
g = gtable_add_grob(g, leg, t = pos$t, l = pos$l + 1)
g = gtable_add_cols(g, unit(6, "pt"), pos$l)
# Draw it
grid.newpage()
grid.draw(g)

Related

How to plot 'outside' of plotting area using ggplot in R?

I recently asked this question. However, I am asking a separate question now as the scope of my new question falls outside the range of the last question.
I am trying to create a heatmap in ggplot... however, outside of the axis I am trying to plot geom_tile. The issue is I cannot find a consistent way to get it to work. For example, the code I am using to plot is:
library(colorspace)
library(ggplot2)
library(ggnewscale)
library(tidyverse)
asd <- expand_grid(paste0("a", 1:9), paste0("b", 1:9))
df <- data.frame(
a = asd$`paste0("a", 1:9)`,
b = asd$`paste0("b", 1:9)`,
c = sample(20, 81, replace = T)
)
# From discrete to continuous
df$a <- match(df$a, sort(unique(df$a)))
df$b <- match(df$b, sort(unique(df$b)))
z <- sample(10, 18, T)
# set color palettes
pal <- rev(diverging_hcl(palette = "Blue-Red", n = 11))
palEdge <- rev(sequential_hcl(palette = "Plasma", n = 11))
# plot
ggplot(df, aes(a, b)) +
geom_tile(aes(fill = c)) +
scale_fill_gradientn(
colors = pal,
guide = guide_colorbar(
frame.colour = "black",
ticks.colour = "black"
),
name = "C"
) +
theme_classic() +
labs(x = "A axis", y = "B axis") +
new_scale_fill() +
geom_tile(data = tibble(a = 1:9,
z = z[1:9]),
aes(x = a, y = 0, fill = z, height = 0.3)) +
geom_tile(data = tibble(b = 1:9,
z = z[10:18]),
aes(x = 0, y = b, fill = z, width = 0.3)) +
scale_fill_gradientn(
colors = palEdge,
guide = guide_colorbar(
frame.colour = "black",
ticks.colour = "black"
),
name = "Z"
)+
coord_cartesian(clip = "off", xlim = c(0.5, NA), ylim = c(0.5, NA)) +
theme(aspect.ratio = 1,
plot.margin = margin(10, 15.5, 25, 25, "pt")
)
This produces something like this:
However, I am trying to find a consistent way to plot something more like this (which I quickly made in photoshop):
The main issue im having is being able to manipulate the coordinates of the new scale 'outside' of the plotting area. Is there a way to move the tiles that are outside so I can position them in an area that makes sense?
There are always the two classic options when plotting outside the plot area:
annotate/ plot with coord_...(clip = "off")
make different plots and combine them.
The latter option usually gives much more flexibility and way less headaches, in my humble opinion.
library(colorspace)
library(tidyverse)
library(patchwork)
asd <- expand_grid(paste0("a", 1:9), paste0("b", 1:9))
df <- data.frame(
a = asd$`paste0("a", 1:9)`,
b = asd$`paste0("b", 1:9)`,
c = sample(20, 81, replace = T)
)
# From discrete to continuous
df$a <- match(df$a, sort(unique(df$a)))
df$b <- match(df$b, sort(unique(df$b)))
z <- sample(10, 18, T)
# set color palettes
pal <- rev(diverging_hcl(palette = "Blue-Red", n = 11))
palEdge <- rev(sequential_hcl(palette = "Plasma", n = 11))
# plot
p_main <- ggplot(df, aes(a, b)) +
geom_tile(aes(fill = c)) +
scale_fill_gradientn("C",colors = pal,
guide = guide_colorbar(frame.colour = "black",
ticks.colour = "black")) +
theme_classic() +
labs(x = "A axis", y = "B axis")
p_bottom <- ggplot() +
geom_tile(data = tibble(a = 1:9, z = z[1:9]),
aes(x = a, y = 0, fill = z, height = 0.3)) +
theme_void() +
scale_fill_gradientn("Z",limits = c(0,10),
colors = palEdge,
guide = guide_colorbar(
frame.colour = "black", ticks.colour = "black"))
p_left <- ggplot() +
theme_void()+
geom_tile(data = tibble(b = 1:9, z = z[10:18]),
aes(x = 0, y = b, fill = z, width = 0.3)) +
scale_fill_gradientn("Z",limits = c(0,10),
colors = palEdge,
guide = guide_colorbar( frame.colour = "black", ticks.colour = "black"))
p_left + p_main +plot_spacer()+ p_bottom +
plot_layout(guides = "collect",
heights = c(1, .1),
widths = c(.1, 1))
Created on 2021-02-21 by the reprex package (v1.0.0)

Grid as bars in ggplot

A common layout in many sites is to draw the grid as shaded bars:
I'm doing this with this function:
grid_bars <- function(data, y, n = 5, fill = "gray90") {
breaks <- pretty(data[[y]], n)
len <- length(breaks)-1
all_bars <- data.frame(
b.id = rep(1:len, 4),
b.x = c(rep(-Inf, len), rep(Inf, len*2), rep(-Inf, len)),
b.y = c(rep(breaks[-length(breaks)], 2), rep(breaks[-1], 2))
)
bars <- all_bars[all_bars$b.id %in% (1:len)[c(FALSE, TRUE)], ]
grid <- list(
geom_polygon(data = bars, aes(b.x, b.y, group = b.id),
fill = fill, colour = fill),
scale_y_continuous(breaks = breaks),
theme(panel.grid = element_blank())
)
return(grid)
}
#-------------------------------------------------
dat <- data.frame(year = 1875:1972,
level = as.vector(LakeHuron))
ggplot(dat, aes(year, level)) +
grid_bars(dat, "level", 10) +
geom_line(colour = "steelblue", size = 1.2) +
theme_classic()
But it needs to specify data and y again. How to take those directly from the ggplot?
After having a look at the options for extending ggplot2 in Hadley Wickham's book on ggplot2 you probably have to set up your own Geom or Stat layer to achieve the desired result. This way you can access the data and aesthetics specified in ggplot() or even pass different data and aesthetics to your fun. Still a newbie in writing extensions for ggplot2 but a first approach may look like so:
library(ggplot2)
# Make bars dataframe
make_bars_df <- function(y, n) {
breaks <- pretty(y, n)
len <- length(breaks) - 1
all_bars <- data.frame(
group = rep(1:len, 4),
x = c(rep(-Inf, len), rep(Inf, len * 2), rep(-Inf, len)),
y = c(rep(breaks[-length(breaks)], 2), rep(breaks[-1], 2))
)
all_bars[all_bars$group %in% (1:len)[c(FALSE, TRUE)], ]
}
# Setup Geom
geom_grid_bars_y <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, n = 5, ...) {
layer(
geom = GeomGridBarsY, mapping = mapping, data = data, stat = stat,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(n = n, ...)
)
}
GeomGridBarsY <- ggproto("GeomGridBarsY", Geom,
required_aes = c("y"),
default_aes = aes(alpha = NA, colour = NA, fill = "gray90", group = NA,
linetype = "solid", size = 0.5, subgroup = NA),
non_missing_aes = aes("n"),
setup_data = function(data, params) {
transform(data)
},
draw_group = function(data, panel_scales, coord, n = n) {
bars <- make_bars_df(data[["y"]], n)
# setup data for GeomPolygon
## If you want this to work with facets you have to take care of the PANEL
bars$PANEL <- factor(1)
# Drop x, y, group from data
d <- data[ , setdiff(names(data), c("x", "y", "group"))]
d <- d[!duplicated(d), ]
# Merge information in data to bars
bars <- merge(bars, d, by = "PANEL")
# Set color = fill
bars[["colour"]] <- bars[["fill"]]
# Draw
grid::gList(
ggplot2::GeomPolygon$draw_panel(bars, panel_scales, coord)
)
},
draw_key = draw_key_rect
)
grid_bars <- function(n = 5, fill = "gray90") {
list(
geom_grid_bars_y(n = n, fill = fill),
scale_y_continuous(breaks = scales::pretty_breaks(n = n)),
theme(panel.grid = element_blank())
)
}
dat <- data.frame(year = 1875:1972,
level = as.vector(LakeHuron))
ggplot(dat, aes(year, level)) +
grid_bars(n = 10, fill = "gray95") +
geom_line(colour = "steelblue", size = 1.2) +
theme_classic()
Just for reference:
A first and simple approach to get grid bars one could simply adjust the size of the grid lines via theme() like so:
# Simple approach via theme
ggplot(dat, aes(year, level)) +
geom_line(colour = "steelblue", size = 1.2) +
scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
theme_classic() +
theme(panel.grid.major.y = element_line(size = 8))
Created on 2020-06-14 by the reprex package (v0.3.0)

Lines on top and bottom of ggplot plot area

I want to add a line on the top and bottom of my plots (bottom line below the x label and axis) created using ggplot2. So far I have added a rectangle around the plot, but I do not want the lines on the sides.
x <- 1:10
y <- rnorm(10,mean = x)
df <- data.frame(x,y)
library(ggplot2)
ggplot(data = df, mapping = aes(x,y)) + geom_point() +
theme(plot.background = element_rect(size = 1, color = 'blue'))
I hope you guys have a solution.
Will something similar to this work?
x <- 1:10
y <- rnorm(10,mean = x)
df <- data.frame(x,y)
ggplot(data = df, mapping = aes(x,y)) + geom_point() +
annotate(geom = 'segment',
y = Inf,
yend = Inf,
x = -Inf,
xend = Inf,
size = 2) +
theme(axis.line.x = element_line(size = 1))
Not a perfect, but working solution. You have to plot huge "-" (size = 1000) outside plot area. This solution is not perfect as you have to manually adjust position of "-" on the y-axis.
df <- data.frame(x = 1:10, y = 1:10)
library(ggplot2)
ggplot(df, aes(x, y)) +
geom_point() +
# Y position adjusted manually
geom_text(aes(5, 2.9, label = "-"), color = "blue", size = 1000) +
# Y position adjusted manually
geom_text(aes(5, 21.2, label = "-"), color = "blue", size = 1000) +
# Plot outside plot area
coord_cartesian(ylim = c(0, 10), clip = "off")
I am not completely happy with the solution as I don't fully grasp
how to change the size of the lines
why they are not perfectly aligned with top and bottom when using patchwork::wrap_plots()
why it does not show the top line using ggpubr::ggarrange() or cowplot::plot_grid()
but based on this code, I suggest the following solution:
library(ggplot2)
df <- data.frame(x = 1:5, y = 1:5)
p <- ggplot(data = df) + aes(x, y) + geom_point()
top_line <- grid::grobTree(grid::linesGrob(x = grid::unit(c(0, 1), "npc"), y = grid::unit(1, "npc")))
bot_line <- grid::grobTree(grid::linesGrob(x = grid::unit(c(0, 1), "npc"), y = grid::unit(0, "npc")))
patchwork::wrap_plots(top_line, p, bot_line,
ncol = 1, nrow = 3,
heights = c(0, 1, 0))
ggpubr::ggarrange(top_line, p, bot_line,
ncol = 1, nrow = 3,
heights = c(0, 1, 0))
cowplot::plot_grid(top_line, p, bot_line,
ncol = 1, nrow = 3,
rel_heights = c(0, 1, 0))
Created on 2022-08-25 with reprex v2.0.2

Not to draw specific part of polygon

I have the following data structure:
y <- rep(1:10, 2)
group <- rep(c('a', 'b'), each = 10)
dens <- c(c(seq(from = 0, to = 0.8, by = 0.1), 0),
c(seq(from = -0, to = -0.8, by = -0.1), 0))
my_dat <- data.frame(group, dens, y, stringsAsFactors = FALSE )
These are calculated density disributions, in order to make a grouped violin plot, such as in
Split violin plot with ggplot2
# Plot 1:
require(ggplot2)
ggplot(my_dat, aes(x = dens, y = y, fill = group)) +
geom_polygon(color = 'black', show.legend = FALSE)
Now this is simplified, because my data contains hundreds of rows for a smooth outline. (However, there is the central vertical line in my case.) I would now like to remove exactly this vertical central line.
(I guess the problem is removing any specified part of the polygon.)
An idea in my example was to overplot this with a vertical line:
#Plot 2
ggplot(my_dat, aes(x = dens, y = y, fill = group)) +
geom_polygon(color = 'black', show.legend = FALSE) +
geom_segment(x = 0,
xend = 0,
y = min(y) + 0.2,
yend = max(y) - 0.2,
color = '#00BFC4')
But to get the end of the over plotting segment line correct is tricky. (I have purposefully left the line a bit too short for demonstration)
edit
the groups are not distributed in a symmetrical fashion, although my example strongly suggests so.
You can always just plot another polygon on top
x <- with(my_dat, chull(dens, y))
my_dat2 <- my_dat[c(x, x[1L]), ]
ggplot(my_dat, aes(x = dens, y = y, fill = group)) +
geom_polygon(show.legend = FALSE) +
geom_polygon(data = my_dat2, aes(group = 1), size = 1,
fill = 'transparent',
# fill = NA, ## or this
color = 'black')
I think the simpler solution is to first draw all the outlines and then all the filled areas. This should work for any arbitrary polygon shapes.
y <- rep(1:10, 2)
group <- rep(c('a', 'b'), each = 10)
dens <- c(c(seq(from = 0, to = 0.8, by = 0.1), 0),
c(seq(from = -0, to = -0.8, by = -0.1), 0))
my_dat <- data.frame(group, dens, y, stringsAsFactors = FALSE )
require(ggplot2)
ggplot(my_dat, aes(x = dens, y = y)) +
geom_polygon(color = 'black', fill = NA, size = 2) +
geom_polygon(aes(fill = group), color = NA)

Positioning x-axis text/label along x-axis based on another field in the data using ggplot

I would like to place each x-axis text/label based on another field. Is there a native way in ggplot2 to achieve this? Presently I am doing it through geom_text. Here are my data and the plot.I have two issues with this approach -
Labels are falling inside the plot area
For a facet the labels should only appear at the bottom-most subplots as below
not in all subplots as is the case below (my plot). (The above image was taken from here)
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A','B','C'),
Year = 2010:2011,PNo = paste0("X-",1:4)),Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A',0,ifelse(mydata$Tag=='B',2,7))
mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +
geom_bar(stat='summary',position = 'dodge',fun.y='mean',width=1) +
facet_wrap(~PNo,ncol=2) +
theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
geom_text(aes(x = dist,label = Tag),color = 'black',size=4,angle = 0,show.legend = F)
I would like to place Tag labels based on dist.
I notice that you have accepted an answer elsewhere, and that you have answered you own question here. But they don't quite answer your original question. In particular, the labels are still inside the plot panel. I offer two possibilities, but neither being straightforward.
The first uses a version of annotation_custom. The default annotation_custom draws the annotation in all panels. But with a small alteration (taken from here), it can be made to draw annotations in selected panels - for your plot, the lower two panels.
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'),
Year = 2010:2011, PNo = paste0("X-", 1:4)), Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))
# The bar plot. Note extra margin above x-axis title.
# This gives space for the annotations between the panel and the title.
p1 = mydata %>% ggplot() +
geom_bar(aes(x = dist, y = Value, fill = factor(Year)),
width = 1, stat = 'identity', position = "dodge") +
facet_wrap(~PNo, ncol = 2) +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.x = element_text(margin = margin(t = 2, unit = "lines")))
# Baptiste's modification to annotation_custom
annotation_custom2 =
function (grob, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf, data) {
layer(data = data, stat = StatIdentity, position = PositionIdentity,
geom = ggplot2:::GeomCustomAnn,
inherit.aes = TRUE, params = list(grob = grob,
xmin = xmin, xmax = xmax,
ymin = ymin, ymax = ymax))
}
# The plot with annotations. (ymin and ymax set to -Inf
# draws the annotation at the bottom of the panel.
# vjust = 1.5 drops them below the panel).
for (i in 1:length(unique(mydata$Tag))) {
p1 = p1 + annotation_custom2(
grob = textGrob(label = unique(mydata$Tag)[i], vjust = 1.5,
gp = gpar(col = 'red', cex = 1)),
xmin = unique(mydata$dist)[i],
xmax = unique(mydata$dist)[i],
ymin = -Inf,
ymax = -Inf,
data=data.frame(PNo=c("X-3", "X-4") )) # The two bottom panels
}
# The annotations are placed outside the panels.
# Therefore, have to turn off clipping to the panels.
g1 = ggplotGrob(p1)
g1$layout$clip[grepl("panel", g1$layout$name)] = "off"
# Draw the chart
grid.newpage()
grid.draw(g1)
The second draws two charts: p1 is your bar plot, and p2 contains the labels only. The trick is to get the x-axes in the two charts to be the same. Then, plot panels are extracted from p2, and placed into a p1, but into a new row just below p1's plot panel.
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'),
Year = 2010:2011,PNo = paste0("X-", 1:4)),Value = round(runif(24, 1, 20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))
# The bar plot
p1 = mydata %>% ggplot(aes(x = dist, y = Value, fill = factor(Year))) +
geom_bar(stat = 'summary', position = 'dodge',fun.y = 'mean', width = 1) +
facet_wrap(~PNo, ncol = 2) +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
# To get the range of x values -
# so that the extent of the x-axis in p1 and in the following p2 are the same
gd = ggplot_build(p1)
xrange = gd$layout$panel_params[[1]]$x.range # xrange used in p2 (see below)
# Plot with labels (A, B, and C) only
p2 = mydata %>% ggplot(aes(x = dist, y = Value)) +
facet_wrap(~PNo, ncol = 2) +
geom_label(aes(x = dist, y = 0, label = Tag), size = 6, inherit.aes = F, color = 'red') +
### geom_text(aes(x = dist, y = 0, label = Tag), size=6, color = 'red') + ### Alternative style for labels
scale_x_continuous(lim = xrange, expand = c(0,0)) +
theme_bw() +
theme(panel.grid = element_blank(),
panel.border = element_rect(colour = NA))
# Grab a plot panel from p2
g2 = ggplotGrob(p2)
panels = subset(g2$layout, grepl("panel", g2$layout$name), t:r)
panels = subset(panels, t == min(t))
g2 = g2[unique(panels$t), min(panels$l):max(panels$r)]
# Add a row to p1 to take the plot panels
library(gtable)
library(grid)
g1 <- ggplotGrob(p1)
pos = max(subset(g1$layout, grepl("panel", g1$layout$name), t))
g1 = gtable_add_rows(g1, height = unit(2, "lines"), pos = pos)
# Add the panel (g2) to the new row
g1 = gtable_add_grob(g1,g2, t = pos + 1, l = min(panels$l), r = max(panels$r))
# Draw the chart
grid.newpage()
grid.draw(g1)
I tried to solve the problem myself but was facing some issue. I posted another question on SO here. Together the answer and question solves this question to some extent. Here is a possible solution.
p <- mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +geom_bar(stat='summary',position = 'dodge',fun.y='mean',width = 1) +
facet_wrap(~PNo,ncol=2) +
theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
geom_label(data = mydata %>% dplyr::filter(PNo %in% c('X-3','X-4')),aes(x = dist,y=0,label = Tag),size=6,inherit.aes=F,color = 'red')
library(grid)
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[grep("panel-2-\\d+", gt$layout$name)] <- "off"
grid.draw(gt)

Resources