How to fill geometric figures created by lines and curves? - r

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.

Related

Add guide lines for y-axis when using coord_polar() for polar histogram

I want to create a polar histogram like this
with dashed lines going from the y-axis ticks to the 0/360 line to help the reader to map the count to the histogram's bar length. I have added the dashed lines with power point but I am wondering whether it is possible to add this with ggplot.
I used this code
library(ggplot2)
# Seed
set.seed(20220315)
# Create random data from 0 to 360
example_dat <- data.frame(rot_y = runif(1000, min = 0, max = 360))
# Create polar plot
ggplot(example_dat, aes(x = rot_y )) +
geom_histogram(binwidth = 15, boundary = -7.5, colour = "black", size = .25) +
scale_x_continuous(limits = c(0,360),
breaks = seq(0, 360, by = 60),
minor_breaks = seq(0, 360, by = 15)) +
coord_polar() +
labs(title = 'Polar histogram of y rotation',
y = 'Count',
x = '')
to generate this plot
I am aware of the work around for drawing a line segment like in this solution: draw straight line between any two point when using coord_polar() in ggplot2 (R)
However, when drawing a straight line with function given in that solution using aes(x = 270, y = 0, xend = 720, yend = 60), the line just disappears, while a line can be seen for something like aes(x = 270, y = 0, xend = 720, yend = 10) but the problem is then it does not extent to the label ticks, which make sense as I told the line to end at 10.
Here's a slightly manual approach using patchwork, assuming the original plot is saved to p. The alignment of the plots uses magic numbers determined by trial and error.
library(patchwork)
l <- ggplot() +
annotate("segment", x = 0, xend = -60,
y = 10*c(0:5), yend = 10*c(0:5),
linetype = "dashed") +
theme_void()
p + inset_element(l, left = 0, top = 0.91, bottom = 0.48, right = 0.52)

Fill area below geom_curve in ggplot

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)
))

I'd like to paint an area but i don't know how to

I mean, I'd want to paint only the square area P1 X (Q1-Q2).
Not the trapezoid (P2+P1) X (Q1-Q2/2).
Here's code that I used. I used ggplot and dplyr. How can I solve this problem?
How can I paint the only square area not the trapezoied area!!!!
library(ggplot2)
library(dplyr)
supply <- Hmisc::bezier(x = c(1, 8, 9),
y = c(1, 5, 9)) %>%
as_data_frame()
demand <- Hmisc::bezier(c(1, 3, 9),
c(9, 3, 1)) %>%
as_data_frame()
fun_supply <- approxfun(supply$x, supply$y, rule = 2)
fun_supply(c(2, 6, 8))
fun_demand <- approxfun(demand$x, demand$y, rule = 2)
intersection_funs <- uniroot(function(x) fun_supply(x) - fun_demand(x), c(1, 9))
intersection_funs
y_root <- fun_demand(intersection_funs$root)
curve_intersect <- function(curve1, curve2) {
# Approximate the functional form of both curves
curve1_f <- approxfun(curve1$x, curve1$y, rule = 2)
curve2_f <- approxfun(curve2$x, curve2$y, rule = 2)
# Calculate the intersection of curve 1 and curve 2 along the x-axis
point_x <- uniroot(function(x) curve1_f(x) - curve2_f(x),
c(min(curve1$x), max(curve1$x)))$root
# Find where point_x is in curve 2
point_y <- curve2_f(point_x)
# Finish
return(list(x = point_x, y = point_y))
}
intersection_xy <- curve_intersect(supply, demand)
intersection_xy
intersection_xy_df <- intersection_xy %>% as_data_frame()
demand2 <- Hmisc::bezier(c(1.5, 3.5, 9.5),
c(9.5, 3.5, 1.5)) %>%
as_data_frame()
supply2 <- Hmisc::bezier(c(1,7,8),
c(3,7,11)) %>%
as_data_frame()
#Make a data frame of the intersections of the supply curve and both demand curves
intersections <- bind_rows(curve_intersect(supply, demand),
curve_intersect(supply2, demand2))
plot_labels <- data_frame(label = c("S", "D","S[1]","D[1]"),
x = c(9, 1, 6.5, 3),
y = c(8, 8, 8, 8))
ggplot(mapping = aes(x = x, y = y)) +
geom_path(data = supply, color = "#0073D9", size = 1, linetype = "dashed") +
geom_path(data = demand, color = "#FF4036", size = 1, linetype = "dashed") +
geom_path(data = demand2, color = "#FF4036", size = 1) +
geom_path(data = supply2, color = "#0073D9", size = 1) +
geom_segment(data = intersections,
aes(x = x, y = 0, xend = x, yend = y), lty = "dotted") +
geom_segment(data = intersections,
aes(x = 0, y = y, xend = x, yend = y), lty = "dotted") +
geom_segment(data = intersections,
aes(x = x, y = y, xend = x, yend= y), lty = "dotted") +
geom_point(data = intersections, size = 3) +
geom_text(data = plot_labels,
aes(x = x, y = y, label = label), parse = TRUE) +
scale_x_continuous(expand = c(0, 0), breaks = intersections$x,
labels = expression(Q[1], Q[2])) +
scale_y_continuous(expand = c(0, 0), breaks = intersections$y,
labels = expression(P[1], P[2]))+
labs(x = "Quantity", y = "Price") +
geom_area(data =intersections, fill="#9999FF", alpha=0.5) +
theme_classic() +
coord_equal()
Could you help me to paint the area that I mentioned.
You might try adding geom_rect(data=intersections[1,], aes(xmin=0, xmax=x, ymin=0, ymax=y),fill='green', alpha=0.5) to your plot call.
So we have:
ggplot(mapping = aes(x = x, y = y)) +
geom_path(data = supply, color = "#0073D9", size = 1, linetype = "dashed") +
geom_path(data = demand, color = "#FF4036", size = 1, linetype = "dashed") +
geom_path(data = demand2, color = "#FF4036", size = 1) +
geom_path(data = supply2, color = "#0073D9", size = 1) +
geom_segment(data = intersections,
aes(x = x, y = 0, xend = x, yend = y), lty = "dotted") +
geom_segment(data = intersections,
aes(x = 0, y = y, xend = x, yend = y), lty = "dotted") +
geom_segment(data = intersections,
aes(x = x, y = y, xend = x, yend= y), lty = "dotted") +
geom_point(data = intersections, size = 3) +
geom_text(data = plot_labels,
aes(x = x, y = y, label = label), parse = TRUE) +
scale_x_continuous(expand = c(0, 0), breaks = intersections$x,
labels = expression(Q[1], Q[2])) +
scale_y_continuous(expand = c(0, 0), breaks = intersections$y,
labels = expression(P[1], P[2]))+
labs(x = "Quantity", y = "Price") +
geom_area(data =intersections, fill="#9999FF", alpha=0.5) +
theme_classic() +
coord_equal()+
geom_rect(data=intersections[1,], aes(xmin=0, xmax=x, ymin=0, ymax=y),fill='green', alpha=0.5)
Edit based on comment:
geom_rect(data=intersections, aes(xmin=x[2], xmax=x[1], ymin=0, ymax=y[1]),fill='green', alpha=0.5)
Though the answer from J Con is in depth and does provide a solution, a cleaner approach in ggplot2 may be to use the annotate function, with geom and other arguments set appropriately. (See link for help page.)
This is because using something like geom_rect involves passing positions and so on as a data.frame, which is a bit more of a hack as, conceptually, from a grammar of graphics perspective, the data layer and the annotation layer are distinct: the act of mapping data variables to graphical aesthetics in a systematic and objective way, and of marking up features within the dataset in a piecemeal and subjective way, are separate activities, and using annotate explicitly for the latter purpose makes this divide clearer in terms of the code and concepts.
Edit
To be more specific, the annotate equivalent of the following:
geom_rect(data=intersections, aes(xmin=x[2], xmax=x[1], ymin=0, ymax=y[1]),fill='green', alpha=0.5)
Would likely be as follows
annotate(
geom = "rect",
xmin = intersections$x[2], x = intersections$x[1],
ymin = 0, ymax = intersections$y[1],
fill = 'green', alpha = 0.5
)
Functionally this is exactly the same, but conceptually it makes the separation between the data layer and the annotation layer much clearer in the code expressed.
Note: Annotate could also be used for the points and text.

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)

Mark range in plot with transparent color

I want to mark some part of a plot by filling the complete area from some x1 to x2 with (transparent) color in ggplot2.
With base R I would do something like:
plot(1:100)
polygon(x = c(0, 25, 25, 0), y = c(-1000, -1000, 1000, 1000), col = "#FF000050")
When doing the same with ggplot2 I'm stuck with the problem that the polygon either does not go to the upper and lower edge of the plot or isn't plotted at all if I limit the y-axis with ylim.
ggplot(data = data.frame(x = 1:100, y = 1:100), aes(x = x, y = y)) +
geom_point() +
#ylim(0, 100) +
geom_polygon(data = data.frame(x = c(0, 25, 25, 0), y = c(-1000, -1000, 1000, 1000)), aes(x = x, y = y), color = "red", fill = "red", alpha = 0.1)
I don't want to limit the solution to geom_polygon, maybe there is a better way to mark this part of the plot. In my real world data plot, I am using geom_bar for a stacked barplot, but I don't think the solution depends on that.
You can use -Inf and +Inf to define the limits of the polygon (or better in this case, a rect).
ggplot2 will ignore them for building the plot limits:
ggplot() +
geom_point(data = data.frame(x = 1:100, y = 1:100), aes(x = x, y = y)) +
geom_polygon(data = data.frame(x = c(0, 25, 25, 0), y = c(-Inf, -Inf, Inf, Inf)), aes(x = x, y = y), color = "red", fill = "red", alpha = 0.1) +
geom_rect(aes(xmin = 30, xmax = 35, ymin = -Inf, ymax = Inf), color = 'green', fill = "green", alpha = .1)
Note that I moved the data assignment from the ggplot call to the geom_point. The motive for this is better explained in this question.
Try this:
ggplot(data = data.frame(x = 1:100, y = 1:100), aes(x = x, y = y)) +
geom_rect(aes(xmin = 0, xmax = 25, ymin = 0, ymax = 100), fill = "red", alpha = 0.01)+
geom_point()+
scale_y_continuous(limits = c(0, 100), expand = c(0, 0))

Resources