This is an awkward question, and I don't want to offend anyone. I'd like to recreate the following figure in R. The plot is based on a published work. I'm not sure what the backstory is. How should I proceed using ggplot?
We can get arbitrarily close in ggplot as long as we are prepared to put the work into preparing the data.
Firstly, we need a data frame of the labels and the segment positions:
df <- data.frame(text = c('Africa', 'Asia', 'Countries in transition',
'North America', 'South America',
'Western Europe'),
y = 6:1,
x = c(0, 0, 1.5, 6.5, 0, 6),
xend = c(5, 6.5, 6, 10, 5, 10))
Secondly, we need the x axis labels:
xlabs <- c('early EIAs,\noften donor\nfunded',
'EIA regulation/guidance\nenacted, increasing EIAs\nquality variable',
'EIA mainstream,\nfine-tuning of\nregulation/guidance')
If you want the polka-dot background, it is possible to do this via ggpattern, but if you want to stick to CRAN-based repositories, we can now use a pattern fill in grid, which you already have installed. You will need to have the latest version of R installed to do this though:
library(grid)
small_circ1 <- circleGrob(
r = unit(0.5, 'mm'),
gp = gpar(fill = '#d4dbed', col = '#d4dbed')
)
small_circ2 <- circleGrob(
r = unit(0.5, 'mm'),
gp = gpar(fill = '#c5d0e5', col = '#c5d0e5')
)
small_circle_pattern1 <- pattern(
small_circ1,
width = unit(2.5, 'mm'),
height = unit(2.5, 'mm'),
extend = 'repeat'
)
small_circle_pattern2 <- pattern(
small_circ2,
width = unit(2.5, 'mm'),
height = unit(2.5, 'mm'),
extend = 'repeat'
)
Now we are ready to plot. The easiest way to get the broken line segments with text is using geom_textsegment from geomtextpath
library(geomtextpath)
ggplot(df, aes(x, y)) +
annotation_custom(rectGrob(gp = gpar(fill = small_circle_pattern1,
col = NA)),
ymin = -Inf, ymax = Inf, xmin = 2, xmax = 6) +
annotation_custom(rectGrob(gp = gpar(fill = '#e6e9f9', col = '#e6e9f9')),
ymin = -Inf, ymax = Inf, xmin = 6, xmax = 10) +
annotation_custom(rectGrob(gp = gpar(fill = small_circle_pattern2,
col = NA)),
ymin = -Inf, ymax = Inf, xmin = 6, xmax = 10) +
geom_textsegment(aes(yend = y, xend = xend, label = text, fontface = 2),
colour = '#556fa1', linewidth = 0.7, size = 5) +
scale_x_continuous(breaks = c(0, 2, 6),
labels = xlabs, expand = c(0, 0)) +
theme_classic() +
theme(axis.text.x = element_text(size = 14, hjust = 0,
margin = margin(20, 0, 0, 0),
colour = '#556fa1'),
axis.line = element_line(colour = '#556fa1'),
axis.ticks = element_blank(),
axis.text.y = element_blank(),
axis.title = element_blank())
It remains unclear, how detailed the reproduction ought to be (same background or same in general=). It is also unclear, which graphics system you want to work in. Should you want to try that in base graphics, the following might provide you with a workable starting point:
dat <- data.frame(continent = c("Afrika", "Asia", "North America",
"South America", "Europe"),
from = c(0, 0, 6, 0, 5),
to = c(4, 5.5, 10, 5, 10))
dat$mean <- (dat$from + dat$to) / 2
plot(NA, xlim = c(0, 10), ylim = c(0,6), xaxt = "n", yaxt = "n",
xlab = "", ylab = "")
for(i in 1:5){
print(c(dat$from[i], dat$to[i]))
lines(x = c(dat$from[i], dat$to[i]),
y = c(6-i, 6-i),
col = "lightblue", lwd = 2)
}
text(dat$mean, y = (5:1)+.3, labels = dat$continent,
col = "lightblue")
As a mere starting point on how to possibly approach the problem in ggplot2:
dat <- data.frame(no = 1:5,
continent = c("Afrika", "Asia", "North America",
"South America", "Europe"),
from = c(0, 0, 6, 0, 5),
to = c(4, 5.5, 10, 5, 10))
library(ggplot2)
ggplot(dat) +
geom_segment(aes(x = from, y = 6-no, xend = to, yend = 6-no), lwd = 2) +
geom_text(aes(x = mean, y = 6.2-no, label = continent)) +
theme_bw()
Related
Trying to use ggpattern for this plot but can't get it to work right. Legend looks okay doesn't translate to what's on plot itself. Not stripes or dots on actual plot?
test <- tibble(names = c("fred", "harry", "tom"),
start = c(1, 3, 5),
end = c(10, 5, 7),
stripe = c("yes", "no", "yes"))
ggplot() +
geom_rect_pattern(data = test,
aes(xmin = names,
xmax = names,
ymin = start,
ymax = end,
color = names,
fill = names,
pattern = stripe), size = 4)
Your issue is that you do not have xmin, xmax, ymin, ymax values. Since you use rectangles (you need to specify 4 corners): e.g:
plot_df <- data.frame(
xmin = c(0, 10, 3),
xmax = c(8, 18, 4),
ymin = c(0, 10, 8),
ymax = c(5, 19, 15),
type = c('a', 'b', 'c'),
colour1 = c('red', 'black', 'blue')
)
After that
ggplot(plot_df) +
geom_rect_pattern(
aes(
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
pattern_fill = I(colour)
),
pattern = 'stripe',
colour = 'black',
pattern_density = 0.3,
fill = NA
) +
theme_bw(18)
To produce the plot without altering your data, you could try:
ggplot() +
geom_rect_pattern(data = test,
aes(xmin = as.numeric(factor(names)) - 0.25,
xmax = as.numeric(factor(names)) + 0.25,
ymin = start,
ymax = end,
fill = names,
pattern = stripe), pattern_fill = 'black', size = 0) +
scale_x_continuous(breaks = seq(length(levels(factor(test$names)))),
labels = levels(factor(test$names))) +
scale_pattern_manual(values = c('none', 'stripe'))
While looking at upskilling myself, I was watching the really quite excellent ggplot2 workshop to get myself better at using the package by understanding how it works at a fundamental level.
As part of that workshop, I was struck by one of the visualisations used in the workshop as being especially useful for explaining a layered hierarchy of dependencies, and I'm looking to figure out how I could generate such a picture (ideally using R).
These two pictures show the two parts of the visualisation I'm trying to reproduce:
Stacked Planes with labels:
Stacked Planes, with transparencies for most, and labels (appropriately highlighted):
I have been able to produce something similar, using rgl, but it's not nearly as nice. Given I am trying to upskill myself in ggplot2, I would like to be able to produce it using ggplot2 (or one of it's extensions), as that would enable me to control some of the "nicities" of the graphic much easier).
Is this possible using ggplot2 or an extension package?
The code for producing it in rgl is:
library(rgl)
# Create some dummy data
dat <- replicate(2, 1:3)
# Initialize the scene, no data plotted
# hardcoded user matrix of a particular view (so I can go straight to that view each time)
userMatrix_orig <- matrix(c(-0.7069399, -0.2729415, 0.6524867, 0.0000000, 0.7072651, -0.2773000, 0.6502926, 0.0000000, 0.003442926, 0.921199083, 0.389076293, 0.000000000, 0, 0, 0, 1), nrow = 4 )
plot3d(dat, type = 'n', xlim = c(-1, 1), ylim = c(-1, 1), zlim = c(-10, 10),
xlab = '', ylab = '', zlab = '', axes=FALSE)
view3d(userMatrix=userMatrix_orig)
material3d(alpha=1.0)
# Add planes
planes3d(1, 1, 1, -2, col = 'paleturquoise', alpha = 0.8, name="hello")
planes3d(1, 1, 1, -4, col = 'palegreen', alpha = 0.8)
planes3d(1, 1, 1, -6, col = 'palevioletred', alpha = 0.8)
planes3d(1, 1, 1, -8, col = 'midnightblue', alpha = 0.8)
planes3d(1, 1, 1, 0, col = 'red', alpha = 0.8)
planes3d(1, 1, 1, 2, col = 'green', alpha = 0.8)
planes3d(1, 1, 1, 4, col = 'orange', alpha = 0.8)
planes3d(1, 1, 1, 6, col = 'blue', alpha = 0.8)
# Label the planes
family_val <- c("sans")
adj_val <- 1
cex_val <- 2.5
text3d(x=1, y =-1, z = -6, texts="data", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = -4, texts="mapping", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = -2, texts="statistics", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 0, texts="scales", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 2, texts="geometries", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 4, texts="facets", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 6, texts="coordinates", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 8, texts="theme", adj = adj_val, family = family_val, cex = cex_val )
and the graphic I produced using that is:
I would recreate the image in ggplot with a function like this:
make_graphic <- function(highlight = 1:8) {
library(ggplot2)
alpha_vals <- c(0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2)
alpha_vals[highlight] <- 1
df <- data.frame(x = rep(c(0.5, 0.75, 1, 0.75, 0.5), 8),
y = rep(c(0.5, 0, 0.5, 1, 0.5), 8) + rep(0:7, each = 5)/2,
z = rep(LETTERS[1:8], each = 5))
ggplot(df, aes(x, y)) +
geom_polygon(aes(fill = z, alpha = z)) +
geom_text(data = data.frame(x = 0.48, y = rev(0.5 + (0:7)/2),
z = rev(LETTERS[1:8]),
a = c("THEME", "COORDINATES", "FACETS",
"GEOMETRIES", "SCALES", "STATISTICS",
"MAPPING", "DATA")), fontface = 2,
family = "opencondensed",
aes(label = a, alpha = z), colour = "white", size = 10, hjust = 1) +
scale_x_continuous(limits = c(0.2, 1)) +
scale_fill_manual(values = c("#a6aaa9", "#ef4e47", "#34a5da", "#ff9d35",
"#8abe5e", "#ffe989", "#c52060", "#3f969a")) +
scale_alpha_manual(values = alpha_vals) +
theme_void() +
theme(legend.position = "none",
plot.background = element_rect(fill = "#222222"))
}
This allows the graphic to be recreated easily by doing:
make_graphic()
And if you want to just highlight the second bottom item, you can do:
make_graphic(2)
Here's an attempt.
Data
library(dplyr)
mydata <- data.frame(
label = c("THEME", "COORDINATES", "FACETS", "GEOMETRIES", "SCALES", "STATISTICS", "MAPPING", "DATA"),
ybase = 8:1,
color = c("#3f969a", "#c52060", "#ffe989", "#8abe5e", "#ff9d35", "#34a5da", "#ef4e47", "#a6aaa9")
) %>%
rowwise() %>%
mutate(
xs = list(c(0, 2, 0, -2)),
ys = lapply(ybase, `+`, c(1.1, 0, -1.1, 0)),
ord = list(1:4)
) %>%
ungroup() %>%
tidyr::unnest(c(xs, ys, ord)) %>%
arrange(ybase, ord)
spldata <- split(mydata, mydata$label)
spldata <- spldata[order(sapply(spldata, function(z) z$ybase[1]))]
The reason I create spldata is because ggplot2 does not (afaik) allow setting the z-order easily, so I will resort (next block) to plotting the polygons iteratively.
Plot, no highlights
library(ggplot2)
ggplot(mydata, aes(xs, ys, group = label)) +
lapply(spldata, function(dat) {
geom_polygon(aes(fill = I(color)), data = dat)
}) +
geom_text(aes(x = -2.2, y = ybase, label = label),
hjust = 1, color = "white", size = 7,
data = ~ filter(., ord == 1)) +
guides(fill = "none", color = "none", alpha = "none") +
scale_x_continuous(expand = expansion(add = c(2.5, 0.2))) +
theme(
plot.background = element_rect(colour = "black", fill = "black"),
panel.background = element_rect(colour = "black", fill = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.text = element_blank(), axis.ticks = element_blank()
)
Plot, with highlight
The changes here:
add alpha = if ... to geom_polygons
split the geom_text into two calls, since I did not want to found colour= aesthetics between polygons and texts
this <- c("THEME", "MAPPING")
ggplot(mydata, aes(xs, ys, group = label)) +
lapply(spldata, function(dat) {
geom_polygon(aes(fill = I(color)),
alpha = if (dat$label[1] %in% this) 1 else 0.2,
data = dat)
}) +
{
if (any(!mydata$label %in% this))
geom_text(aes(x = -2.2, y = ybase, label = label),
hjust = 1, color = "gray50", size = 7,
data = ~ filter(., ord == 1, !label %in% this))
} +
{
if (any(this %in% mydata$label))
geom_text(aes(x = -2.2, y = ybase, label = label),
hjust = 1, color = "white", size = 7,
data = ~ filter(., ord == 1, label %in% this))
} +
guides(fill = "none", color = "none", alpha = "none") +
scale_x_continuous(expand = expansion(add = c(2.5, 0.2))) +
theme(
plot.background = element_rect(colour = "#222222", fill = "#222222"),
panel.background = element_rect(colour = "#222222", fill = "#222222"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.text = element_blank(), axis.ticks = element_blank()
)
(I borrowed From AllanCameron the idea of "one or more" for this in order to be able to highlight more than one (or perhaps none).
After working on it a while myself, I came up with the following function:
library(ggplot2)
generate_layer_diagram <- function(highlight_layers = "all", num_layers = 8,
overwrite_layer_labels = c('DATA','MAPPING','SCALES','STATISTICS','GEOMETRIES','FACETS','COORDINATES','THEMES'),
overwrite_colours = c('grey','blue','red','orange','paleturquoise','palegreen','palevioletred','midnightblue'),
base_colour_set_name="Set3",
base_num_colours=12L,
save_path="",
transparent_background=FALSE) {
base_image_height <- 20.32
base_image_width <- 21.77
scaling_factor <- 0.69
alpha_highlight <- 1.0
alpha_mute <- 0.2
font_size <- 8*scaling_factor
font_weight <- "bold"
if(transparent_background) {
font_colour <- "black"
background_color <- "transparent"
} else {
font_colour <- "white"
background_color <- "black"
}
diamond <- function(side_length, centre) {
base <- matrix(c(1, 0, 0, 1, -1, 0, 0, -1), nrow = 2) * sqrt(2) / 2
trans <- (base * side_length) + centre
as.data.frame(t(trans))
}
if(is.character(highlight_layers) && highlight_layers == "all") {
highlight_layers = c(1:num_layers)
}
highlights <- c(rep(FALSE,num_layers))
highlights[highlight_layers] <- TRUE
layer_labels <- paste0(c("layer_"),c(1:num_layers)) %>% data.table
layer_labels[,labels:=.][,.:=NULL]
if(length(overwrite_layer_labels) > num_layers) {
overwrite_layer_labels <- overwrite_layer_labels[1:num_layers]
}
layer_labels[1:length(overwrite_layer_labels),labels:=overwrite_layer_labels]
base_colour_set <- RColorBrewer::brewer.pal(base_num_colours,base_colour_set_name) %>% data.table()
base_colour_set <- base_colour_set[,colours:=.][,.:=NULL]
if(num_layers > base_num_colours) {
base_colour_set <- base_colour_set[rep(seq_len(nrow(base_colour_set)), ceiling(num_layers/base_num_colours)), ]
}
base_colour_set <- base_colour_set[1:num_layers]
colour_set <- base_colour_set
if(length(overwrite_colours) > num_layers) {
overwrite_colours <- overwrite_colours[1:num_layers]
}
colour_set[1:length(overwrite_colours),colours:=overwrite_colours]
dt <- data.table(side_lengths = rep(c(2),num_layers),
centres = matrix(c(1 + rep(0,num_layers),2 + 0:(num_layers-1)),nrow=num_layers),
colours = colour_set,
labels = layer_labels,
highlights = highlights)
dt[,alphas:=(as.numeric(highlights)*alpha_highlight + as.numeric(!highlights)*alpha_mute)]
myplot <- ggplot() + lapply(c(1:num_layers),function(x) {geom_polygon(data = diamond(dt$side_lengths[x], c(dt$centres.V1[x],dt$centres.V2[x])), mapping = aes(x = V1, y = V2), fill = dt$colours[x], alpha = dt$alphas[x])}) +
lapply(c(1:num_layers),function(z) {annotate("text", x = -(dt$centres.V1[z]/2)*1.1, y = dt$centres.V2[z], label = dt$labels[z], alpha = dt$alphas[z], size=font_size,
fontface = font_weight, hjust=1, colour=font_colour)}) +
coord_cartesian(xlim = c(-2,3), ylim =c(-1, (num_layers+4) )) +
theme_void() +
#theme_classic() + # gets rid of the ugly bounding box
theme( plot.background = element_rect(fill = background_color)
,axis.line = element_blank(), axis.ticks = element_blank(), axis.title = element_blank(), axis.text = element_blank()
#,plot.margin = element_blank()
,panel.grid = element_blank()
,panel.grid.major = element_blank()
,panel.grid.minor = element_blank()
,panel.background = element_rect(fill=NA)
,panel.border = element_rect(fill=NA)
,validate=TRUE) # sets the background and removes the various axes
option_markers <- c(rep(0,num_layers))
option_markers[highlight_layers] <- 1
suffix <- paste0(option_markers,collapse = "_")
if(save_path != ""){
ggsave(paste0(save_path,"\\pic",suffix,".png"), myplot, height=base_image_height*scaling_factor, width=base_image_width*scaling_factor, units = "cm")
} else {
myplot
}
return(myplot)
}
highlight_layers = "all"
num_layers <- 5
overwrite_layer_labels <- c('DATA','MAPPING','SCALES','STATISTICS','GEOMETRIES','FACETS','COORDINATES','THEMES')
overwrite_colours <- c('grey','blue','red','orange','paleturquoise','palegreen','palevioletred','midnightblue')
Ans the various example function calls produce:
generate_layer_diagram()
generate_layer_diagram(c(1:3),num_layers = num_layers)
generate_layer_diagram(1)
generate_layer_diagram(2)
# Data Mapping and Geometries
generate_layer_diagram(c(1,2,5))
which produces:
Thanks to #AllenCameron's excellent answer for the inspiration of passing a vector to highlight multiple layers at once.
Reproducible data:
df <- data.frame(cbind("Thriving" = c(2, 2, NA, runif(9, 2.0, 5.0)), "Performance" = c(2, 3.5, 2.3, 4.2, NA, runif(7, 1.9, 6.9)), "Mastery_Climate" = c(runif(10, 2.2, 6.5), NA, 2.3), "Competitive_Climate" = c(NA, runif(4, 1.0, 3.6), NA, NA, runif(5, 1.5, 2.8)), "Collaboration" = c(runif(8, 2.2, 7.0), NA, NA, 5.5, 2.1)))
With this data I want to create bloxplots using the following command with the packages ggplot2 and tidyr:
df %>%
gather(key = "variable", value = "value") -> n
n$variable <- factor(n$variable, levels = c("Thriving", "Performance", "Mastery_Climate", "Competitive_Climate", "Collaboration"))
ggplot(data = n, aes(y = value, x = variable)) + stat_summary(fun.data = min.mean.sd.max, geom = "boxplot", col = "#323232", fill = "#EFC76C") +
coord_flip() + scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7)) +
expand_limits(y = c(1, 7)) +
labs(x = "", y = "") +
theme(text = element_text(size = 12), panel.background = element_rect(fill = "#EAEDED")) +
theme(plot.margin=unit(c(0, 2, 0, 1.8),"cm"))
The function used in stat_summary is as follows:
min.mean.sd.max <- function(x) {
r <- c(min(x), mean(x) - sd(x), mean(x), mean(x) + sd(x), max(x))
names(r) <- c("ymin", "lower", "middle", "upper", "ymax")
r
}
Now, HERE IT COMES: everything works beautifully, however, now I would like to colour the background in three different colours, green, yellow and red. I know that I have to use geom_rect for that. However, in order to have the boxplots in the foreground, I need to pass the geom_rect argument first - but this breaks my code:
df %>%
gather(key = "variable", value = "value") -> n
n$variable <- factor(n$variable, levels = c("Thriving", "Performance", "Mastery_Climate", "Competitive_Climate", "Collaboration"))
ggplot(data = n, aes(y = value, x = variable)) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 3, fill = "green"), alpha = .01) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 3, ymax = 5, fill = "yellow"), alpha = .01) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 5, ymax = Inf, fill = "red"), alpha = .01) +
stat_summary(fun.data = min.mean.sd.max, geom = "boxplot", col = "#323232", fill = "#EFC76C") +
coord_flip() + scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7)) +
expand_limits(y = c(1, 7)) +
labs(x = "", y = "") +
theme(text = element_text(size = 12), panel.background = element_rect(fill = "#EAEDED")) +
theme(plot.margin=unit(c(0, 2, 0, 1.8),"cm"))
As you can see, I get the error "Error: Discrete value supplied to continuous scale". From research I understand that this is because I needed to change the sequence and it now is a problem that the x-variable is a factor. However, I have been unable to solve this.
It would be great if all the other code could stay the same, it took me ages to put it together. Also, once the boxplots are in the foreground, it would be great if the grid behind would still be visible. Furthermore, I was so confused by the fill in geom_rect, I put in "green" and I get pink, or I put "yellow" and get blue - no clue why.
In any case, any help is very much appreciated. Many greetings!
Edit: Updated answer with better shading control
I think this approach is more idiomatic to ggplot: this puts the shading into a separate table with numeric y values. In a modified ggplot call, all the y values are mapped as numeric values, but the labels for those values are swapped out in the scale_y_continuous line.
rects <- data.frame(xmin = -Inf,
xmax = Inf,
ymin = c(0,3,5),
ymax = c(3,5,Inf),
fill = c("green", "yellow", "red"))
ggplot(data = n, aes(y = value, x = as.numeric(variable))) +
geom_rect(data = rects, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill),
# Control the shading opacity here.
inherit.aes = FALSE, alpha = 0.15) +
stat_summary(fun.data = min.mean.sd.max, geom = "boxplot", col = "#323232", fill = "#EFC76C") +
scale_fill_identity() +
scale_x_continuous(breaks = as.numeric(unique(n$variable)), minor_breaks = NULL,
labels = unique(n$variable)) +
scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7)) +
expand_limits(y = c(1, 7)) +
coord_flip() +
labs(x = "", y = "") +
theme(text = element_text(size = 12), panel.background = element_rect(fill = "#EAEDED")) +
theme(plot.margin=unit(c(0, 2, 0, 1.8),"cm"))
Original answer
geom_rect's coordinates should be pulled outside of the aes() call, and then I get a working solution. However, one problem with this approach is that the background rectangles are actually getting drawn once for each element in the source data, which is why the colors are so bright even with alpha = 0.01.
ggplot(data = n, aes(y = value, x = variable)) +
geom_rect(xmin = -Inf, xmax = Inf, ymin = 0, ymax = 3, fill = "green", alpha = .005) +
geom_rect(xmin = -Inf, xmax = Inf, ymin = 3, ymax = 5, fill = "yellow", alpha = .005) +
geom_rect(xmin = -Inf, xmax = Inf, ymin = 5, ymax = 7, fill = "red", alpha = .005) +
stat_summary(fun.data = min.mean.sd.max, geom = "boxplot", col = "#323232", fill = "#EFC76C") +
coord_flip() + scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7)) +
expand_limits(y = c(1, 7)) +
labs(x = "", y = "") +
theme(text = element_text(size = 12), panel.background = element_rect(fill = "#EAEDED")) +
theme(plot.margin=unit(c(0, 2, 0, 1.8),"cm"))
I am trying to add the below "graphic" to a chart I am doing in R.
I could easily do the graphic in a graphics application and then 'glue' it together with the R graph. However, it could be cool to make everything in R since it has the caveat that the position of the black arrow depends on calculated number. In the below case 6.8.
Any suggestions on how I could trick R to produce something like this?
This is a start for a function in base graphics:
draw <- function(x){
plot(NA, xlim=c(0,7), ylim=c(-.3,1), xaxt="n", yaxt="n", xlab="", ylab="")
lines(x=c(0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,0),
y=rep(c(0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0)))
lines(c(0,7),c(1,1))
for(i in 1:7) text(x = i-0.5, y= 0.5, labels=i)
arrows(0, 1.5, 7, 1.5, code=3)
polygon(x -1 +c(-.1, 0, .1),c(-.3,-0.05,-.3), col="black")
}
draw(4)
draw(3)
If you are interested in a base graphics function, you will probably want to make the width:heigth ratio fixed an change my polygon-triangle into a proper arrow, add some more text and things, but this should get you started:
Here's the ggplot2 solution:
df_nums <- data.frame(number <- 1:7,
fill <- c(rep("white", 5), "darkblue", "white"),
color <- c(rep("black", 5), "white", "black"))
df_text <- data.frame(label = c("Lower Risk", "Higher Risk", "Typically Lower Rewards",
"Typically Higher Rewards"),
hjust = c(0, 1, 0, 1),
x = c(0, 7, 0, 7),
y = c(2.9, 2.9, 2.1, 2.1))
arrow_x_pos <- 6.8 # position of arrow
p1 <- ggplot(df_nums) +
geom_tile(aes(x = number - .5, y = 1, fill = fill), size = 1, color = "black") +
geom_text(aes(x = number - .5, y = 1, color = color, label = number), size = 8) +
scale_color_identity(guide = "none") + scale_fill_identity(guide = "none") +
geom_text(data = df_text, aes(x = x, y = y, label = label, hjust = hjust), size = 5.5,
fontface = "bold") +
geom_text(aes(label = "Risk and Reward Profile", x = 0, y = 3.5),
fontface = "bold", size = 6.5, hjust = 0) +
geom_segment(x = 0, xend = 7, y = 2.5, yend = 2.5, size = 1,
arrow = arrow(length = unit(10,"pt"), ends = "both"),
color = "grey70") +
geom_segment(x = arrow_x_pos - 1, xend = arrow_x_pos - 1, y = -.3, yend = .2, size = 4,
arrow = arrow(length = unit(7, "pt"), type = "closed"),
lineend = "butt", linejoin = "mitre") +
ylim(-.2, 3.6) +
coord_fixed() +
theme_void()
p1
I'm trying to draw some arrows in the margin of a ggplot. From what I've read, you have to turn off the plot clipping to do that. However, when I do that, it deletes a line segment I have on my graph.
library(ggplot2)
library(ggrepel)
library(grid)
#----------------- Fake data practice --------------------- #
mydata <- data.frame(Labels = letters[1:14],
X_Values = seq(1, 14, 1),
Y_Values = rnorm(14, mean = 0, sd = 1),
Influence = seq(1, 14, 1))
mydata$Influencer <- factor(ifelse(mydata$Influence <= 3, 1, 0))
# --- Get min/max from data and use to set range at -1to1 or -2to2
chartMax <- ifelse(min(mydata$Y_Values) < -1 | max(mydata$Y_Values) > 1, 2, 1)
chartMin <- ifelse(chartMax == 1, -1, -2)
yTitle = "Some Title"
# --- Label setting, if greater than 0 nudge up, else nudge down
mydata$Nudger <- ifelse(mydata$Y_Values >= 0, .1, -.1)
p <- ggplot(mydata, aes(x = X_Values, y = Y_Values, group = Influencer)) +
geom_point(aes(size = Influencer, color = Influencer), shape = 18) +
geom_segment(x = 0, xend = 14, y = 0, yend = 0, color = "red", linetype = "dashed", size = 1.2, alpha = .5) +
geom_text_repel(aes(x = X_Values, y = Y_Values, label = Labels),
box.padding = .4,
point.padding = .2,
nudge_y = .1) +
scale_color_manual(values = c("grey", "blue")) +
scale_size_manual(values = c(4, 6)) +
scale_y_continuous(name = "", limits = c(chartMin, chartMax)) +
scale_x_continuous(name = yTitle,
limits = c(1, 15),
breaks = c(2,13),
labels = c("Lower", "Higher")) +
theme_classic() + theme(plot.margin = unit(c(1,3,1,2), "lines"),
legend.position="none",
axis.ticks.x=element_blank(),
axis.text.x = element_text(face = "bold"),
axis.title = element_text(face = "bold"),
axis.line.x = element_line(color = "blue"
,size = 1
,arrow =
arrow(length = unit(0.5, "cm"),
ends = "both"))) +
annotation_custom(
grob = linesGrob(arrow=arrow(type="open", ends="both", length=unit(0.5, "cm")), gp=gpar(col="blue", lwd=2)),
xmin = -1.4, xmax = -1.4, ymin = chartMin, ymax = chartMax
)
p
# Here it works and you see the red dashed line
# Turn off panel clipping
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[gt$layout$name == "panel"] <- "off"
grid.draw(gt)
Ideally, I want a blue arrow that runs alongside the y-axis in the margins. I think I've got that, but I can't loose my dashed red line that runs along the inside the graph.
I can't explain why this is happening (seems like a bug, I suggest raising an issue here), but I can confirm that the issue is related to the line alpha. If we delete the alpha = 0.5 argument from geom_segment then the clipping=off works without deleting the line: