Fill area below geom_curve in ggplot - r

This is my code:
ggplot(data = tibble(x = 1:20, y = 1:20),
aes(x = x, y = y)) +
geom_curve(x = 10, y = 7.55, xend = 15, yend = 7.6,
curvature = .28, size = .7250, color ='black')+
geom_point()
How can I fill the area below this curve? Between x = 10 and x = 12 and considering the ymax being the curve.

I don't know how to extract geom_curve's internal data produced during rendering, but here's a method:
gg <- ggplot(data = tibble(x = 1:20, y = 1:20),
aes(x = x, y = y)) +
geom_curve(x = 10, y = 7.55, xend = 15, yend = 7.6,
curvature = .28, size = .7250, color ='black')+
geom_point()
We'll produce a curve on our own. I'm using 51 here just to get many points; you may want fewer or more depending on the size of your curve and the resolution of your eventual plot.
curvedat <- data.frame(x = seq(10, 15, len = 51))
curvedat$y <- with(curvedat, 4 + 5*abs((x - 10) / 5 - 0.5)^2)
gg + geom_path(data = curvedat)
(The two curves are shown together here in order to demonstrate their similarity. There's no assumption of maintaining the original curve in the plot. Also, the second curve is shown offset down, I'm assuming you would address that in your suitability decision.)
Once you have a curve that looks okay, then wrap two "low" points around it and add as a polygon:
gg +
geom_polygon(data = rbind(
data.frame(x = min(curvedat$x), y = -Inf),
curvedat,
data.frame(x = max(curvedat$x), y = -Inf)
))

Related

How to produce neat label positions in the ggplot2 line chart?

I have a line chart built using ggplot2. It looks following:
Lines are close to each other and data labels are overlapping. It is not convenient. It would be better if light red labels were below the line and green labels where there is room for them. Something of the sort:
This post is helpful. However, I do not know in advance for which line it would be better to put labels above and for which it would be better to keep them below. Therefore I am looking for a generic solution.
ggrepel does a great job in organizing labels. But cannot figure out how to make it work in my case. I tried different parameters. Here is one of the simplest variants (not the best looking):
Questions:
Is there any way to make in R the chart look like on the 2nd picture?
I think ggrepel computes the best label position taking into account the size of the chart. If I export the chart to PowerPoint, for example, the size of the PowerPoint chart might be different from the size used to get optimal data label positions. Is there any way to pass the size of the chart to ggrepel?
Here is a code I used to generate data and charts:
library(ggplot2)
library(ggrepel)
set.seed(1)
x = rep(1:20, 3)
y = c(runif(20, 10, 11),
runif(20, 11, 12),
runif(20, 12, 13))
z = rep(c("a", "b", "c"), each = 20)
df = data.frame(x = x, y = y, z = z)
ggplot(data = df, aes(x = x, y = y, group = z, color = z)) +
geom_line() +
geom_text(aes(label = round(y, 1)), nudge_y = 1) +
ylim(c(0, 20))
ggplot(data = df, aes(x = x, y = y, group = z, color = z)) +
geom_line() +
geom_text_repel(aes(label = round(y, 1)), nudge_y = 1) +
ylim(c(0, 20))
Changing the theme to theme_bw() and removing gridlines from {ggExtra}'s removeGridX() gets the plot closer your second image. I also increased the size of the lines, limited the axes, and changed geom_text_repel to geom_label_repel to improve readability.
library(ggplot2)
library(ggrepel)
library(ggExtra)
set.seed(1)
x = rep(1:20, 3)
y = c(runif(20, 10, 11),
runif(20, 11, 12),
runif(20, 12, 13))
z = rep(c("a", "b", "c"), each = 20)
df = data.frame(x = x, y = y, z = z)
ggplot(data = df, aes(x = x, y = y, group = z, color = z)) +
theme_bw() + removeGridX() +
geom_line(size = 2) +
geom_label_repel(aes(label = round(y, 1)),
nudge_y = 0.5,
point.size = NA,
segment.color = NA,
min.segment.length = 0.1,
key_glyph = draw_key_path) +
scale_x_continuous(breaks=seq(0,20,by=1)) +
scale_y_continuous(breaks = seq(0, 14, 2), limits = c(0, 14))

How to fill geometric figures created by lines and curves?

I'm trying to fill with different colors the 3 triangles in the following graph.
data = data.frame(x=c(125), y=c(220)) #this data is just to be able to use gplot to draw figures
ggplot(data, aes(x = x, y = y)) +
xlim(0,250) +
ylim(-250, 0) +
geom_curve(x = 33, xend = 223, y = -100, yend = -100, curvature = -.65) +
geom_segment(x=128, xend = 33, y=-208, yend = -100) +
geom_segment(x=128, xend = 223, y=-208, yend = -100) +
geom_segment(x=128, xend = 159.67, y=-208, yend = -45) +
geom_segment(x=128, xend = 96.33, y=-208, yend = -45) +
coord_fixed()
How can I do this?
The short answer: It's a pretty evil hack.
Now let's elaborate: As discussed in especially in this GitHub thread, it is not possible to access the coordinates resulting from geom_curve (it uses CurveGrob for plotting and "These values are all calculated at draw time" [#thomasp85]). One effect of its 'calculation at draw time behaviour' can be seen below - it makes a difference if you add coord_plot or not. This is different with geom_spline: Adding coord_fixed does not change the coordinates.
See below in plot one and two: The red curve is created with geom_curve - it loses touch with the geom_segment lines...
#thomasp85 suggested in the GitHub thread that one could use his package ggforce instead. Now, to have real control over the curvature, one needs to use geom_bspline and play around with the curvature.
Once the curvature is found, one can use the coordinates in the ggplot_build object. We can calculate the polygons based on those coordinates (this is also not quite trivial, because one needs to create cuts and add points for the correct 'edges'). See below.
library(tidyverse)
library(ggforce)
mydata = data.frame(x = 128, xend = c(33, 223, 159.67, 96.33), y = -208, yend = c(-100,-100,-45,-45))
#for spline control points.
my_spline <- data.frame(x = c(33, 128, 223), y = c(-100, 24,-100))
Next I demonstrate the difference between 'calculation at draw time (red curve) and 'direct calculation':
With coord_fixed Both red and black curve touch the segments
ggplot(mydata) +
geom_curve(aes(x = 33, xend = 223, y = -100, yend = -100), curvature = -.65, color = 'red') +
geom_segment(aes(x = x, xend = xend, y = y, yend = yend)) +
geom_bspline(data = my_spline, aes(x, y )) +
coord_fixed()
Without coord_fixed The red curve does not touch the segments, but the black curve still does
ggplot(mydata) +
geom_curve(aes(x = 33, xend = 223, y = -100, yend = -100), curvature = -.65, color = 'red') +
geom_segment(aes(x = x, xend = xend, y = y, yend = yend)) +
geom_bspline(data = my_spline, aes(x, y ))
# Final hack
# Get x/y coordinates from ggplot_build
p <- ggplot(mydata) +
geom_bspline(data = my_spline, aes(x, y ))
pb <- ggplot_build(p)$data[[1]]
#create groups for fill
data_polygon <- data.frame(x = pb[['x']], y = pb[['y']]) %>%
mutate(cut_poly = cut(x, c(-Inf, 96.33, 159.67, Inf), labels = letters[1:3]))
#add corner points - repeat extremes from b, otherwise there will be a gap
data_add <- data_polygon %>%
filter(cut_poly == 'b') %>%
slice(which.min(x), which.max(x)) %>%
mutate(cut_poly = letters[c(1,3)]) %>%
bind_rows(data.frame(x = 128, y = -208, cut_poly = letters[1:3], stringsAsFactors = FALSE)) %>%
arrange(x) #important to arrange, otherwise you get irregular polygons
data_plot <- rbind(data_polygon,data_add)
ggplot(data_plot) +
geom_polygon(aes(x, y, fill = cut_poly), color = 'black')
Created on 2019-12-05 by the reprex package (v0.3.0)
You can access the curve data for the geoms generated in ggforce package, which makes the job of creating polygons from curves much easier.
You can then use geom_polygon to draw individual polygons and fill them with different colors
library(ggforce)
p1 <- ggplot() + geom_arc(aes(x0 = 125, y0 = -200, r = 100, start = -pi/3, end = -pi/9))
p2 <- ggplot() + geom_arc(aes(x0 = 125, y0 = -200, r = 100, start = -pi/9, end = pi/9))
p3 <- ggplot() + geom_arc(aes(x0 = 125, y0 = -200, r = 100, start = pi/9, end = pi/3))
df_poly1 <- rbind(c(125,-200),data.frame(x = ggplot_build(p1)$data[[1]]$x,y = ggplot_build(p1)$data[[1]]$y),c(125,-200))
df_poly2 <- rbind(c(125,-200),data.frame(x = ggplot_build(p2)$data[[1]]$x,y = ggplot_build(p2)$data[[1]]$y),c(125,-200))
df_poly3 <- rbind(c(125,-200),data.frame(x = ggplot_build(p3)$data[[1]]$x,y = ggplot_build(p3)$data[[1]]$y),c(125,-200))
ggplot() +
geom_polygon(data = df_poly1, aes(x,y), fill = 'red') +
geom_polygon(data = df_poly2, aes(x,y), fill = 'blue') +
geom_polygon(data = df_poly3, aes(x,y), fill = 'green')
This will produce an image like this.

How to set a constant "ylim" in geom_raster?

I have a kind of "time series", with different measures taken at regular points on the same individuals.
I want to graphically represent 2 of these time series on the same graph (no problem with that), and add a background which depends on a third factor.
Here a reproducible example of what I've done:
df <- data.frame(
x = seq(1, 20),
y = sample(c(1:10), 20, replace = TRUE),
z = sample(c(1:10), 20, replace = TRUE),
w = sample(c("yes", "no"), 20, replace = TRUE)
)
ggplot(df) +
geom_line(aes(x = x, y = y), color = 'darkorange') +
geom_line(aes(x = x, y = z), color = 'royalblue') +
geom_raster(aes(x = x, y = 5, fill = w, alpha = w)) +
scale_alpha_ordinal(range = c(0, 0.8)) +
scale_fill_manual(values = c("gray32", "gray32"))
Which give me almost what I want excepted that I would like my raster to cover my whole y-axis window.
Any idea?
Thank you!
I think it's simplest to use geom_rect here:
ggplot(df) +
geom_line(aes(x = x, y = y), color = 'darkorange') +
geom_line(aes(x = x, y = z), color = 'royalblue') +
geom_rect(aes(xmin = x - 0.5, xmax = x + 0.5,
ymin = -Inf, ymax = Inf, fill = w, alpha = w)) +
scale_alpha_ordinal(range = c(0, 0.8)) +
scale_fill_manual(values = c("gray32", "gray32"))
It's probably also possible with geom_tile and geom_raster, but I couldn't get the range to cover the whole vertical space without also fiddling with coord_cartesian.

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)

Creating a vertical color gradient for a geom_bar plot

I have searched and searched, but I cant seem to find an elegant way of doing this!
I have a dataset Data consisting of Data$x (dates) and Data$y (numbers from 0 to 1)
I want to plot them in a bar-chart:
ggplot(Data) + geom_bar(aes(x = x, y = y, fill = y, stat = "identity")) +
scale_fill_gradient2(low = "red", high = "green", mid = "yellow", midpoint = 0.90)
The result looks like this
However, I wanted to give each bar a gradient in the vertical direction ranging from 0 (red) to y (greener depending on y). Is there any way of doing this smoothly?
I have tried to see if I could impose a picture on the graph as a hack, but I can't impose it on the bars only except in a super super ugly way.
Another, not very pretty, hack using geom_segment. The x start and end positions (x and xend) are hardcoded (- 0.4; + 0.4), so is the size. These numbers needs to be adjusted depending on the number of x values and range of y.
# some toy data
d <- data.frame(x = 1:3, y = 1:3)
# interpolate values from zero to y and create corresponding number of x values
vals <- lapply(d$y, function(y) seq(0, y, by = 0.01))
y <- unlist(vals)
mid <- rep(d$x, lengths(vals))
d2 <- data.frame(x = mid - 0.4,
xend = mid + 0.4,
y = y,
yend = y)
ggplot(data = d2, aes(x = x, xend = xend, y = y, yend = yend, color = y)) +
geom_segment(size = 2) +
scale_color_gradient2(low = "red", mid = "yellow", high = "green",
midpoint = max(d2$y)/2)
A somewhat related question which may give you some other ideas: How to make gradient color filled timeseries plot in R
Doesn't exist as far as I know, but you can manipulate your data to produce it.
library(ggplot2)
df = data.frame(x=c(1:10),y=runif(10))
prepGradient <- function(x,y,spacing=max(y)/100){
stopifnot(length(x)==length(y))
df <- data.frame(x=x,y=y)
newDf = data.frame(x=NULL,y=NULL,z=NULL)
for (r in 1:nrow(df)){
n <- floor(df[r,"y"]/spacing)
for (s in c(1:n)){
tmp <- data.frame(x=df[r,"x"],y=spacing,z=s*spacing)
newDf <- rbind(newDf,tmp)
}
tmp <- data.frame(x=df[r,"x"],y=df[r,"y"]%%spacing,z=df[r,"y"])
newDf <- rbind(newDf,tmp)
}
return(newDf)
}
df2 <- prepGradient(df$x,df$y)
ggplot(df2,aes(x=x,y=y,fill=z)) +
geom_bar(stat="identity") +
scale_fill_gradient2(low="red", high="green", mid="yellow",midpoint=median(df$y))+
ggtitle('Vertical Gradient Example') +
theme_minimal()
Found a less hacky way to do this when answering Change ggplot bar chart fill colors
library(tidyverse)
df <- data.frame(value = c(20, 50, 90),
group = c(1, 2, 3))
df_expanded <- df %>%
rowwise() %>%
summarise(group = group,
value = list(0:value)) %>%
unnest(cols = value)
df_expanded %>%
ggplot() +
geom_tile(aes(
x = group,
y = value,
fill = value,
width = 0.9
)) +
coord_flip() +
scale_fill_viridis_c(option = "C") +
theme(legend.position = "none")
Because this did not explicitly ask for divergent / multi-hue scales (in the title), here a simple hack for a single-hue gradient. This is very much the approach like suggested for a gradient fill under a curve as seen here
library(ggplot2)
d <- data.frame(x = 1:3, y = 1:3)
n_grad <- 1000
grad_df <- data.frame(yintercept = seq(0, 3, len = 200),
alpha = seq(0.3, 0, len = 200))
ggplot(d ) +
geom_col(aes(x, y), fill = "darkblue") +
geom_hline(data = grad_df, aes(yintercept = yintercept, alpha = alpha),
size = 1, colour = "white", show.legend = FALSE) +
## white background looks nicer then
theme_minimal()

Resources